Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# CHANGELOG

## [1.0.1] - 2024

### Fixed
- Fixed `add()` function in tests/calc.py that was incorrectly returning `a - b` instead of `a + b`
- Test `test_add()` now passes with correct addition logic
442 changes: 189 additions & 253 deletions README-ja.md

Large diffs are not rendered by default.

540 changes: 289 additions & 251 deletions README-zh.md

Large diffs are not rendered by default.

91 changes: 42 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,65 +235,58 @@ Treat the team JSONL mailbox protocol in this repo as a teaching implementation,
git clone https://github.com/shareAI-lab/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
cp .env.example .env
```

### Web Platform

Interactive visualizations, step-through diagrams, source viewer, and documentation.
Then configure `ANTHROPIC_API_KEY` or a compatible endpoint in `.env`, and run:

```sh
cd web && npm install && npm run dev # http://localhost:3000
python agents/s01_agent_loop.py
python agents/s18_worktree_task_isolation.py
python agents/s19_mcp_plugin.py
python agents/s_full.py
```

## Learning Path
Suggested order:

```
Phase 1: THE LOOP Phase 2: PLANNING & KNOWLEDGE
================== ==============================
s01 The Agent Loop [1] s03 TodoWrite [5]
while + stop_reason TodoManager + nag reminder
| |
+-> s02 Tool Use [4] s04 Subagents [5]
dispatch map: name->handler fresh messages[] 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]
daemon threads + notify queue shutdown + plan approval FSM
|
s11 Autonomous Agents [14]
idle cycle + auto-claim
|
s12 Worktree Isolation [16]
task coordination + optional isolated execution lanes

[N] = number of tools
```
1. Run `s01` and make sure the minimal loop really works.
2. Read `s00`, then move through `s01 -> s11` in order.
3. Only after the single-agent core plus its control plane feel stable, continue into `s12 -> s19`.
4. Read `s_full.py` last, after the mechanisms already make sense separately.

## Architecture
## How To Read Each Chapter

```
Each chapter is easier to absorb if you keep the same reading rhythm:

1. what problem appears without this mechanism
2. what the new concept means
3. what the smallest correct implementation looks like
4. where the state actually lives
5. how it plugs back into the loop
6. where to stop first, and what can wait until later

If you keep asking:

- "Is this core mainline or just a side detail?"
- "Where does this state actually live?"

go back to:

- [`docs/en/teaching-scope.md`](./docs/en/teaching-scope.md)
- [`docs/en/data-structures.md`](./docs/en/data-structures.md)
- [`docs/en/entity-map.md`](./docs/en/entity-map.md)

## Repository Structure

```text
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
├── agents/ # runnable Python reference implementations per chapter
├── docs/zh/ # Chinese mainline docs
├── docs/en/ # English docs
├── docs/ja/ # Japanese docs
├── skills/ # skill files used in s05
├── web/ # web teaching platform
└── requirements.txt
```

## Documentation
Expand Down
38 changes: 38 additions & 0 deletions agent_architecture_overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Agent 框架演进结构图 (s01-s19)

为了帮你更好地理解整个 agent 教程的内容,我生成了这张系统架构的概念图,并将它整理成了从内到外的六个演进层级。你可以对照这个架构层级来复习后续的章节。

