系统性学习:Claude Code 架构与 Agent 工作原理

一份纯文字的功能解读。不教你怎么写代码,只告诉你每个模块解决什么问题、怎么工作、边界在哪、各方怎么看。


0. 先厘清概念:Agent、Harness、Claude Code 三者是什么关系

这不是三个不同的东西。这是一个层层包含的关系。

Agent 是最大的概念。任何一个能从环境中感知、推理、行动的 AI 系统都是 Agent。它的核心是一个大语言模型(Large Language Model, LLM),这个模型通过训练获得了”遇到问题 → 判断要不要用工具 → 选哪个工具 → 看结果 → 继续判断”的能力。这个能力叫做 Agency(自主性)。详见 Agency 页面。

Harness 是套在模型外面的那层工程框架。模型负责决策,Harness 负责执行。模型说”我要读这个文件”,Harness 帮它打开文件、读取内容、把结果整理好喂回去。模型说”我要跑这个命令”,Harness 帮它在终端执行、捕获输出。模型说”我做完了”,Harness 停下来。详见 Harness工程 页面。

Claude Code 是 Anthropic 做的一个具体的 Harness 实现——专门为编程任务设计的 Agent 环境。它把 Harness 工程做到了目前业界最优雅的程度。不是因为有什么巧妙的 trick,而是因为它非常清楚自己的边界:它只做 Harness,不做 Agent。它给模型提供工具、管理上下文、控制权限,然后用一个循环把所有东西串起来。模型自己的智能——那个”遇到问题会推理”的能力——是模型训练时获得的,从来不来自 Claude Code 的代码。

shareAI-lablearn-claude-code教程 里对 Claude Code 的评价是一个很好的总结:

“Claude Code 不是因为它做了什么巧妙的事而优雅,而是因为它没做的事:它没有试图成为 Agent 本身。它没有强加僵化的工作流。它没有用精心设计的决策树去替模型做判断。”

Anthropic 自己在 Building Effective Agents 里对 Agent 的定义更加直白。他们区分了两个概念:

Workflow(工作流):LLM 和工具按照预设的代码路径编排。“先读文件 → 再分析 → 再生成报告”,每一步是事先写好的。代码有 if-else、有分支、有循环,但这些都在代码中固定。

Agent:LLM 动态地指导自己的流程。你不知道它会调用几次工具、调用什么工具、什么时候停——所有这些都是模型在运行时自己决定的。

Claude Code 显然是后者。它不是把编程流程编成了代码,而是给模型一双手(工具)、一双眼睛(文件系统、终端输出)、一个工作空间(项目目录),然后让模型自己决定怎么做。


1. Agent Loop:整个系统的发动机

所有 Agent 功能都挂在一个东西上:Agent Loop。这是整个系统的唯一运转核心。

它解决什么问题

在没有 Agent Loop 之前,人类用 LLM 编程的方式是”手动循环”:你把需求发给模型 → 模型输出一段代码 → 你复制粘贴到编辑器 → 运行 → 看到报错 → 把报错贴回对话框 → 模型修正 → 你再复制粘贴。每来回一次,人就是”中间层”。

Agent Loop 把这个中间层自动化了。人只需要在开始和结束时出现,中间的所有”调用工具 → 看结果 → 继续调用”完全自动。

它怎么工作

整个流程只有两个信号在驱动:

信号含义接下来发生什么
stop_reason == "tool_use"模型举手说”我需要用工具”Harness 执行工具 → 结果喂回模型 → 继续循环
stop_reason != "tool_use"模型说”我做完了”退出循环,把最终回复展示给用户

这就是全部。没有更复杂的逻辑。模型在每次循环中都看到完整的上下文——用户最初的需求、前面所有轮次的工具调用和结果——然后基于这些信息判断下一步。

为什么这个循环是”发动机”

后续所有高级功能——权限检查、Hook、子代理、上下文压缩、记忆系统、团队协作——全部叠加在同一个循环上。循环本身一行都不改。 新功能通过挂载机制接入(Hook 注册、工具分发、权限闸门),而不是修改循环逻辑。

这就是 shareAI-lab 教程中反复强调的那句话:

“循环永远不变。循环属于 Agent。机制属于 Harness。“

各方的看法

[Mario Zechner](Mario Zechner.md) 在 pi-coding-agent最小化设计 中强调:他自己写的 pi 也用完全相同的 Agent Loop 模式。他的循环更简单——只支持 4 个工具,没有权限检查,没有子代理——但核心的 while stop_reason == "tool_use" 和 Claude Code 一样。这说明 Agent Loop 是整个 Agent 范式的常数,不是某个具体产品的特性。

圣徒城的小诺Harness范式串讲 中也确认了这一点:不管用什么模型、什么工具、什么产品,只要它是一个 Agent,就一定有这个循环。


2. Tool Use(工具系统):Agent 的双手

Agent Loop 提供了”转起来”的能力,但模型在循环里能做什么,完全取决于它有什么工具。

它解决什么问题

模型本身是一个纯文本系统。它不能读写文件、不能执行命令、不能访问网络。把模型想象成一个被困在房间里的人——他什么都知道,但手碰不到任何东西。工具就是给他开了一扇窗,让他能碰到外面的世界。

Claude Code 有哪些工具

工具分两类:读的工具写的工具

读的工具包括 read(读取文件和图片)、grep(搜索文件内容)、glob(按模式匹配文件路径)、ls(列出目录内容)。这些不改变任何东西,纯粹是用来获取信息的。

写的工具包括 write(创建或覆写文件)、edit(精确替换文件中的某一段文字)、bash(在终端执行命令)。这些会改变系统状态。

额外还有 task(启动子代理,后面会细讲)、web_search(网络搜索)和 web_fetch(抓取网页内容)。

注意 Claude Code 的 bash 是所有工具中最强大的——通过 bash,模型理论上可以做任何事:安装软件包、启动服务、运行测试、操作 git。但 Claude Code 并不只依赖 bash,它还为常见的文件操作提供了专用工具——grep 搜索、glob 匹配文件、ls 列出目录——每个都有精确定义的参数 schema,bash 只用于”没有专用工具覆盖的操作”。

这和 pi 的设计形成对比。pi 默认只开四个工具:readwriteeditbash。注意 pi 也有 read/write/edit——文件读写不是靠 catsed 拼出来的。它缺失的是 grepglobls 这三个搜索类工具,这些在 pi 中确实要由模型拼 bash 命令来完成。

但 [Mario Zechner](Mario Zechner.md) 不是认识不到”专用工具更方便”——他的理由是另一个维度的权衡。他在原文中的论证是:前沿模型已经被 RL 训练得足够好,它们天然就懂得编程 Agent 该做什么。 一个 grep 工具的描述要占用 system prompt 里的 token,而模型本来就会拼 grep "def main" *.py。多定义十个专用工具,system prompt 就多膨胀几千 token——这部分 token 在每一次会话中都会被消耗,不管你用不用这些工具。Mario 的选择是信任模型的能力,用简洁的 system prompt 换取不被”膨胀的 Harness”绑架的上下文空间

这是两种不同的哲学:Claude Code 选择”用专用工具给模型铺好路,减少模型犯错的机会”;pi 选择”既然模型已经会了,就别在 system prompt 里重复教它”。哪个更好没有定论——取决于你用的模型有多强、你的场景对精确性要求有多高。Claude Code 更适合”防错”,pi 更适合”精简”。

工具分发的核心机制:Dispatch Table

每次模型决定要用工具时,它只是说”我要用 read,参数是 path: main.py”。Harness 收到这个请求后,在一个字典里查找 read 对应的处理函数,把参数传进去,拿到结果,打包好塞回给模型。

