diff --git a/.gitignore b/.gitignore
index 1870173..bd3748b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,8 @@ __pycache__/
*.so
# Distribution / packaging
+.idea
+*.iml
.Python
build/
develop-eggs/
@@ -195,6 +197,8 @@ cython_debug/
.task_outputs/
.tasks/
.teams/
+.team/
+.worktrees/
# Ruff stuff:
.ruff_cache/
@@ -225,3 +229,5 @@ test_providers.py
# Internal analysis artifacts (not learning material)
analysis/
analysis_progress.md
+TODO.md
+.test-workspace/
diff --git a/README-en.md b/README-en.md
new file mode 100644
index 0000000..cf8c74b
--- /dev/null
+++ b/README-en.md
@@ -0,0 +1,438 @@
+# Learn Claude Code -- Harness Engineering for Real Agents
+
+[English](./README-en.md) | [中文](./README.md) | [日本語](./README-ja.md)
+
+## The Model IS the Agent
+
+Before we talk about code, let's get one thing absolutely straight.
+
+**An agent is a model. Not a framework. Not a prompt chain. Not a drag-and-drop workflow.**
+
+### What an Agent IS
+
+An agent is a neural network -- a Transformer, an RNN, a learned function -- that has been trained, through billions of gradient updates on action-sequence data, to perceive an environment, reason about goals, and take actions to achieve them. The word "agent" in AI has always meant this. Always.
+
+A human is an agent. A biological neural network, shaped by millions of years of evolutionary training, perceiving the world through senses, reasoning through a brain, acting through a body. When DeepMind, OpenAI, or Anthropic say "agent," they mean the same thing the field has meant since its inception: **a model that has learned to act.**
+
+The proof is written in history:
+
+- **2013 -- DeepMind DQN plays Atari.** A single neural network, receiving only raw pixels and game scores, learned to play 7 Atari 2600 games -- surpassing all prior algorithms and beating human experts on 3 of them. By 2015, the same architecture scaled to [49 games and matched professional human testers](https://www.nature.com/articles/nature14236), published in *Nature*. No game-specific rules. No decision trees. One model, learning from experience. That model was the agent.
+
+- **2019 -- OpenAI Five conquers Dota 2.** Five neural networks, having played [45,000 years of Dota 2](https://openai.com/index/openai-five-defeats-dota-2-world-champions/) against themselves in 10 months, defeated **OG** -- the reigning TI8 world champions -- 2-0 on a San Francisco livestream. In a subsequent public arena, the AI won 99.4% of 42,729 games against all comers. No scripted strategies. No meta-programmed team coordination. The models learned teamwork, tactics, and real-time adaptation entirely through self-play.
+
+- **2019 -- DeepMind AlphaStar masters StarCraft II.** AlphaStar [beat professional players 10-1](https://deepmind.google/blog/alphastar-mastering-the-real-time-strategy-game-starcraft-ii/) in a closed-door match, and later achieved [Grandmaster status](https://www.nature.com/articles/d41586-019-03298-6) on European servers -- top 0.15% of 90,000 players. A game with imperfect information, real-time decisions, and a combinatorial action space that dwarfs chess and Go. The agent? A model. Trained. Not scripted.
+
+- **2019 -- Tencent Jueyu dominates Honor of Kings.** Tencent AI Lab's "Jueyu" [defeated KPL professional players](https://www.jiemian.com/article/3371171.html) in a full 5v5 match at the World Champion Cup. In 1v1 mode, pros won only [1 out of 15 games and never survived past 8 minutes](https://developer.aliyun.com/article/851058). Training intensity: one day equaled 440 human years. By 2021, Jueyu surpassed KPL pros across the full hero pool. No handcrafted matchup tables. No scripted compositions. A model that learned the entire game from scratch through self-play.
+
+- **2024-2025 -- LLM agents reshape software engineering.** Claude, GPT, Gemini -- large language models trained on the entirety of human code and reasoning -- are deployed as coding agents. They read codebases, write implementations, debug failures, coordinate in teams. The architecture is identical to every agent before them: a trained model, placed in an environment, given tools to perceive and act. The only difference is the scale of what they've learned and the generality of the tasks they solve.
+
+Every one of these milestones shares the same truth: **the "agent" is never the surrounding code. The agent is always the model.**
+
+### What an Agent Is NOT
+
+The word "agent" has been hijacked by an entire cottage industry of prompt plumbing.
+
+Drag-and-drop workflow builders. No-code "AI agent" platforms. Prompt-chain orchestration libraries. They all share the same delusion: that wiring together LLM API calls with if-else branches, node graphs, and hardcoded routing logic constitutes "building an agent."
+
+It doesn't. What they build is a Rube Goldberg machine -- an over-engineered, brittle pipeline of procedural rules, with an LLM wedged in as a glorified text-completion node. That is not an agent. That is a shell script with delusions of grandeur.
+
+**Prompt plumbing "agents" are the fantasy of programmers who don't train models.** They attempt to brute-force intelligence by stacking procedural logic -- massive rule trees, node graphs, chain-of-prompt waterfalls -- and praying that enough glue code will somehow emergently produce autonomous behavior. It won't. You cannot engineer your way to agency. Agency is learned, not programmed.
+
+Those systems are dead on arrival: fragile, unscalable, fundamentally incapable of generalization. They are the modern resurrection of GOFAI (Good Old-Fashioned AI) -- the symbolic rule systems the field abandoned decades ago, now spray-painted with an LLM veneer. Different packaging, same dead end.
+
+### The Mind Shift: From "Developing Agents" to Developing Harness
+
+When someone says "I'm developing an agent," they can only mean one of two things:
+
+**1. Training the model.** Adjusting weights through reinforcement learning, fine-tuning, RLHF, or other gradient-based methods. Collecting task-process data -- the actual sequences of perception, reasoning, and action in real domains -- and using it to shape the model's behavior. This is what DeepMind, OpenAI, Tencent AI Lab, and Anthropic do. This is agent development in the truest sense.
+
+**2. Building the harness.** Writing the code that gives the model an environment to operate in. This is what most of us do, and it is the focus of this repository.
+
+A harness is everything the agent needs to function in a specific domain:
+
+```
+Harness = Tools + Knowledge + Observation + Action Interfaces + Permissions
+
+ Tools: file I/O, shell, network, database, browser
+ Knowledge: product docs, domain references, API specs, style guides
+ Observation: git diff, error logs, browser state, sensor data
+ Action: CLI commands, API calls, UI interactions
+ Permissions: sandboxing, approval workflows, trust boundaries
+```
+
+The model decides. The harness executes. The model reasons. The harness provides context. The model is the driver. The harness is the vehicle.
+
+**A coding agent's harness is its IDE, terminal, and filesystem access.** A farm agent's harness is its sensor array, irrigation controls, and weather data feeds. A hotel agent's harness is its booking system, guest communication channels, and facility management APIs. The agent -- the intelligence, the decision-maker -- is always the model. The harness changes per domain. The agent generalizes across them.
+
+This repo teaches you to build vehicles. Vehicles for coding. But the design patterns generalize to any domain: farm management, hotel operations, manufacturing, logistics, healthcare, education, scientific research. Anywhere a task needs to be perceived, reasoned about, and acted upon -- an agent needs a harness.
+
+### What Harness Engineers Actually Do
+
+If you are reading this repository, you are likely a harness engineer -- and that is a powerful thing to be. Here is your real job:
+
+- **Implement tools.** Give the agent hands. File read/write, shell execution, API calls, browser control, database queries. Each tool is an action the agent can take in its environment. Design them to be atomic, composable, and well-described.
+
+- **Curate knowledge.** Give the agent domain expertise. Product documentation, architectural decision records, style guides, regulatory requirements. Load them on-demand (s05), not upfront. The agent should know what's available and pull what it needs.
+
+- **Manage context.** Give the agent clean memory. Subagent isolation (s04) prevents noise from leaking. Context compression (s06) prevents history from overwhelming. Task systems (s07) persist goals beyond any single conversation.
+
+- **Control permissions.** Give the agent boundaries. Sandbox file access. Require approval for destructive operations. Enforce trust boundaries between the agent and external systems. This is where safety engineering meets harness engineering.
+
+- **Collect task-process data.** Every action sequence the agent executes in your harness is training signal. The perception-reasoning-action traces from real deployments are the raw material for fine-tuning the next generation of agent models. Your harness doesn't just serve the agent -- it can help improve the agent.
+
+You are not writing the intelligence. You are building the world the intelligence inhabits. The quality of that world -- how clearly the agent can perceive, how precisely it can act, how rich its available knowledge is -- directly determines how effectively the intelligence can express itself.
+
+**Build great harnesses. The agent will do the rest.**
+
+### Why Claude Code -- A Masterclass in Harness Engineering
+
+Why does this repository dissect Claude Code specifically?
+
+Because Claude Code is the most elegant and fully-realized agent harness we have seen. Not because of any single clever trick, but because of what it *doesn't* do: it doesn't try to be the agent. It doesn't impose rigid workflows. It doesn't second-guess the model with elaborate decision trees. It provides the model with tools, knowledge, context management, and permission boundaries -- then gets out of the way.
+
+Look at what Claude Code actually is, stripped to its essence:
+
+```
+Claude Code = one agent loop
+ + tools (bash, read, write, edit, glob, grep, browser...)
+ + on-demand skill loading
+ + context compression
+ + subagent spawning
+ + task system with dependency graph
+ + team coordination with async mailboxes
+ + worktree isolation for parallel execution
+ + permission governance
+```
+
+That's it. That's the entire architecture. Every component is a harness mechanism -- a piece of the world built for the agent to inhabit. The agent itself? It's Claude. A model. Trained by Anthropic on the full breadth of human reasoning and code. The harness doesn't make Claude smart. Claude is already smart. The harness gives Claude hands, eyes, and a workspace.
+
+This is why Claude Code is the ideal teaching subject: **it demonstrates what happens when you trust the model and focus your engineering on the harness.** Every session in this repository (s01-s12) reverse-engineers one harness mechanism from Claude Code's architecture. By the end, you understand not just how Claude Code works, but the universal principles of harness engineering that apply to any agent in any domain.
+
+The lesson is not "copy Claude Code." The lesson is: **the best agent products are built by engineers who understand that their job is harness, not intelligence.**
+
+---
+
+## The Vision: Fill the Universe with Real Agents
+
+This is not just about coding agents.
+
+Every domain where humans perform complex, multi-step, judgment-intensive work is a domain where agents can operate -- given the right harness. The patterns in this repository are universal:
+
+```
+Estate management agent = model + property sensors + maintenance tools + tenant comms
+Agricultural agent = model + soil/weather data + irrigation controls + crop knowledge
+Hotel operations agent = model + booking system + guest channels + facility APIs
+Medical research agent = model + literature search + lab instruments + protocol docs
+Manufacturing agent = model + production line sensors + quality controls + logistics
+Education agent = model + curriculum knowledge + student progress + assessment tools
+```
+
+The loop is always the same. The tools change. The knowledge changes. The permissions change. The agent -- the model -- generalizes.
+
+Every harness engineer reading this repository is learning patterns that apply far beyond software engineering. You are learning to build the infrastructure for an intelligent, automated future. Every well-designed harness deployed in a real domain is one more place where an agent can perceive, reason, and act.
+
+First we fill the workshops. Then the farms, the hospitals, the factories. Then the cities. Then the planet.
+
+**Bash is all you need. Real agents are all the universe needs.**
+
+---
+
+```
+ THE AGENT PATTERN
+ =================
+
+ User --> messages[] --> LLM --> response
+ |
+ More tool call requests?
+ / \
+ yes no
+ | |
+ execute @Tool methods return text
+ return results
+ continue loop -----------------> messages[]
+
+
+ That's the minimal loop. Every AI agent needs this loop.
+ The model decides when to call tools and when to stop.
+ Spring AI's ChatClient.call() automatically drives this loop.
+ This repo teaches you to build everything around this loop --
+ the harness that makes the agent effective in a specific domain.
+```
+
+**12 progressive sessions, from a simple loop to isolated autonomous execution.**
+**Each session adds one harness mechanism. Each mechanism has one motto.**
+
+> **s01** *"One loop & Bash is all you need"* — one tool + one loop = an agent
+>
+> **s02** *"Adding a tool means adding one handler"* — the loop stays the same; new tools register with `@Tool` annotation + `defaultTools()`
+>
+> **s03** *"An agent without a plan drifts"* — list the steps first, then execute; completion doubles
+>
+> **s04** *"Break big tasks down; each subtask gets a clean context"* — subagents use independent messages[], keeping the main conversation clean
+>
+> **s05** *"Load knowledge when you need it, not upfront"* — inject via tool_result, not the system prompt
+>
+> **s06** *"Context will fill up; you need a way to make room"* — three-layer compression strategy for infinite sessions
+>
+> **s07** *"Break big goals into small tasks, order them, persist to disk"* — a file-based task graph with dependencies, laying the foundation for multi-agent collaboration
+>
+> **s08** *"Run slow operations in the background; the agent keeps thinking"* — daemon threads run commands, inject notifications on completion
+>
+> **s09** *"When the task is too big for one, delegate to teammates"* — persistent teammates + async mailboxes
+>
+> **s10** *"Teammates need shared communication rules"* — one request-response pattern drives all negotiation
+>
+> **s11** *"Teammates scan the board and claim tasks themselves"* — no need for the lead to assign each one
+>
+> **s12** *"Each works in its own directory, no interference"* — tasks manage goals, worktrees manage directories, bound by ID
+
+---
+
+## The Core Pattern
+
+```java
+// Spring AI's ChatClient + @Tool annotation implement the Agent loop
+// The model automatically decides when to call tools and when to return text -- the loop is driven by the framework
+
+@SpringBootApplication
+public class S01AgentLoop implements CommandLineRunner {
+
+ @Bean
+ public CommandLineRunner agentLoop(ChatClient.Builder builder) {
+ ChatClient chatClient = builder
+ .defaultSystem("You are a helpful assistant with access to tools.")
+ .defaultTools(new BashTool()) // 注册工具
+ .build();
+
+ return args -> {
+ // 一次 call() 内部自动完成: 调用模型 → 检测工具请求 → 执行工具 → 回传结果 → 再次调用模型...
+ String result = chatClient.prompt()
+ .user(userInput)
+ .call()
+ .content();
+ System.out.println(result);
+ };
+ }
+}
+
+// @Tool 注解让方法自动成为模型可调用的工具
+public class BashTool {
+ @Tool(description = "Execute a shell command and return stdout/stderr")
+ public String executeBash(String command) {
+ // 执行命令并返回结果
+ }
+}
+```
+
+Spring AI's `ChatClient.call()` encapsulates the complete agent loop internally: call the LLM → detect tool call requests → execute `@Tool` methods → return results to the model → repeat until the model returns text. Each session layers one harness mechanism on top of this loop -- without changing the loop itself. The loop belongs to the agent. The mechanisms belong to the harness.
+
+## Scope (Important)
+
+This repository is a 0->1 learning project for harness engineering -- building the environment that surrounds an agent model.
+It intentionally simplifies or omits several production mechanisms:
+
+- Full event/hook buses (for example PreToolUse, SessionStart/End, ConfigChange).
+ s12 includes only a minimal append-only lifecycle event stream for teaching.
+- Rule-based permission governance and trust workflows
+- Session lifecycle controls (resume/fork) and advanced worktree lifecycle controls
+- Full MCP runtime details (transport/OAuth/resource subscribe/polling)
+
+Treat the team JSONL mailbox protocol in this repo as a teaching implementation, not a claim about any specific production internals.
+
+## Quick Start
+
+### Requirements
+
+- **JDK 21+** (recommended: [Eclipse Temurin](https://adoptium.net/) or GraalVM)
+- **Maven 3.9+**
+- An OpenAI-compatible LLM API key (DeepSeek, GLM, Qwen, OpenAI, etc.)
+
+### Clone & Build
+
+```sh
+git clone https://github.com/abel533/learn-claude-code
+cd learn-claude-code
+mvn compile # 编译项目
+```
+
+### Set Environment Variables
+
+```sh
+# Linux / macOS
+export AI_API_KEY=your-api-key
+export AI_BASE_URL=https://api.deepseek.com # 替换为你的模型服务商地址
+export AI_MODEL=deepseek-chat # 替换为你使用的模型名称
+
+# Windows PowerShell
+$env:AI_API_KEY="your-api-key"
+$env:AI_BASE_URL="https://api.deepseek.com"
+$env:AI_MODEL="deepseek-chat"
+```
+
+### Run Sessions
+
+```sh
+# 从第一课开始
+mvn exec:java -Dexec.mainClass=io.mybatis.learn.s01.S01AgentLoop
+
+# 完整递进终点
+mvn exec:java -Dexec.mainClass=io.mybatis.learn.s12.S12WorktreeIsolation
+
+# 总纲: 全部机制合一
+mvn exec:java -Dexec.mainClass=io.mybatis.learn.full.SFullAgent
+```
+
+### Web Platform
+
+Interactive visualizations, step-through animations, source viewer, and documentation for each session.
+
+```sh
+cd web && npm install && npm run dev # http://localhost:3000
+```
+
+### Java Version Features
+
+This project uses the **Java 21 + Spring Boot 3.5.7 + Spring AI 1.0.3** stack. Compared to the original Python version:
+
+- **Compatible with multiple LLM providers** -- adapts to DeepSeek, GLM, Qwen, Moonshot and other models via the OpenAI protocol, no vendor lock-in
+- **`@Tool` annotation handles the tool call loop automatically** -- Spring AI completes the full "model call → tool execution → result return" cycle, no hand-written while loop needed
+- **Java 21 Virtual Threads** -- lightweight concurrency for background tasks and multi-agent collaboration, without thread pool management overhead
+- **Each session is independently runnable** -- every session is a `@SpringBootApplication` + `CommandLineRunner`, launchable with a single `mvn exec:java` command
+- **Type safety** -- Java's strong type system catches errors at compile time, with IDE-friendly auto-completion
+
+## Learning Path
+
+```
+Phase 1: THE LOOP Phase 2: PLANNING & KNOWLEDGE
+================== ==============================
+s01 The Agent Loop [1] s03 TodoWrite [5]
+ ChatClient + @Tool TodoManager + nag reminder
+ | |
+ +-> s02 Tool Use [4] s04 Subagents [5]
+ @Tool registers tools independent ChatClient per child
+ |
+ s05 Skills [5]
+ SKILL.md via tool_result
+ |
+ s06 Context Compact [5]
+ 3-layer compression
+
+Phase 3: PERSISTENCE Phase 4: TEAMS
+================== =====================
+s07 Tasks [8] s09 Agent Teams [9]
+ file-based CRUD + deps graph teammates + JSONL mailboxes
+ | |
+s08 Background Tasks [6] s10 Team Protocols [12]
+ virtual threads + notify queue shutdown + plan approval FSM
+ |
+ s11 Autonomous Agents [14]
+ idle cycle + auto-claim
+ |
+ s12 Worktree Isolation [16]
+ task coordination + on-demand isolated execution lanes
+
+ [N] = number of tools
+```
+
+## Project Structure
+
+```
+learn-claude-code/
+|
+|-- src/main/java/io/mybatis/learn/ # Java implementation (Spring AI + Spring Boot)
+| |-- core/ # shared utilities (AgentRunner, BashTool, EditFileTool, etc.)
+| |-- s01/ S01AgentLoop.java # session 01: Agent Loop
+| |-- s02/ S02ToolUse.java # session 02: Multi-Tool Registration
+| |-- s03/ S03TodoWrite.java # session 03: Plan-Driven Execution
+| |-- s04/ S04Subagent.java # session 04: Subagents
+| |-- s05/ S05SkillLoading.java # session 05: Skill Loading
+| |-- s06/ S06ContextCompact.java # session 06: Context Compression
+| |-- s07/ S07TaskSystem.java # session 07: Task System
+| |-- s08/ S08BackgroundTasks.java # session 08: Background Tasks
+| |-- s09/ S09AgentTeams.java # session 09: Agent Teams
+| |-- s10/ S10TeamProtocols.java # session 10: Team Protocols
+| |-- s11/ S11AutonomousAgents.java# session 11: Autonomous Agents
+| |-- s12/ S12WorktreeIsolation.java# session 12: Worktree Isolation
+| +-- full/ SFullAgent.java # capstone: all mechanisms combined
+|
+|-- agents/ # Python reference implementations (original version, kept for comparison)
+|-- docs/{en,zh,ja}/ # Mental-model-first documentation (3 languages)
+|-- web/ # Interactive learning platform (Next.js)
+|-- skills/ # Skill files for s05
+|-- pom.xml # Maven build config (Spring Boot 3.5.7 + Spring AI 1.0.3)
++-- .github/workflows/ci.yml # CI: typecheck + build
+```
+
+## Documentation
+
+Mental-model-first: problem, solution, ASCII diagram, minimal code.
+Available in [English](./docs/en/) | [中文](./docs/zh/) | [日本語](./docs/ja/).
+
+| Session | Topic | Motto |
+|---------|-------|-------|
+| [s01](./docs/en/s01-the-agent-loop.md) | Agent Loop | *One loop & Bash is all you need* |
+| [s02](./docs/en/s02-tool-use.md) | Tool Use | *Adding a tool means adding one handler* |
+| [s03](./docs/en/s03-todo-write.md) | TodoWrite | *An agent without a plan drifts* |
+| [s04](./docs/en/s04-subagent.md) | Subagents | *Break big tasks down; each subtask gets a clean context* |
+| [s05](./docs/en/s05-skill-loading.md) | Skills | *Load knowledge when you need it, not upfront* |
+| [s06](./docs/en/s06-context-compact.md) | Context Compact | *Context will fill up; you need a way to make room* |
+| [s07](./docs/en/s07-task-system.md) | Task System | *Break big goals into small tasks, order them, persist to disk* |
+| [s08](./docs/en/s08-background-tasks.md) | Background Tasks | *Run slow operations in the background; the agent keeps thinking* |
+| [s09](./docs/en/s09-agent-teams.md) | Agent Teams | *When the task is too big for one, delegate to teammates* |
+| [s10](./docs/en/s10-team-protocols.md) | Team Protocols | *Teammates need shared communication rules* |
+| [s11](./docs/en/s11-autonomous-agents.md) | Autonomous Agents | *Teammates scan the board and claim tasks themselves* |
+| [s12](./docs/en/s12-worktree-task-isolation.md) | Worktree + Task Isolation | *Each works in its own directory, no interference* |
+
+## What's Next -- from understanding to shipping
+
+After the 12 sessions you understand how harness engineering works inside out. Two ways to put that knowledge to work:
+
+### Kode Agent CLI -- Open-Source Coding Agent CLI
+
+> `npm i -g @shareai-lab/kode`
+
+Skill & LSP support, Windows-ready, pluggable with GLM / MiniMax / DeepSeek and other open models. Install and go.
+
+GitHub: **[shareAI-lab/Kode-cli](https://github.com/shareAI-lab/Kode-cli)**
+
+### Kode Agent SDK -- Embed Agent Capabilities in Your App
+
+The official Claude Code Agent SDK communicates with a full CLI process under the hood -- each concurrent user means a separate terminal process. Kode SDK is a standalone library with no per-user process overhead, embeddable in backends, browser extensions, embedded devices, or any runtime.
+
+GitHub: **[shareAI-lab/Kode-agent-sdk](https://github.com/shareAI-lab/Kode-agent-sdk)**
+
+---
+
+## Sister Repo: from *on-demand sessions* to *always-on assistant*
+
+The harness this repo teaches is **use-and-discard** -- open a terminal, give the agent a task, close when done, next session starts blank. That is the Claude Code model.
+
+[OpenClaw](https://github.com/openclaw/openclaw) proved another possibility: on top of the same agent core, two harness mechanisms turn the agent from "poke it to make it move" into "it wakes up every 30 seconds to look for work":
+
+- **Heartbeat** -- every 30s the harness sends the agent a message to check if there is anything to do. Nothing? Go back to sleep. Something? Act immediately.
+- **Cron** -- the agent can schedule its own future tasks, executed automatically when the time comes.
+
+Add multi-channel IM routing (WhatsApp / Telegram / Slack / Discord, 13+ platforms), persistent context memory, and a Soul personality system, and the agent goes from a disposable tool to an always-on personal AI assistant.
+
+**[claw0](https://github.com/shareAI-lab/claw0)** is our companion teaching repo that deconstructs these harness mechanisms from scratch:
+
+```
+claw agent = agent core + heartbeat + cron + IM chat + memory + soul
+```
+
+```
+learn-claude-code claw0
+(agent harness core: (proactive always-on harness:
+ loop, tools, planning, heartbeat, cron, IM channels,
+ teams, worktree isolation) memory, soul personality)
+```
+
+## License
+
+MIT
+
+---
+
+**The model is the agent. The code is the harness. Build great harnesses. The agent will do the rest.**
+
+**Bash is all you need. Real agents are all the universe needs.**
diff --git a/README-ja.md b/README-ja.md
index 8717201..ee8ab0c 100644
--- a/README-ja.md
+++ b/README-ja.md
@@ -1,6 +1,6 @@
# Learn Claude Code -- 真の Agent のための Harness Engineering
-[English](./README.md) | [中文](./README-zh.md) | [日本語](./README-ja.md)
+[English](./README-en.md) | [中文](./README.md) | [日本語](./README-ja.md)
## モデルこそが Agent である
@@ -143,20 +143,20 @@ Claude Code = 一つの agent loop
User --> messages[] --> LLM --> response
|
- stop_reason == "tool_use"?
+ ツール呼び出しリクエストあり?
/ \
- yes no
+ あり なし
| |
- execute tools return text
- append results
- loop back -----------------> messages[]
+ @Tool メソッドを実行 テキストを返す
+ 結果を返却
+ ループ継続 -----------------> messages[]
- 最小ループ。すべての AI Agent にこのループが必要だ。
+ 最小ループ。すべての AI エージェントにこのループが必要。
モデルがツール呼び出しと停止を決める。
- コードはモデルの要求を実行するだけ。
- このリポジトリはこのループを囲むすべて --
- Agent を特定ドメインで効果的にする Harness -- の作り方を教える。
+ Spring AI の ChatClient.call() がこのループを自動駆動する。
+ 本リポジトリはこのループを囲むすべて --
+ エージェントを特定ドメインで効果的にする Harness -- の作り方を教える。
```
**12 の段階的セッション、シンプルなループから分離された自律実行まで。**
@@ -164,7 +164,7 @@ Claude Code = 一つの agent loop
> **s01** *"One loop & Bash is all you need"* — 1つのツール + 1つのループ = エージェント
>
-> **s02** *"ツールを足すなら、ハンドラーを1つ足すだけ"* — ループは変わらない。新ツールは dispatch map に登録するだけ
+> **s02** *"ツールを足すなら、ハンドラーを1つ足すだけ"* — ループは変わらない。新ツールは `@Tool` アノテーション + `defaultTools()` で登録するだけ
>
> **s03** *"計画のないエージェントは行き当たりばったり"* — まずステップを書き出し、それから実行
>
@@ -190,34 +190,43 @@ Claude Code = 一つの agent loop
## コアパターン
-```python
-def agent_loop(messages):
- while True:
- response = client.messages.create(
- model=MODEL, system=SYSTEM,
- messages=messages, tools=TOOLS,
- )
- messages.append({"role": "assistant",
- "content": response.content})
+```java
+// Spring AI の ChatClient + @Tool 注解实现 Agent 循环
+// 模型自动决定何时调用工具、何时返回文本 -- 循环由框架驱动
- if response.stop_reason != "tool_use":
- return
+@SpringBootApplication
+public class S01AgentLoop implements CommandLineRunner {
- results = []
- for block in response.content:
- if block.type == "tool_use":
- output = TOOL_HANDLERS[block.name](**block.input)
- results.append({
- "type": "tool_result",
- "tool_use_id": block.id,
- "content": output,
- })
- messages.append({"role": "user", "content": results})
+ @Bean
+ public CommandLineRunner agentLoop(ChatClient.Builder builder) {
+ ChatClient chatClient = builder
+ .defaultSystem("You are a helpful assistant with access to tools.")
+ .defaultTools(new BashTool()) // 注册工具
+ .build();
+
+ return args -> {
+ // 一次 call() 内部自动完成: 调用模型 → 检测工具请求 → 执行工具 → 回传结果 → 再次调用模型...
+ String result = chatClient.prompt()
+ .user(userInput)
+ .call()
+ .content();
+ System.out.println(result);
+ };
+ }
+}
+
+// @Tool 注解让方法自动成为模型可调用的工具
+public class BashTool {
+ @Tool(description = "Execute a shell command and return stdout/stderr")
+ public String executeBash(String command) {
+ // 执行命令并返回结果
+ }
+}
```
-各セッションはこのループの上に 1 つの Harness メカニズムを重ねる -- ループ自体は変わらない。ループは Agent のもの。メカニズムは Harness のもの。
+Spring AI の `ChatClient.call()` は完全なエージェントループを内部にカプセル化:モデル呼び出し → ツール呼び出しリクエスト検出 → `@Tool` メソッド実行 → 結果をモデルに返却 → モデルがテキストを返すまで繰り返し。各セッションはこのループの上に 1 つの Harness メカニズムを重ねる -- ループ自体は変わらない。ループはエージェントのもの。メカニズムは Harness のもの。
-## スコープ (重要)
+## 範囲説明 (重要)
このリポジトリは Harness 工学の 0->1 学習プロジェクト -- Agent モデルを囲む環境の構築を学ぶ。
学習を優先するため、以下の本番メカニズムは意図的に簡略化または省略している:
@@ -232,15 +241,45 @@ def agent_loop(messages):
## クイックスタート
-```sh
-git clone https://github.com/shareAI-lab/learn-claude-code
-cd learn-claude-code
-pip install -r requirements.txt
-cp .env.example .env # .env を編集して ANTHROPIC_API_KEY を入力
+### 環境要件
-python agents/s01_agent_loop.py # ここから開始
-python agents/s12_worktree_task_isolation.py # 全セッションの到達点
-python agents/s_full.py # 総括: 全メカニズム統合
+- **JDK 21+** (推奨 [Eclipse Temurin](https://adoptium.net/) または GraalVM)
+- **Maven 3.9+**
+- OpenAI プロトコル互換の LLM API Key (DeepSeek、智谱 GLM、通義千問、OpenAI 等)
+
+### クローンとビルド
+
+```sh
+git clone https://github.com/abel533/learn-claude-code
+cd learn-claude-code
+mvn compile # 编译项目
+```
+
+### 環境変数の設定
+
+```sh
+# Linux / macOS
+export AI_API_KEY=your-api-key
+export AI_BASE_URL=https://api.deepseek.com # 替换为你的模型服务商地址
+export AI_MODEL=deepseek-chat # 替换为你使用的模型名称
+
+# Windows PowerShell
+$env:AI_API_KEY="your-api-key"
+$env:AI_BASE_URL="https://api.deepseek.com"
+$env:AI_MODEL="deepseek-chat"
+```
+
+### セッションの実行
+
+```sh
+# 从第一课开始
+mvn exec:java -Dexec.mainClass=io.mybatis.learn.s01.S01AgentLoop
+
+# 完整递进终点
+mvn exec:java -Dexec.mainClass=io.mybatis.learn.s12.S12WorktreeIsolation
+
+# 总纲: 全部机制合一
+mvn exec:java -Dexec.mainClass=io.mybatis.learn.full.SFullAgent
```
### Web プラットフォーム
@@ -251,16 +290,26 @@ python agents/s_full.py # 総括: 全メカニズム統合
cd web && npm install && npm run dev # http://localhost:3000
```
+### Java 版の特色
+
+本プロジェクトは **Java 21 + Spring Boot 3.5.7 + Spring AI 1.0.3** 技術スタックを採用しており、オリジナルの Python 版に比べ以下の特色がある:
+
+- **複数のLLMプロバイダーに対応** -- OpenAI プロトコルを通じて DeepSeek、智谱 GLM、通義千問、Moonshot 等の国産モデルに対応、特定ベンダーに縛られない
+- **`@Tool` アノテーションによるツール呼び出しループの自動処理** -- Spring AI フレームワークが「モデル呼び出し → ツール実行 → 結果返却」の完全なループを自動処理、while ループの手書き不要
+- **Java 21 仮想スレッド** -- 軽量な並行処理でバックグラウンドタスクとマルチエージェント協調を実現、スレッドプール管理のオーバーヘッド不要
+- **各セッション独立実行可能** -- 各セッションは `@SpringBootApplication` + `CommandLineRunner` であり、`mvn exec:java` 一行で起動可能
+- **型安全** -- Java の強い型システムがコンパイル時にエラーを検出、IDE の自動補完にも優しい
+
## 学習パス
```
フェーズ1: ループ フェーズ2: 計画と知識
================== ==============================
s01 エージェントループ [1] s03 TodoWrite [5]
- while + stop_reason TodoManager + nag リマインダー
+ ChatClient + @Tool TodoManager + nag リマインダー
| |
+-> s02 Tool Use [4] s04 サブエージェント [5]
- dispatch map: name->handler 子ごとに新しい messages[]
+ @Tool で複数ツール登録 各サブエージェントに独立 ChatClient
|
s05 Skills [5]
SKILL.md を tool_result で注入
@@ -274,7 +323,7 @@ s07 タスクシステム [8] s09 エージェントチーム
ファイルベース CRUD + 依存グラフ チームメイト + JSONL メールボックス
| |
s08 バックグラウンドタスク [6] s10 チームプロトコル [12]
- デーモンスレッド + 通知キュー シャットダウン + プラン承認 FSM
+ 仮想スレッド + 通知キュー シャットダウン + プラン承認 FSM
|
s11 自律エージェント [14]
アイドルサイクル + 自動クレーム
@@ -290,10 +339,27 @@ s08 バックグラウンドタスク [6] s10 チームプロトコル
```
learn-claude-code/
|
-|-- agents/ # Python リファレンス実装 (s01-s12 + s_full 総括)
+|-- src/main/java/io/mybatis/learn/ # Java 実装 (Spring AI + Spring Boot)
+| |-- core/ # 共通ツールクラス (AgentRunner, BashTool, EditFileTool 等)
+| |-- s01/ S01AgentLoop.java # セッション 01: エージェントループ
+| |-- s02/ S02ToolUse.java # セッション 02: マルチツール登録
+| |-- s03/ S03TodoWrite.java # セッション 03: 計画駆動
+| |-- s04/ S04Subagent.java # セッション 04: サブエージェント
+| |-- s05/ S05SkillLoading.java # セッション 05: Skill ロード
+| |-- s06/ S06ContextCompact.java # セッション 06: コンテキスト圧縮
+| |-- s07/ S07TaskSystem.java # セッション 07: タスクシステム
+| |-- s08/ S08BackgroundTasks.java # セッション 08: バックグラウンドタスク
+| |-- s09/ S09AgentTeams.java # セッション 09: エージェントチーム
+| |-- s10/ S10TeamProtocols.java # セッション 10: チームプロトコル
+| |-- s11/ S11AutonomousAgents.java# セッション 11: 自律エージェント
+| |-- s12/ S12WorktreeIsolation.java# セッション 12: Worktree 分離
+| +-- full/ SFullAgent.java # 総括: 全メカニズム統合
+|
+|-- agents/ # Python リファレンス実装 (オリジナル版、対照用に保存)
|-- docs/{en,zh,ja}/ # メンタルモデル優先のドキュメント (3言語)
|-- web/ # インタラクティブ学習プラットフォーム (Next.js)
|-- skills/ # s05 の Skill ファイル
+|-- pom.xml # Maven ビルド設定 (Spring Boot 3.5.7 + Spring AI 1.0.3)
+-- .github/workflows/ci.yml # CI: 型チェック + ビルド
```
diff --git a/README-zh.md b/README-zh.md
deleted file mode 100644
index fff83e4..0000000
--- a/README-zh.md
+++ /dev/null
@@ -1,372 +0,0 @@
-# Learn Claude Code -- 真正的 Agent Harness 工程
-
-[English](./README.md) | [中文](./README-zh.md) | [日本語](./README-ja.md)
-
-## 模型就是 Agent
-
-在讨论代码之前,先把一件事彻底说清楚。
-
-**Agent 是模型。不是框架。不是提示词链。不是拖拽式工作流。**
-
-### Agent 到底是什么
-
-Agent 是一个神经网络 -- Transformer、RNN、一个被训练出来的函数 -- 经过数十亿次梯度更新,在行动序列数据上学会了感知环境、推理目标、采取行动。"Agent" 这个词在 AI 领域从诞生之日起就是这个意思。从来都是。
-
-人类就是 agent。一个由数百万年进化训练出来的生物神经网络,通过感官感知世界,通过大脑推理,通过身体行动。当 DeepMind、OpenAI 或 Anthropic 说 "agent" 时,他们说的和这个领域自诞生以来就一直在说的完全一样:**一个学会了行动的模型。**
-
-历史已经写好了铁证:
-
-- **2013 -- DeepMind DQN 玩 Atari。** 一个神经网络,只接收原始像素和游戏分数,学会了 7 款 Atari 2600 游戏 -- 超越所有先前算法,在其中 3 款上击败人类专家。到 2015 年,同一架构扩展到 [49 款游戏,达到职业人类测试员水平](https://www.nature.com/articles/nature14236),论文发表在 *Nature*。没有游戏专属规则。没有决策树。一个模型,从经验中学习。那个模型就是 agent。
-
-- **2019 -- OpenAI Five 征服 Dota 2。** 五个神经网络,在 10 个月内与自己对战了 [45,000 年的 Dota 2](https://openai.com/index/openai-five-defeats-dota-2-world-champions/),在旧金山直播赛上 2-0 击败了 **OG** -- TI8 世界冠军。随后的公开竞技场中,AI 在 42,729 场比赛中胜率 99.4%。没有脚本化的策略。没有元编程的团队协调逻辑。模型完全通过自我对弈学会了团队协作、战术和实时适应。
-
-- **2019 -- DeepMind AlphaStar 制霸星际争霸 II。** AlphaStar 在闭门赛中 [10-1 击败职业选手](https://deepmind.google/blog/alphastar-mastering-the-real-time-strategy-game-starcraft-ii/),随后在欧洲服务器上达到[宗师段位](https://www.nature.com/articles/d41586-019-03298-6) -- 90,000 名玩家中的前 0.15%。一个信息不完全、实时决策、组合动作空间远超国际象棋和围棋的游戏。Agent 是什么?是模型。训练出来的。不是编出来的。
-
-- **2019 -- 腾讯绝悟统治王者荣耀。** 腾讯 AI Lab 的 "绝悟" 于 2019 年 8 月 2 日世冠杯半决赛上[以 5v5 击败 KPL 职业选手](https://www.jiemian.com/article/3371171.html)。在 1v1 模式下,职业选手 [15 场只赢 1 场,最多坚持不到 8 分钟](https://developer.aliyun.com/article/851058)。训练强度:一天等于人类 440 年。到 2021 年,绝悟在全英雄池 BO5 上全面超越 KPL 职业选手水准。没有手工编写的英雄克制表。没有脚本化的阵容编排。一个从零开始通过自我对弈学习整个游戏的模型。
-
-- **2024-2025 -- LLM Agent 重塑软件工程。** Claude、GPT、Gemini -- 在人类全部代码和推理上训练的大语言模型 -- 被部署为编程 agent。它们阅读代码库,编写实现,调试故障,团队协作。架构与之前每一个 agent 完全相同:一个训练好的模型,放入一个环境,给予感知和行动的工具。唯一的不同是它们学到的东西的规模和解决任务的通用性。
-
-每一个里程碑都共享同一个真理:**"Agent" 从来都不是外面那层代码。Agent 永远是模型本身。**
-
-### Agent 不是什么
-
-"Agent" 这个词已经被一整个提示词水管工产业劫持了。
-
-拖拽式工作流构建器。无代码 "AI Agent" 平台。提示词链编排库。它们共享同一个幻觉:把 LLM API 调用用 if-else 分支、节点图、硬编码路由逻辑串在一起就算是 "构建 Agent" 了。
-
-不是的。它们做出来的东西是鲁布·戈德堡机械 -- 一个过度工程化的、脆弱的过程式规则流水线,LLM 被楔在里面当一个美化了的文本补全节点。那不是 Agent。那是一个有着宏大妄想的 shell 脚本。
-
-**提示词水管工式 "Agent" 是不做模型的程序员的意淫。** 他们试图通过堆叠过程式逻辑来暴力模拟智能 -- 庞大的规则树、节点图、链式提示词瀑布流 -- 然后祈祷足够多的胶水代码能涌现出自主行为。不会的。你不可能通过工程手段编码出 agency。Agency 是学出来的,不是编出来的。
-
-那些系统从诞生之日起就已经死了:脆弱、不可扩展、根本不具备泛化能力。它们是 GOFAI(Good Old-Fashioned AI,经典符号 AI)的现代还魂 -- 几十年前就被学界抛弃的符号规则系统,现在喷了一层 LLM 的漆又登场了。换了个包装,同一条死路。
-
-### 心智转换:从 "开发 Agent" 到开发 Harness
-
-当一个人说 "我在开发 Agent" 时,他只可能是两个意思之一:
-
-**1. 训练模型。** 通过强化学习、微调、RLHF 或其他基于梯度的方法调整权重。收集任务过程数据 -- 真实领域中感知、推理、行动的实际序列 -- 用它们来塑造模型的行为。这是 DeepMind、OpenAI、腾讯 AI Lab、Anthropic 在做的事。这是最本义的 Agent 开发。
-
-**2. 构建 Harness。** 编写代码,为模型提供一个可操作的环境。这是我们大多数人在做的事,也是本仓库的核心。
-
-Harness 是 agent 在特定领域工作所需要的一切:
-
-```
-Harness = Tools + Knowledge + Observation + Action Interfaces + Permissions
-
- Tools: 文件读写、Shell、网络、数据库、浏览器
- Knowledge: 产品文档、领域资料、API 规范、风格指南
- Observation: git diff、错误日志、浏览器状态、传感器数据
- Action: CLI 命令、API 调用、UI 交互
- Permissions: 沙箱隔离、审批流程、信任边界
-```
-
-模型做决策。Harness 执行。模型做推理。Harness 提供上下文。模型是驾驶者。Harness 是载具。
-
-**编程 agent 的 harness 是它的 IDE、终端和文件系统。** 农业 agent 的 harness 是传感器阵列、灌溉控制和气象数据。酒店 agent 的 harness 是预订系统、客户沟通渠道和设施管理 API。Agent -- 那个智能、那个决策者 -- 永远是模型。Harness 因领域而变。Agent 跨领域泛化。
-
-这个仓库教你造载具。编程用的载具。但设计模式可以泛化到任何领域:庄园管理、农田运营、酒店运作、工厂制造、物流调度、医疗保健、教育培训、科学研究。只要有一个任务需要被感知、推理和执行 -- agent 就需要一个 harness。
-
-### Harness 工程师到底在做什么
-
-如果你在读这个仓库,你很可能是一名 harness 工程师 -- 这是一个强大的身份。以下是你真正的工作:
-
-- **实现工具。** 给 agent 一双手。文件读写、Shell 执行、API 调用、浏览器控制、数据库查询。每个工具都是 agent 在环境中可以采取的一个行动。设计它们时要原子化、可组合、描述清晰。
-
-- **策划知识。** 给 agent 领域专长。产品文档、架构决策记录、风格指南、合规要求。按需加载(s05),不要前置塞入。Agent 应该知道有什么可用,然后自己拉取所需。
-
-- **管理上下文。** 给 agent 干净的记忆。子 agent 隔离(s04)防止噪声泄露。上下文压缩(s06)防止历史淹没。任务系统(s07)让目标持久化到单次对话之外。
-
-- **控制权限。** 给 agent 边界。沙箱化文件访问。对破坏性操作要求审批。在 agent 和外部系统之间实施信任边界。这是安全工程与 harness 工程的交汇点。
-
-- **收集任务过程数据。** Agent 在你的 harness 中执行的每一条行动序列都是训练信号。真实部署中的感知-推理-行动轨迹是微调下一代 agent 模型的原材料。你的 harness 不仅服务于 agent -- 它还可以帮助进化 agent。
-
-你不是在编写智能。你是在构建智能栖居的世界。这个世界的质量 -- agent 能看得多清楚、行动得多精准、可用知识有多丰富 -- 直接决定了智能能多有效地表达自己。
-
-**造好 Harness。Agent 会完成剩下的。**
-
-### 为什么是 Claude Code -- Harness 工程的大师课
-
-为什么这个仓库专门拆解 Claude Code?
-
-因为 Claude Code 是我们所见过的最优雅、最完整的 agent harness 实现。不是因为某个巧妙的技巧,而是因为它 *没做* 的事:它没有试图成为 agent 本身。它没有强加僵化的工作流。它没有用精心设计的决策树去替模型做判断。它给模型提供了工具、知识、上下文管理和权限边界 -- 然后让开了。
-
-把 Claude Code 剥到本质来看:
-
-```
-Claude Code = 一个 agent loop
- + 工具 (bash, read, write, edit, glob, grep, browser...)
- + 按需 skill 加载
- + 上下文压缩
- + 子 agent 派生
- + 带依赖图的任务系统
- + 异步邮箱的团队协调
- + worktree 隔离的并行执行
- + 权限治理
-```
-
-就这些。这就是全部架构。每一个组件都是 harness 机制 -- 为 agent 构建的栖居世界的一部分。Agent 本身呢?是 Claude。一个模型。由 Anthropic 在人类推理和代码的全部广度上训练而成。Harness 没有让 Claude 变聪明。Claude 本来就聪明。Harness 给了 Claude 双手、双眼和一个工作空间。
-
-这就是 Claude Code 作为教学标本的意义:**它展示了当你信任模型、把工程精力集中在 harness 上时会发生什么。** 本仓库的每一个课程(s01-s12)都在逆向工程 Claude Code 架构中的一个 harness 机制。学完之后,你理解的不只是 Claude Code 怎么工作,而是适用于任何领域、任何 agent 的 harness 工程通用原则。
-
-启示不是 "复制 Claude Code"。启示是:**最好的 agent 产品,出自那些明白自己的工作是 harness 而非 intelligence 的工程师之手。**
-
----
-
-## 愿景:用真正的 Agent 铺满宇宙
-
-这不只关乎编程 agent。
-
-每一个人类从事复杂、多步骤、需要判断力的工作的领域,都是 agent 可以运作的领域 -- 只要有对的 harness。本仓库中的模式是通用的:
-
-```
-庄园管理 agent = 模型 + 物业传感器 + 维护工具 + 租户通信
-农业 agent = 模型 + 土壤/气象数据 + 灌溉控制 + 作物知识
-酒店运营 agent = 模型 + 预订系统 + 客户渠道 + 设施 API
-医学研究 agent = 模型 + 文献检索 + 实验仪器 + 协议文档
-制造业 agent = 模型 + 产线传感器 + 质量控制 + 物流系统
-教育 agent = 模型 + 课程知识 + 学生进度 + 评估工具
-```
-
-循环永远不变。工具在变。知识在变。权限在变。Agent -- 那个模型 -- 泛化一切。
-
-每一个读这个仓库的 harness 工程师都在学习远超软件工程的模式。你在学习为一个智能的、自动化的未来构建基础设施。每一个部署在真实领域的好 harness,都是 agent 能够感知、推理、行动的又一个阵地。
-
-先铺满工作室。然后是农田、医院、工厂。然后是城市。然后是星球。
-
-**Bash is all you need. Real agents are all the universe needs.**
-
----
-
-```
- THE AGENT PATTERN
- =================
-
- User --> messages[] --> LLM --> response
- |
- stop_reason == "tool_use"?
- / \
- yes no
- | |
- execute tools return text
- append results
- loop back -----------------> messages[]
-
-
- 这是最小循环。每个 AI Agent 都需要这个循环。
- 模型决定何时调用工具、何时停止。
- 代码只是执行模型的要求。
- 本仓库教你构建围绕这个循环的一切 --
- 让 agent 在特定领域高效工作的 harness。
-```
-
-**12 个递进式课程, 从简单循环到隔离化的自治执行。**
-**每个课程添加一个 harness 机制。每个机制有一句格言。**
-
-> **s01** *"One loop & Bash is all you need"* — 一个工具 + 一个循环 = 一个智能体
->
-> **s02** *"加一个工具, 只加一个 handler"* — 循环不用动, 新工具注册进 dispatch map 就行
->
-> **s03** *"没有计划的 agent 走哪算哪"* — 先列步骤再动手, 完成率翻倍
->
-> **s04** *"大任务拆小, 每个小任务干净的上下文"* — 子智能体用独立 messages[], 不污染主对话
->
-> **s05** *"用到什么知识, 临时加载什么知识"* — 通过 tool_result 注入, 不塞 system prompt
->
-> **s06** *"上下文总会满, 要有办法腾地方"* — 三层压缩策略, 换来无限会话
->
-> **s07** *"大目标要拆成小任务, 排好序, 记在磁盘上"* — 文件持久化的任务图, 为多 agent 协作打基础
->
-> **s08** *"慢操作丢后台, agent 继续想下一步"* — 后台线程跑命令, 完成后注入通知
->
-> **s09** *"任务太大一个人干不完, 要能分给队友"* — 持久化队友 + 异步邮箱
->
-> **s10** *"队友之间要有统一的沟通规矩"* — 一个 request-response 模式驱动所有协商
->
-> **s11** *"队友自己看看板, 有活就认领"* — 不需要领导逐个分配, 自组织
->
-> **s12** *"各干各的目录, 互不干扰"* — 任务管目标, worktree 管目录, 按 ID 绑定
-
----
-
-## 核心模式
-
-```python
-def agent_loop(messages):
- while True:
- response = client.messages.create(
- model=MODEL, system=SYSTEM,
- messages=messages, tools=TOOLS,
- )
- messages.append({"role": "assistant",
- "content": response.content})
-
- if response.stop_reason != "tool_use":
- return
-
- results = []
- for block in response.content:
- if block.type == "tool_use":
- output = TOOL_HANDLERS[block.name](**block.input)
- results.append({
- "type": "tool_result",
- "tool_use_id": block.id,
- "content": output,
- })
- messages.append({"role": "user", "content": results})
-```
-
-每个课程在这个循环之上叠加一个 harness 机制 -- 循环本身始终不变。循环属于 agent。机制属于 harness。
-
-## 范围说明 (重要)
-
-本仓库是一个 0->1 的 harness 工程学习项目 -- 构建围绕 agent 模型的工作环境。
-为保证学习路径清晰,仓库有意简化或省略了部分生产机制:
-
-- 完整事件 / Hook 总线 (例如 PreToolUse、SessionStart/End、ConfigChange)。
- s12 仅提供教学用途的最小 append-only 生命周期事件流。
-- 基于规则的权限治理与信任流程
-- 会话生命周期控制 (resume/fork) 与更完整的 worktree 生命周期控制
-- 完整 MCP 运行时细节 (transport/OAuth/资源订阅/轮询)
-
-仓库中的团队 JSONL 邮箱协议是教学实现,不是对任何特定生产内部实现的声明。
-
-## 快速开始
-
-```sh
-git clone https://github.com/shareAI-lab/learn-claude-code
-cd learn-claude-code
-pip install -r requirements.txt
-cp .env.example .env # 编辑 .env 填入你的 ANTHROPIC_API_KEY
-
-python agents/s01_agent_loop.py # 从这里开始
-python agents/s12_worktree_task_isolation.py # 完整递进终点
-python agents/s_full.py # 总纲: 全部机制合一
-```
-
-### Web 平台
-
-交互式可视化、分步动画、源码查看器, 以及每个课程的文档。
-
-```sh
-cd web && npm install && npm run dev # http://localhost:3000
-```
-
-## 学习路径
-
-```
-第一阶段: 循环 第二阶段: 规划与知识
-================== ==============================
-s01 Agent 循环 [1] s03 TodoWrite [5]
- while + stop_reason TodoManager + nag 提醒
- | |
- +-> s02 Tool Use [4] s04 子智能体 [5]
- dispatch map: name->handler 每个子智能体独立 messages[]
- |
- s05 Skills [5]
- SKILL.md 通过 tool_result 注入
- |
- s06 Context Compact [5]
- 三层上下文压缩
-
-第三阶段: 持久化 第四阶段: 团队
-================== =====================
-s07 任务系统 [8] s09 智能体团队 [9]
- 文件持久化 CRUD + 依赖图 队友 + JSONL 邮箱
- | |
-s08 后台任务 [6] s10 团队协议 [12]
- 守护线程 + 通知队列 关机 + 计划审批 FSM
- |
- s11 自治智能体 [14]
- 空闲轮询 + 自动认领
- |
- s12 Worktree 隔离 [16]
- 任务协调 + 按需隔离执行通道
-
- [N] = 工具数量
-```
-
-## 项目结构
-
-```
-learn-claude-code/
-|
-|-- agents/ # Python 参考实现 (s01-s12 + s_full 总纲)
-|-- docs/{en,zh,ja}/ # 心智模型优先的文档 (3 种语言)
-|-- web/ # 交互式学习平台 (Next.js)
-|-- skills/ # s05 的 Skill 文件
-+-- .github/workflows/ci.yml # CI: 类型检查 + 构建
-```
-
-## 文档
-
-心智模型优先: 问题、方案、ASCII 图、最小化代码。
-[English](./docs/en/) | [中文](./docs/zh/) | [日本語](./docs/ja/)
-
-| 课程 | 主题 | 格言 |
-|------|------|------|
-| [s01](./docs/zh/s01-the-agent-loop.md) | Agent 循环 | *One loop & Bash is all you need* |
-| [s02](./docs/zh/s02-tool-use.md) | Tool Use | *加一个工具, 只加一个 handler* |
-| [s03](./docs/zh/s03-todo-write.md) | TodoWrite | *没有计划的 agent 走哪算哪* |
-| [s04](./docs/zh/s04-subagent.md) | 子智能体 | *大任务拆小, 每个小任务干净的上下文* |
-| [s05](./docs/zh/s05-skill-loading.md) | Skills | *用到什么知识, 临时加载什么知识* |
-| [s06](./docs/zh/s06-context-compact.md) | Context Compact | *上下文总会满, 要有办法腾地方* |
-| [s07](./docs/zh/s07-task-system.md) | 任务系统 | *大目标要拆成小任务, 排好序, 记在磁盘上* |
-| [s08](./docs/zh/s08-background-tasks.md) | 后台任务 | *慢操作丢后台, agent 继续想下一步* |
-| [s09](./docs/zh/s09-agent-teams.md) | 智能体团队 | *任务太大一个人干不完, 要能分给队友* |
-| [s10](./docs/zh/s10-team-protocols.md) | 团队协议 | *队友之间要有统一的沟通规矩* |
-| [s11](./docs/zh/s11-autonomous-agents.md) | 自治智能体 | *队友自己看看板, 有活就认领* |
-| [s12](./docs/zh/s12-worktree-task-isolation.md) | Worktree + 任务隔离 | *各干各的目录, 互不干扰* |
-
-## 学完之后 -- 从理解到落地
-
-12 个课程走完, 你已经从内到外理解了 harness 工程的运作原理。两种方式把知识变成产品:
-
-### Kode Agent CLI -- 开源 Coding Agent CLI
-
-> `npm i -g @shareai-lab/kode`
-
-支持 Skill & LSP, 适配 Windows, 可接 GLM / MiniMax / DeepSeek 等开放模型。装完即用。
-
-GitHub: **[shareAI-lab/Kode-cli](https://github.com/shareAI-lab/Kode-cli)**
-
-### Kode Agent SDK -- 把 Agent 能力嵌入你的应用
-
-官方 Claude Code Agent SDK 底层与完整 CLI 进程通信 -- 每个并发用户 = 一个终端进程。Kode SDK 是独立库, 无 per-user 进程开销, 可嵌入后端、浏览器插件、嵌入式设备等任意运行时。
-
-GitHub: **[shareAI-lab/Kode-agent-sdk](https://github.com/shareAI-lab/Kode-agent-sdk)**
-
----
-
-## 姊妹教程: 从*被动临时会话*到*主动常驻助手*
-
-本仓库教的 harness 属于 **用完即走** 型 -- 开终端、给 agent 任务、做完关掉, 下次重开是全新会话。Claude Code 就是这种模式。
-
-但 [OpenClaw](https://github.com/openclaw/openclaw) 证明了另一种可能: 在同样的 agent core 之上, 加两个 harness 机制就能让 agent 从 "踹一下动一下" 变成 "自己隔 30 秒醒一次找活干":
-
-- **心跳 (Heartbeat)** -- 每 30 秒 harness 给 agent 发一条消息, 让它检查有没有事可做。没事就继续睡, 有事立刻行动。
-- **定时任务 (Cron)** -- agent 可以给自己安排未来要做的事, 到点自动执行。
-
-再加上 IM 多通道路由 (WhatsApp/Telegram/Slack/Discord 等 13+ 平台)、不清空的上下文记忆、Soul 人格系统, agent 就从一个临时工具变成了始终在线的个人 AI 助手。
-
-**[claw0](https://github.com/shareAI-lab/claw0)** 是我们的姊妹教学仓库, 从零拆解这些 harness 机制:
-
-```
-claw agent = agent core + heartbeat + cron + IM chat + memory + soul
-```
-
-```
-learn-claude-code claw0
-(agent harness 内核: (主动式常驻 harness:
- 循环、工具、规划、 心跳、定时任务、IM 通道、
- 团队、worktree 隔离) 记忆、Soul 人格)
-```
-
-## 许可证
-
-MIT
-
----
-
-**模型就是 Agent。代码是 Harness。造好 Harness,Agent 会完成剩下的。**
-
-**Bash is all you need. Real agents are all the universe needs.**
diff --git a/README.md b/README.md
index c25a634..2c55b3b 100644
--- a/README.md
+++ b/README.md
@@ -1,136 +1,137 @@
-[English](./README.md) | [中文](./README-zh.md) | [日本語](./README-ja.md)
-# Learn Claude Code -- Harness Engineering for Real Agents
+# Learn Claude Code -- 真正的 Agent Harness 工程
-## The Model IS the Agent
+[English](./README-en.md) | [中文](./README.md) | [日本語](./README-ja.md)
-Before we talk about code, let's get one thing absolutely straight.
+## 模型就是 Agent
-**An agent is a model. Not a framework. Not a prompt chain. Not a drag-and-drop workflow.**
+在讨论代码之前,先把一件事彻底说清楚。
-### What an Agent IS
+**Agent 是模型。不是框架。不是提示词链。不是拖拽式工作流。**
-An agent is a neural network -- a Transformer, an RNN, a learned function -- that has been trained, through billions of gradient updates on action-sequence data, to perceive an environment, reason about goals, and take actions to achieve them. The word "agent" in AI has always meant this. Always.
+### Agent 到底是什么
-A human is an agent. A biological neural network, shaped by millions of years of evolutionary training, perceiving the world through senses, reasoning through a brain, acting through a body. When DeepMind, OpenAI, or Anthropic say "agent," they mean the same thing the field has meant since its inception: **a model that has learned to act.**
+Agent 是一个神经网络 -- Transformer、RNN、一个被训练出来的函数 -- 经过数十亿次梯度更新,在行动序列数据上学会了感知环境、推理目标、采取行动。"Agent" 这个词在 AI 领域从诞生之日起就是这个意思。从来都是。
-The proof is written in history:
+人类就是 agent。一个由数百万年进化训练出来的生物神经网络,通过感官感知世界,通过大脑推理,通过身体行动。当 DeepMind、OpenAI 或 Anthropic 说 "agent" 时,他们说的和这个领域自诞生以来就一直在说的完全一样:**一个学会了行动的模型。**
-- **2013 -- DeepMind DQN plays Atari.** A single neural network, receiving only raw pixels and game scores, learned to play 7 Atari 2600 games -- surpassing all prior algorithms and beating human experts on 3 of them. By 2015, the same architecture scaled to [49 games and matched professional human testers](https://www.nature.com/articles/nature14236), published in *Nature*. No game-specific rules. No decision trees. One model, learning from experience. That model was the agent.
+历史已经写好了铁证:
-- **2019 -- OpenAI Five conquers Dota 2.** Five neural networks, having played [45,000 years of Dota 2](https://openai.com/index/openai-five-defeats-dota-2-world-champions/) against themselves in 10 months, defeated **OG** -- the reigning TI8 world champions -- 2-0 on a San Francisco livestream. In a subsequent public arena, the AI won 99.4% of 42,729 games against all comers. No scripted strategies. No meta-programmed team coordination. The models learned teamwork, tactics, and real-time adaptation entirely through self-play.
+- **2013 -- DeepMind DQN 玩 Atari。** 一个神经网络,只接收原始像素和游戏分数,学会了 7 款 Atari 2600 游戏 -- 超越所有先前算法,在其中 3 款上击败人类专家。到 2015 年,同一架构扩展到 [49 款游戏,达到职业人类测试员水平](https://www.nature.com/articles/nature14236),论文发表在 *Nature*。没有游戏专属规则。没有决策树。一个模型,从经验中学习。那个模型就是 agent。
-- **2019 -- DeepMind AlphaStar masters StarCraft II.** AlphaStar [beat professional players 10-1](https://deepmind.google/blog/alphastar-mastering-the-real-time-strategy-game-starcraft-ii/) in a closed-door match, and later achieved [Grandmaster status](https://www.nature.com/articles/d41586-019-03298-6) on European servers -- top 0.15% of 90,000 players. A game with imperfect information, real-time decisions, and a combinatorial action space that dwarfs chess and Go. The agent? A model. Trained. Not scripted.
+- **2019 -- OpenAI Five 征服 Dota 2。** 五个神经网络,在 10 个月内与自己对战了 [45,000 年的 Dota 2](https://openai.com/index/openai-five-defeats-dota-2-world-champions/),在旧金山直播赛上 2-0 击败了 **OG** -- TI8 世界冠军。随后的公开竞技场中,AI 在 42,729 场比赛中胜率 99.4%。没有脚本化的策略。没有元编程的团队协调逻辑。模型完全通过自我对弈学会了团队协作、战术和实时适应。
-- **2019 -- Tencent Jueyu dominates Honor of Kings.** Tencent AI Lab's "Jueyu" [defeated KPL professional players](https://www.jiemian.com/article/3371171.html) in a full 5v5 match at the World Champion Cup. In 1v1 mode, pros won only [1 out of 15 games and never survived past 8 minutes](https://developer.aliyun.com/article/851058). Training intensity: one day equaled 440 human years. By 2021, Jueyu surpassed KPL pros across the full hero pool. No handcrafted matchup tables. No scripted compositions. A model that learned the entire game from scratch through self-play.
+- **2019 -- DeepMind AlphaStar 制霸星际争霸 II。** AlphaStar 在闭门赛中 [10-1 击败职业选手](https://deepmind.google/blog/alphastar-mastering-the-real-time-strategy-game-starcraft-ii/),随后在欧洲服务器上达到[宗师段位](https://www.nature.com/articles/d41586-019-03298-6) -- 90,000 名玩家中的前 0.15%。一个信息不完全、实时决策、组合动作空间远超国际象棋和围棋的游戏。Agent 是什么?是模型。训练出来的。不是编出来的。
-- **2024-2025 -- LLM agents reshape software engineering.** Claude, GPT, Gemini -- large language models trained on the entirety of human code and reasoning -- are deployed as coding agents. They read codebases, write implementations, debug failures, coordinate in teams. The architecture is identical to every agent before them: a trained model, placed in an environment, given tools to perceive and act. The only difference is the scale of what they've learned and the generality of the tasks they solve.
+- **2019 -- 腾讯绝悟统治王者荣耀。** 腾讯 AI Lab 的 "绝悟" 于 2019 年 8 月 2 日世冠杯半决赛上[以 5v5 击败 KPL 职业选手](https://www.jiemian.com/article/3371171.html)。在 1v1 模式下,职业选手 [15 场只赢 1 场,最多坚持不到 8 分钟](https://developer.aliyun.com/article/851058)。训练强度:一天等于人类 440 年。到 2021 年,绝悟在全英雄池 BO5 上全面超越 KPL 职业选手水准。没有手工编写的英雄克制表。没有脚本化的阵容编排。一个从零开始通过自我对弈学习整个游戏的模型。
-Every one of these milestones shares the same truth: **the "agent" is never the surrounding code. The agent is always the model.**
+- **2024-2025 -- LLM Agent 重塑软件工程。** Claude、GPT、Gemini -- 在人类全部代码和推理上训练的大语言模型 -- 被部署为编程 agent。它们阅读代码库,编写实现,调试故障,团队协作。架构与之前每一个 agent 完全相同:一个训练好的模型,放入一个环境,给予感知和行动的工具。唯一的不同是它们学到的东西的规模和解决任务的通用性。
-### What an Agent Is NOT
+每一个里程碑都共享同一个真理:**"Agent" 从来都不是外面那层代码。Agent 永远是模型本身。**
-The word "agent" has been hijacked by an entire cottage industry of prompt plumbing.
+### Agent 不是什么
-Drag-and-drop workflow builders. No-code "AI agent" platforms. Prompt-chain orchestration libraries. They all share the same delusion: that wiring together LLM API calls with if-else branches, node graphs, and hardcoded routing logic constitutes "building an agent."
+"Agent" 这个词已经被一整个提示词水管工产业劫持了。
-It doesn't. What they build is a Rube Goldberg machine -- an over-engineered, brittle pipeline of procedural rules, with an LLM wedged in as a glorified text-completion node. That is not an agent. That is a shell script with delusions of grandeur.
+拖拽式工作流构建器。无代码 "AI Agent" 平台。提示词链编排库。它们共享同一个幻觉:把 LLM API 调用用 if-else 分支、节点图、硬编码路由逻辑串在一起就算是 "构建 Agent" 了。
-**Prompt plumbing "agents" are the fantasy of programmers who don't train models.** They attempt to brute-force intelligence by stacking procedural logic -- massive rule trees, node graphs, chain-of-prompt waterfalls -- and praying that enough glue code will somehow emergently produce autonomous behavior. It won't. You cannot engineer your way to agency. Agency is learned, not programmed.
+不是的。它们做出来的东西是鲁布·戈德堡机械 -- 一个过度工程化的、脆弱的过程式规则流水线,LLM 被楔在里面当一个美化了的文本补全节点。那不是 Agent。那是一个有着宏大妄想的 shell 脚本。
-Those systems are dead on arrival: fragile, unscalable, fundamentally incapable of generalization. They are the modern resurrection of GOFAI (Good Old-Fashioned AI) -- the symbolic rule systems the field abandoned decades ago, now spray-painted with an LLM veneer. Different packaging, same dead end.
+**提示词水管工式 "Agent" 是不做模型的程序员的意淫。** 他们试图通过堆叠过程式逻辑来暴力模拟智能 -- 庞大的规则树、节点图、链式提示词瀑布流 -- 然后祈祷足够多的胶水代码能涌现出自主行为。不会的。你不可能通过工程手段编码出 agency。Agency 是学出来的,不是编出来的。
-### The Mind Shift: From "Developing Agents" to Developing Harness
+那些系统从诞生之日起就已经死了:脆弱、不可扩展、根本不具备泛化能力。它们是 GOFAI(Good Old-Fashioned AI,经典符号 AI)的现代还魂 -- 几十年前就被学界抛弃的符号规则系统,现在喷了一层 LLM 的漆又登场了。换了个包装,同一条死路。
-When someone says "I'm developing an agent," they can only mean one of two things:
+### 心智转换:从 "开发 Agent" 到开发 Harness
-**1. Training the model.** Adjusting weights through reinforcement learning, fine-tuning, RLHF, or other gradient-based methods. Collecting task-process data -- the actual sequences of perception, reasoning, and action in real domains -- and using it to shape the model's behavior. This is what DeepMind, OpenAI, Tencent AI Lab, and Anthropic do. This is agent development in the truest sense.
+当一个人说 "我在开发 Agent" 时,他只可能是两个意思之一:
-**2. Building the harness.** Writing the code that gives the model an environment to operate in. This is what most of us do, and it is the focus of this repository.
+**1. 训练模型。** 通过强化学习、微调、RLHF 或其他基于梯度的方法调整权重。收集任务过程数据 -- 真实领域中感知、推理、行动的实际序列 -- 用它们来塑造模型的行为。这是 DeepMind、OpenAI、腾讯 AI Lab、Anthropic 在做的事。这是最本义的 Agent 开发。
-A harness is everything the agent needs to function in a specific domain:
+**2. 构建 Harness。** 编写代码,为模型提供一个可操作的环境。这是我们大多数人在做的事,也是本仓库的核心。
+
+Harness 是 agent 在特定领域工作所需要的一切:
```
Harness = Tools + Knowledge + Observation + Action Interfaces + Permissions
- Tools: file I/O, shell, network, database, browser
- Knowledge: product docs, domain references, API specs, style guides
- Observation: git diff, error logs, browser state, sensor data
- Action: CLI commands, API calls, UI interactions
- Permissions: sandboxing, approval workflows, trust boundaries
+ Tools: 文件读写、Shell、网络、数据库、浏览器
+ Knowledge: 产品文档、领域资料、API 规范、风格指南
+ Observation: git diff、错误日志、浏览器状态、传感器数据
+ Action: CLI 命令、API 调用、UI 交互
+ Permissions: 沙箱隔离、审批流程、信任边界
```
-The model decides. The harness executes. The model reasons. The harness provides context. The model is the driver. The harness is the vehicle.
+模型做决策。Harness 执行。模型做推理。Harness 提供上下文。模型是驾驶者。Harness 是载具。
-**A coding agent's harness is its IDE, terminal, and filesystem access.** A farm agent's harness is its sensor array, irrigation controls, and weather data feeds. A hotel agent's harness is its booking system, guest communication channels, and facility management APIs. The agent -- the intelligence, the decision-maker -- is always the model. The harness changes per domain. The agent generalizes across them.
+**编程 agent 的 harness 是它的 IDE、终端和文件系统。** 农业 agent 的 harness 是传感器阵列、灌溉控制和气象数据。酒店 agent 的 harness 是预订系统、客户沟通渠道和设施管理 API。Agent -- 那个智能、那个决策者 -- 永远是模型。Harness 因领域而变。Agent 跨领域泛化。
-This repo teaches you to build vehicles. Vehicles for coding. But the design patterns generalize to any domain: farm management, hotel operations, manufacturing, logistics, healthcare, education, scientific research. Anywhere a task needs to be perceived, reasoned about, and acted upon -- an agent needs a harness.
+这个仓库教你造载具。编程用的载具。但设计模式可以泛化到任何领域:庄园管理、农田运营、酒店运作、工厂制造、物流调度、医疗保健、教育培训、科学研究。只要有一个任务需要被感知、推理和执行 -- agent 就需要一个 harness。
-### What Harness Engineers Actually Do
+### Harness 工程师到底在做什么
-If you are reading this repository, you are likely a harness engineer -- and that is a powerful thing to be. Here is your real job:
+如果你在读这个仓库,你很可能是一名 harness 工程师 -- 这是一个强大的身份。以下是你真正的工作:
-- **Implement tools.** Give the agent hands. File read/write, shell execution, API calls, browser control, database queries. Each tool is an action the agent can take in its environment. Design them to be atomic, composable, and well-described.
+- **实现工具。** 给 agent 一双手。文件读写、Shell 执行、API 调用、浏览器控制、数据库查询。每个工具都是 agent 在环境中可以采取的一个行动。设计它们时要原子化、可组合、描述清晰。
-- **Curate knowledge.** Give the agent domain expertise. Product documentation, architectural decision records, style guides, regulatory requirements. Load them on-demand (s05), not upfront. The agent should know what's available and pull what it needs.
+- **策划知识。** 给 agent 领域专长。产品文档、架构决策记录、风格指南、合规要求。按需加载(s05),不要前置塞入。Agent 应该知道有什么可用,然后自己拉取所需。
-- **Manage context.** Give the agent clean memory. Subagent isolation (s04) prevents noise from leaking. Context compression (s06) prevents history from overwhelming. Task systems (s07) persist goals beyond any single conversation.
+- **管理上下文。** 给 agent 干净的记忆。子 agent 隔离(s04)防止噪声泄露。上下文压缩(s06)防止历史淹没。任务系统(s07)让目标持久化到单次对话之外。
-- **Control permissions.** Give the agent boundaries. Sandbox file access. Require approval for destructive operations. Enforce trust boundaries between the agent and external systems. This is where safety engineering meets harness engineering.
+- **控制权限。** 给 agent 边界。沙箱化文件访问。对破坏性操作要求审批。在 agent 和外部系统之间实施信任边界。这是安全工程与 harness 工程的交汇点。
-- **Collect task-process data.** Every action sequence the agent executes in your harness is training signal. The perception-reasoning-action traces from real deployments are the raw material for fine-tuning the next generation of agent models. Your harness doesn't just serve the agent -- it can help improve the agent.
+- **收集任务过程数据。** Agent 在你的 harness 中执行的每一条行动序列都是训练信号。真实部署中的感知-推理-行动轨迹是微调下一代 agent 模型的原材料。你的 harness 不仅服务于 agent -- 它还可以帮助进化 agent。
-You are not writing the intelligence. You are building the world the intelligence inhabits. The quality of that world -- how clearly the agent can perceive, how precisely it can act, how rich its available knowledge is -- directly determines how effectively the intelligence can express itself.
+你不是在编写智能。你是在构建智能栖居的世界。这个世界的质量 -- agent 能看得多清楚、行动得多精准、可用知识有多丰富 -- 直接决定了智能能多有效地表达自己。
-**Build great harnesses. The agent will do the rest.**
+**造好 Harness。Agent 会完成剩下的。**
-### Why Claude Code -- A Masterclass in Harness Engineering
+### 为什么是 Claude Code -- Harness 工程的大师课
-Why does this repository dissect Claude Code specifically?
+为什么这个仓库专门拆解 Claude Code?
-Because Claude Code is the most elegant and fully-realized agent harness we have seen. Not because of any single clever trick, but because of what it *doesn't* do: it doesn't try to be the agent. It doesn't impose rigid workflows. It doesn't second-guess the model with elaborate decision trees. It provides the model with tools, knowledge, context management, and permission boundaries -- then gets out of the way.
+因为 Claude Code 是我们所见过的最优雅、最完整的 agent harness 实现。不是因为某个巧妙的技巧,而是因为它 *没做* 的事:它没有试图成为 agent 本身。它没有强加僵化的工作流。它没有用精心设计的决策树去替模型做判断。它给模型提供了工具、知识、上下文管理和权限边界 -- 然后让开了。
-Look at what Claude Code actually is, stripped to its essence:
+把 Claude Code 剥到本质来看:
```
-Claude Code = one agent loop
- + tools (bash, read, write, edit, glob, grep, browser...)
- + on-demand skill loading
- + context compression
- + subagent spawning
- + task system with dependency graph
- + team coordination with async mailboxes
- + worktree isolation for parallel execution
- + permission governance
+Claude Code = 一个 agent loop
+ + 工具 (bash, read, write, edit, glob, grep, browser...)
+ + 按需 skill 加载
+ + 上下文压缩
+ + 子 agent 派生
+ + 带依赖图的任务系统
+ + 异步邮箱的团队协调
+ + worktree 隔离的并行执行
+ + 权限治理
```
-That's it. That's the entire architecture. Every component is a harness mechanism -- a piece of the world built for the agent to inhabit. The agent itself? It's Claude. A model. Trained by Anthropic on the full breadth of human reasoning and code. The harness doesn't make Claude smart. Claude is already smart. The harness gives Claude hands, eyes, and a workspace.
+就这些。这就是全部架构。每一个组件都是 harness 机制 -- 为 agent 构建的栖居世界的一部分。Agent 本身呢?是 Claude。一个模型。由 Anthropic 在人类推理和代码的全部广度上训练而成。Harness 没有让 Claude 变聪明。Claude 本来就聪明。Harness 给了 Claude 双手、双眼和一个工作空间。
-This is why Claude Code is the ideal teaching subject: **it demonstrates what happens when you trust the model and focus your engineering on the harness.** Every session in this repository (s01-s12) reverse-engineers one harness mechanism from Claude Code's architecture. By the end, you understand not just how Claude Code works, but the universal principles of harness engineering that apply to any agent in any domain.
+这就是 Claude Code 作为教学标本的意义:**它展示了当你信任模型、把工程精力集中在 harness 上时会发生什么。** 本仓库的每一个课程(s01-s12)都在逆向工程 Claude Code 架构中的一个 harness 机制。学完之后,你理解的不只是 Claude Code 怎么工作,而是适用于任何领域、任何 agent 的 harness 工程通用原则。
-The lesson is not "copy Claude Code." The lesson is: **the best agent products are built by engineers who understand that their job is harness, not intelligence.**
+启示不是 "复制 Claude Code"。启示是:**最好的 agent 产品,出自那些明白自己的工作是 harness 而非 intelligence 的工程师之手。**
---
-## The Vision: Fill the Universe with Real Agents
+## 愿景:用真正的 Agent 铺满宇宙
-This is not just about coding agents.
+这不只关乎编程 agent。
-Every domain where humans perform complex, multi-step, judgment-intensive work is a domain where agents can operate -- given the right harness. The patterns in this repository are universal:
+每一个人类从事复杂、多步骤、需要判断力的工作的领域,都是 agent 可以运作的领域 -- 只要有对的 harness。本仓库中的模式是通用的:
```
-Estate management agent = model + property sensors + maintenance tools + tenant comms
-Agricultural agent = model + soil/weather data + irrigation controls + crop knowledge
-Hotel operations agent = model + booking system + guest channels + facility APIs
-Medical research agent = model + literature search + lab instruments + protocol docs
-Manufacturing agent = model + production line sensors + quality controls + logistics
-Education agent = model + curriculum knowledge + student progress + assessment tools
+庄园管理 agent = 模型 + 物业传感器 + 维护工具 + 租户通信
+农业 agent = 模型 + 土壤/气象数据 + 灌溉控制 + 作物知识
+酒店运营 agent = 模型 + 预订系统 + 客户渠道 + 设施 API
+医学研究 agent = 模型 + 文献检索 + 实验仪器 + 协议文档
+制造业 agent = 模型 + 产线传感器 + 质量控制 + 物流系统
+教育 agent = 模型 + 课程知识 + 学生进度 + 评估工具
```
-The loop is always the same. The tools change. The knowledge changes. The permissions change. The agent -- the model -- generalizes.
+循环永远不变。工具在变。知识在变。权限在变。Agent -- 那个模型 -- 泛化一切。
-Every harness engineer reading this repository is learning patterns that apply far beyond software engineering. You are learning to build the infrastructure for an intelligent, automated future. Every well-designed harness deployed in a real domain is one more place where an agent can perceive, reason, and act.
+每一个读这个仓库的 harness 工程师都在学习远超软件工程的模式。你在学习为一个智能的、自动化的未来构建基础设施。每一个部署在真实领域的好 harness,都是 agent 能够感知、推理、行动的又一个阵地。
-First we fill the workshops. Then the farms, the hospitals, the factories. Then the cities. Then the planet.
+先铺满工作室。然后是农田、医院、工厂。然后是城市。然后是星球。
**Bash is all you need. Real agents are all the universe needs.**
@@ -142,212 +143,278 @@ First we fill the workshops. Then the farms, the hospitals, the factories. Then
User --> messages[] --> LLM --> response
|
- stop_reason == "tool_use"?
+ 还有工具调用请求?
/ \
- yes no
+ 有 无
| |
- execute tools return text
- append results
- loop back -----------------> messages[]
+ 执行 @Tool 方法 返回文本
+ 回传结果
+ 继续循环 -----------------> messages[]
- That's the minimal loop. Every AI agent needs this loop.
- The MODEL decides when to call tools and when to stop.
- The CODE just executes what the model asks for.
- This repo teaches you to build what surrounds this loop --
- the harness that makes the agent effective in a specific domain.
+ 这是最小循环。每个 AI Agent 都需要这个循环。
+ 模型决定何时调用工具、何时停止。
+ Spring AI 的 ChatClient.call() 自动驱动此循环。
+ 本仓库教你构建围绕这个循环的一切 --
+ 让 agent 在特定领域高效工作的 harness。
```
-**12 progressive sessions, from a simple loop to isolated autonomous execution.**
-**Each session adds one harness mechanism. Each mechanism has one motto.**
+**12 个递进式课程, 从简单循环到隔离化的自治执行。**
+**每个课程添加一个 harness 机制。每个机制有一句格言。**
-> **s01** *"One loop & Bash is all you need"* — one tool + one loop = an agent
+> **s01** *"One loop & Bash is all you need"* — 一个工具 + 一个循环 = 一个智能体
>
-> **s02** *"Adding a tool means adding one handler"* — the loop stays the same; new tools register into the dispatch map
+> **s02** *"加一个工具, 只加一个 handler"* — 循环不用动, 新工具用 `@Tool` 注解 + `defaultTools()` 注册就行
>
-> **s03** *"An agent without a plan drifts"* — list the steps first, then execute; completion doubles
+> **s03** *"没有计划的 agent 走哪算哪"* — 先列步骤再动手, 完成率翻倍
>
-> **s04** *"Break big tasks down; each subtask gets a clean context"* — subagents use independent messages[], keeping the main conversation clean
+> **s04** *"大任务拆小, 每个小任务干净的上下文"* — 子智能体用独立 messages[], 不污染主对话
>
-> **s05** *"Load knowledge when you need it, not upfront"* — inject via tool_result, not the system prompt
+> **s05** *"用到什么知识, 临时加载什么知识"* — 通过 tool_result 注入, 不塞 system prompt
>
-> **s06** *"Context will fill up; you need a way to make room"* — three-layer compression strategy for infinite sessions
+> **s06** *"上下文总会满, 要有办法腾地方"* — 三层压缩策略, 换来无限会话
>
-> **s07** *"Break big goals into small tasks, order them, persist to disk"* — a file-based task graph with dependencies, laying the foundation for multi-agent collaboration
+> **s07** *"大目标要拆成小任务, 排好序, 记在磁盘上"* — 文件持久化的任务图, 为多 agent 协作打基础
>
-> **s08** *"Run slow operations in the background; the agent keeps thinking"* — daemon threads run commands, inject notifications on completion
+> **s08** *"慢操作丢后台, agent 继续想下一步"* — 后台线程跑命令, 完成后注入通知
>
-> **s09** *"When the task is too big for one, delegate to teammates"* — persistent teammates + async mailboxes
+> **s09** *"任务太大一个人干不完, 要能分给队友"* — 持久化队友 + 异步邮箱
>
-> **s10** *"Teammates need shared communication rules"* — one request-response pattern drives all negotiation
+> **s10** *"队友之间要有统一的沟通规矩"* — 一个 request-response 模式驱动所有协商
>
-> **s11** *"Teammates scan the board and claim tasks themselves"* — no need for the lead to assign each one
+> **s11** *"队友自己看看板, 有活就认领"* — 不需要领导逐个分配, 自组织
>
-> **s12** *"Each works in its own directory, no interference"* — tasks manage goals, worktrees manage directories, bound by ID
+> **s12** *"各干各的目录, 互不干扰"* — 任务管目标, worktree 管目录, 按 ID 绑定
---
-## The Core Pattern
+## 核心模式
-```python
-def agent_loop(messages):
- while True:
- response = client.messages.create(
- model=MODEL, system=SYSTEM,
- messages=messages, tools=TOOLS,
- )
- messages.append({"role": "assistant",
- "content": response.content})
+```java
+// Spring AI 的 ChatClient + @Tool 注解实现 Agent 循环
+// 模型自动决定何时调用工具、何时返回文本 -- 循环由框架驱动
- if response.stop_reason != "tool_use":
- return
+@SpringBootApplication
+public class S01AgentLoop implements CommandLineRunner {
- results = []
- for block in response.content:
- if block.type == "tool_use":
- output = TOOL_HANDLERS[block.name](**block.input)
- results.append({
- "type": "tool_result",
- "tool_use_id": block.id,
- "content": output,
- })
- messages.append({"role": "user", "content": results})
+ @Bean
+ public CommandLineRunner agentLoop(ChatClient.Builder builder) {
+ ChatClient chatClient = builder
+ .defaultSystem("You are a helpful assistant with access to tools.")
+ .defaultTools(new BashTool()) // 注册工具
+ .build();
+
+ return args -> {
+ // 一次 call() 内部自动完成: 调用模型 → 检测工具请求 → 执行工具 → 回传结果 → 再次调用模型...
+ String result = chatClient.prompt()
+ .user(userInput)
+ .call()
+ .content();
+ System.out.println(result);
+ };
+ }
+}
+
+// @Tool 注解让方法自动成为模型可调用的工具
+public class BashTool {
+ @Tool(description = "Execute a shell command and return stdout/stderr")
+ public String executeBash(String command) {
+ // 执行命令并返回结果
+ }
+}
```
-Every session layers one harness mechanism on top of this loop -- without changing the loop itself. The loop belongs to the agent. The mechanisms belong to the harness.
+Spring AI 的 `ChatClient.call()` 内部封装了完整的 agent 循环:调用大模型 → 检测工具调用请求 → 执行 `@Tool` 方法 → 将结果回传模型 → 重复直到模型返回文本。每个课程在这个循环之上叠加一个 harness 机制 -- 循环本身始终不变。循环属于 agent。机制属于 harness。
-## Scope (Important)
+## 范围说明 (重要)
-This repository is a 0->1 learning project for harness engineering -- building the environment that surrounds an agent model.
-It intentionally simplifies or omits several production mechanisms:
+本仓库是一个 0->1 的 harness 工程学习项目 -- 构建围绕 agent 模型的工作环境。
+为保证学习路径清晰,仓库有意简化或省略了部分生产机制:
-- Full event/hook buses (for example PreToolUse, SessionStart/End, ConfigChange).
- s12 includes only a minimal append-only lifecycle event stream for teaching.
-- Rule-based permission governance and trust workflows
-- Session lifecycle controls (resume/fork) and advanced worktree lifecycle controls
-- Full MCP runtime details (transport/OAuth/resource subscribe/polling)
+- 完整事件 / Hook 总线 (例如 PreToolUse、SessionStart/End、ConfigChange)。
+ s12 仅提供教学用途的最小 append-only 生命周期事件流。
+- 基于规则的权限治理与信任流程
+- 会话生命周期控制 (resume/fork) 与更完整的 worktree 生命周期控制
+- 完整 MCP 运行时细节 (transport/OAuth/资源订阅/轮询)
-Treat the team JSONL mailbox protocol in this repo as a teaching implementation, not a claim about any specific production internals.
+仓库中的团队 JSONL 邮箱协议是教学实现,不是对任何特定生产内部实现的声明。
-## Quick Start
+## 快速开始
+
+### 环境要求
+
+- **JDK 21+** (推荐 [Eclipse Temurin](https://adoptium.net/) 或 GraalVM)
+- **Maven 3.9+**
+- 一个兼容 OpenAI 协议的大模型 API Key (DeepSeek、智谱 GLM、通义千问、OpenAI 等)
+
+### 克隆与构建
```sh
-git clone https://github.com/shareAI-lab/learn-claude-code
+git clone https://github.com/abel533/learn-claude-code
cd learn-claude-code
-pip install -r requirements.txt
-cp .env.example .env # Edit .env with your ANTHROPIC_API_KEY
-
-python agents/s01_agent_loop.py # Start here
-python agents/s12_worktree_task_isolation.py # Full progression endpoint
-python agents/s_full.py # Capstone: all mechanisms combined
+mvn compile # 编译项目
```
-### Web Platform
+### 设置环境变量
-Interactive visualizations, step-through diagrams, source viewer, and documentation.
+```sh
+# Linux / macOS
+export AI_API_KEY=your-api-key
+export AI_BASE_URL=https://api.deepseek.com # 替换为你的模型服务商地址
+export AI_MODEL=deepseek-chat # 替换为你使用的模型名称
+
+# Windows PowerShell
+$env:AI_API_KEY="your-api-key"
+$env:AI_BASE_URL="https://api.deepseek.com"
+$env:AI_MODEL="deepseek-chat"
+```
+
+### 运行课程
+
+```sh
+# 从第一课开始
+mvn exec:java -Dexec.mainClass=io.mybatis.learn.s01.S01AgentLoop
+
+# 完整递进终点
+mvn exec:java -Dexec.mainClass=io.mybatis.learn.s12.S12WorktreeIsolation
+
+# 总纲: 全部机制合一
+mvn exec:java -Dexec.mainClass=io.mybatis.learn.full.SFullAgent
+```
+
+### Web 平台
+
+交互式可视化、分步动画、源码查看器, 以及每个课程的文档。
```sh
cd web && npm install && npm run dev # http://localhost:3000
```
-## Learning Path
+### Java 版本特色
+
+本项目使用 **Java 21 + Spring Boot 3.5.7 + Spring AI 1.0.3** 技术栈,相比原始 Python 版本有以下特色:
+
+- **兼容多种大模型服务商** -- 通过 OpenAI 协议适配 DeepSeek、智谱 GLM、通义千问、Moonshot 等国产模型,无需绑定特定厂商
+- **`@Tool` 注解自动处理工具调用循环** -- Spring AI 框架自动完成"模型调用 → 工具执行 → 结果回传"的完整循环,无需手写 while 循环
+- **Java 21 虚拟线程** -- 轻量级并发实现后台任务与多智能体协作,无需线程池管理开销
+- **每节课独立可运行** -- 每个课程都是一个 `@SpringBootApplication` + `CommandLineRunner`,`mvn exec:java` 一行命令即可启动
+- **类型安全** -- Java 强类型系统在编译期捕获错误,IDE 自动补全友好
+
+## 学习路径
```
-Phase 1: THE LOOP Phase 2: PLANNING & KNOWLEDGE
+第一阶段: 循环 第二阶段: 规划与知识
================== ==============================
-s01 The Agent Loop [1] s03 TodoWrite [5]
- while + stop_reason TodoManager + nag reminder
+s01 Agent 循环 [1] s03 TodoWrite [5]
+ ChatClient + @Tool TodoManager + nag 提醒
| |
- +-> s02 Tool Use [4] s04 Subagents [5]
- dispatch map: name->handler fresh messages[] per child
+ +-> s02 Tool Use [4] s04 子智能体 [5]
+ @Tool 注册多个工具 每个子智能体独立 ChatClient
|
s05 Skills [5]
- SKILL.md via tool_result
+ SKILL.md 通过 tool_result 注入
|
s06 Context Compact [5]
- 3-layer compression
+ 三层上下文压缩
-Phase 3: PERSISTENCE Phase 4: TEAMS
+第三阶段: 持久化 第四阶段: 团队
================== =====================
-s07 Tasks [8] s09 Agent Teams [9]
- file-based CRUD + deps graph teammates + JSONL mailboxes
+s07 任务系统 [8] s09 智能体团队 [9]
+ 文件持久化 CRUD + 依赖图 队友 + JSONL 邮箱
| |
-s08 Background Tasks [6] s10 Team Protocols [12]
- daemon threads + notify queue shutdown + plan approval FSM
+s08 后台任务 [6] s10 团队协议 [12]
+ 虚拟线程 + 通知队列 关机 + 计划审批 FSM
|
- s11 Autonomous Agents [14]
- idle cycle + auto-claim
+ s11 自治智能体 [14]
+ 空闲轮询 + 自动认领
|
- s12 Worktree Isolation [16]
- task coordination + optional isolated execution lanes
+ s12 Worktree 隔离 [16]
+ 任务协调 + 按需隔离执行通道
- [N] = number of tools
+ [N] = 工具数量
```
-## Architecture
+## 项目结构
```
learn-claude-code/
|
-|-- agents/ # Python reference implementations (s01-s12 + s_full capstone)
-|-- docs/{en,zh,ja}/ # Mental-model-first documentation (3 languages)
-|-- web/ # Interactive learning platform (Next.js)
-|-- skills/ # Skill files for s05
-+-- .github/workflows/ci.yml # CI: typecheck + build
+|-- src/main/java/io/mybatis/learn/ # Java 实现 (Spring AI + Spring Boot)
+| |-- core/ # 公共工具类 (AgentRunner, BashTool, EditFileTool 等)
+| |-- s01/ S01AgentLoop.java # 课程 01: Agent 循环
+| |-- s02/ S02ToolUse.java # 课程 02: 多工具注册
+| |-- s03/ S03TodoWrite.java # 课程 03: 计划驱动
+| |-- s04/ S04Subagent.java # 课程 04: 子智能体
+| |-- s05/ S05SkillLoading.java # 课程 05: Skill 加载
+| |-- s06/ S06ContextCompact.java # 课程 06: 上下文压缩
+| |-- s07/ S07TaskSystem.java # 课程 07: 任务系统
+| |-- s08/ S08BackgroundTasks.java # 课程 08: 后台任务
+| |-- s09/ S09AgentTeams.java # 课程 09: 智能体团队
+| |-- s10/ S10TeamProtocols.java # 课程 10: 团队协议
+| |-- s11/ S11AutonomousAgents.java# 课程 11: 自治智能体
+| |-- s12/ S12WorktreeIsolation.java# 课程 12: Worktree 隔离
+| +-- full/ SFullAgent.java # 总纲: 全部机制合一
+|
+|-- agents/ # Python 参考实现 (原始版本, 保留作为对照)
+|-- docs/{en,zh,ja}/ # 心智模型优先的文档 (3 种语言)
+|-- web/ # 交互式学习平台 (Next.js)
+|-- skills/ # s05 的 Skill 文件
+|-- pom.xml # Maven 构建配置 (Spring Boot 3.5.7 + Spring AI 1.0.3)
++-- .github/workflows/ci.yml # CI: 类型检查 + 构建
```
-## Documentation
+## 文档
-Mental-model-first: problem, solution, ASCII diagram, minimal code.
-Available in [English](./docs/en/) | [中文](./docs/zh/) | [日本語](./docs/ja/).
+心智模型优先: 问题、方案、ASCII 图、最小化代码。
+[English](./docs/en/) | [中文](./docs/zh/) | [日本語](./docs/ja/)
-| Session | Topic | Motto |
-|---------|-------|-------|
-| [s01](./docs/en/s01-the-agent-loop.md) | The Agent Loop | *One loop & Bash is all you need* |
-| [s02](./docs/en/s02-tool-use.md) | Tool Use | *Adding a tool means adding one handler* |
-| [s03](./docs/en/s03-todo-write.md) | TodoWrite | *An agent without a plan drifts* |
-| [s04](./docs/en/s04-subagent.md) | Subagents | *Break big tasks down; each subtask gets a clean context* |
-| [s05](./docs/en/s05-skill-loading.md) | Skills | *Load knowledge when you need it, not upfront* |
-| [s06](./docs/en/s06-context-compact.md) | Context Compact | *Context will fill up; you need a way to make room* |
-| [s07](./docs/en/s07-task-system.md) | Tasks | *Break big goals into small tasks, order them, persist to disk* |
-| [s08](./docs/en/s08-background-tasks.md) | Background Tasks | *Run slow operations in the background; the agent keeps thinking* |
-| [s09](./docs/en/s09-agent-teams.md) | Agent Teams | *When the task is too big for one, delegate to teammates* |
-| [s10](./docs/en/s10-team-protocols.md) | Team Protocols | *Teammates need shared communication rules* |
-| [s11](./docs/en/s11-autonomous-agents.md) | Autonomous Agents | *Teammates scan the board and claim tasks themselves* |
-| [s12](./docs/en/s12-worktree-task-isolation.md) | Worktree + Task Isolation | *Each works in its own directory, no interference* |
+| 课程 | 主题 | 格言 |
+|------|------|------|
+| [s01](./docs/zh/s01-the-agent-loop.md) | Agent 循环 | *One loop & Bash is all you need* |
+| [s02](./docs/zh/s02-tool-use.md) | Tool Use | *加一个工具, 只加一个 handler* |
+| [s03](./docs/zh/s03-todo-write.md) | TodoWrite | *没有计划的 agent 走哪算哪* |
+| [s04](./docs/zh/s04-subagent.md) | 子智能体 | *大任务拆小, 每个小任务干净的上下文* |
+| [s05](./docs/zh/s05-skill-loading.md) | Skills | *用到什么知识, 临时加载什么知识* |
+| [s06](./docs/zh/s06-context-compact.md) | Context Compact | *上下文总会满, 要有办法腾地方* |
+| [s07](./docs/zh/s07-task-system.md) | 任务系统 | *大目标要拆成小任务, 排好序, 记在磁盘上* |
+| [s08](./docs/zh/s08-background-tasks.md) | 后台任务 | *慢操作丢后台, agent 继续想下一步* |
+| [s09](./docs/zh/s09-agent-teams.md) | 智能体团队 | *任务太大一个人干不完, 要能分给队友* |
+| [s10](./docs/zh/s10-team-protocols.md) | 团队协议 | *队友之间要有统一的沟通规矩* |
+| [s11](./docs/zh/s11-autonomous-agents.md) | 自治智能体 | *队友自己看看板, 有活就认领* |
+| [s12](./docs/zh/s12-worktree-task-isolation.md) | Worktree + 任务隔离 | *各干各的目录, 互不干扰* |
-## What's Next -- from understanding to shipping
+## 学完之后 -- 从理解到落地
-After the 12 sessions you understand how harness engineering works inside out. Two ways to put that knowledge to work:
+12 个课程走完, 你已经从内到外理解了 harness 工程的运作原理。两种方式把知识变成产品:
-### Kode Agent CLI -- Open-Source Coding Agent CLI
+### Kode Agent CLI -- 开源 Coding Agent CLI
> `npm i -g @shareai-lab/kode`
-Skill & LSP support, Windows-ready, pluggable with GLM / MiniMax / DeepSeek and other open models. Install and go.
+支持 Skill & LSP, 适配 Windows, 可接 GLM / MiniMax / DeepSeek 等开放模型。装完即用。
GitHub: **[shareAI-lab/Kode-cli](https://github.com/shareAI-lab/Kode-cli)**
-### Kode Agent SDK -- Embed Agent Capabilities in Your App
+### Kode Agent SDK -- 把 Agent 能力嵌入你的应用
-The official Claude Code Agent SDK communicates with a full CLI process under the hood -- each concurrent user means a separate terminal process. Kode SDK is a standalone library with no per-user process overhead, embeddable in backends, browser extensions, embedded devices, or any runtime.
+官方 Claude Code Agent SDK 底层与完整 CLI 进程通信 -- 每个并发用户 = 一个终端进程。Kode SDK 是独立库, 无 per-user 进程开销, 可嵌入后端、浏览器插件、嵌入式设备等任意运行时。
GitHub: **[shareAI-lab/Kode-agent-sdk](https://github.com/shareAI-lab/Kode-agent-sdk)**
---
-## Sister Repo: from *on-demand sessions* to *always-on assistant*
+## 姊妹教程: 从*被动临时会话*到*主动常驻助手*
-The harness this repo teaches is **use-and-discard** -- open a terminal, give the agent a task, close when done, next session starts blank. That is the Claude Code model.
+本仓库教的 harness 属于 **用完即走** 型 -- 开终端、给 agent 任务、做完关掉, 下次重开是全新会话。Claude Code 就是这种模式。
-[OpenClaw](https://github.com/openclaw/openclaw) proved another possibility: on top of the same agent core, two harness mechanisms turn the agent from "poke it to make it move" into "it wakes up every 30 seconds to look for work":
+但 [OpenClaw](https://github.com/openclaw/openclaw) 证明了另一种可能: 在同样的 agent core 之上, 加两个 harness 机制就能让 agent 从 "踹一下动一下" 变成 "自己隔 30 秒醒一次找活干":
-- **Heartbeat** -- every 30s the harness sends the agent a message to check if there is anything to do. Nothing? Go back to sleep. Something? Act immediately.
-- **Cron** -- the agent can schedule its own future tasks, executed automatically when the time comes.
+- **心跳 (Heartbeat)** -- 每 30 秒 harness 给 agent 发一条消息, 让它检查有没有事可做。没事就继续睡, 有事立刻行动。
+- **定时任务 (Cron)** -- agent 可以给自己安排未来要做的事, 到点自动执行。
-Add multi-channel IM routing (WhatsApp / Telegram / Slack / Discord, 13+ platforms), persistent context memory, and a Soul personality system, and the agent goes from a disposable tool to an always-on personal AI assistant.
+再加上 IM 多通道路由 (WhatsApp/Telegram/Slack/Discord 等 13+ 平台)、不清空的上下文记忆、Soul 人格系统, agent 就从一个临时工具变成了始终在线的个人 AI 助手。
-**[claw0](https://github.com/shareAI-lab/claw0)** is our companion teaching repo that deconstructs these harness mechanisms from scratch:
+**[claw0](https://github.com/shareAI-lab/claw0)** 是我们的姊妹教学仓库, 从零拆解这些 harness 机制:
```
claw agent = agent core + heartbeat + cron + IM chat + memory + soul
@@ -355,23 +422,17 @@ claw agent = agent core + heartbeat + cron + IM chat + memory + soul
```
learn-claude-code claw0
-(agent harness core: (proactive always-on harness:
- loop, tools, planning, heartbeat, cron, IM channels,
- teams, worktree isolation) memory, soul personality)
+(agent harness 内核: (主动式常驻 harness:
+ 循环、工具、规划、 心跳、定时任务、IM 通道、
+ 团队、worktree 隔离) 记忆、Soul 人格)
```
-## About
-
-
-Scan with Wechat to follow us,
-or follow on X: [shareAI-Lab](https://x.com/baicai003)
-
-## License
+## 许可证
MIT
---
-**The model is the agent. The code is the harness. Build great harnesses. The agent will do the rest.**
+**模型就是 Agent。代码是 Harness。造好 Harness,Agent 会完成剩下的。**
**Bash is all you need. Real agents are all the universe needs.**
diff --git a/docs/en/s01-the-agent-loop.md b/docs/en/s01-the-agent-loop.md
index 4056468..2d57497 100644
--- a/docs/en/s01-the-agent-loop.md
+++ b/docs/en/s01-the-agent-loop.md
@@ -20,97 +20,299 @@ A language model can reason about code, but it can't *touch* the real world -- c
^ |
| tool_result |
+----------------+
- (loop until stop_reason != "tool_use")
+ (ChatClient.call() auto-loops until no tool calls)
```
-One exit condition controls the entire flow. The loop runs until the model stops calling tools.
+A single `call()` invocation controls the entire flow. Spring AI loops automatically until the model stops calling tools.
## How It Works
-1. User prompt becomes the first message.
+### 1. Build ChatClient: Inject Model + Register Tools
-```python
-messages.append({"role": "user", "content": query})
+Inject `ChatModel` via Spring Boot auto-configuration, build the client with `ChatClient.builder()`, set the system prompt and tools.
+
+```java
+// TIP: The Python version creates client = Anthropic() and MODEL at module level.
+// Spring AI injects ChatModel via auto-configuration, then builds ChatClient with builder.
+public S01AgentLoop(ChatModel chatModel) {
+ this.chatClient = ChatClient.builder(chatModel)
+ .defaultSystem("You are a coding agent at " + System.getProperty("user.dir")
+ + ". Use bash to solve tasks. Act, don't explain.")
+ .defaultTools(new BashTool()) // Tool object with @Tool annotation
+ .build();
+}
```
-2. Send messages + tool definitions to the LLM.
+### 2. `@Tool` Annotation: Declarative Tool Registration
-```python
-response = client.messages.create(
- model=MODEL, system=SYSTEM, messages=messages,
- tools=TOOLS, max_tokens=8000,
-)
+Spring AI automatically discovers and registers tools via the `@Tool` annotation. At startup, the framework scans objects passed to `defaultTools()`, extracts all `@Tool` method signatures and descriptions, generates the tool schema the LLM needs (name, parameters, description), and automatically includes it in every `call()` request.
+
+```java
+// BashTool -- corresponds to the Python version's run_bash() function
+public class BashTool {
+ @Tool(description = "Run a shell command and return stdout + stderr")
+ public String bash(@ToolParam(description = "The shell command to execute")
+ String command) {
+ // Dangerous command check + ProcessBuilder execution + timeout control + output truncation
+ // ...
+ }
+}
```
-3. Append the assistant response. Check `stop_reason` -- if the model didn't call a tool, we're done.
+> Comparison with Python's manual registration:
+> - Python: `TOOLS = [{"name": "bash", "input_schema": {...}}]` + `TOOL_HANDLERS = {"bash": run_bash}`
+> - Java: Just `@Tool` + `@ToolParam` annotations; the framework auto-generates schemas and dispatches methods
+
+### 3. Spring AI Internal Auto-Loop: How `call()` Works Under the Hood
+
+**This is the most critical difference between the Java and Python versions.** The Python version requires a hand-written while loop to drive tool calls:
```python
-messages.append({"role": "assistant", "content": response.content})
-if response.stop_reason != "tool_use":
- return
-```
-
-4. Execute each tool call, collect results, append as a user message. Loop back to step 2.
-
-```python
-results = []
-for block in response.content:
- if block.type == "tool_use":
- output = run_bash(block.input["command"])
- results.append({
- "type": "tool_result",
- "tool_use_id": block.id,
- "content": output,
- })
-messages.append({"role": "user", "content": results})
-```
-
-Assembled into one function:
-
-```python
-def agent_loop(query):
- messages = [{"role": "user", "content": query}]
+# Python version -- manual loop
+def agent_loop(messages):
while True:
- response = client.messages.create(
- model=MODEL, system=SYSTEM, messages=messages,
- tools=TOOLS, max_tokens=8000,
- )
+ response = client.messages.create(model=MODEL, messages=messages, tools=TOOLS)
+ # Collect assistant message
messages.append({"role": "assistant", "content": response.content})
-
if response.stop_reason != "tool_use":
- return
-
- results = []
+ return response # Model no longer calling tools, exit loop
+ # Execute tools and feed back results
for block in response.content:
if block.type == "tool_use":
- output = run_bash(block.input["command"])
- results.append({
- "type": "tool_result",
- "tool_use_id": block.id,
- "content": output,
- })
- messages.append({"role": "user", "content": results})
+ result = TOOL_HANDLERS[block.name](block.input)
+ messages.append({"role": "user", "content": [{"type": "tool_result", ...}]})
```
-That's the entire agent in under 30 lines. Everything else in this course layers on top -- without changing the loop.
+Spring AI's `ChatClient.call()` **encapsulates fully equivalent logic internally**:
+
+```
+call() internal flow:
+ ┌─────────────────────────────────────────────────────┐
+ │ 1. Assemble request: system prompt + user msg + tools │
+ │ 2. Send to LLM │
+ │ 3. Parse response │
+ │ ├── Has tool_use? ──→ Yes: │
+ │ │ a. Extract tool name and arguments │
+ │ │ b. Invoke corresponding @Tool method via reflection │
+ │ │ c. Append tool_result to message list │
+ │ │ d. Go back to step 2 (auto-loop) │
+ │ └── No ──→ Return final text │
+ └─────────────────────────────────────────────────────┘
+```
+
+Key points:
+- **Tool detection**: Spring AI checks if the response contains `tool_use` content blocks (equivalent to Python's `stop_reason == "tool_use"`)
+- **Reflection dispatch**: The framework uses Java reflection to find and invoke the `@Tool` method matching the tool name returned by the LLM (equivalent to Python's `TOOL_HANDLERS[block.name]`)
+- **Result feedback**: Tool execution results are automatically wrapped as `tool_result` messages and appended to the conversation (equivalent to Python's manual `tool_result` content block construction)
+- **Loop termination**: When the model returns pure text (no tool calls), `call()` returns the final result
+
+Thus, Python's ~15-line while loop is condensed into a single `.call()` in Java.
+
+### 4. `AgentRunner.interactive()`: The REPL Interaction Loop
+
+`AgentRunner` is a shared REPL (Read-Eval-Print Loop) utility class used across all lessons, corresponding to the `input()` loop in Python's `if __name__ == "__main__"` block.
+
+```java
+public class AgentRunner {
+ /**
+ * Start an interactive REPL loop.
+ * @param prefix Prompt prefix (e.g., "s01")
+ * @param handler Function that processes user input and returns Agent response
+ */
+ public static void interactive(String prefix, Function handler) {
+ Scanner scanner = new Scanner(System.in);
+ System.out.println("Type 'q' or 'exit' to quit");
+ while (true) {
+ System.out.print("\033[36m" + prefix + " >> \033[0m"); // Colored prompt
+ String input;
+ try {
+ if (!scanner.hasNextLine()) break;
+ input = scanner.nextLine().trim();
+ } catch (Exception e) {
+ break;
+ }
+ if (input.isEmpty() || "exit".equalsIgnoreCase(input) || "q".equalsIgnoreCase(input)) {
+ break;
+ }
+ try {
+ String response = handler.apply(input); // Call Agent handler
+ if (response != null && !response.isBlank()) {
+ System.out.println(response);
+ }
+ } catch (Exception e) {
+ System.err.println("Error: " + e.getMessage());
+ }
+ System.out.println();
+ }
+ System.out.println("Bye!");
+ }
+}
+```
+
+Workflow: `Scanner` reads input → `handler.apply()` sends to Agent → print response → loop. The `handler` is a functional interface; each lesson passes in its own Agent invocation logic.
+
+### 5. Assembled into a Complete Agent Class
+
+```java
+@SpringBootApplication(scanBasePackages = "io.mybatis.learn.core")
+public class S01AgentLoop implements CommandLineRunner {
+
+ private final ChatClient chatClient;
+
+ public S01AgentLoop(ChatModel chatModel) {
+ this.chatClient = ChatClient.builder(chatModel)
+ .defaultSystem("You are a coding agent at ...")
+ .defaultTools(new BashTool())
+ .build();
+ }
+
+ @Override
+ public void run(String... args) {
+ AgentRunner.interactive("s01", userMessage ->
+ chatClient.prompt()
+ .user(userMessage)
+ .call() // ← This single call = Python's entire while loop
+ .content()
+ );
+ }
+}
+```
+
+> **TIPS — Key Python → Java Adaptations:**
+> - Python's `while True` + `stop_reason` manual loop → Spring AI `ChatClient.call()` built-in auto-loop
+> - Python's `TOOLS` array + `TOOL_HANDLERS` dict → `@Tool` annotation + `defaultTools()` auto-registration with reflection dispatch
+> - Python's `client = Anthropic()` → Spring Boot auto-configured `ChatModel` injection
+> - Python's `input()` interaction → `AgentRunner.interactive()` wrapping Scanner REPL + functional interface
+
+Under 40 lines of core code, and that's the entire agent. The next 11 chapters all layer mechanisms on top of this loop -- the loop itself never changes.
## What Changed
-| Component | Before | After |
-|---------------|------------|--------------------------------|
-| Agent loop | (none) | `while True` + stop_reason |
-| Tools | (none) | `bash` (one tool) |
-| Messages | (none) | Accumulating list |
-| Control flow | (none) | `stop_reason != "tool_use"` |
+| Component | Before | After |
+|---------------|------------|-------------------------------------------------|
+| Agent loop | (none) | `ChatClient.call()` built-in tool loop |
+| Tools | (none) | `BashTool` (single `@Tool` tool) |
+| Messages | (none) | Managed internally by Spring AI |
+| Control flow | (none) | Framework auto-detects: returns final text when no tool calls |
+
+```java
+// Core code -- build + call
+ChatClient chatClient = ChatClient.builder(chatModel)
+ .defaultSystem("You are a coding agent ...")
+ .defaultTools(new BashTool())
+ .build();
+
+AgentRunner.interactive("s01", userMessage ->
+ chatClient.prompt().user(userMessage).call().content()
+);
+```
## Try It
```sh
cd learn-claude-code
-python agents/s01_agent_loop.py
+mvn exec:java -Dexec.mainClass=io.mybatis.learn.s01.S01AgentLoop
```
-1. `Create a file called hello.py that prints "Hello, World!"`
-2. `List all Python files in this directory`
+> Set environment variables before running: `AI_API_KEY`, `AI_BASE_URL`, `AI_MODEL`
+>
+> **The default protocol is OpenAI** (compatible with all OpenAI API-format services, including OpenAI official, Azure OpenAI, and any third-party model services offering an OpenAI-compatible interface).
+> To use the Anthropic protocol (Claude native API), expand the section below.
+
+
+Switching AI Protocols (OpenAI ↔ Anthropic)
+
+This project switches the underlying protocol via **Spring AI Starter dependency + configuration file**. Java business code (`ChatModel`, `ChatClient`) **requires no changes**.
+
+#### Option 1: OpenAI Protocol (Default)
+
+`pom.xml` dependency:
+
+```xml
+
+ org.springframework.ai
+ spring-ai-starter-model-openai
+
+```
+
+`application.yml` configuration:
+
+```yaml
+spring:
+ ai:
+ openai:
+ api-key: ${AI_API_KEY:sk-xxx}
+ base-url: ${AI_BASE_URL:https://api.openai.com}
+ chat:
+ options:
+ model: ${AI_MODEL:gpt-4o}
+```
+
+Environment variable example:
+
+```sh
+export AI_API_KEY=sk-proj-xxxxxxxx
+export AI_BASE_URL=https://api.openai.com # Replace with any OpenAI-compatible endpoint
+export AI_MODEL=gpt-4o
+```
+
+> **TIP**: Many third-party model services (e.g., DeepSeek, Mistral, Qwen) provide OpenAI-compatible APIs. Simply change `AI_BASE_URL` and `AI_MODEL` to connect — no protocol switch needed.
+
+#### Option 2: Anthropic Protocol (Claude Native API)
+
+**Step 1**: Edit `pom.xml` — replace the OpenAI starter with the Anthropic starter:
+
+```xml
+
+
+
+
+
+ org.springframework.ai
+ spring-ai-starter-model-anthropic
+
+```
+
+**Step 2**: Edit `application.yml` — replace `spring.ai.openai` with `spring.ai.anthropic`:
+
+```yaml
+spring:
+ ai:
+ anthropic:
+ api-key: ${AI_API_KEY}
+ base-url: ${AI_BASE_URL:https://api.anthropic.com}
+ chat:
+ options:
+ model: ${AI_MODEL:claude-sonnet-4-20250514}
+```
+
+**Step 3**: Set environment variables:
+
+```sh
+export AI_API_KEY=sk-ant-xxxxxxxx
+export AI_BASE_URL=https://api.anthropic.com
+export AI_MODEL=claude-sonnet-4-20250514
+```
+
+#### How Switching Works
+
+Spring AI's `ChatModel` is a unified abstraction interface. Different Starters provide different implementations:
+
+| Starter Dependency | Auto-injected ChatModel | Config Prefix |
+|---|---|---|
+| `spring-ai-starter-model-openai` | `OpenAiChatModel` | `spring.ai.openai.*` |
+| `spring-ai-starter-model-anthropic` | `AnthropicChatModel` | `spring.ai.anthropic.*` |
+
+Business code always programs against the `ChatModel` interface. Switching protocols only requires changing the dependency and configuration — no Java code changes needed.
+
+
+
+Try these prompts(English prompts work better with LLMs, but Chinese also works):
+
+1. `Create a file called Hello.java that prints "Hello, World!"`
+2. `List all Java files in this directory`
3. `What is the current git branch?`
4. `Create a directory called test_output and write 3 files in it`
diff --git a/docs/en/s02-tool-use.md b/docs/en/s02-tool-use.md
index 279774b..662141d 100644
--- a/docs/en/s02-tool-use.md
+++ b/docs/en/s02-tool-use.md
@@ -2,98 +2,138 @@
`s01 > [ s02 ] s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`
-> *"Adding a tool means adding one handler"* -- the loop stays the same; new tools register into the dispatch map.
+> *"Adding a tool means adding one @Tool method"* -- the loop stays the same; new tools are passed into `defaultTools()`.
>
> **Harness layer**: Tool dispatch -- expanding what the model can reach.
## Problem
-With only `bash`, the agent shells out for everything. `cat` truncates unpredictably, `sed` fails on special characters, and every bash call is an unconstrained security surface. Dedicated tools like `read_file` and `write_file` let you enforce path sandboxing at the tool level.
+With only `bash`, the agent shells out for everything. `cat` truncates unpredictably, `sed` fails on special characters, and every bash call is an unconstrained security surface. Dedicated tools (`read_file`, `write_file`) let you enforce path sandboxing at the tool level.
The key insight: adding tools does not require changing the loop.
## Solution
```
-+--------+ +-------+ +------------------+
-| User | ---> | LLM | ---> | Tool Dispatch |
-| prompt | | | | { |
-+--------+ +---+---+ | bash: run_bash |
- ^ | read: run_read |
- | | write: run_wr |
- +-----------+ edit: run_edit |
- tool_result | } |
- +------------------+
++--------+ +-------+ +--------------------+
+| User | ---> | LLM | ---> | defaultTools() |
+| prompt | | | | { |
++--------+ +---+---+ | BashTool |
+ ^ | ReadFileTool |
+ | | WriteFileTool |
+ +-----------+ EditFileTool |
+ tool_result | } |
+ +--------------------+
-The dispatch map is a dict: {tool_name: handler_function}.
-One lookup replaces any if/elif chain.
+Spring AI auto-registers and dispatches via @Tool annotations.
+No hand-written dispatch map needed -- the framework scans annotated methods on tool objects.
```
## How It Works
-1. Each tool gets a handler function. Path sandboxing prevents workspace escape.
+1. Each tool is a standalone class declared with `@Tool` annotation. `PathValidator` provides path sandboxing to prevent workspace escape.
-```python
-def safe_path(p: str) -> Path:
- path = (WORKDIR / p).resolve()
- if not path.is_relative_to(WORKDIR):
- raise ValueError(f"Path escapes workspace: {p}")
- return path
+```java
+// PathValidator -- corresponds to the Python version's safe_path() function
+public class PathValidator {
+ private final Path workDir;
-def run_read(path: str, limit: int = None) -> str:
- text = safe_path(path).read_text()
- lines = text.splitlines()
- if limit and limit < len(lines):
- lines = lines[:limit]
- return "\n".join(lines)[:50000]
-```
+ public Path resolve(String relativePath) {
+ Path resolved = workDir.resolve(relativePath).toAbsolutePath().normalize();
+ if (!resolved.startsWith(workDir)) {
+ throw new IllegalArgumentException("Path escapes workspace: " + relativePath);
+ }
+ return resolved;
+ }
+}
-2. The dispatch map links tool names to handlers.
+// ReadFileTool -- corresponds to the Python version's run_read() function
+public class ReadFileTool {
+ private final PathValidator pathValidator;
-```python
-TOOL_HANDLERS = {
- "bash": lambda **kw: run_bash(kw["command"]),
- "read_file": lambda **kw: run_read(kw["path"], kw.get("limit")),
- "write_file": lambda **kw: run_write(kw["path"], kw["content"]),
- "edit_file": lambda **kw: run_edit(kw["path"], kw["old_text"],
- kw["new_text"]),
+ @Tool(description = "Read file contents. Optionally limit the number of lines returned.")
+ public String readFile(
+ @ToolParam(description = "Relative path to the file") String path,
+ @ToolParam(description = "Maximum number of lines to read", required = false) Integer limit) {
+ Path filePath = pathValidator.resolve(path);
+ List lines = Files.readAllLines(filePath);
+ if (limit != null && limit > 0 && limit < lines.size()) {
+ lines = lines.subList(0, limit);
+ }
+ return String.join("\n", lines);
+ }
}
```
-3. In the loop, look up the handler by name. The loop body itself is unchanged from s01.
+2. Tool registration simply passes objects to `defaultTools()`. Spring AI scans `@Tool` annotated methods and automatically handles name mapping and parameter binding.
-```python
-for block in response.content:
- if block.type == "tool_use":
- handler = TOOL_HANDLERS.get(block.name)
- output = handler(**block.input) if handler \
- else f"Unknown tool: {block.name}"
- results.append({
- "type": "tool_result",
- "tool_use_id": block.id,
- "content": output,
- })
+```java
+// Corresponds to the Python version's TOOL_HANDLERS dict
+// Python: TOOL_HANDLERS = {"bash": fn, "read_file": fn, "write_file": fn, "edit_file": fn}
+// Java: Just pass tool objects; @Tool annotations handle auto-registration
+this.chatClient = ChatClient.builder(chatModel)
+ .defaultSystem("You are a coding agent ...")
+ .defaultTools(
+ new BashTool(), // bash command execution
+ new ReadFileTool(), // file reading
+ new WriteFileTool(), // file writing
+ new EditFileTool() // file editing (find & replace)
+ )
+ .build();
```
-Add a tool = add a handler + add a schema entry. The loop never changes.
+3. The calling code is identical to s01. The loop is managed by the framework; developers only focus on tool implementation.
+
+```java
+// Compared to s01, the only change is that defaultTools() receives 3 more tool objects
+// The loop code is exactly the same -- this is the core insight of s02
+AgentRunner.interactive("s02", userMessage ->
+ chatClient.prompt()
+ .user(userMessage)
+ .call()
+ .content()
+);
+```
+
+Add a tool = add a `@Tool` class + pass it to `defaultTools()`. The loop never changes.
+
+> **TIPS — Key Python → Java Adaptations:**
+> - Python's `TOOL_HANDLERS` dict → Spring AI `@Tool` annotation + `defaultTools()` auto-registration and dispatch
+> - Python's `safe_path()` function → `PathValidator` class (same path escape check logic)
+> - Python's `lambda **kw` parameter unpacking → `@ToolParam` annotation auto-binds parameters
+> - Python's `block.type == "tool_use"` check → Spring AI handles detection and dispatch internally
## What Changed From s01
-| Component | Before (s01) | After (s02) |
-|----------------|--------------------|----------------------------|
-| Tools | 1 (bash only) | 4 (bash, read, write, edit)|
-| Dispatch | Hardcoded bash call | `TOOL_HANDLERS` dict |
-| Path safety | None | `safe_path()` sandbox |
-| Agent loop | Unchanged | Unchanged |
+| Component | Before (s01) | After (s02) |
+|----------------|-----------------------|------------------------------------------------|
+| Tools | 1 (`BashTool`) | 4 (`Bash`, `ReadFile`, `WriteFile`, `EditFile`) |
+| Dispatch | `defaultTools(bash)` | `defaultTools(bash, read, write, edit)` |
+| Path safety | None | `PathValidator` sandbox |
+| Agent loop | Unchanged | Unchanged |
+
+```java
+// s01 → s02 only change: defaultTools() receives 3 more tool objects
+.defaultTools(
+ new BashTool(),
+ new ReadFileTool(), // +new
+ new WriteFileTool(), // +new
+ new EditFileTool() // +new
+)
+```
## Try It
```sh
cd learn-claude-code
-python agents/s02_tool_use.py
+mvn exec:java -Dexec.mainClass=io.mybatis.learn.s02.S02ToolUse
```
-1. `Read the file requirements.txt`
-2. `Create a file called greet.py with a greet(name) function`
-3. `Edit greet.py to add a docstring to the function`
-4. `Read greet.py to verify the edit worked`
+> Set environment variables before running: `AI_API_KEY`, `AI_BASE_URL`, `AI_MODEL`
+
+Try these prompts (English prompts work better with LLMs, but Chinese also works):
+
+1. `Read the file pom.xml`
+2. `Create a file called Greet.java with a greet(name) method`
+3. `Edit Greet.java to add a Javadoc comment to the method`
+4. `Read Greet.java to verify the edit worked`
diff --git a/docs/en/s03-todo-write.md b/docs/en/s03-todo-write.md
index e446114..23a88fe 100644
--- a/docs/en/s03-todo-write.md
+++ b/docs/en/s03-todo-write.md
@@ -2,13 +2,13 @@
`s01 > s02 > [ s03 ] s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`
-> *"An agent without a plan drifts"* -- list the steps first, then execute.
+> *"An agent without a plan drifts"* -- list the steps first, then execute. Doubles the completion rate.
>
> **Harness layer**: Planning -- keeping the model on course without scripting the route.
## Problem
-On multi-step tasks, the model loses track. It repeats work, skips steps, or wanders off. Long conversations make this worse -- the system prompt fades as tool results fill the context. A 10-step refactoring might complete steps 1-3, then the model starts improvising because it forgot steps 4-10.
+On multi-step tasks, the model loses track -- repeats work, skips steps, or wanders off. Long conversations make this worse: tool results keep filling the context, gradually diluting the system prompt's influence. A 10-step refactoring might complete steps 1-3, then the model starts improvising because steps 4-10 have been pushed out of attention.
## Solution
@@ -28,69 +28,93 @@ On multi-step tasks, the model loses track. It repeats work, skips steps, or wan
| [x] task C |
+-----------------------+
|
- if rounds_since_todo >= 3:
- inject into tool_result
+ Inject latest todo state into
+ system prompt via defaultSystem()
+ on each request
```
## How It Works
1. TodoManager stores items with statuses. Only one item can be `in_progress` at a time.
-```python
-class TodoManager:
- def update(self, items: list) -> str:
- validated, in_progress_count = [], 0
- for item in items:
- status = item.get("status", "pending")
- if status == "in_progress":
- in_progress_count += 1
- validated.append({"id": item["id"], "text": item["text"],
- "status": status})
- if in_progress_count > 1:
- raise ValueError("Only one task can be in_progress")
- self.items = validated
- return self.render()
-```
+```java
+public class TodoManager {
-2. The `todo` tool goes into the dispatch map like any other tool.
+ public record TodoItem(String id, String text, String status) {}
-```python
-TOOL_HANDLERS = {
- # ...base tools...
- "todo": lambda **kw: TODO.update(kw["items"]),
+ private List items = new ArrayList<>();
+
+ @Tool(description = "Update the full task list to track progress. "
+ + "Each item must have id, text, status (pending/in_progress/completed). "
+ + "Only one task can be in_progress at a time. Max 20 items.")
+ public String updateTodos(
+ @ToolParam(description = "The complete list of todo items")
+ List items) {
+ if (items.size() > 20) return "Error: Max 20 todos allowed";
+ List validated = new ArrayList<>();
+ int inProgressCount = 0;
+ for (TodoItem item : items) {
+ String status = (item.status() != null)
+ ? item.status().toLowerCase() : "pending";
+ if ("in_progress".equals(status)) inProgressCount++;
+ validated.add(new TodoItem(item.id(), item.text().trim(), status));
+ }
+ if (inProgressCount > 1)
+ return "Error: Only one task can be in_progress at a time";
+ this.items = validated;
+ return render();
+ }
}
```
-3. A nag reminder injects a nudge if the model goes 3+ rounds without calling `todo`.
+2. `TodoManager` is registered via `defaultTools()`; the `@Tool` annotated method is automatically exposed as a tool.
-```python
-if rounds_since_todo >= 3 and messages:
- last = messages[-1]
- if last["role"] == "user" and isinstance(last.get("content"), list):
- last["content"].insert(0, {
- "type": "text",
- "text": "Update your todos.",
- })
+```java
+ChatClient chatClient = ChatClient.builder(chatModel)
+ .defaultSystem(system)
+ .defaultTools(
+ new BashTool(),
+ new ReadFileTool(),
+ new WriteFileTool(),
+ new EditFileTool(),
+ todoManager // @Tool annotated method auto-registered
+ )
+ .build();
```
-The "one in_progress at a time" constraint forces sequential focus. The nag reminder creates accountability.
+3. System prompt injection: on each user input, inject the latest todo state into the system prompt with emphasis on update instructions.
+
+```java
+// Dynamic system prompt: includes current todo state
+String system = "You are a coding agent at " + workDir + ".\n"
+ + "Use the todo tool to plan multi-step tasks. "
+ + "Mark in_progress before starting, completed when done.\n"
+ + "IMPORTANT: You MUST call updateTodos regularly.\n\n"
+ + "\n" + todoManager.render() + "\n";
+```
+
+The "only one in_progress at a time" constraint forces sequential focus. Continuously injecting todo state into the system prompt creates accountability pressure -- the model sees its own plan every turn and won't forget to update it.
+
+> **TIP**: The Python version tracks `rounds_since_todo` inside the tool loop and injects `` text after 3 consecutive rounds without a todo call. Spring AI's ChatClient manages the tool loop automatically and doesn't allow mid-loop injection, so system prompt injection is used instead to achieve the same effect.
## What Changed From s02
-| Component | Before (s02) | After (s03) |
-|----------------|------------------|----------------------------|
-| Tools | 4 | 5 (+todo) |
-| Planning | None | TodoManager with statuses |
-| Nag injection | None | `` after 3 rounds|
-| Agent loop | Simple dispatch | + rounds_since_todo counter|
+| Component | Before (s02) | After (s03) |
+|----------------|------------------|--------------------------------------|
+| Tools | 4 | 5 (+TodoManager `@Tool`) |
+| Planning | None | TodoManager with statuses |
+| State injection| None | System prompt injection `` |
+| ChatClient | Fixed system prompt | Rebuilt each turn, dynamic todo state injection |
## Try It
```sh
cd learn-claude-code
-python agents/s03_todo_write.py
+mvn exec:java -Dexec.mainClass=io.mybatis.learn.s03.S03TodoWrite
```
-1. `Refactor the file hello.py: add type hints, docstrings, and a main guard`
-2. `Create a Python package with __init__.py, utils.py, and tests/test_utils.py`
-3. `Review all Python files and fix any style issues`
+Try these prompts (English prompts work better with LLMs, but Chinese also works):
+
+1. `Refactor the file Hello.java: add JavaDoc, improve naming, and keep main method behavior unchanged`
+2. `Create a Java package with utils and tests`
+3. `Review all Java files and fix any style issues`
diff --git a/docs/en/s04-subagent.md b/docs/en/s04-subagent.md
index 8a6ff2a..f056f60 100644
--- a/docs/en/s04-subagent.md
+++ b/docs/en/s04-subagent.md
@@ -8,7 +8,7 @@
## Problem
-As the agent works, its messages array grows. Every file read, every bash output stays in context permanently. "What testing framework does this project use?" might require reading 5 files, but the parent only needs the answer: "pytest."
+As the agent works, its messages array grows. Every file read, every bash output stays in context permanently. "What testing framework does this project use?" might require reading 5 files, but the parent only needs one word: "pytest."
## Solution
@@ -28,67 +28,75 @@ Parent context stays clean. Subagent context is discarded.
## How It Works
-1. The parent gets a `task` tool. The child gets all base tools except `task` (no recursive spawning).
+1. The parent agent has a `task` tool. The subagent gets all base tools except `task` (no recursive spawning).
-```python
-PARENT_TOOLS = CHILD_TOOLS + [
- {"name": "task",
- "description": "Spawn a subagent with fresh context.",
- "input_schema": {
- "type": "object",
- "properties": {"prompt": {"type": "string"}},
- "required": ["prompt"],
- }},
-]
-```
-
-2. The subagent starts with `messages=[]` and runs its own loop. Only the final text returns to the parent.
-
-```python
-def run_subagent(prompt: str) -> str:
- sub_messages = [{"role": "user", "content": prompt}]
- for _ in range(30): # safety limit
- response = client.messages.create(
- model=MODEL, system=SUBAGENT_SYSTEM,
- messages=sub_messages,
- tools=CHILD_TOOLS, max_tokens=8000,
+```java
+// Parent Agent: has base tools + SubagentTool
+this.chatClient = ChatClient.builder(chatModel)
+ .defaultSystem("You are a coding agent. "
+ + "Use the task tool to delegate subtasks.")
+ .defaultTools(
+ new BashTool(),
+ new ReadFileTool(),
+ new WriteFileTool(),
+ new EditFileTool(),
+ new SubagentTool(chatModel) // Parent Agent exclusive
)
- sub_messages.append({"role": "assistant",
- "content": response.content})
- if response.stop_reason != "tool_use":
- break
- results = []
- for block in response.content:
- if block.type == "tool_use":
- handler = TOOL_HANDLERS.get(block.name)
- output = handler(**block.input)
- results.append({"type": "tool_result",
- "tool_use_id": block.id,
- "content": str(output)[:50000]})
- sub_messages.append({"role": "user", "content": results})
- return "".join(
- b.text for b in response.content if hasattr(b, "text")
- ) or "(no summary)"
+ .build();
```
-The child's entire message history (possibly 30+ tool calls) is discarded. The parent receives a one-paragraph summary as a normal `tool_result`.
+2. The subagent starts with a brand new `ChatClient` and an independent context. Only the final text returns to the parent.
+
+```java
+@Tool(description = "Spawn a subagent with fresh context. "
+ + "Use for exploration or subtasks that might pollute the main context.")
+public String task(
+ @ToolParam(description = "The task prompt") String prompt,
+ @ToolParam(description = "Short description", required = false)
+ String description) {
+
+ // Create a brand new ChatClient -- this IS "context isolation"
+ ChatClient subClient = ChatClient.builder(chatModel)
+ .defaultSystem("You are a coding subagent. "
+ + "Complete the task, then summarize findings.")
+ .defaultTools( // Base tools, no task (prevents recursion)
+ new BashTool(),
+ new ReadFileTool(),
+ new WriteFileTool(),
+ new EditFileTool()
+ )
+ .build();
+
+ String result = subClient.prompt()
+ .user(prompt)
+ .call()
+ .content();
+
+ // Only the final text is returned; subagent context is discarded
+ return (result != null) ? result : "(no summary)";
+}
+```
+
+The subagent may have run multiple tool calls, but its entire message history is discarded. The parent receives only a summary text, returned as a normal `tool_result`. Spring AI's `ChatClient.call()` manages the tool loop internally -- no need to manually limit iteration count.
## What Changed From s03
-| Component | Before (s03) | After (s04) |
-|----------------|------------------|---------------------------|
-| Tools | 5 | 5 (base) + task (parent) |
-| Context | Single shared | Parent + child isolation |
-| Subagent | None | `run_subagent()` function |
-| Return value | N/A | Summary text only |
+| Component | Before (s03) | After (s04) |
+|----------------|------------------|---------------------------------------|
+| Tools | 5 | 5 (base) + SubagentTool (parent only) |
+| Context | Single shared | Parent + child isolation (independent ChatClient) |
+| Subagent | None | `SubagentTool.task()` method |
+| Return value | N/A | Summary text only |
## Try It
```sh
cd learn-claude-code
-python agents/s04_subagent.py
+mvn exec:java -Dexec.mainClass=io.mybatis.learn.s04.S04Subagent
```
+Try these prompts (English prompts work better with LLMs, but Chinese also works):
+
1. `Use a subtask to find what testing framework this project uses`
-2. `Delegate: read all .py files and summarize what each one does`
+2. `Delegate: read all .java files and summarize what each one does`
3. `Use a task to create a new module, then verify it from here`
diff --git a/docs/en/s05-skill-loading.md b/docs/en/s05-skill-loading.md
index 0cf1938..6861e1a 100644
--- a/docs/en/s05-skill-loading.md
+++ b/docs/en/s05-skill-loading.md
@@ -8,7 +8,7 @@
## Problem
-You want the agent to follow domain-specific workflows: git conventions, testing patterns, code review checklists. Putting everything in the system prompt wastes tokens on unused skills. 10 skills at 2000 tokens each = 20,000 tokens, most of which are irrelevant to any given task.
+You want the agent to follow domain-specific workflows: git conventions, testing patterns, code review checklists. Putting everything in the system prompt wastes tokens -- 10 skills at 2000 tokens each = 20,000 tokens, most of which are irrelevant to any given task.
## Solution
@@ -35,7 +35,7 @@ Layer 1: skill *names* in system prompt (cheap). Layer 2: full *body* via tool_r
## How It Works
-1. Each skill is a directory containing a `SKILL.md` with YAML frontmatter.
+1. Each skill is a directory containing a `SKILL.md` file with YAML frontmatter.
```
skills/
@@ -45,42 +45,87 @@ skills/
SKILL.md # ---\n name: code-review\n description: Review code\n ---\n ...
```
-2. SkillLoader scans for `SKILL.md` files, uses the directory name as the skill identifier.
+2. SkillLoader recursively scans for `SKILL.md` files, using the directory name as the skill identifier.
-```python
-class SkillLoader:
- def __init__(self, skills_dir: Path):
- self.skills = {}
- for f in sorted(skills_dir.rglob("SKILL.md")):
- text = f.read_text()
- meta, body = self._parse_frontmatter(text)
- name = meta.get("name", f.parent.name)
- self.skills[name] = {"meta": meta, "body": body}
+```java
+public class SkillLoader {
- def get_descriptions(self) -> str:
- lines = []
- for name, skill in self.skills.items():
- desc = skill["meta"].get("description", "")
- lines.append(f" - {name}: {desc}")
- return "\n".join(lines)
+ private static final Pattern FRONTMATTER_PATTERN =
+ Pattern.compile("^---\\n(.*?)\\n---\\n(.*)", Pattern.DOTALL);
- def get_content(self, name: str) -> str:
- skill = self.skills.get(name)
- if not skill:
- return f"Error: Unknown skill '{name}'."
- return f"\n{skill['body']}\n"
+ private final Map skills = new LinkedHashMap<>();
+
+ record SkillInfo(Map meta, String body, String path) {}
+
+ public SkillLoader(Path skillsDir) {
+ loadAll(skillsDir);
+ }
+
+ /** Recursively scan all SKILL.md files under the skills directory */
+ private void loadAll(Path skillsDir) {
+ if (!Files.exists(skillsDir)) return;
+ try (Stream paths = Files.walk(skillsDir)) {
+ paths.filter(p -> p.getFileName().toString().equals("SKILL.md"))
+ .sorted()
+ .forEach(p -> {
+ String text = Files.readString(p);
+ var parsed = parseFrontmatter(text);
+ String name = parsed.meta().getOrDefault("name",
+ p.getParent().getFileName().toString());
+ skills.put(name, new SkillInfo(
+ parsed.meta(), parsed.body(), p.toString()));
+ });
+ }
+ }
+
+ /** Layer 1: Get short descriptions of all skills (for system prompt injection) */
+ public String getDescriptions() {
+ if (skills.isEmpty()) return "(no skills available)";
+ StringBuilder sb = new StringBuilder();
+ for (var entry : skills.entrySet()) {
+ String desc = entry.getValue().meta()
+ .getOrDefault("description", "No description");
+ sb.append(" - ").append(entry.getKey())
+ .append(": ").append(desc).append("\n");
+ }
+ return sb.toString().stripTrailing();
+ }
+
+ /** Layer 2: Load full content of a specified skill (as @Tool method) */
+ @Tool(description = "Load specialized knowledge by name.")
+ public String loadSkill(
+ @ToolParam(description = "Skill name to load") String name) {
+ SkillInfo skill = skills.get(name);
+ if (skill == null)
+ return "Error: Unknown skill '" + name + "'. Available: "
+ + String.join(", ", skills.keySet());
+ return "\n"
+ + skill.body() + "\n";
+ }
+}
```
-3. Layer 1 goes into the system prompt. Layer 2 is just another tool handler.
+3. Layer 1 goes into the system prompt. Layer 2 is loaded on demand via the `@Tool` annotated method on SkillLoader.
-```python
-SYSTEM = f"""You are a coding agent at {WORKDIR}.
-Skills available:
-{SKILL_LOADER.get_descriptions()}"""
+```java
+public S05SkillLoading(ChatModel chatModel) {
+ Path skillsDir = Path.of(System.getProperty("user.dir"), "skills");
+ SkillLoader skillLoader = new SkillLoader(skillsDir);
-TOOL_HANDLERS = {
- # ...base tools...
- "load_skill": lambda **kw: SKILL_LOADER.get_content(kw["name"]),
+ // Layer 1: Skill metadata injected into system prompt
+ String system = "You are a coding agent at " + System.getProperty("user.dir") + ".\n"
+ + "Use loadSkill to access specialized knowledge.\n\n"
+ + "Skills available:\n"
+ + skillLoader.getDescriptions();
+
+ this.chatClient = ChatClient.builder(chatModel)
+ .defaultSystem(system)
+ .defaultTools(
+ new BashTool(), new ReadFileTool(),
+ new WriteFileTool(), new EditFileTool(),
+ skillLoader // Layer 2: loadSkill @Tool method
+ )
+ .build();
}
```
@@ -88,20 +133,22 @@ The model learns what skills exist (cheap) and loads them when relevant (expensi
## What Changed From s04
-| Component | Before (s04) | After (s05) |
-|----------------|------------------|----------------------------|
-| Tools | 5 (base + task) | 5 (base + load_skill) |
-| System prompt | Static string | + skill descriptions |
-| Knowledge | None | skills/\*/SKILL.md files |
-| Injection | None | Two-layer (system + result)|
+| Component | Before (s04) | After (s05) |
+|----------------|------------------|--------------------------------|
+| Tools | 5 (base + task) | 5 (base + load_skill) |
+| System prompt | Static string | + skill descriptions |
+| Knowledge | None | skills/\*/SKILL.md files |
+| Injection | None | Two-layer (system + result) |
## Try It
```sh
cd learn-claude-code
-python agents/s05_skill_loading.py
+mvn exec:java -Dexec.mainClass=io.mybatis.learn.s05.S05SkillLoading
```
+Try these prompts (English prompts work better with LLMs, but Chinese also works):
+
1. `What skills are available?`
2. `Load the agent-builder skill and follow its instructions`
3. `I need to do a code review -- load the relevant skill first`
diff --git a/docs/en/s06-context-compact.md b/docs/en/s06-context-compact.md
index d3e2d46..79bc82f 100644
--- a/docs/en/s06-context-compact.md
+++ b/docs/en/s06-context-compact.md
@@ -8,7 +8,7 @@
## Problem
-The context window is finite. A single `read_file` on a 1000-line file costs ~4000 tokens. After reading 30 files and running 20 bash commands, you hit 100,000+ tokens. The agent cannot work on large codebases without compression.
+The context window is finite. A single `read_file` on a 1000-line file costs ~4000 tokens; after reading 30 files and running 20 commands, you easily blow past 100k tokens. Without compression, the agent simply cannot work on large codebases.
## Solution
@@ -44,82 +44,142 @@ continue [Layer 2: auto_compact]
## How It Works
-1. **Layer 1 -- micro_compact**: Before each LLM call, replace old tool results with placeholders.
+1. **Layer 1 -- Context window management**: Spring AI's ChatClient manages the tool loop automatically and doesn't allow mid-loop compression injection. The Java version achieves an equivalent effect by limiting the number of conversation turns injected into the system prompt (keeping only the most recent N turns) and truncating content.
-```python
-def micro_compact(messages: list) -> list:
- tool_results = []
- for i, msg in enumerate(messages):
- if msg["role"] == "user" and isinstance(msg.get("content"), list):
- for j, part in enumerate(msg["content"]):
- if isinstance(part, dict) and part.get("type") == "tool_result":
- tool_results.append((i, j, part))
- if len(tool_results) <= KEEP_RECENT:
- return messages
- for _, _, part in tool_results[:-KEEP_RECENT]:
- if len(part.get("content", "")) > 100:
- part["content"] = f"[Previous: used {tool_name}]"
- return messages
+```java
+/** Estimate token count: rough estimate of 4 chars ≈ 1 token */
+public int estimateTokens() {
+ int chars = history.stream().mapToInt(t -> t.content().length()).sum();
+ return chars / 4;
+}
+
+/** Get conversation history summary (for system prompt injection, keeping only recent turns) */
+public String getContextSummary() {
+ if (history.isEmpty()) return "";
+ StringBuilder sb = new StringBuilder("\n\n");
+ int start = Math.max(0, history.size() - KEEP_RECENT * 2);
+ for (int i = start; i < history.size(); i++) {
+ ConversationTurn turn = history.get(i);
+ sb.append("[").append(turn.role()).append("]: ")
+ .append(turn.content(), 0, Math.min(500, turn.content().length()))
+ .append("\n");
+ }
+ sb.append("");
+ return sb.toString();
+}
```
-2. **Layer 2 -- auto_compact**: When tokens exceed threshold, save full transcript to disk, then ask the LLM to summarize.
+2. **Layer 2 -- auto_compact**: When tokens exceed the threshold, save the full conversation to disk and have the LLM summarize it.
-```python
-def auto_compact(messages: list) -> list:
- # Save transcript for recovery
- transcript_path = TRANSCRIPT_DIR / f"transcript_{int(time.time())}.jsonl"
- with open(transcript_path, "w") as f:
- for msg in messages:
- f.write(json.dumps(msg, default=str) + "\n")
- # LLM summarizes
- response = client.messages.create(
- model=MODEL,
- messages=[{"role": "user", "content":
- "Summarize this conversation for continuity..."
- + json.dumps(messages, default=str)[:80000]}],
- max_tokens=2000,
- )
- return [
- {"role": "user", "content": f"[Compressed]\n\n{response.content[0].text}"},
- {"role": "assistant", "content": "Understood. Continuing."},
- ]
+```java
+public String compact() {
+ // Save transcript to disk (full history is not lost)
+ Files.createDirectories(transcriptDir);
+ Path transcriptPath = transcriptDir.resolve(
+ "transcript_" + System.currentTimeMillis() + ".jsonl");
+ try (BufferedWriter writer = Files.newBufferedWriter(transcriptPath)) {
+ for (ConversationTurn turn : history) {
+ writer.write(objectMapper.writeValueAsString(turn));
+ writer.newLine();
+ }
+ }
+
+ // LLM generates summary
+ String conversationText = history.stream()
+ .map(t -> t.role() + ": " + t.content())
+ .reduce("", (a, b) -> a + "\n" + b);
+ if (conversationText.length() > 80000) {
+ conversationText = conversationText.substring(0, 80000);
+ }
+
+ ChatClient summaryClient = ChatClient.builder(chatModel).build();
+ String summary = summaryClient.prompt()
+ .user("Summarize this conversation for continuity. Include: "
+ + "1) What was accomplished, 2) Current state, "
+ + "3) Key decisions.\n\n" + conversationText)
+ .call().content();
+
+ // Replace history with summary
+ history.clear();
+ history.add(new ConversationTurn("system",
+ "[Conversation compressed. Transcript: " + transcriptPath
+ + "]\n\n" + summary));
+ return summary;
+}
```
-3. **Layer 3 -- manual compact**: The `compact` tool triggers the same summarization on demand.
+3. **Layer 3 -- manual compact**: The `CompactTool` triggers the same summarization mechanism on demand.
-4. The loop integrates all three:
+```java
+public class CompactTool {
+ private final ContextCompactor compactor;
-```python
-def agent_loop(messages: list):
- while True:
- micro_compact(messages) # Layer 1
- if estimate_tokens(messages) > THRESHOLD:
- messages[:] = auto_compact(messages) # Layer 2
- response = client.messages.create(...)
- # ... tool execution ...
- if manual_compact:
- messages[:] = auto_compact(messages) # Layer 3
+ public CompactTool(ContextCompactor compactor) {
+ this.compactor = compactor;
+ }
+
+ @Tool(description = "Trigger manual conversation compression to free up context space.")
+ public String compact(
+ @ToolParam(description = "What to preserve in summary",
+ required = false) String focus) {
+ compactor.requestCompact();
+ return "Compression triggered. Context will be summarized.";
+ }
+}
```
-Transcripts preserve full history on disk. Nothing is truly lost -- just moved out of active context.
+4. The REPL layer integrates all three layers (Spring AI's ChatClient manages the tool loop automatically; compression is triggered at the user message level):
+
+```java
+AgentRunner.interactive("s06", userMessage -> {
+ // Layer 2: Auto-compact check (before each user input)
+ if (compactor.needsAutoCompact()) {
+ System.out.println("[auto_compact triggered]");
+ compactor.compact();
+ }
+ compactor.addTurn("user", userMessage);
+
+ // Dynamic system prompt: includes conversation context summary
+ String system = baseSystem + compactor.getContextSummary();
+ ChatClient chatClient = ChatClient.builder(chatModel)
+ .defaultSystem(system)
+ .defaultTools(new BashTool(), new ReadFileTool(),
+ new WriteFileTool(), new EditFileTool(), compactTool)
+ .build();
+
+ String response = chatClient.prompt()
+ .user(userMessage).call().content();
+ compactor.addTurn("assistant", response != null ? response : "");
+
+ // Layer 3: Manual compact (if the agent called the compact tool)
+ if (compactor.isCompactRequested()) {
+ compactor.compact();
+ }
+ return response;
+});
+```
+
+Full history is preserved on disk via transcripts. Nothing is truly lost -- just moved out of active context.
## What Changed From s05
-| Component | Before (s05) | After (s06) |
-|----------------|------------------|----------------------------|
-| Tools | 5 | 5 (base + compact) |
-| Context mgmt | None | Three-layer compression |
-| Micro-compact | None | Old results -> placeholders|
-| Auto-compact | None | Token threshold trigger |
-| Transcripts | None | Saved to .transcripts/ |
+| Component | Before (s05) | After (s06) |
+|----------------|------------------|--------------------------------|
+| Tools | 5 | 5 (base + compact) |
+| Context mgmt | None | Three-layer compression |
+| Context window mgmt | None | Limited turn injection + content truncation |
+| Auto-compact | None | Token threshold trigger |
+| Transcripts | None | Saved to .transcripts/ |
## Try It
```sh
cd learn-claude-code
-python agents/s06_context_compact.py
+mvn exec:java -Dexec.mainClass=io.mybatis.learn.s06.S06ContextCompact
```
-1. `Read every Python file in the agents/ directory one by one` (watch micro-compact replace old results)
+Try these prompts (English prompts work better with LLMs, but Chinese also works):
+
+1. `Read every Java file in the src/ directory one by one` (observe context window management)
2. `Keep reading files until compression triggers automatically`
3. `Use the compact tool to manually compress the conversation`
diff --git a/docs/en/s07-task-system.md b/docs/en/s07-task-system.md
index 0eece39..5202ec3 100644
--- a/docs/en/s07-task-system.md
+++ b/docs/en/s07-task-system.md
@@ -10,7 +10,7 @@
s03's TodoManager is a flat checklist in memory: no ordering, no dependencies, no status beyond done-or-not. Real goals have structure -- task B depends on task A, tasks C and D can run in parallel, task E waits for both C and D.
-Without explicit relationships, the agent can't tell what's ready, what's blocked, or what can run concurrently. And because the list lives only in memory, context compression (s06) wipes it clean.
+Without explicit relationships, the agent can't tell what's ready, what's blocked, or what can run concurrently. And because the list lives only in memory, context compaction (s06) wipes it clean.
## Solution
@@ -48,57 +48,98 @@ This task graph becomes the coordination backbone for everything after s07: back
## How It Works
-1. **TaskManager**: one JSON file per task, CRUD with dependency graph.
+1. **TaskManager**: one JSON file per task, CRUD with dependency graph. Uses Jackson `ObjectMapper` for JSON serialization.
-```python
-class TaskManager:
- def __init__(self, tasks_dir: Path):
- self.dir = tasks_dir
- self.dir.mkdir(exist_ok=True)
- self._next_id = self._max_id() + 1
+```java
+public class TaskManager {
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+ private final Path dir;
+ private int nextId;
- def create(self, subject, description=""):
- task = {"id": self._next_id, "subject": subject,
- "status": "pending", "blockedBy": [],
- "blocks": [], "owner": ""}
- self._save(task)
- self._next_id += 1
- return json.dumps(task, indent=2)
+ public TaskManager(Path tasksDir) {
+ this.dir = tasksDir;
+ Files.createDirectories(dir);
+ this.nextId = maxId() + 1;
+ }
+
+ @Tool(description = "Create a new task with subject and optional description")
+ public String taskCreate(
+ @ToolParam(description = "Short subject of the task") String subject,
+ @ToolParam(description = "Detailed description", required = false) String description) {
+ Map task = new LinkedHashMap<>();
+ task.put("id", nextId);
+ task.put("subject", subject);
+ task.put("status", "pending");
+ task.put("blockedBy", new ArrayList<>());
+ task.put("blocks", new ArrayList<>());
+ save(task);
+ nextId++;
+ return MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(task);
+ }
+}
```
2. **Dependency resolution**: completing a task clears its ID from every other task's `blockedBy` list, automatically unblocking dependents.
-```python
-def _clear_dependency(self, completed_id):
- for f in self.dir.glob("task_*.json"):
- task = json.loads(f.read_text())
- if completed_id in task.get("blockedBy", []):
- task["blockedBy"].remove(completed_id)
- self._save(task)
+```java
+private void clearDependency(int completedId) {
+ try (Stream files = Files.list(dir)) {
+ files.filter(f -> f.getFileName().toString().matches("task_\\d+\\.json"))
+ .forEach(f -> {
+ Map task = MAPPER.readValue(
+ Files.readString(f), new TypeReference<>() {});
+ List blockedBy = (List) task.get("blockedBy");
+ if (blockedBy != null && blockedBy.remove(Integer.valueOf(completedId))) {
+ save(task);
+ }
+ });
+ }
+}
```
-3. **Status + dependency wiring**: `update` handles transitions and dependency edges.
+3. **Status transitions + dependency wiring**: `taskUpdate` handles status transitions and dependency edges. When status changes to `completed`, it automatically calls `clearDependency`; `blockedBy`/`blocks` are bidirectional relationships.
-```python
-def update(self, task_id, status=None,
- add_blocked_by=None, add_blocks=None):
- task = self._load(task_id)
- if status:
- task["status"] = status
- if status == "completed":
- self._clear_dependency(task_id)
- self._save(task)
+```java
+@Tool(description = "Update a task's status or dependencies.")
+public String taskUpdate(
+ @ToolParam(description = "Task ID") int taskId,
+ @ToolParam(description = "New status", required = false) String status,
+ @ToolParam(description = "Task IDs that block this task", required = false) List addBlockedBy,
+ @ToolParam(description = "Task IDs that this task blocks", required = false) List addBlocks) {
+ Map task = load(taskId);
+ if (status != null) {
+ task.put("status", status);
+ if ("completed".equals(status)) {
+ clearDependency(taskId);
+ }
+ }
+ // Handle addBlockedBy / addBlocks bidirectional dependencies ...
+ save(task);
+ return MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(task);
+}
```
-4. Four task tools go into the dispatch map.
+4. **Spring AI auto-registers tools**: Pass `TaskManager` as a `defaultTools` argument to `ChatClient`. Spring AI automatically recognizes `@Tool` annotated methods -- no manual dispatch map needed.
-```python
-TOOL_HANDLERS = {
- # ...base tools...
- "task_create": lambda **kw: TASKS.create(kw["subject"]),
- "task_update": lambda **kw: TASKS.update(kw["task_id"], kw.get("status")),
- "task_list": lambda **kw: TASKS.list_all(),
- "task_get": lambda **kw: TASKS.get(kw["task_id"]),
+```java
+@SpringBootApplication(scanBasePackages = "io.mybatis.learn.core")
+public class S07TaskSystem implements CommandLineRunner {
+
+ private final ChatClient chatClient;
+
+ public S07TaskSystem(ChatModel chatModel) {
+ Path tasksDir = Path.of(System.getProperty("user.dir"), ".tasks");
+ TaskManager taskManager = new TaskManager(tasksDir);
+
+ this.chatClient = ChatClient.builder(chatModel)
+ .defaultSystem("You are a coding agent. Use task tools to plan and track work.")
+ .defaultTools(
+ new BashTool(), new ReadFileTool(),
+ new WriteFileTool(), new EditFileTool(),
+ taskManager // @Tool methods in TaskManager are auto-registered
+ )
+ .build();
+ }
}
```
@@ -118,9 +159,11 @@ From s07 onward, the task graph is the default for multi-step work. s03's Todo r
```sh
cd learn-claude-code
-python agents/s07_task_system.py
+mvn exec:java -Dexec.mainClass=io.mybatis.learn.s07.S07TaskSystem
```
+Try these prompts (English prompts work better with LLMs, but Chinese also works):
+
1. `Create 3 tasks: "Setup project", "Write code", "Write tests". Make them depend on each other in order.`
2. `List all tasks and show the dependency graph`
3. `Complete task 1 and then list tasks to see task 2 unblocked`
diff --git a/docs/en/s08-background-tasks.md b/docs/en/s08-background-tasks.md
index ffd9333..8083350 100644
--- a/docs/en/s08-background-tasks.md
+++ b/docs/en/s08-background-tasks.md
@@ -2,7 +2,7 @@
`s01 > s02 > s03 > s04 > s05 > s06 | s07 > [ s08 ] s09 > s10 > s11 > s12`
-> *"Run slow operations in the background; the agent keeps thinking"* -- daemon threads run commands, inject notifications on completion.
+> *"Run slow operations in the background; the agent keeps thinking"* -- background threads run commands, inject notifications on completion.
>
> **Harness layer**: Background execution -- the model thinks while the harness waits.
@@ -32,78 +32,107 @@ Agent --[spawn A]--[spawn B]--[other work]----
## How It Works
-1. BackgroundManager tracks tasks with a thread-safe notification queue.
+1. BackgroundManager tracks tasks with thread-safe concurrent containers. Java uses `ConcurrentHashMap` and `CopyOnWriteArrayList` instead of Python's manual locking.
-```python
-class BackgroundManager:
- def __init__(self):
- self.tasks = {}
- self._notification_queue = []
- self._lock = threading.Lock()
+```java
+public class BackgroundManager {
+ private static final int TIMEOUT_SECONDS = 300;
+
+ private final Map tasks = new ConcurrentHashMap<>();
+ private final List notificationQueue = new CopyOnWriteArrayList<>();
+ private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
+
+ record TaskInfo(String status, String result, String command) {}
+ public record Notification(String taskId, String status, String command, String result) {}
+}
```
-2. `run()` starts a daemon thread and returns immediately.
+2. `backgroundRun()` submits a virtual thread (Java 21) and returns immediately. Compared to Python's `daemon=True` threads, virtual threads are lighter and scheduled by the JVM.
-```python
-def run(self, command: str) -> str:
- task_id = str(uuid.uuid4())[:8]
- self.tasks[task_id] = {"status": "running", "command": command}
- thread = threading.Thread(
- target=self._execute, args=(task_id, command), daemon=True)
- thread.start()
- return f"Background task {task_id} started"
+```java
+@Tool(description = "Run a command in a background thread. Returns task_id immediately without waiting.")
+public String backgroundRun(
+ @ToolParam(description = "The shell command to run in background") String command) {
+ String taskId = UUID.randomUUID().toString().substring(0, 8);
+ tasks.put(taskId, new TaskInfo("running", null, command));
+
+ executor.submit(() -> execute(taskId, command));
+
+ return "Background task " + taskId + " started: "
+ + command.substring(0, Math.min(80, command.length()));
+}
```
-3. When the subprocess finishes, its result goes into the notification queue.
+3. When the subprocess finishes, the result goes into the notification queue. Uses `ProcessBuilder` for command execution with timeout control.
-```python
-def _execute(self, task_id, command):
- try:
- r = subprocess.run(command, shell=True, cwd=WORKDIR,
- capture_output=True, text=True, timeout=300)
- output = (r.stdout + r.stderr).strip()[:50000]
- except subprocess.TimeoutExpired:
- output = "Error: Timeout (300s)"
- with self._lock:
- self._notification_queue.append({
- "task_id": task_id, "result": output[:500]})
+```java
+private void execute(String taskId, String command) {
+ String status, output;
+ try {
+ ProcessBuilder pb = new ProcessBuilder("sh", "-c", command);
+ pb.redirectErrorStream(true);
+ Process process = pb.start();
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(process.getInputStream()))) {
+ output = reader.lines().collect(Collectors.joining("\n"));
+ }
+ boolean finished = process.waitFor(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ if (!finished) { process.destroyForcibly(); status = "timeout"; }
+ else { status = "completed"; }
+ } catch (Exception e) { output = "Error: " + e.getMessage(); status = "error"; }
+
+ tasks.put(taskId, new TaskInfo(status, output, command));
+ notificationQueue.add(new Notification(taskId, status, command, output));
+}
```
-4. The agent loop drains notifications before each LLM call.
+4. Drain the notification queue on each user input and inject into the system prompt. Spring AI's `ChatClient` manages the internal tool loop, so notifications are drained and built into the system prompt on each user input instead -- the core concept remains the same: fire and forget.
-```python
-def agent_loop(messages: list):
- while True:
- notifs = BG.drain_notifications()
- if notifs:
- notif_text = "\n".join(
- f"[bg:{n['task_id']}] {n['result']}" for n in notifs)
- messages.append({"role": "user",
- "content": f"\n{notif_text}\n"
- f""})
- messages.append({"role": "assistant",
- "content": "Noted background results."})
- response = client.messages.create(...)
+```java
+AgentRunner.interactive("s08", userMessage -> {
+ // Drain background task notifications (corresponds to Python's pre-loop drain_notifications)
+ var notifs = bgManager.drainNotifications();
+ String bgContext = "";
+ if (!notifs.isEmpty()) {
+ String notifText = notifs.stream()
+ .map(n -> "[bg:" + n.taskId() + "] " + n.status() + ": " + n.result())
+ .collect(Collectors.joining("\n"));
+ bgContext = "\n\n\n" + notifText + "\n";
+ }
+
+ String system = "You are a coding agent. Use backgroundRun for long-running commands."
+ + bgContext;
+
+ ChatClient chatClient = ChatClient.builder(chatModel)
+ .defaultSystem(system)
+ .defaultTools(new BashTool(), new ReadFileTool(),
+ new WriteFileTool(), new EditFileTool(), bgManager)
+ .build();
+
+ return chatClient.prompt().user(userMessage).call().content();
+});
```
The loop stays single-threaded. Only subprocess I/O is parallelized.
## What Changed From s07
-| Component | Before (s07) | After (s08) |
-|----------------|------------------|----------------------------|
-| Tools | 8 | 6 (base + background_run + check)|
-| Execution | Blocking only | Blocking + background threads|
-| Notification | None | Queue drained per loop |
-| Concurrency | None | Daemon threads |
+| Component | Before (s07) | After (s08) |
+|----------------|------------------|------------------------------------|
+| Tools | 8 | 6 (base + backgroundRun + check) |
+| Execution | Blocking only | Blocking + virtual threads (Java 21)|
+| Notification | None | ConcurrentLinkedQueue drained per turn |
+| Concurrency | None | Virtual threads (lighter, JVM-scheduled) |
## Try It
```sh
cd learn-claude-code
-python agents/s08_background_tasks.py
+mvn exec:java -Dexec.mainClass=io.mybatis.learn.s08.S08BackgroundTasks
```
+Try these prompts (English prompts work better with LLMs, but Chinese also works):
+
1. `Run "sleep 5 && echo done" in the background, then create a file while it runs`
2. `Start 3 background tasks: "sleep 2", "sleep 4", "sleep 6". Check their status.`
3. `Run pytest in the background and keep working on other things`
diff --git a/docs/en/s09-agent-teams.md b/docs/en/s09-agent-teams.md
index 34b54b3..b082fac 100644
--- a/docs/en/s09-agent-teams.md
+++ b/docs/en/s09-agent-teams.md
@@ -2,7 +2,7 @@
`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > [ s09 ] s10 > s11 > s12`
-> *"When the task is too big for one, delegate to teammates"* -- persistent teammates + async mailboxes.
+> *"When the task is too big for one, delegate to teammates"* -- persistent teammates + JSONL mailboxes.
>
> **Harness layer**: Team mailboxes -- multiple models, coordinated through files.
@@ -10,7 +10,7 @@
Subagents (s04) are disposable: spawn, work, return summary, die. No identity, no memory between invocations. Background tasks (s08) run shell commands but can't make LLM-guided decisions.
-Real teamwork needs: (1) persistent agents that outlive a single prompt, (2) identity and lifecycle management, (3) a communication channel between agents.
+Real teamwork needs three things: (1) persistent agents that outlive a single prompt, (2) identity and lifecycle management, (3) a communication channel between agents.
## Solution
@@ -37,89 +37,137 @@ Communication:
## How It Works
-1. TeammateManager maintains config.json with the team roster.
+1. TeammateManager maintains the team roster via config.json.
-```python
-class TeammateManager:
- def __init__(self, team_dir: Path):
- self.dir = team_dir
- self.dir.mkdir(exist_ok=True)
- self.config_path = self.dir / "config.json"
- self.config = self._load_config()
- self.threads = {}
+```java
+// src/main/java/io/mybatis/learn/s09/TeammateManager.java
+public class TeammateManager {
+ private final ChatModel chatModel;
+ private final MessageBus bus;
+ private final Path configPath;
+ private final ObjectMapper mapper = new ObjectMapper();
+ private Map config;
+ // Python uses threading.Thread + dict; Java uses ConcurrentHashMap for natural thread safety
+ private final Map threads = new ConcurrentHashMap<>();
+
+ public TeammateManager(ChatModel chatModel, MessageBus bus, Path teamDir) {
+ this.chatModel = chatModel;
+ this.bus = bus;
+ this.configPath = teamDir.resolve("config.json");
+ Files.createDirectories(teamDir);
+ this.config = loadConfig();
+ }
```
2. `spawn()` creates a teammate and starts its agent loop in a thread.
-```python
-def spawn(self, name: str, role: str, prompt: str) -> str:
- member = {"name": name, "role": role, "status": "working"}
- self.config["members"].append(member)
- self._save_config()
- thread = threading.Thread(
- target=self._teammate_loop,
- args=(name, role, prompt), daemon=True)
- thread.start()
- return f"Spawned teammate '{name}' (role: {role})"
+```java
+// Python uses threading.Thread; Java uses Thread.startVirtualThread() for virtual threads
+public synchronized String spawn(String name, String role, String prompt) {
+ Map member = new LinkedHashMap<>();
+ member.put("name", name);
+ member.put("role", role);
+ member.put("status", "working");
+ ((List