Learn Claude Code Notes
The model is the agent. The code is the harness. Build great harnesses. The agent will do the rest.
模型是智能体,代码是 harness。构建优秀的 harness,智能体自会完成剩余工作。
关于本书
这是 learn-claude-code 项目的中文学习笔记。
原文档:learn-claude-code/docs/zh/
核心思想
Agent = Model(模型即智能体)
Harness = Tools + Knowledge + Context + Permissions(工具 + 知识 + 上下文 + 权限)
Product = Agent + Harness(完整产品 = 智能体 + 环境)
学习路径
本书分为 4 个阶段,共 12 个 session:
| 阶段 | Sessions | 主题 | 递进关系 |
|---|---|---|---|
| Phase 1 | s01-s02 | The Loop - 基础循环与工具 | 建立模型与世界的连接 |
| Phase 2 | s03-s06 | Planning & Knowledge - 规划与知识管理 | 让单 Agent 在有限上下文窗口内高效 |
| Phase 3 | s07-s08 | Persistence - 持久化与并行 | 为多 Agent 协作提供共享任务状态 |
| Phase 4 | s09-s12 | Teams - 多 Agent 协作 | 在共享状态上实现团队沟通与自组织 |
四阶段递进关系:
Phase 1: 循环基础
↓
Phase 2: 上下文管理 ──→ 单 Agent 能力完善
↓
Phase 3: 任务持久化 ──→ 为多 Agent 协作提供基础设施(任务图 + 并行执行)
↓
Phase 4: 多 Agent 协作 ──→ 基于共享状态的团队沟通、协议、自治
详细总结见:总结
核心概念
Agent、Model、Harness 的关系
这是学习本项目前最重要的概念区分:
Agent = Model(模型本身就是智能体)
Harness = Tools + Knowledge + Context + Permissions(工具 + 知识 + 上下文 + 权限)
Product = Agent + Harness(完整产品 = 智能体 + 环境)
层次图
┌─────────────────────────────────────────────────────────┐
│ Claude Code (完整产品) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Harness (环境/工具层) │ │
│ │ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │ │
│ │ │ Tools │ │ Knowledge│ │ Context │ │Permission│ │ │
│ │ │ bash, │ │ SKILL.md │ │ compression│ │ sandbox │ │ │
│ │ │ read... │ │ docs │ │ subagent │ │ approval│ │ │
│ │ └─────────┘ └──────────┘ └──────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ + │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Agent = Model (智能体层) │ │
│ │ Claude (Anthropic 训练的 LLM) │ │
│ │ - 决定何时调用工具 │ │
│ │ - 决定何时停止 │ │
│ │ - 推理和决策 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
关键区分
| 概念 | 是什么 | 谁构建 | 例子 |
|---|---|---|---|
| Model | 训练出来的神经网络权重 | Anthropic/OpenAI | Claude, GPT-4 |
| Agent | = Model(模型本身就是智能体) | 训练出来 | “Claude 这个模型” |
| Harness | 给 Agent 提供的环境/工具 | 应用开发者 | Claude Code 的代码部分 |
| Product | Agent + Harness | 产品团队 | Claude Code CLI |
The Agent Pattern
所有 session 都基于这个核心循环:
flowchart TD
A[User] --> B["messages[]"]
B --> C[LLM]
C --> D{"stop_reason == tool_use?"}
D -->|yes| E[execute tools]
D -->|no| F[return text]
E --> G[append results]
G --> B
Harness 的组成
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
本项目的定位
This repository teaches you to build vehicles.
这个项目教你构建车辆(harness),而不是训练司机(agent)。
你学到的技能:
- ✅ 实现工具(给 Agent 手和脚)
- ✅ 管理上下文(给 Agent 清晰的记忆)
- ✅ 控制权限(给 Agent 安全的边界)
- ✅ 持久化任务(给 Agent 长远的目标)
你不需要做的:
- ❌ 训练模型
- ❌ 调整模型权重
- ❌ 设计神经网络架构
Phase 1: The Loop
One loop & Bash is all you need
阶段概述
Phase 1 建立最核心的 Agent 循环和工具分发机制。这是所有后续 session 的基础。
Sessions
s01: The Agent Loop (Agent 循环)
一句话总结
“One loop & Bash is all you need” – 一个工具 + 一个循环 = 一个 Agent。
关键问题
1. Agent Loop 的核心思想是什么?
核心:建立模型与真实世界的连接。语言模型本身只能推理,无法执行实际操作。通过一个简单的循环,将模型的 tool_use 请求与实际执行连接起来。
2. Agent Loop 的工作流程?
+--------+ +-------+ +---------+
| User | ---> | LLM | ---> | Tool |
| prompt | | | | execute |
+--------+ +---+---+ +----+----+
^ |
| tool_result |
+----------------+
步骤:
- 用户 prompt 作为第一条消息加入 messages 列表
- 发送 messages + tools 定义给 LLM
- 追加助手响应到 messages
- 如果
stop_reason != "tool_use",循环结束 - 执行每个 tool_use,收集结果
- 将结果作为 user 消息追加到 messages
- 回到步骤 2
3. stop_reason 的作用?
控制循环退出:
stop_reason == "tool_use":模型请求调用工具,继续循环stop_reason != "tool_use":模型完成工作,退出循环
if response.stop_reason != "tool_use":
return # 退出循环
4. 消息列表如何累积?
messages = [{"role": "user", "content": query}]
# 循环中:
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": results})
工具结果格式:
{
"type": "tool_result",
"tool_use_id": block.id,
"content": output,
}
❓ 易误解点
Q:stop_reason 是工具执行后返回的吗?
A:不是。stop_reason 是 LLM API 响应的一部分,由模型决定是否需要调用工具。工具执行后返回的是 tool_result,不会改变 stop_reason。
🔗 原文位置
s02: Tool Use (工具使用)
一句话总结
“Adding a tool means adding one handler” – 加一个工具只加一个 handler,循环不用改,新工具注册进 dispatch map 就行。
关键问题
1. 为什么需要专用工具?
只有 bash 的问题:
cat截断不可预测sed遇到特殊字符容易出错- 每次 bash 调用都是不受约束的安全面
- 无法在工具层面做路径沙箱
专用工具的优势:
read_file: 可以控制读取行数(limit 参数)write_file: 自动创建目录,路径安全检查edit_file: 精确文本替换,避免 sed 的转义问题- 所有文件操作都经过
safe_path()沙箱检查
2. Dispatch Map 的作用?
作用:将工具名映射到处理函数,避免 if/elif 链式判断。
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"]),
}
使用方式:
handler = TOOL_HANDLERS.get(block.name)
output = handler(**block.input) if handler else f"Unknown tool: {block.name}"
优势:
- O(1) 时间复杂度查找
- 工具注册简单:只需在字典中添加一项
- 循环体完全不需要修改
3. 路径沙箱 (safe_path) 如何工作?
作用:防止文件操作逃逸工作区,增强安全性。
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
示例:
safe_path("file.txt") # OK: /workspace/file.txt
safe_path("../secret.txt") # Error: Path escapes workspace
🔗 原文位置
Phase 2: Planning & Knowledge
An agent without a plan drifts
阶段概述
Phase 2 解决一个核心问题:如何在有限上下文窗口内高效工作。
核心策略:磁盘与上下文的分工——把不急需的数据放磁盘,上下文只保留当前需要的内容。
- s03 TodoWrite - 进度存磁盘,不占上下文
- s04 Subagents - 探索性工作隔离到独立上下文
- s05 Skills - 知识存磁盘,按需加载
- s06 Context Compact - 历史存磁盘,当前内容压缩
Sessions
s03: TodoWrite (待办写入)
一句话总结
“An agent without a plan drifts” — 没有计划的 agent 走哪算哪。通过带状态的 TodoManager 强制顺序聚焦,连续 3 轮不更新计划就注入 nag reminder 制造问责压力。
关键问题
1. 为什么需要 TodoWrite?
问题:多步任务中模型会丢失进度 – 重复做过的事、跳步、跑偏。对话越长越严重,工具结果不断填满上下文,系统提示的影响力被稀释。
解决:TodoManager 提供外部记忆,将进度持久化到磁盘(不在上下文里)。Agent 不需要翻阅历史消息来回忆「做到哪了」,减少了对上下文的依赖。
2. TodoManager 的核心约束是什么?
同一时间只允许一个 in_progress – 强制顺序聚焦,避免多任务并行导致的注意力分散。
if in_progress_count > 1:
raise ValueError("Only one task can be in_progress")
3. Nag Reminder 的工作机制?
Nag = 唠叨(英文:不停催促某人做某事)
触发条件:模型连续 3 轮以上不调用 todo 工具。
注入方式:在最后一个 user message 的 content 列表头部插入 <reminder> 标签。
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": "<reminder>Update your todos.</reminder>",
})
🔗 原文位置
s04: Subagents (子代理)
一句话总结
“Break big tasks down; each subtask gets a clean context” — 大任务拆小,每个小任务干净的上下文。Subagent 以 messages=[] 启动,运行独立循环,只返回摘要文本,保持父 Agent 上下文清洁。
关键问题
1. 为什么需要 Subagents?
问题:Agent 工作越久,messages 数组越胖。每次读文件、跑命令的输出都永久留在上下文里。
场景:“这个项目用什么测试框架?” 可能要读 5 个文件,但父 Agent 只需要一个词:“pytest”。
解决:Subagent 承担探索性工作,只返回浓缩的摘要。
2. Subagent 的上下文隔离如何实现?
独立 messages 数组:
def run_subagent(prompt: str) -> str:
sub_messages = [{"role": "user", "content": prompt}] # 从零开始
for _ in range(30): # safety limit
response = client.messages.create(...)
sub_messages.append({"role": "assistant", "content": response.content})
# ... 执行工具,追加结果 ...
# 整个消息历史直接丢弃,只返回摘要
return "".join(b.text for b in response.content if hasattr(b, "text"))
父 Agent 视角:Subagent 只是一个返回字符串的 task 工具。
3. 工具权限如何控制?
父 Agent 工具 = 子 Agent 工具 + task 工具
PARENT_TOOLS = CHILD_TOOLS + [
{"name": "task",
"description": "Spawn a subagent with fresh context.",
"input_schema": {...}}
]
禁止递归:Subagent 没有 task 工具,防止无限生成子代理。
❓ 易误解点
Q:Subagent 的消息历史会保留吗?
A:不会。Subagent 的完整 messages[] 在运行结束后直接丢弃,只返回最终的摘要文本给父 Agent。这是保持父 Agent 上下文清洁的关键。
🔗 原文位置
s05: Skills (技能加载)
一句话总结
“Load knowledge when you need it, not upfront” — 用到什么知识,临时加载什么知识。两层注入策略:系统提示只放 Skill 名称(便宜),tool_result 按需加载完整内容(贵)。
关键问题
1. 为什么需要 Skills 机制?
问题:希望 Agent 遵循特定领域工作流(git 约定、测试模式、代码审查清单)。全塞进系统提示太浪费 – 10 个 Skill,每个 2000 token,就是 20,000 token,大部分跟当前任务无关。
解决:按需加载,模型开口要时才给。
2. 两层注入策略是什么?
Layer 1 - 系统提示(常驻,低成本):
You are a coding agent.
Skills available:
- git: Git workflow helpers ~100 tokens/skill
- test: Testing best practices
Layer 2 - tool_result(按需,高成本):
<skill name="git">
Full git workflow instructions... ~2000 tokens
Step 1: ...
</skill>
3. 技能文件如何组织?
skills/
pdf/
SKILL.md # YAML frontmatter + 正文
code-review/
SKILL.md
设计:用目录名作为 Skill 标识,YAML frontmatter 存元数据(name, description),正文是完整指令。
🔗 原文位置
s06: Context Compact (上下文压缩)
一句话总结
“Context will fill up; you need a way to make room” — 上下文总会满,要有办法腾地方。三层压缩策略为无限会话腾出空间。
关键问题
1. 三层压缩分别指什么?
三层压缩:
- Micro Compact(微型压缩): 替换旧 tool_result 为占位符(每轮静默执行)
- Auto Compact(自动压缩): token 超限时,保存对话 + LLM 总结(自动触发)
- Manual Compact(手动压缩): 模型调用
compact工具时执行压缩(手动触发)
关键理解:Layer 2 和 Layer 3 是同一套逻辑(都调用 auto_compact),区别只是触发方式(自动 vs 手动)。
2. Micro Compact 做了什么?
目标:替换旧的 tool_result 为占位符,减少 token 消耗。
策略:
- 保留最近 3 个 tool_result(
KEEP_RECENT = 3) - 更早的结果替换为
"[Previous: used {tool_name}]" - 例外:
read_file的结果不压缩(原文档未提及——因为文件内容是参考资料,压缩后会导致模型重新读文件,浪费 token)
def micro_compact(messages: list) -> list:
# 遍历所有 user message 中的 tool_result
for msg_idx, msg in enumerate(messages):
if msg["role"] == "user" and isinstance(msg.get("content"), list):
for part_idx, part in enumerate(msg["content"]):
if isinstance(part, dict) and part.get("type") == "tool_result":
# 压缩 tool_result 内容
# ...
防御性编程:使用 isinstance 检查类型,避免崩溃。
3. Auto Compact 何时触发?
触发条件:当 estimate_tokens(messages) > 50000 时自动触发。
执行流程:
- 保存对话:完整消息历史保存到
.transcripts/transcript_{timestamp}.jsonl - LLM 总结:让模型总结对话(包含:已完成什么、当前状态、关键决策)
- 替换消息:所有 messages 替换为一条摘要消息
def auto_compact(messages: list) -> list:
# 1. 保存完整对话到磁盘
TRANSCRIPT_DIR.mkdir(exist_ok=True)
transcript_path = TRANSCRIPT_DIR / f"transcript_{int(time.time())}.jsonl"
# 2. 让 LLM 总结对话
summary = client.messages.create(...,
"Summarize this conversation for continuity. Include: "
"1) What was accomplished, 2) Current state, 3) Key decisions made.")
# 3. 替换所有消息为摘要
return [{"role": "user",
"content": f"[Conversation compressed. Transcript: {transcript_path}]\n\n{summary}"}]
效果:
压缩前:messages = [msg1, msg2, ..., msg100] # ~50000 tokens
压缩后:messages = [summary_msg] # ~500 tokens
4. Manual Compact 如何触发?
触发方式:模型主动调用 compact 工具。
# 模型调用 compact 工具
{"type": "tool_use", "name": "compact", "input": {"focus": "当前任务进度"}}
# agent_loop 检测到 manual_compact
if manual_compact:
print("[manual compact]")
messages[:] = auto_compact(messages) # 和 Auto Compact 相同逻辑
return # 压缩后退出,下一轮从摘要继续
使用场景:
- 用户主动要求:“上下文太长了,压缩一下”
- 模型判断需要压缩时
5. Message Schema 详解?(补充)
为什么需要理解这个?
micro_compact 需要用 isinstance 检查,因为 API 的 content 类型是动态的:
Message 结构:
{
"role": "user" | "assistant",
"content": str | list # 字符串或 ContentBlock 列表
}
ContentBlock 类型:
| 位置 | type | 说明 | content 类型 |
|---|---|---|---|
| User | text | 文本消息 | 字符串 |
| User | image | 图片 | 对象 (base64) |
| User | document | 文档 (PDF/TXT) | 对象 (base64/text) |
| User | tool_result | 工具结果 | 字符串 或 列表 |
| Assistant | text | 文本回复 | 字符串 |
| Assistant | tool_use | 工具调用 | 对象 |
嵌套层级(最多 3 层):
Level 0: message.content (列表)
│
└─ Level 1: content[n] (ContentBlock)
│
└─ tool_result.content (列表,仅多模态时)
│
└─ Level 2: content[m] (原子元素,不能再是列表)
为什么 micro_compact 需要 isinstance 检查?
content可能是字符串(普通消息)或列表(多工具/多模态)tool_result.content可能是字符串或列表(多模态结果)- 需要安全地遍历,避免对非列表类型调用列表操作
6. 三层压缩流程图?
每轮循环
│
├─→ [Layer 1: micro_compact] ──→ 替换旧 tool_result 为占位符(静默)
│
├─→ [检查:tokens > 50000?]
│ │
│ ├─ no → 继续
│ │
│ └─ yes → [Layer 2: auto_compact]
│ ├─ 保存到 .transcripts/
│ ├─ LLM 总结
│ └─ 替换 messages 为摘要
│
└─→ [模型调用 compact 工具?]
│
└─ yes → [Layer 3: manual compact]
└─ 同 Layer 2 逻辑
📚 官方文档参考
❓ 易误解点
Q:message.content 是什么类型?
A:可能是字符串或列表。当一次调用多个工具时,结果是列表;普通文本消息是字符串。
Q:tool_result.content 是什么类型?
A:同样可能是字符串或列表。多模态结果(如图片 + 文字)是列表,纯文本结果是字符串。micro_compact 需要用 isinstance 检查来安全处理这两种情况。
Q:为什么 read_file 的结果不压缩?
A:因为 read_file 的输出是参考资料(文件内容),压缩后会丢失信息,导致模型需要重新读取文件,反而浪费 token。这是代码中的设计巧思,原文档未提及。
🔗 原文位置
Phase 3: Persistence(持久化)
Break big goals into small tasks, order them, persist to disk
阶段概述
Phase 3 解决两个问题:
- 如何让任务图持久化到磁盘?(s07 Task System)
- 如何让慢操作不阻塞 Agent?(s08 Background Tasks)
与 Phase 2 的区别:
- Phase 2 的磁盘 = 暂存(进度/知识/历史,可重建)
- Phase 3 的磁盘 = 状态(任务图/依赖关系,丢失会导致协作失败)
Sessions
s07: Task System (任务系统)
一句话总结
“Break big goals into small tasks, order them, persist to disk” — 大目标拆成小任务,排好序,记在磁盘上。文件持久化的任务图(DAG),用 blockedBy 表达依赖关系,为多 Agent 协作打基础。
关键问题
1. 为什么需要 Task System?
s03 TodoManager 的局限:
- 扁平清单,没有顺序、没有依赖
- 只活在内存里,上下文压缩(s06)后丢失
- 状态只有“做完/没做完“
真实需求:任务 B 依赖任务 A,任务 C 和 D 可以并行,任务 E 要等 C 和 D 都完成。
2. 任务图如何表示?
每个任务是一个 JSON 文件:
{
"id": 4,
"subject": "Write tests",
"status": "pending",
"blockedBy": [2, 3],
"owner": ""
}
DAG 示例:
+----------+
+--> | task 2 | --+
| | pending | |
+----------+ +----------+ +--> +----------+
| task 1 | | task 4 |
| completed| --> +----------+ +--> | blocked |
+----------+ | task 3 | --+ +----------+
| pending |
+----------+
3. 依赖如何解除?
完成任务时自动解锁后续任务:
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) # 自动解锁
4. 任务图回答的三个问题?
- 什么可以做? →
status == "pending"且blockedBy == [] - 什么被卡住? →
blockedBy非空 - 什么做完了? →
status == "completed"
🔗 原文位置
s08: Background Tasks (后台任务)
一句话总结
“Run slow operations in the background; the agent keeps thinking” — 慢操作丢后台,agent 继续想下一步。守护线程跑命令,完成后注入通知队列,循环保持单线程,只有子进程 I/O 被并行化。
关键问题
1. 为什么需要 Background Tasks?
问题:有些命令要跑几分钟(npm install、pytest、docker build)。阻塞式循环下模型只能干等。
场景:用户说 “装依赖,顺便建个配置文件”,阻塞模式下 Agent 只能一个一个来。
解决:后台执行 + 通知队列,Agent 可以继续做其他事。
2. 通知队列如何工作?
class BackgroundManager:
def __init__(self):
self.tasks = {}
self._notification_queue = [] # 线程安全的队列
self._lock = threading.Lock()
执行流程:
run()启动守护线程,立即返回- 子进程完成后,结果进入通知队列
- 每次 LLM 调用前排空队列,注入到 messages
3. 结果如何注入?
def agent_loop(messages: list):
while True:
# 1. 排空通知队列
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"<background-results>\n{notif_text}\n</background-results>"})
# 2. 正常 LLM 调用
response = client.messages.create(...)
时间线:
Agent --[spawn A]--[spawn B]--[other work]----
| |
v v
[A runs] [B runs] (并行执行)
| |
+-- results injected before next LLM call --+
🔗 原文位置
Phase 4: Teams
When the task is too big for one, delegate to teammates
阶段概述
Phase 4 实现多 Agent 协作系统:
- s09: 创建队友,建立通信机制
- s10: 定义通信协议和状态机
- s11: 队友自主认领任务
- s12: worktree 隔离执行环境
Sessions
s09: Agent Teams (Agent 团队)
一句话总结
“When the task is too big for one, delegate to teammates” — 任务太大,分给队友。Lead + teammates + JSONL 收件箱,建立多 Agent 协作基础。
关键问题
1. 发送和读取权限?
每个 agent 可以:
✓ 给任何 teammate 发送消息(只要知道名字)
✓ 只能读取自己的 inbox
2. Lead 和队友的工具有何区别?
Lead 有 9 个工具:
TOOLS = [
{"name": "bash", ...}, {"name": "read_file", ...},
{"name": "write_file", ...}, {"name": "edit_file", ...},
{"name": "spawn_teammate", ...}, {"name": "list_teammates", ...},
{"name": "send_message", ...}, {"name": "read_inbox", ...},
{"name": "broadcast", ...},
]
队友只有 6 个工具(少了 spawn_teammate、list_teammates、broadcast):
def _teammate_tools(self) -> list:
return [
{"name": "bash", ...}, {"name": "read_file", ...},
{"name": "write_file", ...}, {"name": "edit_file", ...},
{"name": "send_message", ...}, {"name": "read_inbox", ...},
]
3. 队友如何知道其他成员?
队友没有 list_teammates 工具,由 Lead 在 spawn 的 prompt 中告知:
spawn_teammate(
name="alice",
role="coder",
prompt="You are alice, the coder. Bob is the reviewer. "
"When you finish coding, send bob a message for review."
)
4. 完整通信图?
┌─────────────────────────────────────────────────────────┐
│ Lead (领导) │
│ 工具:spawn_teammate, list_teammates, send_message │
│ read_inbox, broadcast │
└───────────────┬─────────────────────┬───────────────────┘
│ │
send_message send_message
to="alice" to="bob"
│ │
v v
┌───────────────────────┐ ┌───────────────────────┐
│ Alice (Coder) │ │ Bob (Reviewer) │
│ 工具:send_message │ │ 工具:send_message │
│ read_inbox │ │ read_inbox │
└───────────────────────┘ └───────────────────────┘
│ │
└──────────┬──────────┘
│
send_message
to="lead"
↓
┌────────────────────────┐
│ lead.jsonl (Lead 邮箱) │
└────────────────────────┘
5. JSONL 的作用?
JSONL = 每行是一个完整 JSON。
{"type": "message", "from": "alice", "content": "hello"}
{"type": "message", "from": "bob", "content": "hi"}
优势:追加写入无需读改写整个文件。
🔗 原文位置
s10: Team Protocols (团队协议)
一句话总结
“Teammates need shared communication rules” — 队友需要共享通信规则。请求审批 FSM + 队友状态机,建立标准化协作协议。
关键问题
1. FSM 是什么?s10 中有哪几个 FSM?
FSM = Finite State Machine(有限状态机) — 系统在一组有限状态间转换的数学模型。
s10 中有两个 FSM:
| FSM | 状态流转 | 归属 |
|---|---|---|
| 协议 FSM | pending → approved/rejected | 请求(request) |
| Teammate FSM | working/idle → shutdown | 队友(teammate) |
关系:协议 FSM 驱动 Teammate FSM
shutdown 请求 approved → teammate 状态变为 shutdown
2. 计划模式由谁决定触发和审批?
| 触发 | Teammate 自主决定(LLM 根据 prompt 判断) |
| 审批 | Lead 自主决定 approve/reject |
| 用户 | 通过指挥 Lead 间接控制 |
3. 通信结构?
User → Lead ↔ Teammate 1
↕
Teammate 2
❓ 易误解点
Q:s10 中有几个 FSM?
A:两个。一个是协议 FSM(请求状态:pending → approved/rejected),归属于请求本身;另一个是Teammate FSM(队友状态:working/idle → shutdown),归属于队友。协议 FSM 的转换会驱动 Teammate FSM 的转换。
🔗 原文位置
s11: Autonomous Agents (自治 Agent)
一句话总结
“Teammates scan the board and claim tasks themselves” — 队友扫描看板自己认领任务。IDLE 轮询 + 自动认领 + 身份重注入,实现自组织团队。
关键问题
1. 自治性的体现?
s10 vs s11 的核心区别:
| 特性 | s10 | s11 |
|---|---|---|
| 任务分配 | Lead 手动指派 | 队友自己扫描看板认领 |
| 空闲行为 | 无 | 轮询收件箱 + 任务看板 |
| 工具集 | 12 个 | 14 个 (+idle, +claim_task) |
| 超时机制 | 无 | 60 秒空闲自动 shutdown |
自治性体现:
- 队友进入 IDLE 状态后,自动扫描
.tasks/目录查找未认领的任务 - 找到 pending 状态、无 owner、未被阻塞的任务时,自动调用
claim_task认领 - 无需 Lead 逐个分配任务,实现自组织
2. IDLE 阶段工作机制?
def _idle_poll(self, name, messages):
for _ in range(IDLE_TIMEOUT // POLL_INTERVAL): # 60s / 5s = 12 次
time.sleep(POLL_INTERVAL) # 每 5 秒轮询一次
# 1. 检查收件箱
inbox = BUS.read_inbox(name)
if inbox:
messages.append({"role": "user",
"content": f"<inbox>{inbox}</inbox>"})
return True # 有新消息,恢复 WORK 状态
# 2. 扫描任务看板
unclaimed = scan_unclaimed_tasks()
if unclaimed:
claim_task(unclaimed[0]["id"], name)
messages.append({"role": "user",
"content": f"<auto-claimed>Task #{unclaimed[0]['id']}: "
f"{unclaimed[0]['subject']}</auto-claimed>"})
return True # 认领到任务,恢复 WORK 状态
return False # 60 秒超时,进入 SHUTDOWN
轮询内容:
- 收件箱:检查是否有新消息
- 任务看板:扫描
.tasks/task_*.json文件
认领条件:
status == "pending"owner为空blockedBy为空
3. 身份重注入解决了什么问题?
问题:Context Compact (s06) 后,消息列表被压缩,Agent 可能忘记自己的身份。
实现:
if len(messages) <= 3: # 上下文过短
messages.insert(0, {"role": "user",
"content": f"<identity>You are '{name}', role: {role}, "
f"team: {team_name}. Continue your work.</identity>"})
4. 队友生命周期?
+-------+
| spawn | ← Lead 创建
+---+---+
|
v
+-------+ tool_use +-------+
| WORK | <------------- | LLM |
+---+---+ +-------+
|
| stop_reason != tool_use
v
+--------+
| IDLE | 每 5 秒轮询,持续最多 60 秒
+---+----+
|
+---> 收件箱有消息 -------------> 恢复 WORK
|
+---> 有未认领任务 -------------> 认领 -> 恢复 WORK
|
+---> 60 秒超时 ----------------> SHUTDOWN
🔗 原文位置
s12: Worktree Isolation (Worktree 隔离)
一句话总结
“Each works in its own directory, no interference” — 各干各的目录,互不干扰。任务管目标,worktree 管目录,用任务 ID 绑定,永不碰撞的并行执行通道。
关键问题
1. 为什么需要 Worktree 隔离?
问题:到 s11,Agent 已经能自主认领和完成任务。但所有任务共享一个目录。两个 Agent 同时重构不同模块 – A 改 config.py,B 也改 config.py,未提交的改动互相污染。
解决:给每个任务一个独立的 git worktree 目录,用任务 ID 把两边关联起来。
2. 控制平面与执行平面如何绑定?
控制平面 (.tasks/) 执行平面 (.worktrees/)
+------------------+ +------------------------+
| task_1.json | | auth-refactor/ |
| status: in_progress <------> branch: wt/auth-refactor
| worktree: "auth-refactor" | task_id: 1 |
+------------------+ +------------------------+
绑定同时写入两侧状态:
def bind_worktree(self, task_id, worktree):
task = self._load(task_id)
task["worktree"] = worktree
if task["status"] == "pending":
task["status"] = "in_progress" # 自动推进状态
self._save(task)
3. Worktree 生命周期?
创建:
WORKTREES.create("auth-refactor", task_id=1)
# -> git worktree add -b wt/auth-refactor .worktrees/auth-refactor HEAD
执行:
subprocess.run(command, shell=True, cwd=worktree_path, ...)
收尾(二选一):
worktree_keep(name)– 保留目录worktree_remove(name, complete_task=True)– 删除目录 + 完成任务
4. 事件流的作用?
每个生命周期步骤写入 .worktrees/events.jsonl:
{
"event": "worktree.remove.after",
"task": {"id": 1, "status": "completed"},
"worktree": {"name": "auth-refactor", "status": "removed"},
"ts": 1730000000
}
作用:崩溃后从 .tasks/ + .worktrees/index.json + events.jsonl 重建现场。
🔗 原文位置
总结
12 个 Session 回顾
| Session | 主题 | 一句话总结 |
|---|---|---|
| s01 | The Agent Loop | One loop & Bash is all you need |
| s02 | Tool Use | Adding a tool means adding one handler |
| s03 | TodoWrite | An agent without a plan drifts |
| s04 | Subagents | Break big tasks down; each subtask gets a clean context |
| s05 | Skills | Load knowledge when you need it, not upfront |
| s06 | Context Compact | Context will fill up; you need a way to make room |
| s07 | Task System | Break big goals into small tasks, order them, persist to disk |
| s08 | Background Tasks | Run slow operations in the background; the agent keeps thinking |
| s09 | Agent Teams | When the task is too big for one, delegate to teammates |
| s10 | Team Protocols | Teammates need shared communication rules |
| s11 | Autonomous Agents | Teammates scan the board and claim tasks themselves |
| s12 | Worktree Isolation | Each works in its own directory, no interference |
四个阶段
Phase 1: THE LOOP Phase 2: PLANNING & KNOWLEDGE
================== ==============================
s01 The Agent Loop s03 TodoWrite
while + stop_reason TodoManager + nag reminder
| |
+-> s02 Tool Use s04 Subagents
dispatch map fresh messages[]
|
s05 Skills
SKILL.md via tool_result
|
s06 Context Compact
3-layer compression
Phase 3: PERSISTENCE Phase 4: TEAMS
================== =====================
s07 Tasks s09 Agent Teams
file-based CRUD + deps graph teammates + JSONL mailboxes
| |
s08 Background Tasks s10 Team Protocols
daemon threads + notify queue shutdown + plan approval FSM
|
s11 Autonomous Agents
idle cycle + auto-claim
|
s12 Worktree Isolation
task coordination + isolated lanes
核心公式
Agent = Model(模型即智能体)
Harness = Tools + Knowledge + Context + Permissions
Product = Agent + Harness
The Agent Pattern
flowchart TD
A[User] --> B["messages[]"]
B --> C[LLM]
C --> D{"stop_reason == tool_use?"}
D -->|yes| E[execute tools]
D -->|no| F[return text]
E --> G[append results]
G --> B
关键洞察
-
循环不变,工具插件化 – 从 s01 到 s12,Agent Loop 本身从未改变,变化的是周围的 harness 机制
-
上下文是稀缺资源 – s04(Subagent 隔离)、s05(Skills 按需加载)、s06(Context Compact)都在解决同一个问题:保持上下文清洁
-
持久化是协作的基础 – s07(Task System)和 s12(Worktree)将状态写入磁盘,使多 Agent 协作成为可能
-
状态机驱动协作 – s10-s12 用 FSM 管理队友状态和请求审批,实现自组织的团队
The model is the agent. The code is the harness. Build great harnesses. The agent will do the rest.