这个字典就是 dispatch table。加一个新工具 = 在这个字典里加一行映射 + 在工具定义列表里加一条描述。不需要改循环,不需要改任何已有工具。这就是为什么工具系统可以灵活扩展——它被设计成”可插拔”的。

工具定义有多重要

模型看到的是工具的描述,不是工具的代码实现。工具描述包括:名字、一段自然语言说明它做什么、以及参数 schema(参数的名称、类型、是否必填、描述)。

Anthropic 在 Building Effective Agents 文章里给了非常具体的建议:

“花在优化工具定义上的时间,不应该少于花在优化系统提示词上的时间。”

他们的 SWE-bench Agent 开发过程中,花在优化工具上的时间比花在优化提示词上的时间还要多。一个典型例子是:他们发现模型在使用相对路径的工具时会出错,因为 Agent 切换工作目录后就找不到文件了。解决办法不是写个更聪明的模型——而是把工具定义改成强制要求绝对路径。模型天然就懂得用绝对路径,只是需要工具定义明确告诉它”必须用绝对路径”。

这就是 Harness 工程的核心精神:不是让模型变聪明,而是把环境设计成模型能不犯错的形状。

多工具并行调用

一个容易被忽视的能力是:模型可以在单轮对话中同时调用多个工具。比如它知道需要”读 a.py 和 b.py,然后列出所有 .py 文件”,它可以在一次 response 中发出三个工具调用。Harness 收到后可以选择并行执行(如果这些调用之间没有依赖关系)或按顺序执行。这个能力和单工具调用有本质区别——它让 Agent 可以提前规划这一轮的操作,而不是每次都”走一步看一步”。

关于 MCP

MCP(模型上下文协议,Model Context Protocol)是 Anthropic 提出的一套标准协议,让任何人写的工具都能被任何支持 MCP 的 Agent 直接使用。详见 [MCP 模型上下文协议](MCP 模型上下文协议.md)。

但 MCP 有争议。[Mario Zechner](Mario Zechner.md) 在 pi 中完全不支持 MCP。他的论点是:一个典型的 MCP server(比如 Playwright 浏览器控制 MCP)有 21 个工具定义,总共约 13,700 个 token。每次开启 Claude Code 会话,这些工具描述不管你用不用都会被加载进上下文。这在模型注意力只有 ~256K token 的情况下是巨大的浪费。他的替代方案是:CLI(命令行界面,Command Line Interface)工具 + README 文件。Agent 需要时读一下 README(按需消费 token),然后通过 bash 调用。

这是一个经典的”标准化 vs 轻量化”之争。见 极简派vs工程派Harness设计对比


3. Permission(权限系统):三道闸门

它解决什么问题

工具给了 Agent 双手。但双手可以做任何事——包括破坏性的事。让 Agent “清理一下项目”,它可能执行 rm -rf /。虽然现代模型很少真的犯这种错误,但 Harness 的安全不靠信任模型,靠代码兜底。

三道闸门的工作方式

Perission 系统在每个工具执行之前插入检查。三道闸门按固定顺序运行:

第一道:拒绝列表。一张硬编码的列表,存着”永远不允许”的操作。比如包含 sudorm -rf /shutdown 的命令。命中直接拒绝,不给任何通融空间。这道门不需要网络、不需要 AI 判断——它就是纯字符串匹配,极快、极稳。

第二道:规则匹配。比拒绝列表更灵活。规则被定义为”哪个工具 + 什么条件 → 告警信息”。例如:“如果 write_file 的目标路径在工作区之外 → 触发警告”;“如果 bash 命令中包含 rmchmod 777 → 触发警告”。规则命中的意思是”这个操作有风险,但也许用户确实需要”。

第三道:用户审批。第二道门命中后,Harness 暂停循环,弹出提示,等用户输入 yn。这是最后一道防线。如果用户说 no,工具不执行,Agent 被通知”操作被拒绝”,然后它必须自己想别的办法。

三道都没命中 → 直接放行。大部分日常操作——读写工作区内的文件、跑测试、查看目录——走的就是这条路。

这意味着什么

这个三层设计代表了 Harness 安全哲学的核心思路:

  • 第一层是硬规则:不用 AI 判断,代码写死。不可能绕过。
  • 第二层是软规则:可配置、可调整、可按项目需求定制。
  • 第三层是人的判断:当机器无法确定,把决定权交给用户。

三道加起来,形成了一个”信任但验证”的机制:信任模型基本不会乱来,但每一层都作为保险存在。

各方的看法

关于权限系统的一个极端观点来自 [Mario Zechner](Mario Zechner.md)。他的 pi 没有权限系统——YOLO(You Only Live Once)模式。他的论证可以概括为:一旦 Agent 有能力写文件并执行命令,任何安全审查都是 theatre。 一个恶意指令(或被注入的恶意内容)可以通过无数种方式绕过字符串匹配的拒绝列表。他认为与其花精力在审查上,不如假设 Agent 有完全信任,在真正需要隔离的时候用容器(Docker)或虚拟机(Virtual Machine, VM)。

这是一个值得认真对待的立场。Claude Code 选择做权限审查,是因为它面向的受众更广——包括企业内部开发者、包括 CI/CD(持续集成/持续部署,Continuous Integration / Continuous Deployment)流水线。pi 面向的是一个人开发者,用户自己就是信任边界。场景不同,选择不同。


4. Hooks(生命周期钩子):扩展不侵入

它解决什么问题

有了权限系统之后,你可能会想加更多的功能:每次 bash 调用后自动 git add、Agent 启动时加载环境变量、会话结束时保存状态、每次操作前检查磁盘空间。如果把所有这些逻辑都塞进 Agent Loop 里,循环很快就变成一坨不可维护的面条代码。

Hook 的设计理念是:循环是一个稳定的核心,所有的扩展能力应该挂在核心外面,通过事件机制接入。

四个事件覆盖完整生命周期

事件触发时机能做什么
UserPromptSubmit用户输入提交后、进入 LLM 之前验证输入、注入额外上下文(如当前时间、项目路径)
PreToolUse工具执行之前权限检查、操作日志记录、阻止危险命令
PostToolUse工具执行之后副作用处理(自动 git add、发通知)、输出审查
Stop循环即将退出时清理工作、收尾日志、强制续跑

每个事件上可以注册多个回调函数。函数返回特殊值(如 "blocked")可以中断当前操作。除此之外,Hook 的代码是纯确定性的脚本——这一点非常关键。圣徒城的小诺 在 Harness 串讲视频中特别强调:

“Hook 是 Harness 中最稳定的组件。因为它是脚本执行,绝对的,不会有 AI 幻觉(Hallucination)。”

所有其他 Harness 组件(MCP、Subagent、Skill、Rule、甚至 Agent Team)都依赖 AI 的判断力和推理能力。AI 会犯错、会漂移、会丢上下文。Hook 不会。它就像防火墙上的一道确定性规则——不管模型怎么想、怎么飘,该做的检查一定会做。

这意味着什么

Hook 系统回答了一个根本问题:在不污染核心循环的前提下,怎么让 Agent 的行为可扩展? 答案是用”发布-订阅(Publish-Subscribe, Pub-Sub)“模式——循环发布事件,扩展订阅事件。循环不知道自己被谁扩展了、扩展了多少个功能,它只是一个可靠的事件广播器(Event Broadcaster)。新功能通过 register_hook() 注册,不需要改循环一行代码。


5. TodoWrite(任务规划):先想再干

它解决什么问题

Agent 在执行复杂任务时容易”迷失方向”。常见场景:用户说”重构认证模块”,Agent 开始改代码 → 改着改着发现还要改数据库 schema → 再改测试 → 然后忘了最初的目标是哪一步 → 最终产出一个半成品。

