第 27 章 Capstone —— 完整系统的合奏
一个完整的 coding agent 不是 12 个机制的简单堆叠,而是一套精心设计的渐进式架构——每一层机制的引入都严格建立在前一层的基础之上,且不改变已有层的核心结构;理解这条叠加路径,比理解任何单个机制都重要。
27.1 完整 Agent 系统的 12 个机制集成
渐进叠加路径
12 个机制不是同时设计出来的,而是按照严格的依赖序逐层叠加。每一层解决前一层遗留的核心矛盾,同时保持 Agent Loop 本身不变。
graph LR
S1["1. Loop<br>while tool_use"] --> S2["2. Tools<br>dispatch map"]
S2 --> S3["3. Planning<br>TodoWrite"]
S3 --> S4["4. Subagent<br>context isolation"]
S4 --> S5["5. Skills<br>on-demand knowledge"]
S5 --> S6["6. Compress<br>three-layer compaction"]
S6 --> S7["7. Tasks<br>persistent DAG"]
S7 --> S8["8. Background<br>daemon threads"]
S8 --> S9["9. Teams<br>persistent teammates"]
S9 --> S10["10. Protocols<br>request-response FSM"]
S10 --> S11["11. Autonomous<br>idle + auto-claim"]
S11 --> S12["12. Worktree<br>directory isolation"]
style S1 fill:#e8f5e9
style S6 fill:#fff3e0
style S12 fill:#e3f2fd下面逐层展开每一步引入的动机、核心矛盾、以及它如何不修改前面层的代码就完成集成。
第 1 层 Loop。没有循环的模型是一个问答接口——用户手动复制工具输出、粘贴回 prompt、再次调用 API。while stop_reason == "tool_use" 把人从循环中解放出来。Agent 的全部状态就是一个 append-only 的 messages[] 列表。此时只有一个工具 bash,工具数量 = 1。
第 2 层 Tools。纯 bash 的问题是安全面太大——cat 截断不可控,sed 对特殊字符脆弱,所有操作都是不受约束的 shell 命令。将 read_file / write_file / edit_file 提取为独立工具,每个工具在 handler 层实现路径沙箱校验(safe_path)。关键设计:工具注册为 {name: handler} 的 dispatch map,循环体本身零修改。工具数量 = 4。
第 3 层 Planning。多步任务中模型会"漂移"——完成步骤 1-3 后忘了步骤 4-10。TodoWrite 工具引入了三个约束:只能有一个 in_progress 项(强制顺序聚焦)、状态持久化在 TodoManager 对象中、3 轮不调用 todo 就注入 <reminder>(accountability)。循环体唯一的变化是增加了一个 rounds_since_todo 计数器。工具数量 = 5。
第 4 层 Subagent。随着工具调用的积累,messages[] 会被大量文件内容和命令输出填满。子 Agent 方案:parent 通过 task 工具派出一个拥有独立 messages[] 的 child,child 完成探索后只返回一段摘要文本。child 的整个对话历史被丢弃,parent 的上下文保持干净。关键约束:child 不拥有 task 工具(禁止递归生成),最多 30 轮循环(安全上限)。工具数量 = 5(base 不变,parent 多一个 task)。
第 5 层 Skills。将所有领域知识塞进 system prompt 浪费 token——10 个 skill 各 2000 tokens = 20K tokens,大部分对当前任务无关。两层注入策略:Layer 1 把 skill 名称和描述放进 system prompt(~100 tokens/skill),Layer 2 通过 load_skill 工具按需加载完整内容到 tool_result。SkillLoader 扫描 skills/*/SKILL.md,解析 YAML frontmatter 提取元数据。工具数量 = 5。
第 6 层 Compress。上下文窗口有限——读 30 个文件 + 跑 20 个命令就超过 100K tokens。三层压缩策略:
| 层级 | 触发条件 | 操作 | 信息损失 |
|---|---|---|---|
| micro_compact | 每轮自动 | 将 3 轮前的 tool_result 替换为 [cleared] | 低(近期结果保留) |
| auto_compact | tokens > threshold | LLM 摘要全部对话,原始记录存盘到 .transcripts/ | 中(摘要有损) |
| manual compact | 模型主动调用 compress 工具 | 同 auto_compact | 中 |
存盘机制保证信息不真正丢失——只是从活跃上下文移到磁盘。工具数量 = 5(compress 替换了一个 slot)。
第 7 层 Tasks。TodoManager 的三个致命局限:内存中(压缩后丢失)、扁平列表(无依赖关系)、单状态(done-or-not)。TaskManager 把每个任务持久化为 .tasks/task_N.json,引入 blockedBy / blocks 边构成 DAG。完成一个任务时自动从所有其他任务的 blockedBy 列表中移除该 ID,实现级联解锁。四个新工具:task_create / task_update / task_list / task_get。工具数量 = 8。
第 8 层 Background。npm install、pytest、docker build 这些命令可能跑几分钟。BackgroundManager 用 daemon thread 异步执行命令,主循环继续推进。完成后结果进入 Queue,在下一次 LLM 调用前 drain 出来注入 <background-results> 到 messages。关键约束:循环本身仍然单线程,只有 subprocess I/O 被并行化。工具数量 = 6(不同的工具配置,增加 background_run / check_background)。
第 9 层 Teams。Subagent 是一次性的(fire-and-forget),Background 只能跑 shell 命令不能做 LLM 推理。TeammateManager 解决了这两个限制:每个 teammate 是一个持久的 agent loop,运行在独立线程中,有自己的名字、角色和生命周期(working -> idle -> shutdown)。通信通过 MessageBus——append-only JSONL 信箱(.team/inbox/{name}.jsonl),发送时追加一行 JSON,读取时全量读出并清空。工具数量 = 9。
第 10 层 Protocols。没有结构化协议,teammate 之间只能发送自由文本消息——无法可靠地请求关机、审批计划。两个协议共享同一个 FSM:pending -> approved | rejected,通过 request_id 做关联。Shutdown 协议防止暴力终止(文件写到一半、config 不一致),Plan Approval 协议让 lead 在高风险变更前审查 teammate 的计划。工具数量 = 12。
第 11 层 Autonomous。前面所有层中,teammate 都是 lead 显式指派任务的——10 个未分配的任务需要 lead 手动分配 10 次。自治 Agent 增加了 idle phase:teammate 完成当前工作后进入轮询状态(每 5 秒一次,最多 60 秒),同时检查信箱和任务板。发现未认领的 pending 任务就自动 claim 并恢复工作。Identity re-injection 解决压缩后的身份遗忘:如果 messages[] 长度 <= 3(说明刚被压缩过),在开头注入 <identity> block。工具数量 = 14。
第 12 层 Worktree。前 11 层都共享一个工作目录——两个 Agent 同时修改 config.py 会互相覆盖。Git Worktree 给每个任务分配独立的目录检出(git worktree add -b wt/<name> .worktrees/<name> HEAD),用 task_id 建立控制平面(.tasks/)与执行平面(.worktrees/)的双向绑定。所有 worktree 共享同一个 .git 对象数据库,commit 历史全局可见。工具数量 = 16。
工具数量演进
工具数量的增长曲线反映了系统复杂度的演进:
| 层级 | 机制 | 工具数 | 新增工具 |
|---|---|---|---|
| 1 | Loop | 1 | bash |
| 2 | Tools | 4 | read_file, write_file, edit_file |
| 3 | Planning | 5 | TodoWrite |
| 4 | Subagent | 5 | task(parent 独有) |
| 5 | Skills | 5 | load_skill(替换 task) |
| 6 | Compress | 5 | compress(替换 load_skill 的 slot 配置) |
| 7 | Tasks | 8 | task_create, task_update, task_list, task_get |
| 8 | Background | 6 | background_run, check_background |
| 9 | Teams | 9 | spawn_teammate, send_message, read_inbox |
| 10 | Protocols | 12 | shutdown_request, shutdown_response, plan_approval |
| 11 | Autonomous | 14 | idle, claim_task |
| 12 | Worktree | 16 | worktree_create, worktree_remove |
在完整系统(capstone)中,所有工具同时可用。dispatch map 包含 22+ 个 handler:
TOOL_HANDLERS = {
# Base tools (Layer 1-2)
"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"]),
# Planning (Layer 3)
"TodoWrite": lambda **kw: TODO.update(kw["items"]),
# Subagent (Layer 4)
"task": lambda **kw: run_subagent(kw["prompt"], kw.get("agent_type", "Explore")),
# Skills (Layer 5)
"load_skill": lambda **kw: SKILLS.load(kw["name"]),
# Compress (Layer 6)
"compress": lambda **kw: "Compressing...",
# Task System (Layer 7)
"task_create": lambda **kw: TASK_MGR.create(kw["subject"]),
"task_get": lambda **kw: TASK_MGR.get(kw["task_id"]),
"task_update": lambda **kw: TASK_MGR.update(kw["task_id"], kw.get("status")),
"task_list": lambda **kw: TASK_MGR.list_all(),
# Background (Layer 8)
"background_run": lambda **kw: BG.run(kw["command"], kw.get("timeout", 120)),
"check_background": lambda **kw: BG.check(kw.get("task_id")),
# Teams (Layer 9)
"spawn_teammate": lambda **kw: TEAM.spawn(kw["name"], kw["role"], kw["prompt"]),
"list_teammates": lambda **kw: TEAM.list_all(),
"send_message": lambda **kw: BUS.send("lead", kw["to"], kw["content"]),
"read_inbox": lambda **kw: json.dumps(BUS.read_inbox("lead")),
"broadcast": lambda **kw: BUS.broadcast("lead", kw["content"], TEAM.member_names()),
# Protocols (Layer 10)
"shutdown_request": lambda **kw: handle_shutdown_request(kw["teammate"]),
"plan_approval": lambda **kw: handle_plan_review(kw["request_id"], kw["approve"]),
# Autonomous (Layer 11)
"idle": lambda **kw: "Lead does not idle.",
"claim_task": lambda **kw: TASK_MGR.claim(kw["task_id"], "lead"),
}集成后的主循环
所有 12 层机制集成到同一个 agent_loop 函数中,结构依然清晰:
def agent_loop(messages: list):
rounds_without_todo = 0
while True:
# Layer 6: compression pipeline
microcompact(messages)
if estimate_tokens(messages) > TOKEN_THRESHOLD:
messages[:] = auto_compact(messages)
# Layer 8: drain background notifications
notifs = BG.drain()
if notifs:
txt = "\n".join(f"[bg:{n['task_id']}] {n['status']}: {n['result']}" for n in notifs)
messages.append({"role": "user", "content": f"<background-results>\n{txt}\n</background-results>"})
messages.append({"role": "assistant", "content": "Noted background results."})
# Layer 9-10: check lead inbox (messages + shutdown + plan approval)
inbox = BUS.read_inbox("lead")
if inbox:
messages.append({"role": "user", "content": f"<inbox>{json.dumps(inbox)}</inbox>"})
messages.append({"role": "assistant", "content": "Noted inbox messages."})
# Layer 1: LLM call (unchanged since Layer 1)
response = client.messages.create(
model=MODEL, system=SYSTEM, messages=messages,
tools=TOOLS, max_tokens=8000,
)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
return
# Layer 2: tool dispatch (unchanged since Layer 2)
results = []
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: {block.name}"
results.append({"type": "tool_result", "tool_use_id": block.id,
"content": str(output)})
# Layer 3: nag reminder
if TODO.has_open_items() and rounds_without_todo >= 3:
results.insert(0, {"type": "text", "text": "<reminder>Update your todos.</reminder>"})
messages.append({"role": "user", "content": results})核心循环体从 Layer 1 到 Layer 12 没有被修改过——新机制通过三种方式接入:(1)在循环前注入信息(compress、drain、inbox),(2)在 dispatch map 中添加 handler,(3)在 tool_result 中附加 metadata(nag reminder)。这是"开放-封闭原则"在 agent 架构中的直接体现。
数据流全景
graph TB
subgraph "每轮循环 before LLM call"
MC["micro_compact<br>清理旧 tool_result"] --> AC{"tokens > threshold?"}
AC -->|yes| COMPACT["auto_compact<br>LLM 摘要 + 存盘"]
AC -->|no| DRAIN["drain background<br>notifications"]
COMPACT --> DRAIN
DRAIN --> INBOX["check lead inbox<br>messages / shutdown / plan"]
end
INBOX --> LLM["LLM Call<br>model + system + messages + tools"]
LLM -->|"stop_reason = end_turn"| DONE["Return to user"]
LLM -->|"stop_reason = tool_use"| DISPATCH["Tool Dispatch"]
subgraph "Tool Dispatch"
DISPATCH --> BASE["bash / read / write / edit"]
DISPATCH --> TODO_T["TodoWrite"]
DISPATCH --> SUB["task (subagent)"]
DISPATCH --> SKILL["load_skill"]
DISPATCH --> TASK_T["task_create / update / list / get"]
DISPATCH --> BG_T["background_run / check"]
DISPATCH --> TEAM_T["spawn / send / broadcast / inbox"]
DISPATCH --> PROTO["shutdown_request / plan_approval"]
DISPATCH --> AUTO["idle / claim_task"]
DISPATCH --> COMP_T["compress"]
end
BASE --> RESULT["Append tool_result to messages"]
TODO_T --> RESULT
SUB --> RESULT
SKILL --> RESULT
TASK_T --> RESULT
BG_T --> RESULT
TEAM_T --> RESULT
PROTO --> RESULT
AUTO --> RESULT
COMP_T --> RESULT
RESULT --> MC27.2 各项目的完整架构对照表
以下对照表覆盖教学实现(Python)、Codex(OpenAI)、OpenCode(Go/Rust 社区项目)、DeepAgents(LangGraph 多 Agent 框架)和轻量实现(Rust 社区 coding agent)五个项目的架构维度。
核心机制对照
| 机制 | 教学实现 (Python) | Codex | OpenCode | DeepAgents | 轻量实现 (Rust) |
|---|---|---|---|---|---|
| Loop | while stop_reason == "tool_use" | 相同模式,Rust 实现 | Go for 循环,相同退出条件 | LangGraph StateGraph 驱动 | loop { match stop_reason } |
| Tools | dict dispatch map,4 base tools | Rust trait dispatch,apply_patch 替代 edit | Go interface dispatch,内置 LSP 集成 | Python decorator + tool registry | Rust enum dispatch,4 base tools |
| Planning | TodoWrite(内存 checklist) | TodoWrite(相同语义) | 无显式 todo 工具,依赖模型自管理 | write_todos + plan state | 无 |
| Subagent | run_subagent() 独立 messages[] | task 工具,Explore / Implement 两种 agent_type | 无独立子 Agent | task() 委派,隔离上下文 | 无 |
| Skills | SKILL.md + SkillLoader 两层注入 | CLAUDE.md / AGENTS.md 作为项目级指令 | 无 skill 系统 | system_prompt 分层配置 | 无 |
| Compress | micro_compact + auto_compact + manual | 三层压缩,相同架构 | 单层 context truncation | 无显式压缩,依赖长上下文模型 | 无 |
| Tasks | JSON 文件 DAG (.tasks/) | 相同文件格式 | 无持久化任务系统 | TODO 列表 + plan persistence | 无 |
| Background | daemon thread + Queue | 异步 sandbox 执行 | Go goroutine 并发 | 子智能体本身异步运行 | Tokio async task |
| Teams | TeammateManager + MessageBus | 多 codex 实例并行 | 无多 Agent 协作 | 多 Agent 架构原生支持 | 无 |
| Protocols | shutdown + plan_approval FSM | 通过 API 协调 | 无 | 编排器-子智能体协议 | 无 |
| Autonomous | idle phase + auto-claim task | Codex 自主循环 | 用户驱动,非自主 | 编排器决策驱动 | 无 |
| Worktree | git worktree + task_id binding | 每个任务独立 sandbox | 无 worktree 隔离 | 每个子智能体独立沙箱 | 无 |
基础设施对照
| 维度 | 教学实现 (Python) | Codex | OpenCode | DeepAgents | 轻量实现 (Rust) |
|---|---|---|---|---|---|
| MCP | 无 | 内置 MCP client | 内置 MCP client,stdio/SSE 双传输 | 可通过 tool 扩展 | 无 |
| Provider | 单 provider (Anthropic) | OpenAI API | 多 provider 抽象层 (OpenAI/Anthropic/Gemini/Ollama) | LangChain model 抽象 | 单 provider |
| Sandbox | subprocess + safe_path | 容器化沙箱 (网络隔离) | 无沙箱 | Daytona/Modal/RunLoop 远程沙箱 | subprocess |
| Persistence | .tasks/ + .team/ + .transcripts/ | 会话历史 + 项目配置 | 会话 JSON 存储 | Plan + Memory store | 无 |
| UI | CLI REPL(input() 循环) | CLI + TUI (Ink/React) | TUI (Bubble Tea / Charm) | Web UI (Streamlit/Gradio) | CLI REPL |
| LSP | 无 | 无内置 LSP | 内置 LSP client(诊断注入) | 无 | 无 |
| HTTP API | 无 | REST API (cloud mode) | 无 | LangServe / FastAPI | 无 |
| SDK | 纯 Python 脚本 | TypeScript SDK + Rust core | Go module | Python pip 包 | Cargo crate |
对照表的三个关键观察
观察 1:核心循环高度同构。五个项目的 Agent Loop 在语义上完全一致——都是 while tool_use: dispatch → append → call LLM。差异只在实现语言和 API 格式。这印证了第 1-2 章的核心论点:Agent 的内核是通用的,不随项目、语言或规模改变。
观察 2:机制覆盖度与产品定位强相关。教学实现覆盖全部 12 层(教学目的),Codex 覆盖 11 层(缺少的以不同形式实现),DeepAgents 侧重 Subagent/Teams/Sandbox(面向多 Agent 研究),OpenCode 侧重 Provider/MCP/LSP/UI(面向开发者日常使用),轻量 Rust 实现只覆盖 3 层(追求极简和性能)。没有一个项目需要全部机制——产品定位决定了哪些层是核心、哪些层是可选。
观察 3:隔离策略的分歧。教学实现用 git worktree(文件系统级隔离),Codex 用容器沙箱(OS 级隔离),DeepAgents 用远程沙箱(网络级隔离),OpenCode 和轻量实现没有隔离。隔离粒度越粗,安全性越强但延迟越高——这是安全性与开发体验之间的根本权衡。
27.3 设计哲学分野
五个项目背后隐藏着三条设计哲学的分歧线。理解这些分歧,比比较具体功能更有价值——它们决定了架构的长期演化方向。
"Trust the Model" vs "Enforce at Boundary"
Trust the Model 哲学:假设模型有足够强的推理能力,harness 的职责是尽量少地干预。代表项目是教学实现和 DeepAgents。
具体体现:
- 教学实现的 TodoWrite 没有强制使用——模型可以选择不写 todo 就直接干活,harness 只提供一个温和的
<reminder>提示 - DeepAgents 的编排器完全由模型决定何时并行化、分配多少子智能体——harness 不设硬限制
- Subagent 的 30 轮上限是安全阀而非流程控制——正常情况下模型应该自己决定何时停止
Enforce at Boundary 哲学:假设模型会犯错(它确实会),harness 的职责是在边界处强制约束。代表项目是 Codex。
具体体现:
- Codex 的沙箱限制网络访问——不信任模型的 "我不会发起恶意请求" 承诺
- 权限分级(read-only / suggest / auto-edit)在 harness 层硬编码,模型无法绕过
apply_patch替代write_file+edit_file——用 unified diff 格式约束修改范围,减少全文覆写的风险
两种哲学不是对错之分,而是对模型能力的不同预期。模型越强,Trust 策略越有效;模型越弱或任务越高风险,Enforce 策略越必要。在实践中,成熟的产品往往是两者的混合:信任模型的推理能力,但在安全边界(文件系统、网络、权限)处强制约束。
单体 vs 模块化
单体架构:所有机制编译为一个二进制/一个脚本。代表项目是教学实现(单个 Python 文件)和轻量 Rust 实现。
优势:
- 部署简单——一个文件、一条命令
- 调试直观——
print加断点即可追踪整个流程 - 无进程间通信开销
代价:
- 扩展需要修改源码
- 无法独立替换某一层(如换一个更好的压缩策略需要改核心代码)
- 测试粒度受限
模块化架构:每个机制是独立模块,通过接口契约组合。代表项目是 Codex(Rust core + TypeScript CLI + Protocol 层)和 OpenCode(Go module + provider 抽象 + MCP client)。
优势:
- Provider 层可以无代码替换(从 OpenAI 切到 Anthropic 只改配置)
- MCP 工具可以运行时动态加载
- 各模块独立测试、独立部署
代价:
- 接口设计的认知负担——抽象层越多,理解全局行为越难
- 模块间的数据序列化/反序列化开销
- 版本兼容性管理
教学实现故意选择单体架构:用一个文件展示 12 个机制的全貌,让读者能够从第 1 行读到最后一行,在脑中建立完整的执行模型。生产系统则几乎必然走向模块化——但模块化的前提是先理解单体的完整流程。
框架 vs 产品
这条分歧线决定了项目的用户群体和交互模式。
框架提供积木,用户组装。DeepAgents 是典型——它提供 create_deep_agent() API、sandbox 抽象、tool registry,用户编写 Python 代码来定义自己的 Agent 系统。教学实现本质上也是框架——它教你怎么用积木搭建,不提供开箱即用的产品体验。
框架的价值在于灵活性:
- 可以定制任意的 system prompt、tool 集合、team 拓扑
- 可以嵌入到更大的系统中(如 RL 训练管线、CI/CD 管线)
- 适合研究和实验
框架的代价是门槛:用户必须理解每个机制的语义才能正确组装。
产品提供体验,用户使用。Codex、Claude Code、OpenCode 是典型——npm install -g && codex "fix this bug" 一行命令即可使用。用户不需要知道内部有 micro_compact、TodoWrite、BackgroundManager。
产品的价值在于封装:
- 最佳实践已经内化到默认配置中
- 安全约束已经嵌入到沙箱和权限系统中
- 用户只需要关心任务本身,不需要关心 Agent 的内部状态
产品的代价是固化:用户很难修改核心行为——Codex 的沙箱策略、Claude Code 的压缩阈值,都不是用户可以随意调整的。
三条分歧线的交叉产生了不同的项目定位:
graph TB
subgraph "Trust + 单体 + 框架"
A["教学实现<br>理解原理"]
end
subgraph "Enforce + 模块化 + 产品"
B["Codex / Claude Code<br>生产使用"]
end
subgraph "Trust + 模块化 + 框架"
C["DeepAgents<br>研究实验"]
end
subgraph "Trust + 模块化 + 产品"
D["OpenCode<br>开发者工具"]
end
subgraph "Trust + 单体 + 产品"
E["轻量 Rust 实现<br>极简效率"]
end没有一种组合是普遍最优的。选择取决于三个问题的回答:
- 你的模型有多强? —— 强模型倾向 Trust,弱模型必须 Enforce
- 你的系统有多大? —— 小系统适合单体,大系统必须模块化
- 你的用户是谁? —— 开发者/研究者适合框架,终端用户需要产品
从教学实现的 737 行 Python 到 Codex 的数万行 Rust+TypeScript,核心不变量只有一个:while stop_reason == "tool_use": execute → append → call LLM。所有的设计哲学分歧、架构选择、功能取舍,都是围绕这个循环的包装和约束。理解了循环,就理解了一切 coding agent 的骨架;理解了 12 层机制的叠加路径,就理解了从骨架到完整系统的建造过程。