![Agent 架构概览图](file:///C:/Users/31239/.gemini/antigravity/brain/6fb7cb52-28ae-4d15-a43f-07730c416899/artifacts/agent_architecture_blueprint.png)

### 对应的知识层级映射:

**🟣 核心圈:单兵作战的基础 (Foundations)**
* `s01 Agent Loop`
* `s02 Tool Use`
* `s03 Todo Write`

**🔵 第二层:能力扩展 (Skills & Specialization)**
* `s04 Subagent`
* `s05 Skill Loading`

**🟢 第三层:状态与安全管控 (State & Security)**
* `s06 Context Compact`
* `s07 Permission System`
* `s08 Hook System`
* `s09 Memory System`

**🟡 第四层:健壮性兜底 (Robustness)**
* `s10 System Prompt`
* `s11 Error Recovery`

**🟠 第五层:高级任务引擎 (Advanced Task Engine)**
* `s12 Task System`
* `s13 Background Tasks`
* `s14 Cron Scheduler`

**🔴 最外围:多智能体协作与生态 (Multi-Agent & Ecosystem)**
* `s15 Agent Teams`
* `s16 Team Protocols`
* `s17 Autonomous Agents`
* `s18 Worktree Isolation`
* `s19 MCP Plugin`
2 changes: 1 addition & 1 deletion agents/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# agents/ - Harness implementations (s01-s12) + full reference (s_full)
# agents/ - Harness implementations (s01-s19) + capstone reference (s_full)
# Each file is self-contained and runnable: python agents/s01_agent_loop.py
# The model is the agent. These files are the harness.
157 changes: 101 additions & 56 deletions agents/s01_agent_loop.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,23 @@
#!/usr/bin/env python3
# Harness: the loop -- the model's first connection to the real world.
# Harness: the loop -- keep feeding real tool results back into the model.
"""
s01_agent_loop.py - The Agent Loop

The entire secret of an AI coding agent in one pattern:

while stop_reason == "tool_use":
response = LLM(messages, tools)
execute tools
append results

+----------+ +-------+ +---------+
| User | ---> | LLM | ---> | Tool |
| prompt | | | | execute |
+----------+ +---+---+ +----+----+
^ |
| tool_result |
+---------------+
(loop continues)

This is the core loop: feed tool results back to the model
until the model decides to stop. Production agents layer
policy, hooks, and lifecycle controls on top.
This file teaches the smallest useful coding-agent pattern:

user message
-> model reply
-> if tool_use: execute tools
-> write tool_result back to messages
-> continue

It intentionally keeps the loop small, but still makes the loop state explicit
so later chapters can grow from the same structure.
"""

import os
import subprocess
from dataclasses import dataclass

try:
import readline
Expand All @@ -49,11 +41,14 @@
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
MODEL = os.environ["MODEL_ID"]

SYSTEM = f"You are a coding agent at {os.getcwd()}. Use bash to solve tasks. Act, don't explain."
SYSTEM = (
f"You are a coding agent at {os.getcwd()}. "
"Use bash to inspect and change the workspace. Act first, then report clearly."
)

TOOLS = [{
"name": "bash",
"description": "Run a shell command.",
"description": "Run a shell command in the current workspace.",
"input_schema": {
"type": "object",
"properties": {"command": {"type": "string"}},
Expand All @@ -62,43 +57,92 @@
}]


@dataclass
class LoopState:
# The minimal loop state: history, loop count, and why we continue.
messages: list
turn_count: int = 1
transition_reason: str | None = None


def run_bash(command: str) -> str:
dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
if any(d in command for d in dangerous):
if any(item in command for item in dangerous):
return "Error: Dangerous command blocked"
try:
r = subprocess.run(command, shell=True, cwd=os.getcwd(),
capture_output=True, text=True, timeout=120)
out = (r.stdout + r.stderr).strip()
return out[:50000] if out else "(no output)"
result = subprocess.run(
command,
shell=True,
cwd=os.getcwd(),
capture_output=True,
text=True,
timeout=120,
)
except subprocess.TimeoutExpired:
return "Error: Timeout (120s)"
except (FileNotFoundError, OSError) as e:
return f"Error: {e}"


# -- The core pattern: a while loop that calls tools until the model stops --
def agent_loop(messages: list):
while True:
response = client.messages.create(
model=MODEL, system=SYSTEM, messages=messages,
tools=TOOLS, max_tokens=8000,
)
# Append assistant turn
messages.append({"role": "assistant", "content": response.content})
# If the model didn't call a tool, we're done
if response.stop_reason != "tool_use":
return
# Execute each tool call, collect results
results = []
for block in response.content:
if block.type == "tool_use":
print(f"\033[33m$ {block.input['command']}\033[0m")
output = run_bash(block.input["command"])
print(output[:200])
results.append({"type": "tool_result", "tool_use_id": block.id,
"content": output})
messages.append({"role": "user", "content": results})
output = (result.stdout + result.stderr).strip()
return output[:50000] if output else "(no output)"


def extract_text(content) -> str:
if not isinstance(content, list):
return ""
texts = []
for block in content:
text = getattr(block, "text", None)
if text:
texts.append(text)
return "\n".join(texts).strip()


def execute_tool_calls(response_content) -> list[dict]:
results = []
for block in response_content:
if block.type != "tool_use":
continue
command = block.input["command"]
print(f"\033[33m$ {command}\033[0m")
output = run_bash(command)
print(output[:200])
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output,
})
return results


def run_one_turn(state: LoopState) -> bool:
response = client.messages.create(
model=MODEL,
system=SYSTEM,
messages=state.messages,
tools=TOOLS,
max_tokens=8000,
)
state.messages.append({"role": "assistant", "content": response.content})

if response.stop_reason != "tool_use":
state.transition_reason = None
return False

results = execute_tool_calls(response.content)
if not results:
state.transition_reason = None
return False

state.messages.append({"role": "user", "content": results})
state.turn_count += 1
state.transition_reason = "tool_result"
return True


def agent_loop(state: LoopState) -> None:
while run_one_turn(state):
pass


if __name__ == "__main__":
Expand All @@ -110,11 +154,12 @@ def agent_loop(messages: list):
break
if query.strip().lower() in ("q", "exit", ""):
break

history.append({"role": "user", "content": query})
agent_loop(history)
response_content = history[-1]["content"]
if isinstance(response_content, list):
for block in response_content:
if hasattr(block, "text"):
print(block.text)
state = LoopState(messages=history)
agent_loop(state)

final_text = extract_text(history[-1]["content"])
if final_text:
print(final_text)
print()
Loading