Skip to content

第 27 章 Capstone —— 完整系统的合奏

一个完整的 coding agent 不是 12 个机制的简单堆叠,而是一套精心设计的渐进式架构——每一层机制的引入都严格建立在前一层的基础之上,且不改变已有层的核心结构;理解这条叠加路径,比理解任何单个机制都重要。

27.1 完整 Agent 系统的 12 个机制集成

渐进叠加路径

12 个机制不是同时设计出来的,而是按照严格的依赖序逐层叠加。每一层解决前一层遗留的核心矛盾,同时保持 Agent Loop 本身不变。

mermaid
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_resultSkillLoader 扫描 skills/*/SKILL.md,解析 YAML frontmatter 提取元数据。工具数量 = 5。

第 6 层 Compress。上下文窗口有限——读 30 个文件 + 跑 20 个命令就超过 100K tokens。三层压缩策略:

层级触发条件操作信息损失
micro_compact每轮自动将 3 轮前的 tool_result 替换为 [cleared]低(近期结果保留)
auto_compacttokens > thresholdLLM 摘要全部对话,原始记录存盘到 .transcripts/中(摘要有损)
manual compact模型主动调用 compress 工具同 auto_compact

存盘机制保证信息不真正丢失——只是从活跃上下文移到磁盘。工具数量 = 5(compress 替换了一个 slot)。

第 7 层 TasksTodoManager 的三个致命局限:内存中(压缩后丢失)、扁平列表(无依赖关系)、单状态(done-or-not)。TaskManager 把每个任务持久化为 .tasks/task_N.json,引入 blockedBy / blocks 边构成 DAG。完成一个任务时自动从所有其他任务的 blockedBy 列表中移除该 ID,实现级联解锁。四个新工具:task_create / task_update / task_list / task_get。工具数量 = 8。

第 8 层 Backgroundnpm installpytestdocker 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。

工具数量演进

工具数量的增长曲线反映了系统复杂度的演进:

层级机制工具数新增工具
1Loop1bash
2Tools4read_file, write_file, edit_file
3Planning5TodoWrite
4Subagent5task(parent 独有)
5Skills5load_skill(替换 task)
6Compress5compress(替换 load_skill 的 slot 配置)
7Tasks8task_create, task_update, task_list, task_get
8Background6background_run, check_background
9Teams9spawn_teammate, send_message, read_inbox
10Protocols12shutdown_request, shutdown_response, plan_approval
11Autonomous14idle, claim_task
12Worktree16worktree_create, worktree_remove

在完整系统(capstone)中,所有工具同时可用。dispatch map 包含 22+ 个 handler:

python
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 函数中,结构依然清晰:

python
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 架构中的直接体现。

数据流全景

mermaid
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 --> MC

27.2 各项目的完整架构对照表

以下对照表覆盖教学实现(Python)、Codex(OpenAI)、OpenCode(Go/Rust 社区项目)、DeepAgents(LangGraph 多 Agent 框架)和轻量实现(Rust 社区 coding agent)五个项目的架构维度。

核心机制对照

机制教学实现 (Python)CodexOpenCodeDeepAgents轻量实现 (Rust)
Loopwhile stop_reason == "tool_use"相同模式,Rust 实现Go for 循环,相同退出条件LangGraph StateGraph 驱动loop { match stop_reason }
Toolsdict dispatch map,4 base toolsRust trait dispatch,apply_patch 替代 editGo interface dispatch,内置 LSP 集成Python decorator + tool registryRust enum dispatch,4 base tools
PlanningTodoWrite(内存 checklist)TodoWrite(相同语义)无显式 todo 工具,依赖模型自管理write_todos + plan state
Subagentrun_subagent() 独立 messages[]task 工具,Explore / Implement 两种 agent_type无独立子 Agenttask() 委派,隔离上下文
SkillsSKILL.md + SkillLoader 两层注入CLAUDE.md / AGENTS.md 作为项目级指令无 skill 系统system_prompt 分层配置
Compressmicro_compact + auto_compact + manual三层压缩,相同架构单层 context truncation无显式压缩,依赖长上下文模型
TasksJSON 文件 DAG (.tasks/)相同文件格式无持久化任务系统TODO 列表 + plan persistence
Backgrounddaemon thread + Queue异步 sandbox 执行Go goroutine 并发子智能体本身异步运行Tokio async task
TeamsTeammateManager + MessageBus多 codex 实例并行无多 Agent 协作多 Agent 架构原生支持
Protocolsshutdown + plan_approval FSM通过 API 协调编排器-子智能体协议
Autonomousidle phase + auto-claim taskCodex 自主循环用户驱动,非自主编排器决策驱动
Worktreegit worktree + task_id binding每个任务独立 sandbox无 worktree 隔离每个子智能体独立沙箱

基础设施对照

维度教学实现 (Python)CodexOpenCodeDeepAgents轻量实现 (Rust)
MCP内置 MCP client内置 MCP client,stdio/SSE 双传输可通过 tool 扩展
Provider单 provider (Anthropic)OpenAI API多 provider 抽象层 (OpenAI/Anthropic/Gemini/Ollama)LangChain model 抽象单 provider
Sandboxsubprocess + safe_path容器化沙箱 (网络隔离)无沙箱Daytona/Modal/RunLoop 远程沙箱subprocess
Persistence.tasks/ + .team/ + .transcripts/会话历史 + 项目配置会话 JSON 存储Plan + Memory store
UICLI REPL(input() 循环)CLI + TUI (Ink/React)TUI (Bubble Tea / Charm)Web UI (Streamlit/Gradio)CLI REPL
LSP无内置 LSP内置 LSP client(诊断注入)
HTTP APIREST API (cloud mode)LangServe / FastAPI
SDK纯 Python 脚本TypeScript SDK + Rust coreGo modulePython 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 的压缩阈值,都不是用户可以随意调整的。

三条分歧线的交叉产生了不同的项目定位:

mermaid
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

没有一种组合是普遍最优的。选择取决于三个问题的回答:

  1. 你的模型有多强? —— 强模型倾向 Trust,弱模型必须 Enforce
  2. 你的系统有多大? —— 小系统适合单体,大系统必须模块化
  3. 你的用户是谁? —— 开发者/研究者适合框架,终端用户需要产品

从教学实现的 737 行 Python 到 Codex 的数万行 Rust+TypeScript,核心不变量只有一个:while stop_reason == "tool_use": execute → append → call LLM。所有的设计哲学分歧、架构选择、功能取舍,都是围绕这个循环的包装和约束。理解了循环,就理解了一切 coding agent 的骨架;理解了 12 层机制的叠加路径,就理解了从骨架到完整系统的建造过程。