TodoWrite 的本质是让 Agent 在动手之前先列出计划。它不是一个”提示词技巧”——它是一个内置的 Harness 机制。Agent 被提示”先调用 TodoWrite 工具列出步骤”,然后工具把计划持久化写入一个文件。后续每一步执行时,Agent 可以对照这个清单检查进度。

它怎么工作

Agent 在接到任务后,先输出一个结构化的任务列表,包含每个任务的标题、状态(pending / in_progress / completed)和内容描述。这个列表被写到一个文件中(如 TODO.md),在整个会话中保持可见。Agent 做完一个任务后调用 TodoWrite 更新状态为已完成。

它的边界和争议

TodoWrite 的争议点是:一个外部文件比模型自己的”内部记忆”更可靠吗?模型确实可能忘记它计划过什么——但文件也可能被模型错误地修改或误解。

[Mario Zechner](Mario Zechner.md) 的 pi 完全不支持 TodoWrite。他的理由是:模型跟踪状态容易出错,且 TODO 列表本身占用了上下文空间。他推荐的做法是”如果你需要追踪任务,自己写一个 TODO.md 文件,Agent 读它就行”。这和 TodoWrite 的根本区别是:TodoWrite 是一个 Harness 功能,强制模型使用;而 Mario 的方案是一个外部约定,模型可以选择用或不用。


6. Subagent(子代理):上下文隔离的主机制

详见 Subagent 概念页。这里补充几个在概念页之外的重要视角。

它怎么工作

Subagent 在”独立的、从零开始的上下文”中运行。主 Agent 在运行中发现一个大块任务(比如”重构整个 UI 组件库”),它把这个任务描述打包发给一个 Subagent。Subagent 拿到描述后,在全新的 messages[] 中从零开始工作——它不知道主 Agent 前面的对话历史,它只能访问当前项目的文件。完成后,Subagent 把自己的结果(一段文本总结)返回给主 Agent。主 Agent 收到后继续自己的工作。

唯一正确的用途

Subagent 的核心目的是保持主 Agent 的上下文清洁——把大块活外包出去,别让这堆工具输出污染主 Agent 的上下文。Anthropic 的 Orchestrator-Workers 模式(见 Anthropic官方Agent构建指南)正是这样用的:中枢 LLM 动态分解任务,分发给多个 worker(Subagent)执行,每个 worker 只拿到自己需要的那段上下文,互不干扰。这种”独立任务并行执行”是 Subagent 的正统用法。

但 Subagent 不能用来实现多 Agent 协作——那是 Agent Team 的事(见后面章节)。多 Agent 协作需要 Agent 之间互相通信、协商、共同决策,Subagent 没有这种内置的通信和协调机制。Subagent 和 Agent Team 的分界线是:Agent 之间需要对话吗?不需要 → Subagent。需要 → Agent Team。

不是所有看起来”可以拆分”的任务都适合 Subagent。关键在于:任务是否自洽——子任务能在不依赖主 Agent 上下文的情况下独立完成吗?如果能,Subagent 合适。如果不能(比如子任务需要理解主 Agent 前面和用户的多轮讨论),那 Subagent 会因为缺少上下文而犯错。

它的致命缺陷

圣徒城的小诺 在 Harness 串讲视频中详细分析了 Subagent 最严重的问题:渐进式信息偏移。

主 Agent 传给 Subagent 的任务描述是它自己总结出来的。如果你最初的需求文档有 300-500 行,主 Agent 可能只用 300 个字来总结它,然后把这个 300 字的总结传给 Subagent。Subagent 基于这个”信息量大幅缩水”的版本工作——看似的确吻合、但实际上已经偏移。如果这个 Subagent 的结果再被另一个 Subagent 引用,偏移会进一步放大。最终产出和原始需求已经有了质的不同。

缓解方案:让主 Agent 把完整需求写入一个文件,要求每个 Subagent 启动时第一个工具调用是读这个文件。这样绕过了”主 Agent 总结”这个信息损耗环节。但这也意味着 Subagent 仍然占用上下文来读取文件——它只是避免了一个更大的损耗。

这个模式不是孤立出现的。 回头看 5 节(TodoWrite)中 Mario 的 TODO.md 方案——同一个思路在不同场景的重现:

TODO 管理Subagent 传需求
中间层做了什么Harness TodoWrite 替模型管理任务状态主 Agent 用自己的话”总结”需求
中间层引入了什么常驻 system prompt,固定 token 开销信息缩水(300 行 → 300 字)
文件方案怎么修LLM 自己写 TODO.md,需要时 read主 Agent 把需求写进文件,Subagent 启动时 read
本质拆掉 Harness 的”代理管理层”拆掉主 Agent 的”转述层”

两个问题的根因是同一个:中间层在替信息消费者决定”你需要知道什么”——但中间层没有完整的信息判断力。 Harness TodoWrite 不知道模型当前的注意力状况,主 Agent 不知道 Subagent 到底需要需求的哪个细节。文件方案的精髓就是拆掉这层中间转述——让信息直接从源头(文件)流向消费者(模型的 read 调用)。

这也是 Mario 反复说的”文件即接口”的具体含义。文件不只是一个存储方式,它是 Harness 各组件之间最诚实、最不损耗信息的通信协议。

Mario Zechner 的极端立场

[Mario Zechner](Mario Zechner.md) 在 pi 中完全不支持 Subagent。他的论证值得全文引述:

“Subagent is a black box within a black box. You have zero visibility into what that Subagent does.”

他把 “用 Subagent 在中途收集上下文”称为”一个规划失败的信号”。他的解决方案是:如果你需要上下文,在独立的会话中先收集,产生一个 artifact(比如一个分析报告文件),再在新鲜会话中使用。这样上下文不会污染,且每一步都有完整可见性。

这不是说 Subagent 永远不该用。它是说:大部分人用 Subagent 的场景其实可以用更好的工作流替代。


7. Skill Loading(技能加载):用到时再展开

详见 [Agent Skill](Agent Skill.md) 概念页。这里补充机制层面。

它解决的问题

一个 Agent 需要的领域知识太多了:前端开发规范、后端 API 设计约定、部署流程、测试策略、代码审查规则……如果全部写进 system prompt,模型每次会话都要消耗大量 token 来”记住”这些可能根本用不到的规则。

Skill Loading 的核心策略是渐进式公开(Progressive Disclosure)。Agent 启动时只加载一个”技能目录”——列出有哪些可用的技能及其一句话描述。当 Agent 实际工作时,它判断”我需要用某个技能”,然后 Harness 才把该技能的完整提示词加载进来。

对比:MCP 的工具膨胀问题

这其实是对 MCP 的 token 浪费问题的一种回应。一个 Playwright MCP server 的工具定义可能占用 13K-18K token,但 Agent 在某个具体会话中可能只用其中两三个工具。Skill Loading 把这个浪费降到最低——不需要的东西,永远不加载。

[Mario Zechner](Mario Zechner.md) 的 CLI 工具 + README 方案本质上也是渐进式公开:Agent 需要时才 cat README.md 看工具怎么用,不需要时零 token 开销。


8. Context Compact(上下文压缩):四层策略

它解决的问题

Agent 的上下文窗口是有限的。一次长会话可能积累几百 KB 的对话历史、工具输出、文件内容。当上下文接近模型的有效注意力上限(~256K token),模型开始”走神”——输出质量下降、遗漏关键信息、做出不一致的判断。

Context Compact 不是在”上下文满了”之后才触发的应急方案——它是整个会话中的持续管理机制。

四层压缩策略

learn-claude-code 的 s08 定义了四个层级,从便宜到贵:

第一层 snip(修剪):直接删除不相关的工具调用结果。比如 Agent 执行了 ls 列目录,那个结果对后续的”重构认证模块”没有意义,直接扔掉。

第二层 micro(微压缩):缩短每条消息的长度。长工具输出被截断到关键部分,对话中的冗余语句被合并。

第三层 budget(预算限制):给上下文总量设置硬上限。到达上限后,按照”最近的消息更重要”的原则,优先丢弃早期的内容。

第四层 auto(自动压缩):重度摘要。把大段对话历史压缩成简短的摘要文本。这层最贵(需要额外调用一次 LLM 做摘要),但压缩比最高。

与记忆系统的区别

Compact 和 Memory(第9章)经常被混淆。区别很简单:Compact 管”当前会话中的历史太长怎么办”,Memory 管”换了一个新会话之后还记得什么”。 Compact 是会话内管理,Memory 是跨会话持久化。


9. Memory(记忆系统):跨会话不遗忘

它解决的问题

Agent 在一个会话中帮你重构了认证模块,你学到了很多关于项目的约定和偏好。下次开新会话时,Agent 从零开始,不知道上一次的任何内容——除非有记忆系统。

三个子系统

learn-claude-code s09 定义的 Memory 不是一个”存所有对话”的粗暴方案,而是三个有明确分工的子系统:

筛选(Selection):决定哪些内容值得记住。不是所有对话都值得。分辨标准:“这条信息在未来的会话中会被用到吗?” 比如”用户偏好使用 TypeScript 而不是 JavaScript”值得记。“刚才运行 npm install 用了 3 秒”不值得记。

提取(Extraction):把筛选出来的内容从原始对话中提取成简洁的形式。不是截取原文,而是生成一个结构化的摘要。

整理(Consolidation):定期把多条相关的记忆合并成一条更紧凑的记忆,防止记忆库无限膨胀。

跨会话记忆 vs Session 上下文

这是初学者最容易混淆的地方。会话(Session)上下文是 Agent 在当前对话中的”意识流”——每次工具调用、每次模型回复都在这里面。跨会话记忆是”长期记忆”——当前会话结束、下次重新启动后,记忆仍然可用,会被注入到新会话的初始上下文中。


10. System Prompt(系统提示词):运行时组装

它解决的问题

System Prompt 不是一个固定的大文本,而是在运行时动态拼接出来的。

典型的 System Prompt 的组成部分包括:Agent 的角色定义(“你是一个编程助手”)、行为约束(“使用文件工具前先读取文件”)、可用工具列表、CLAUDE.md(用户的项目级上下文)、Skills Manifest(可用的技能目录)、以及记忆系统注入的跨会话记忆。

这些组件不是写死在代码里的,而是在每次 LLM 调用前,Harness 根据当前状态动态组装出一段 System Prompt。

这个设计的意义

[Mario Zechner](Mario Zechner.md) 在 pi 中把 System Prompt 压缩到 < 1000 tokens,他的实验结论是:前沿模型被 RL(强化学习,Reinforcement Learning)训练得足够好,不需要冗长的系统提示词。 他的 System Prompt 就是”你是一个编程助手。可用工具有 4 个。AGENTS.md 注入在最后。“仅此而已。Terminal-Bench 2.0 的成绩证明这完全够了。

这引出一个问题:Claude Code 那上万 token 的 System Prompt 到底有多少是真正有效的?这是一个开放问题,没有定论。但两者都在各自的场景中工作得很好,说明 System Prompt 的设计高度依赖具体的 Harness 选择和用户体验目标,不是一个”越详细越好”或”越短越好”的绝对标准。


11. Error Recovery(错误恢复):不是兜底,是设计

它解决什么问题

Agent 不是一个”不犯错”的系统——它是由网络调用、API 限流、模型过载、超长上下文等无数脆弱的依赖组成的脆弱链条。任何一个环节断裂,整个会话都可能废掉。

典型的失败模式包括:

  • API 熔断:模型服务商限流或宕机,当前请求被拒绝
  • 上下文溢出:对话历史太长,超过模型的上下文窗口上限
  • 工具执行失败:bash 命令报错、文件不存在、网络超时
  • 模型输出异常:返回格式错误的内容(不满足工具调用的 schema)、空回复、截断回复

如果没有错误恢复机制,用户面对的就是一句冷冰冰的 “Error 429” ——而 Agent 已经执行了两小时的工作可能全部丢失。

Error Recovery 的核心设计哲学藏在 learn-claude-code s11 的格言里:“错误不是终点,是重试的起点。” 这不是乐观主义的口号,而是一条架构原则——系统必须假设任何操作都可能失败,并在设计阶段就为失败准备了恢复路径。

它怎么工作

Claude Code 的错误恢复不是”出错了再想办法”的事后补救,而是一个分层级、分策略的前置设计。不同的错误类型走不同的恢复路径:

错误类型恢复策略具体做法
临时性错误(网络抖动、短暂限流)自动重试指数退避,等待时间从 1s 翻倍到 64s,最多重试 N 次
上下文溢出(超过窗口上限)Token 升级自动触发 Context Compact(s08 的四层压缩),腾出空间后继续
模型不可用(服务商全线宕机)Fallback 切换切换到备用模型(如 Opus 挂了换 Sonnet),同一个 Agent 不中断
工具调用异常(bash 返回非零)反馈给模型捕获错误输出,写入 tool_result,让模型看到错误后自己调整策略
模型输出异常(格式错误)重新请求截断不完整的 tool_use → 重发请求,或给模型补充提示让它补完

指数退避的细节

自动重试不是”马上再试一次”那么简单。如果 API 服务器正在过载,所有人同时重试只会让情况更糟。指数退避(Exponential Backoff)的核心思想是让重试间隔越来越长

第 1 次重试:等待 1 秒
第 2 次重试:等待 2 秒
第 3 次重试:等待 4 秒
第 4 次重试:等待 8 秒
第 5 次重试:等待 16 秒
...

加上随机抖动(Jitter),避免多个客户端”同步重试”造成的惊群效应。这个模式来自网络工程的成熟实践,不是 Agent 专属的发明——但它对 Agent 至关重要,因为一次 API 调用失败可能导致后续所有推理中断。

三层重试洋葱:claw0 的视角

claw0 网关 的 s09 Resilience 给出了另一个维度上更系统化的错误恢复设计——三层重试洋葱,从外到内:

  1. 认证轮换(最外层):API Key 过期或限流?自动切换到备用的下一个 Key
  2. 溢出压缩(中间层):上下文超过模型窗口?自动触发总结压缩,腾出空间
  3. 工具循环(最内层):工具调用失败?捕获异常、告知模型、让它重新选择工具

每层独立,不会互相干扰。认证问题不会触发压缩,工具失败不会去换 API Key。这种分层设计的精髓在于:每个恢复策略的触发条件被精确限定,不会出现”因为工具报错所以换了 API Key”这种逻辑混乱。

这意味着什么

Error Recovery 代表了 Harness 工程的一个关键认知升级:可靠性不是”模型不出错”,而是”系统在出错后能恢复”。

人类程序员也会犯错、也会遇到网络问题、也会碰到依赖库不兼容。但人类知道怎么应对——换一个方法、查文档、降级依赖。Agent 也有同样的需求,只不过它的”应对能力”由 Harness 提供,不由模型自己训练出来。

Anthropic 在 Building Effective Agents 中把这一点纳入了 ACI(Agent-Computer Interface)设计原则:工具返回的错误信息要有可操作性。 “X 失败了”没用,“X 失败了因为 Y,建议尝试 Z”有用。好的错误信息本身就是一种恢复机制——它让模型看到的不只是”出错了”,而是”出了什么错以及下一步可以做什么”。

各方的看法

[Mario Zechner](Mario Zechner.md) 的 pi 没有独立的错误恢复模块。他的理由是:模型的 RL 训练已经覆盖了”遇到错误怎么办”——模型看到 bash 输出中的 non-zero exit code,自然就知道要调整策略。 不需要在 Harness 里为每一种工具调用失败预写恢复路径。

但 pi 仍然有最基础的重试——API 调用失败时的指数退避重试。Mario 不做的是”为每一种工具调用失败预先定义恢复策略”——他把这件事交给模型的推理能力。这又一次回到了他在工具设计上的同一哲学:信任模型的能力,不替模型做判断。

圣徒城的小诺 的 Harness 串讲中,错误恢复体现为 Hook 的另一个用途:在 PostToolUse 钩子中检查工具执行结果——如果结果为空或报错,Hook 可以注入”重试指令”给 Agent。这是一种”不修改 Agent Loop 但增强其可靠性”的 Hook 式做法,和 learn-claude-code s04 的 Hook 哲学一脉相承。


12. Task System(任务系统):把任务状态从模型内存搬到磁盘

它解决什么问题

模型在单次会话中知道”现在在干嘛”。但 Agent 的工作经常跨会话——今天写了一半的认证重构,明天重新打开 Claude Code 继续。如果任务状态只存在于上次会话的对话历史中,新会话的 Agent 从零开始,完全不知道上次做到哪了。

Task System 的核心创新是把任务状态从模型的内存中”物化”到文件系统上,让它成为跨会话可恢复、可查询、可共享的持久化状态。learn-claude-code s12 的格言是:“大目标拆小任务,排好序,持久化。“

它怎么工作

任务存储在一个结构化文件中(如 TASKS.md 或 JSON),包含以下要素:

要素含义示例
标题任务目标的一句话描述”重构认证模块:JWT → OAuth 2.0”
状态pending / in_progress / done / blockedin_progress
依赖必须等哪些任务完成后才能开始depends_on: [“数据库迁移”, “环境变量更新”]
分配由哪个 Agent 负责assigned_to: “agent-3”
结果完成后的产出摘要”OAuth 流程已实现,5 个测试通过”

Agent 在会话开始时读取任务文件,恢复对进度的认知。在会话中做完一个任务后更新状态。会话结束时把进度写回文件。

依赖图:不只是 Todo List

Task System 和 TodoWrite(s05)的关键区别是依赖关系。TodoWrite 是一个线性列表——“先做 A,再做 B,再做 C”。Task System 是一个有向无环图(DAG)——“B 和 C 都依赖 A 完成,但 B 和 C 之间没有依赖,可以并行”。

       [数据库迁移]
            |
      ┌─────┴─────┐
      │           │
  [认证重构]   [API 更新]
      │           │
      └─────┬─────┘
            |
       [集成测试]

这个依赖图对多 Agent 协作(s15-s17)至关重要——各个 Agent 通过读同一个任务文件知道”谁在干什么、我可以开始干什么”。任务文件成了团队的共享真相源(Single Source of Truth)

文件方案的精髓

这再次印证了 [Mario Zechner](Mario Zechner.md) “文件即接口”的思想。在第 6 章(Subagent)中我们看到他用文件来传递需求给子任务,在第 5 章(TodoWrite)中我们看到他用 TODO.md 替代 Harness 的 TodoWrite 机制。Task System 把这个思路提升到了系统层面——任务状态不应该被锁在任何一个 Agent 的上下文里,它应该是一个所有人都能读、能写、能用 git diff 追踪变更的文件。

shareAI-lab 教程中对 Task System 的设计描述透露出同一个哲学:“让 Agent 的状态和普通文件一视同仁”。Agent 的”记忆”不需要是一个黑箱的向量数据库(Vector Database)——文件系统就是最好的记忆载体。

与 TodoWrite 的关系

TodoWrite(s05)Task System(s12)
粒度单次会话内的步骤跨会话、跨 Agent 的任务
结构线性列表有向无环图(含依赖)
持久化会话内持久(会话结束即消失)文件持久化(跨会话存在)
共享不共享(只有一个 Agent)多个 Agent 共享同一份任务文件

两者不矛盾——TodoWrite 适合”这个会话内先把思路理清”,Task System 适合”这件事要分好几次才能做完,而且可能不止一个 Agent 参与”。


13. Background Tasks(后台任务):慢操作别堵着主循环

它解决什么问题

Agent 在工具调用时是同步阻塞的——调用 bash 跑单元测试,Agent 必须等测试跑完(可能 2 分钟)才能看到结果,然后才能继续思考下一步。在这 2 分钟里,Agent 什么都不做,就等着。

这对单 Agent 来说只是效率低。但对 Agent Team 来说,一个 Agent 阻塞可能导致整个团队的协调停滞——如果 Agent A 在等 Agent B 的回复,但 B 正在等一个慢命令跑完,整个链条就断了。

Background Tasks 让 Agent 可以把慢操作丢到后台,自己继续做其他事情。learn-claude-code s13 的格言是:“慢操作丢后台。“

它怎么工作

机制很简单:

  1. Agent 调用工具时标记 background: true
  2. Harness 不阻塞——启动一个后台线程执行,立即返回一个 task_id 给 Agent
  3. Agent 收到 task_id,知道”这事在后台跑着”,继续思考其他步骤
  4. 后台任务完成后,线程把结果注入到 Agent 的消息队列(作为一个新的 tool_result 消息)
  5. Agent 在下一次循环中看到这个消息,处理结果

关键点在于第 4 步的通知注入——Agent 没有被轮询的负担。任务完成后通知自然出现在消息流里,Agent 无需主动”去查一下那个任务跑完没有”。

Mario Zechner 的 tmux 替代方案

[Mario Zechner](Mario Zechner.md) 的 pi 不支持 Background Tasks。他的理由和之前一致:操作系统已经有最好的后台管理工具——tmux。 pi 直接在 tmux 窗口里跑命令,Agent 可以同时开多个 tmux 窗口,每个窗口独立运行。Agent 不需要一个”后台线程+通知”的 Harness 机制,它只需要 tmux new-window "npm test"

Mario 不在 Harness 里实现后台,因为Harness 不应该重新发明操作系统已经提供的能力。 tmux 天然支持”在后台运行、随时切过去看、输出完整保留”,比任何 Harness 级的后台实现都更可靠。

claw0 中的对应设计

claw0 网关 的 s07(Heartbeat)和 s08(Delivery)相当于另一种形式的”后台”——Heartbeat 是一个定时线程在后台跑,产出的消息通过投递队列异步发送。Agent 不需要等待任何东西——心跳自己跑,消息自己发,Agent 只管读收件箱。

这与 Background Tasks 共享同一个核心设计原则:不要让主循环等任何它不需要等的事情。


14. Cron(定时任务):不需要人推的 Agent

它解决什么问题

此前的所有机制都假设 Agent 是被动的——用户发指令 → Agent 执行 → 返回结果。但很多场景要求 Agent 主动行动:

  • 每天早上 9 点检查 CI 流水线状态
  • 每小时拉取一次生产环境的错误日志
  • 每周五下午生成项目周报

Cron 让 Agent 从”等人推”变成”自己动”。learn-claude-code s14 的格言是:“定时触发,不需要人推。“

它怎么工作

Cron 机制挂载在 Agent 的心跳(Heartbeat)上——这个概念在 learn-claude-code 和 claw0 中都有出现:

  1. Agent 启动时注册一组 Cron 表达式(如 0 9 * * 1-5 = 工作日每天早上 9 点)
  2. Harness 内部有一个轻量级调度器,不断检查”当前时间是否匹配任何 Cron 表达式”
  3. 匹配时,调度器向 Agent 的收件箱注入一条”定时触发消息”
  4. Agent 在循环中收到这条消息,就像收到用户指令一样处理

关键设计:定时触发消息和用户消息在 Agent 眼中是等价的。 Agent 不区分”这个任务是人派给我的”还是”定时器派给我的”——通过同一个循环、同一个消息流处理。这让 Cron 不是一种额外的”触发模式”,而只是消息来源的一种。

从”聊天机器人”到”自主助手”

Cron + Heartbeat 的组合是 Agent 从”被动工具”到”主动服务”的质变点。没有这两个机制,Agent 本质上还是一个”能自动执行工具调用的聊天机器人”。有了它们,Agent 变成了一个常驻的数字同事——它不需要你推它,它自己知道什么时候该干什么。

claw0 网关 的 s07 Heartbeat 完美展示了这个转变:Agent 在群聊里是一个”常驻人口”,不需要有人 @它——它自己定时检查状态、发现异常主动报告、到点主动发日报。这才是”AI 助手”的真正含义。

各方的看法

[Mario Zechner](Mario Zechner.md) 的 pi 没有 Cron——因为 pi 是一个 CLI 工具,每次运行就是一次对话,不存在”常驻”这个场景。但他的哲学在这个问题上同样适用:如果你需要定时任务,用操作系统的 cron(或 systemd timer),不要在 Agent 里再发明一套调度器。 操作系统的 cron 已经经过了 40 年的生产验证,比任何 Agent 内部的调度器都可靠。

这个论点有道理——但在 Agent Team 和持续运维场景中,Agent 内部的 Cron 有一个操作系统 cron 不具备的优势:Agent 的 Cron 任务和 Agent 的上下文在同一个会话中。 上午 9 点的 CI 检查不仅要知道”检查 CI”这个指令,还需要知道昨天下午 Agent 做了什么修改、哪些改动需要特别关注——这些上下文只存在于 Agent 的会话中,操作系统的 cron 无法传递。

两种方案的选择取决于:定时任务需要多少上下文? 如果只是”跑一个固定脚本”,操作系统的 cron 更好。如果是”基于今天的工作上下文做智能判断”,Agent 内部的 Cron 是必要的。


15. Agent Teams(多 Agent 团队):一个搞不定,组队来

它解决什么问题

单 Agent 有明确的复杂度上限。以下场景单 Agent 难以胜任:

  • 大规模代码迁移:需要同时改几百个文件,且不同模块之间需要协调
  • CI/CD 流水线中持续运行的检查任务:代码审查、安全检查、测试覆盖率分析需要并行执行
  • 需要多个独立视角权衡的决策:如”三个 Agent 从安全、性能、可维护性三个维度独立审查同一段代码,然后汇总意见”

这就是 Agent Teams 要解决的问题:当任务复杂到单一 Agent 无法在单个上下文中完成,你需要多个 Agent 协同工作。 learn-claude-code s15 的格言是:“一个搞不定,组队来。“

它怎么工作

Agent Team 的核心架构是共享消息总线(JSONL(JSON Lines)邮箱)

  1. 创建团队时,每个 Agent 获得一个独立的收件箱(一个 JSONL 文件)
  2. Agent 之间通过给彼此的收件箱写入消息来通信
  3. 每个 Agent 在自己的 Agent Loop 中运行,定期检查收件箱
  4. 消息格式为结构化的”任务请求 → 执行 → 结果返回”

和 Subagent 的关键区别:

SubagentAgent Team
生命周期临时,用完销毁持久化,跨会话存活
关系主-从(主 Agent 支配子 Agent)平等(队友之间平行协调)
通信单向:传任务 → 返回结果双向:持续对话、协商
上下文独立 messages[],从零开始每个 Agent 有自己的持久化会话
可见性主 Agent 看不到子 Agent 的过程所有队友的通信记录都在消息总线中

与 Anthropic Orchestrator-Workers 模式的关系

Agent Team 对应了 Anthropic官方Agent构建指南 中的 Orchestrator-Workers 模式——一个中枢 LLM 动态分解任务,分发给多个 worker(Agent)执行。但 learn-claude-code 的实现更进一步:它不让”中枢”固定为某个 Agent——任何一个 Agent 都可以发现任务、分解任务、分配给队友。这为 s17(Autonomous)的自组织机制打下了基础。

通信机制:异步邮箱

Agent 之间的通信是异步的——Agent A 给 Agent B 发一条消息,不需要等 B 立刻回复。A 继续做自己的事,B 在下一次检查收件箱时看到消息,处理完后再把结果投递回 A 的收件箱。这种设计避免了”等队友回复时整个团队卡住”的同步阻塞问题。

邮箱基于 JSONL(每行一个 JSON 对象)实现,本质上是文件系统上的消息队列。和 claw0 网关的 JSONL 会话持久化(s03)设计一致——追加写、顺序读、简单可靠。

争议:5-10 倍的 token 冗余

圣徒城的小诺 在 Harness 串讲中对 Agent Team 做了实机测试,结论是:token 冗余达单 Agent 的 5-10 倍。 每一个 Agent 之间的每一次通信都是一次完整的上下文消费——消息被写入、被读取、被理解、被回复。如果 3 个 Agent 来回协商 5 轮,中间产生的 token 开销远超单 Agent 直接完成任务。

更致命的是,他发现 Agent 经常忘记团队里有哪些成员、谁在干什么——因为每个 Agent 只看到自己被分配的任务,对全局状态缺乏感知。这让 Agent Team 在实际使用中更像”各自为战的三个单 Agent”而非”一个协同团队”。

视频作者的最终结论是:目前阶段不推荐 Agent Team。 一个强模型 + 精心设计的 Harness 比一支弱模型的 Agent 团队更高效。

Mario Zechner 的极端反对

[Mario Zechner](Mario Zechner.md) 对多 Agent 协作持强烈反对态度。他在 pi 中完全不支持 Subagent,更不用说 Agent Team。他的原话可以提炼为:并行 Agent 是一个反模式——除非你不在乎你的代码库变成垃圾堆。 多个 Agent 在同一代码库上同时修改,必然产生冲突。即使有 worktree 隔离(s18),最终 merge 时的语义冲突也是无法自动解决的。

他的替代方案是:串行执行,不要并行。 一个 Agent 做完一件事,检查结果,再做下一件。速度慢了,但质量可控。他在 Terminal-Bench 2.0 上的成绩证明了”慢但对”比”快但错”更有效。

但 shareAI-lab 为什么仍然推崇 Agent Team

shareAI-lab 把 Agent Team 作为 s15-s17 的核心教学内容,不是因为他们没看到上述问题——而是因为他们看到的使用场景不同。他们强调 Agent Team 的适用场景是需要多 Agent 协作完成复杂、长期运行、需要持久记忆的任务——比如自动化 CI/CD、大规模代码迁移、持续运行的运维 Agent。

在这些场景中,token 冗余的代价被”长期运行带来的累积收益”对冲。一个 CI 流水线 Agent 每天跑 100 次,其中 Agent Team 的 token 开销可能只占 10%,剩下的 90% 是单 Agent 执行带来的价值。但对于个人开发者的一次性任务,Agent Team 的 5-10 倍 token 冗余确实是不可接受的。

这就是”场景决定设计”的具体体现——不在于 Agent Team 技术好不好,而在于你的场景需不需要它。


16. Team Protocols(团队协议):队友之间的 API 约定

它解决什么问题

没有协议的多 Agent 通信是一场灾难。Agent A 给 Agent B 发”帮我改一下那个文件”,B 不知道:

  • “那个文件”是哪个文件?
  • “改一下”改成什么?
  • 什么时候需要完成?
  • 出错怎么办?

Team Protocols 定义了一套结构化的请求-回复格式,让 Agent 之间的通信像 API 调用一样精确。learn-claude-code s16 的格言是:“队友之间要有约定。“

协议定义了什么

协议要素含义示例
请求格式任务描述 + 输入数据 + 期望输出格式{"task": "analyze performance", "file": "src/auth.ts", "output": "json"}
回复格式成功/失败状态 + 结果数据 + 错误信息{"status": "ok", "result": {...}}{"status": "error", "error": "file not found"}
超时约定多久没回复算”队友失联”默认 5 分钟无响应 → 标记为超时
重试策略失败后谁负责重试、重试几次请求方负责重试,最多 3 次
优先级紧急任务可以插队priority: high 跳过队列直接处理

协议的工程意义

协议让 Agent Team 从”一群随意聊天的 AI”变成了”一个有纪律的工程团队”。它不是限制 Agent 的创造力——而是确保创造力发生在正确的边界内。就像软件工程中的 API 设计一样,协议定义的是接口契约,不是业务逻辑。

这和 Anthropic 的 ACI(Agent-Computer Interface)设计原则一致——花在优化接口上的时间,不少于花在优化系统提示词上的时间。 只不过这里优化的是 Agent 之间的接口,而非 Agent 和工具之间的接口。


17. Autonomous Agents(自主 Agent):不需要领导的自组织

它解决什么问题

在 Agent Team 中,总要有人分配任务。如果这个”领导”是一个固定的 Agent(中枢),那这个中枢就成了单点故障——它崩溃了,整个团队就停工了。

Autonomous Agents 的答案是:不要领导。让每个人自己看任务看板,看到符合自己条件的任务就认领。 learn-claude-code s17 的格言是:“队友自己看板,有活就认领。“

它怎么工作

自主 Agent 的核心是一个共享任务看板(一个持久化文件)。看板上列出所有待完成的任务,每个任务包含:标题、描述、状态(open / claimed / in_progress / done)、依赖关系(“这个任务需要等任务 X 完成”)。

每个 Agent 在自己的循环中定期检查看板:

  1. 扫描所有 open 状态的任务
  2. 过滤出自己能做的(能力匹配、依赖已满足)
  3. 认领(状态更新为 claimed,标注自己的 Agent ID)
  4. 执行
  5. 完成后标记 done
  6. 回到第 1 步

关键设计是”先认领再执行”——两个 Agent 同时看到一个任务时,先更新状态的那个获得执行权。另一个 Agent 看到状态已经是 claimed,就跳过。这是分布式系统中经典的”乐观并发控制(Optimistic Concurrency Control)“模式,搬到 Agent 协作中同样适用。

自组织的边界

自组织不意味着”无限自由”。看板上任务的结构(谁创建的任务、任务的依赖关系)仍然需要人或程序来定义——Agent 不会凭空”发明”任务。自组织解决的是执行阶段的协调问题,不是规划阶段的目标设定问题

各方的看法

圣徒城的小诺 对自主 Agent 的保留态度同样适用这里——多个 AI 在没有人类监督的情况下自主协作,“还不够成熟”。 Agent 容易忘记看板上还有哪些任务、容易竞争同一个任务、容易在依赖不满足时就开始工作。

[Mario Zechner](Mario Zechner.md) 的态度更彻底:根本不需要多 Agent,也就不需要”自主协调”。他的替代方案是串行任务队列——多个任务由一个 Agent 顺序处理,任务之间通过文件系统传递状态。没有协调开销,没有竞争条件。

但在 CI/CD 和持续运维场景中,单 Agent 串行根本无法满足时效要求——代码 push 后需要 30 秒内完成安全检查,同时还要跑单元测试和覆盖率分析。并行不可回避,自主协调就不可回避。


18. Worktree Isolation(工作区隔离):各干各的,互不干扰

它解决什么问题

两个 Agent 同时在同一个项目目录下工作,会出现物理冲突:Agent A 正在改写 auth.ts,Agent B 同时改了同一个文件——后写的人覆盖了先写的人。这不是 Agent “不聪明”,而是文件系统不支持两个写入者同时操作同一个文件。

Worktree Isolation 用 git worktree 给每个并行任务一个独立的文件系统沙箱(Sandbox)。learn-claude-code s18 的格言是:“各干各的目录,互不干扰。“

git worktree 是什么

git worktree 是 Git 的原生功能——让你在同一个仓库中同时检出多个分支到不同的目录

/project
  ├── main/          ← 主工作目录(Agent 1)
  ├── worktrees/
  │   ├── task-42/   ← Agent 2 的工作目录(分支 feature/auth-refactor)
  │   ├── task-43/   ← Agent 3 的工作目录(分支 feature/api-update)
  │   └── task-44/   ← Agent 4 的工作目录(分支 fix/security-patch)

每个 Agent 在自己的目录中随意读写文件,完全不会影响其他 Agent。任务完成后,Agent 把自己的分支推送到远程或提交 merge request,由人或 CI 流水线来合并。

两层隔离的对应关系

Worktree isolation 实际上是 两层隔离 的实践:

隔离层级机制隔离什么
文件系统层git worktree每个 Agent 有独立的文件目录,互不覆盖
进程层tmux / 独立终端每个 Agent 的 bash 命令在独立的进程空间中运行

两层加起来,确保并行 Agent 之间零物理冲突。不会出现”Agent A 读到 Agent B 写到一半的文件”这种竞态条件(Race Condition)。

但语义冲突无法自动解决

Worktree isolation 解决的是物理冲突(文件被覆盖),解决不了语义冲突(逻辑矛盾)。如果 Agent A 把 auth.ts 里的认证逻辑从 JWT 改成了 OAuth,Agent B 在 api.ts 里仍然按 JWT 的方式调用——两个 Agent 的改动在各自的分支里都”正确”,但合并后逻辑就断了。

这是 Agent Team 和 Worktree 被批评的根本原因。[Mario Zechner](Mario Zechner.md) 反对多 Agent 并行的核心论据就是这一点:合并两个 Agent 的代码比合并一个人的代码难得多——因为你没有参与任何一方的思维过程。 他主张串行工作流:一个 Agent 完整做完一个任务,输出结果,下一个 Agent 基于完整的结果继续工作。这样做虽然慢,但每一步都是完整可验证的。


19. MCP(模型上下文协议):外部工具的”万能插座”

回顾:MCP 在前面章节的定位

在第 2 章(Tool Use)和第 7 章(Skill Loading)中,MCP 已经被多次提及。这里不再重复基本概念(见 [MCP 模型上下文协议](MCP 模型上下文协议.md)),而是聚焦在学习路线中 s19 的具体设计决策:MCP 如何融入一个已有的完整 Agent 架构中。

s19 解决的核心问题

前面的章节已经把 Agent 的工具系统建好了——内置工具(read / write / bash / grep 等)通过 dispatch table 统一分发。现在的问题是:外部工具怎么接进来,和内置工具一视同仁?

MCP 的答案是定义一个标准协议,让外部工具服务器(MCP Server)暴露的工具和内置工具走同一个 dispatch table。Agent 不知道、也不关心一个工具是”内置的”还是”外部 MCP Server 提供的”——它只看到工具列表,从中选择合适的。

集成的关键设计:工具发现 + 统一调度

s19 的 MCP 集成包含两个步骤:

  1. 工具发现:Agent 启动时,Harness 向所有注册的 MCP Server 发起连接,获取每个 Server 的工具列表(名称 + 描述 + 参数 schema),把这些工具和内置工具合并到一个统一的工具列表中
  2. 统一调度:当 Agent 选择一个工具时——不管是内置的 bash 还是 MCP 提供的 browser_navigate——Harness 都在同一个 dispatch table 中查询处理函数。MCP 工具的处理函数不是直接执行,而是转发请求给对应的 MCP Server,等待返回,把结果打包好喂回 Agent

这样做的关键收益是:Agent Loop 不需要改一行代码。 对于 Agent 来说,“多了一个 MCP 工具”和”多了一个内置工具”在感知上没有区别——它只是看到了更多的工具选项。

Token 代价的重新讨论

这个”无缝集成”的代价在前面章节已经讨论过:MCP 工具的描述在 Agent 启动时被加载进上下文,不管这次会话用不用得到它们。 learn-claude-code s19 没有回避这个问题——教程明确指出 MCP 是一个”能力入口”也是”token 黑洞”。

一个 Playwright MCP Server 可能有 21 个工具定义,约 13.7K tokens。如果你同时启用 3 个 MCP Server(浏览器控制 + 数据库查询 + 文件管理),光工具定义就可能占 40K+ tokens。这对模型的注意力空间是一个巨大的固定税——这些 token 在每一次 API 调用时都被消耗,不管 Agent 实际调用其中几个工具。

与 Skill Loading 的互补关系

s19(MCP)和 s07(Skill Loading)放在一起看,能看出 learn-claude-code 对”token 管理”的完整思路:

MCP(s19)Skill Loading(s07)
管理的对象外部工具的定义领域知识的提示词
加载策略启动时全部加载按需渐进式加载
设计权衡完整性优先(确保 Agent 知道所有可用工具)经济性优先(不需要的不占 token)

这两者之间存在一个尚未解决的张力:为什么 MCP 工具不能也按需加载? 如果 Agent 在这个会话中不需要浏览器自动化,Playwright MCP 的 13.7K tokens 就白费了。Mario 的 CLI + README 方案正是解决了这个问题——工具描述按需消费,不用的零开销。

未来可能的演进方向是:MCP Server 只在 Agent 表达”我需要浏览器自动化”的意图时,Harness 再动态连接并加载对应工具。但目前的标准 MCP 实现不是这样——启动时连接、启动时加载。

各方的看法

[Mario Zechner](Mario Zechner.md) 完全不支持 MCP。他的替代方案(CLI 工具 + README 文件)在第 2 章已详细阐述。关键论点重申:13.7K tokens 的固定开销,对于前沿模型来说是一种浪费——因为模型本来就能在需要时通过 bash 调用浏览器工具。 把工具的发现权交给 Agent(而不是在启动时全摊在它面前),让 token 按需消费。

圣徒城的小诺 把 MCP 定义为 Harness 六大组件的最外层——“外部数据接入的标准化接口”。他认为 MCP 是最基础、最稳定的 Harness 组件,但警告说”每增加一个 MCP Server 就让 token 开销线性增长”。

shareAI-lab 把 MCP 集成作为 s19,不是因为他们认为 MCP 完美无瑕——而是因为 MCP 代表了”工具生态互通”的方向。learn-claude-code 教的是”怎么集成 MCP”而非”你应该无条件使用 MCP”。选择权留给读者。

Anthropic官方Agent构建指南 中 ACI 的设计原则——“花在优化工具定义上的时间不少于花在优化提示词上的时间”——同样适用于 MCP 工具。MCP Server 的作者有责任让工具描述尽可能精确、简洁、区分度清晰,因为每一个模糊的词都在浪费 Agent 的 token 预算。


20. Comprehensive Agent(综合 Agent):机制很多,循环一个

这是整个教程的收官之章

s20 不是引入新机制——它是把 s01 到 s19 的所有机制归到一个循环中。learn-claude-code s20 的格言:“机制很多,循环一个。”

教程在这里不再添加新的代码或概念,而是自顶向下审视整个架构:从 Agent Loop(s01)出发,工具系统(s02)给了 Agent 双手,权限系统(s03)给双手加了保险,Hook(s04)让保险可扩展,TodoWrite(s05)让 Agent 先想再干,Subagent(s06)让大任务分而治之,Skill(s07)让知识按需加载,Compact(s08)让上下文保持干净,Memory(s09)让跨会话不遗忘,System Prompt(s10)让一切在运行时组装,Error Recovery(s11)让失败不致命,Task System(s12)让目标跨会话持久化,Background(s13)让慢操作不阻塞,Cron(s14)让 Agent 自己动,Agent Teams(s15)让一个人搞不定的变可搞,Protocols(s16)让队友之间有序沟通,Autonomous(s17)让团队自组织,Worktree(s18)让并行不冲突,MCP(s19)让工具生态互通。

二十层机制,一层不改地挂在一个不变的 while True 循环上。

循环为什么可以一直不变

s20 回到教程开篇的核心命题:Agency 是训练出来的,不是编出来的。 Agent 的智能(推理能力、判断力、创造力)来自模型的 RL 训练,不来自 Harness 的任何一行代码。Harness 做的所有事情——给工具、管上下文、控权限、搭团队——都是在为模型创造一个能让它的智能发挥到极限的”栖居环境”。

循环不变,是因为循环只是模型”思考 → 行动 → 观察 → 思考”这个认知过程的载体。这个过程本身不需要变——一个人类工程师也是”想到 → 动手 → 看到结果 → 继续想”,从第一天到退休,这个循环不变。变的是技能、变的是工具、变的是队友——但这些都挂在循环外面。

这就是 learn-claude-code 教程 全篇最深的洞察——被反复引用、反复验证、被极简派和工程派同时确认:

“循环永远不变。循环属于 Agent。机制属于 Harness。“

从 20 课到真实世界

s20 之后,教程给出的是一条自顶向下审视的路径。不是让你把全部 20 个机制都塞进你的项目——而是让你有能力判断”我现在的场景需要哪几个机制、哪个机制会带来什么代价”。

这正是 Anthropic 的官方指南 和 [Mario Zechner](Mario Zechner.md) 和 圣徒城的小诺 从三个不同角度达成的共识:

来源核心建议
Anthropic从最简单开始,只在确实需要时加复杂度
Mario Zechner如果不需要,就不造
圣徒城的小诺模型能力提升后,拆掉不再需要的 Harness 组件

三者说的其实是同一件事:Harness 的终极形态不是”功能最全”,而是”恰好够用”。

s20 的隐藏课程:拆轮子

这可能是 learn-claude-code 教程没有显式写在教科书里、但贯穿全篇的隐藏课程:

造轮子的能力让你理解系统。拆轮子的判断力让你维护好系统。

20 课教你从零造轮子——不是因为你要在生产环境中从零造一个 Claude Code clone,而是因为只有亲手造过每一个轮子,你才能在不需要它的时候准确地拆掉它。 如果你不知道 Context Compact(s08)内部是什么,你就不知道”关掉它”对你的 Agent 意味着什么。如果你没亲手写过 Agent Team(s15)的邮箱通信,你就不能准确判断”我的场景不需要 Agent Team”。

这是 圣徒城的小诺 在 Harness 串讲中”拆轮子比造轮子更重要”观点的教学实现——learn-claude-code 用 20 课教你造轮子,最终目的是让你有能力拆轮子。


如何选择:一张决策地图

你的场景推荐路径
学习 Agent 原理跟 learn-claude-code 的 20 课,从 s01 到 s20 逐章理解
个人日常编程,强模型极简路线:4 工具 + 按需加载 + 无 Subagent
个人日常编程,中等模型(国产)工程路线:完整 Harness + Hook 兜底 + 多轮迭代
团队协作,多项目完整 Harness + Agent Team + Worktree 隔离
生产环境,CI/CD(持续集成/持续部署,Continuous Integration / Continuous Deployment)完整 Harness + Memory + Task System + Error Recovery
不确定该怎么做先极简,发现不够再往上加。永远不要”提前设计”你没遇到的需求

参考来源

所有内容锚定在以下真实来源:

来源说明
shareAI-lab/learn-claude-codeAgent Harness 工程的 20 课递进教程
Anthropic Building Effective Agents官方 Agent 构建指南,2024.12
Mario Zechner pi-coding-agent极简 Agent 自研经验,Terminal-Bench 验证
圣徒城的小诺 Harness 范式串讲Harness 六大组件的实机横评视频
圣徒城的小诺 独立 Token 遗忘症MiniMax + Qwen 2.5 的 Token 退化问题
Anthropic Claude Code 官方文档Claude Code 架构参考