乐于分享
好东西不私藏

我看完Claude Code源码后,总结出AI Agent最核心的Harness应该这么搭

我看完Claude Code源码后,总结出AI Agent最核心的Harness应该这么搭

Anthropic 刚刚亲手教会了你,怎样搭出最好的 AI Agent Harness

Claude Code 的源码就摆在那里:55 个目录、331 个模块,这是当下生产环境里经历过最多实战检验的 Agent 架构之一。我把每一个文件都拆开看了。每一个架构决策、每一条重试路径、每一种压缩策略、每一个权限阶段。

这不是一次拆解。

这是一份蓝图。

下面,我会把它里面的每一条原则都摊开讲清楚,以及你该如何把这些原则用到自己的系统里,构建一个真正扛得住生产环境的 Harness。

Claude Code 的架构,揭示了行业最爱谈的 Framework 到底漏了什么

你一定听过那三个层次:模型权重、上下文、Harness
行业里几乎每一场大会、每一篇教程、每一个框架 README 都在重复这三层。

Model Weights(模型权重):被冻结的智能本体,也就是你通过 API 调用的那个东西。
Context(上下文):提示词、对话历史、检索到的文档。
Harness:包在模型外面的那层脚手架,包括工具、循环、错误处理。

这个划分本身并没有错。

Princeton NLP 的 SWE-agent 论文已经证明过:在 SWE-bench 上,只改界面设计、不改模型,效果就能带来 64% 的相对提升。同样的 GPT-4,同样的任务,变化的只有环境。性能增益主要来自第 2 层和第 3 层,不是第 1 层。

但当你真正打开 Claude Code 的源码时,你会意识到:Anthropic 不是在为“模型”构建系统,他们是在为“系统”本身构建系统。

在 Claude Code 内部:

  • 一个 四级 CLAUDE.md 指令层级,让企业管理员可以通过 MDM 强制执行策略;
  • 项目维护者可以设定团队约定;
  • 个人开发者还能在本地做私有覆盖;
  • 一个磁盘持久化的任务列表加上基于文件的锁,确保并行子 Agent 不会互相污染状态;
  • Git worktree 隔离,让 5 个 Agent 能在同一个仓库上开 5 条分支同时工作,互不冲突;
  • 一个权限流水线,把拒绝规则从企业级一路级联到项目级、用户级、会话级。

这些都不是 Harness。
这些都不是 Context。
这些也都不是 Weights。

这些是 Infrastructure(基础设施)

  • 多租户(multi-tenancy)
  • 基于角色的访问控制(RBAC)
  • 资源隔离(resource isolation)
  • 状态持久化(state persistence)
  • 分布式协同(distributed coordination)

所以,真正的框架其实有四层

Model Weights:冻结的智能。
Context:运行时输入。
Harness:Agent 被精心设计过的执行环境。
Infrastructure:多租户、RBAC、资源隔离、状态持久化、分布式协同。

大多数团队只谈前三层,因为前三层更“有意思”,更适合拿来讨论。
但真正让产品死掉的,是第四层。

Claude Code 是我见过第一个把四层都认真对待的 Agent 系统,而它的架构在每一个细节里都体现了这一点。

核心 Agent Loop:它是 Async Generator,不是 While Loop

Claude Code 的心脏在 query.ts 里:1729 行 TypeScript
而最关键的决策,藏在一个函数签名里:

ounter(lineasync function* query(...) { ... }

这个 function* 的分量,比它看起来重要得多。

Async Generator 可以持续地产出值、按需暂停,并且允许任意调用方在任意时刻中断流程。

Agent Loop 从来都不应该是一个“请求—响应”的一次性循环。
它应该是一个长时间运行、支持流式输出、可取消的过程。

Generator 天然就把这些能力带进来了,不需要你再往外硬补。

而大多数教程教你的,通常是这种东西:

ounter(lineounter(lineounter(lineounter(lineounter(linewhile (!done) {  const response = await callModel(...)  const toolResults = await runTools(response)  messages.push(response, toolResults)}

这种写法在教程里能跑,到了生产环境会迅速崩掉,原因有五个。

1)没有 Streaming

用户只能盯着空白屏幕,等模型 10 到 30 秒。
而 Claude Code 的 Generator 会随着 token 到达,不断产出 StreamEvent 对象。

用户能逐字逐句看到模型正在工作。
当用户能“看见” Agent 在做什么时,他们会更信任它。
而用户越信任它,就越愿意给它更高的自主权。
真正有价值的工作,往往发生在这种自主权之上。

2)没有 Cancellation

在 while-loop 版本里,按下 Ctrl+C 往往意味着你还得从外部额外接一套 abort 机制。
但对于 Generator 来说,调用方只要不再调用 .next(),流程就会自然终止;finally 会被执行,清理逻辑也会自动触发。

Claude Code 把 AbortSignal 一层层往下传,而 Generator 让这种设计变得非常自然。

3)没有 Composability

Claude Code 的 REPL UI 消费这个 Generator。
子 Agent 消费这个 Generator。
测试也消费这个 Generator。

一个 query() 函数,三个调用方,零重复。

Generator 本质上是一个通用的流式接口。

4)没有 Backpressure

如果模型生成速度比终端渲染速度更快,while-loop 会把所有内容都堆在内存里。
Generator 则会在消费者暂时不拉取时,暂停生产。

在超长会话里,这个差异直接决定了:
你的内存占用是保持可控,还是一路膨胀到把进程撑死。

5)循环内部没有 Error Recovery

这才是真正严肃的地方。

每一轮迭代都分五个阶段

Claude Code 的每一次 Agent Loop 迭代,都要经过五个阶段。
这五个阶段,就是它之所以稳健的原因。

Phase 1:Setup

在调用模型之前,Loop 会先:

  • 应用 tool result budget;
  • 如果对话已经很长,则运行 compaction 策略;
  • 校验 token 数量。

大多数 Harness 做法是:直接把原始消息数组扔给模型,然后祈祷别炸。

Claude Code 不是。

Phase 2:Model Invocation

Loop 会通过依赖注入接口调用 queryModelWithStreaming(),外面再包一层能处理 10 类错误的重试系统。

更关键的是:流式工具执行器会在这个阶段就开始执行工具,而不是等模型整段输出完。

比如一个 Grep 调用,只要它的输入 JSON 在流里一完整,执行器就立刻启动它——甚至下一个工具调用都还没完全流出来。

Phase 3:Error Recovery & Compaction

模型响应结束后,Loop 会检查是否存在可恢复错误:

  • prompt-too-long?压缩后重试;
  • max_output_tokens 撞上限?从 32K 升到 64K 再重试;
  • context overflow?对媒体较重的消息做响应式压缩。

这些在 Claude Code 里不是外围 try/catch 的边角料,而是状态机里的一级状态

Phase 4:Tool Execution

那些还没被流式执行器提前跑掉的工具,会在这里执行。
工具结果一完成,就立刻 yield 给 UI。

同时,Claude Code 还会让 Haiku 异步生成工具使用摘要,避免主模型把 token 浪费在“记账”这种事情上。

Phase 5:Continuation Decision

模型的 stop_reason 决定这一轮之后是否还需要继续发起工具调用。
系统还会检查:

  • turn counter 是否超过 maxTurns
  • hooks 是否请求停止;
  • abort signal 是否触发。

如果要继续,状态递增,重新回到 Phase 1。

错误恢复是在循环内部,而不是循环外围。

每一个阶段都知道自己可能出什么错,也知道该怎么恢复。
这就是“一个遇到 rate limit 就崩的 Agent”和“一个会退避、重试、切换备选模型、继续把活干完的 Agent”之间的本质差异。

依赖注入,让它变得可测试

这个 Loop 通过一个 QueryDeps 接口接收自己的依赖。

也就是说,你可以注入一个 mock 的 callModel,让它产出预设事件,然后验证:

  • context overflow 是否处理正确;
  • tool failure 是否处理正确;
  • cancellation 是否处理正确。

大多数 Agent Harness 无法测试,不是因为它们太复杂,而是因为它们把 API 调用硬编码进了 Loop。

Claude Code 的 Loop 更像一个纯状态机 + 注入式副作用的系统,因此天然可测。

工具执行:为什么“并发分类”会改变一切

Claude Code 自带 45+ 内置工具
重点不是数量,而是它们的执行方式

大部分 Harness 的做法是:

  1. 模型生成工具调用;
  2. Harness 顺序执行这些工具;
  3. 把结果回传给模型。

这样当然安全,但速度慢。

另一种极端做法是:全部并行。
这样很快,但也很危险——两个并行文件写操作写到同一路径,状态就污染了。

Claude Code 的做法是:先给每个工具打并发标签

编排层 toolOrchestration.ts 会把工具调用拆成批次:

  • 只读工具(如 GlobGrepReadWebFetch)并发执行,最高 10 个并行;
  • 写操作工具(如带副作用的 BashEditWrite)串行执行。

于是你得到的是:

  • 并行的速度;
  • 串行的安全性;
  • 没有 race condition。

Claude Code 可以一边并行搜 5 个文件,一边最后只顺序改 1 个文件。
多工具回合能拿到 2 到 5 倍加速,一整个 session 累积下来,就是几分钟的差距。

Streaming Tool Executor:模型还没说完,工具就已经开跑了

StreamingToolExecutor 才是更有意思的组件。

大多数 Harness 会等模型把整段输出写完,再开始执行工具。
Claude Code 不等。它在流式输出中途就启动执行。

对于一个包含 3 个工具调用的回合,这意味着你能直接隐藏 2 到 5 秒的延迟。
模型一边继续生成自己下一步的描述,前面的工具一边已经在后台跑起来了。

等模型说完,前面工具的结果可能已经等在那里。

而且,那些麻烦情况它也处理到了:

  • 如果并行批次里某个工具失败了,会用每个工具自己的 siblingAbortController 杀掉同批次兄弟进程,但父级 query controller 还活着,整段对话不会死。模型会收到错误,然后继续恢复。
  • 如果流式过程失败并回退到非流式模式,执行器会丢弃排队中的工具,并为在途任务生成 synthetic error result。
  • 即使工具 2 比工具 1 更早完成,结果也会按原始顺序 yield,确保对模型和用户来说,叙事仍然连贯。

Tool Result Budgeting:别让 1MB 日志把上下文窗口塞爆

如果用户跑了一个 Bash 命令,直接吐出 1MB 日志,而你原封不动塞给模型,上下文窗口马上就会被垃圾信息占满。

Claude Code 为此设计了一整套 budgeting 系统:

  • 每个工具声明自己的 maxResultSizeChars
  • 超过上限的结果会持久化到磁盘;
  • 模型拿到的是:文件路径引用 + 前 N 个字符预览
  • applyToolResultBudget() 会在每次 API 调用前运行,把工具结果总体 token 数控制住。

用户一定会:

  • 对超大文件执行 cat
  • 跑出成 MB 的日志流;
  • 把上下文灌满噪音。

如果没有 budgeting,Agent 很快就会失去连贯性。
这种细节从来不会出现在那些好看的架构图里,但它决定了一个 Agent 到底能不能活过真实使用。

大规模 Prompt Engineering:System Prompt 本质上是缓存问题

Claude Code 里的 system prompt 不是一整块字符串。
它是一个带缓存元数据的结构化 section 数组

其中最关键的设计,是一个边界标记:

SYSTEM_PROMPT_DYNAMIC_BOUNDARY

这个边界把 prompt 切成两个区域。

边界以上的内容:

  • 所有用户都一样;
  • 所有 session 都一样;
  • 因此可以在 API 层命中全局 prompt cache

这大概占了整个 prompt 的 80%
也就是说,你不需要为每个用户、每次 API 调用,都重新 tokenize 那 577+ 行的固定内容。

边界以下的 section,则分成两类:

  • memoized:每个 session 只算一次;
  • volatile:每一轮都重新计算。

而 volatile section 会被尽量压缩,因为它每变一次,都会让它之后的缓存全部失效。

我几乎没在任何 Agent 教程、框架文档、会议分享里,看到有人认真讨论“如何为缓存效率来设计 prompt”。
但它恰恰是整个代码库里杠杆最高的决策之一。

在规模化场景里,这可能直接决定你的 Agent 是每次会话 0.02 美元,还是 0.20 美元

CLAUDE.md 层级:可组合的记忆系统

Claude Code 有一个四级指令层级,本质上是一套可组合的记忆系统。

更高层覆盖更低层:

  • 企业管理员在组织级统一下发编码规范;
  • 用户层配置个人偏好;
  • 项目层定义团队约定;
  • 开发者还可以保留私有 override,并且不进入版本控制。

它还支持 @include 指令进行组合:

ounter(lineounter(line@./docs/coding-standards.md@~/shared-rules.md

企业级配置甚至可以与 **MDM(移动设备管理)**联动,实现策略强制下发。

这已经不是 Harness Engineering 了。
这是基础设施工程

为什么上下文注入放在 System Prompt 之外

用户上下文,比如:

  • git status
  • CLAUDE.md 内容
  • 当前日期

Claude Code 会把它们包在 <system-reminder> 标签里,作为第一条 user message 注入进去,而不是塞进 system prompt。

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line<system-reminder>As you answer the user's questions, you can use the following context:# claudeMd[contents of CLAUDE.md files]# currentDateToday's date is 2026-04-07.</system-reminder>

原因很简单:

这些上下文每一轮都会变化
如果你把它们放进 system prompt,那么缓存会从变化点开始全部失效。
而把它们移动到 user message,就能让 system prompt 在每一轮里都保持 cache-stable。

这是个很小的实现细节。
但对成本的影响,非常大。

上下文窗口管理:四种 Compaction 策略

大多数 Agent Harness 在快撞上下文上限时只有两种结局:

  • 截断旧消息;
  • 直接崩溃。

Claude Code 不一样。
它通过四种压缩策略,让会话长度可以理论上无限延展。并且这四种策略按从便宜到昂贵的顺序触发。

Strategy 1:Microcompact

每一轮 API 调用前都会跑。

如果某个工具已经调用过,且结果和上一次相比没有变化,系统就会把完整结果替换成缓存引用。
例如对同一个文件反复执行 Read,这种方式每个 session 就能省掉几千个 token。

成本几乎为零。

Strategy 2:Snip Compact

当 token 即将逼近上限时触发,而且会先于昂贵的 summarization。

它会从对话开头移除消息,但保留最近消息构成的一个 protected tail(受保护尾部)
整个过程不需要额外模型调用。

有损,但很快。

Strategy 3:Auto Compact

当 token 使用量越过阈值,而 snip 还不够时触发。

这时系统会发起一次独立模型调用,对先前对话做摘要,然后用摘要替换旧消息。
系统还会记录 compaction state,避免出现“对摘要再摘要、再对摘要的摘要做摘要”的死循环。

Strategy 4:Context Collapse

这是给超长时间运行 session 准备的,通常受 feature flag 控制。

它会进行多阶段压缩:

  1. 先压工具结果;
  2. 再压 thinking blocks;
  3. 最后压整个 section。

这是一种昂贵方案,只留给那些已经连续跑了几个小时的会话。

为什么这个层级顺序如此重要

最便宜的先跑,最贵的最后跑。
只有在前面都不够用时,昂贵策略才会出场。

很多 Harness 一上来就做 summarization。
但 summarization 的成本是双重的:

  • 你要花一次模型调用成本;
  • 你还要把生成出来的 summary 再放进后续上下文。

而 microcompact + snip 在大量场景里,根本不需要任何额外模型调用

这套层级意味着:
你只在便宜方案失效时,才为昂贵压缩买单。

另一个关键点是那个 protected tail
做压缩时,最近的 N 轮对话永远不被摘要掉。

这样模型就能在保持当前计划完整性的同时,把更早的历史压缩掉,不会“刚做完什么就忘了”。

权限系统:七级信任流水线

大多数 Agent Harness 的权限机制只有一个开关:

  • 允许
  • 拒绝

Claude Code 不是。它有一个七阶段权限流水线

规则支持基于工具名与输入的 glob-like 匹配。

例如:

  • “允许所有 bash” 太粗暴了,没有人希望 Agent 可以直接跑 rm -rf /
  • “拒绝所有 bash” 又会让 Agent 直接失去生产力;
  • 真正合理的做法是:允许 git 命令和 npm test,其他 bash 操作都弹确认

Claude Code 的规则引擎就是按这个粒度设计的。

更重要的是,它允许渐进式信任

  • 新用户先从默认模式开始,对每次动作逐个批准;
  • 随着信任增加,再升级到 acceptEdits 或 bypassPermissions

不是安全和速度二选一。
而是一条连续谱。

Hooks 是最后的逃生阀

Claude Code 还允许你通过 hooks 挂自定义策略:

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line{  "hooks": {    "PreToolUse": [{      "matcher": { "tool": "bash", "action": ".*rm.*" },      "commands": [{ "cmd": "/path/to/safety-check.sh" }]    }]  }}

你的脚本会收到工具调用详情,然后返回:

  • {"decision": "approve"}
  • 或 {"decision": "block"}

企业组织可以借这个能力构建自己的 guardrails:

  • 阻止破坏性操作;
  • 完成任务后自动发 Slack;
  • 每次文件写入后自动跑 linter。

并且完全不需要改源码

错误恢复:那 823 行 Retry 系统不是装饰品

services/api/withRetry.ts 有 823 行
这里面的每一行,几乎都对应过一次真实的生产故障。

429:Rate Limited

检查 Retry-After 头:

  • 小于 20 秒:重试,继续保留 fast mode;
  • 超过 20 秒:进入 30 分钟 cooldown;
  • 如果带有 overage-disabled 头:永久关闭 fast mode,并解释原因。

529:Server Overloaded

连续跟踪 529 次数:

  • 如果连续 3 次 529,并且有 fallback model:切换模型;
  • 如果是后台任务:直接退出,避免级联故障;
  • 如果是前台任务:退避后重试。

400:Context Overflow

从错误里解析出实际 token 和上限 token。
然后重算:

available = limit - input - 1000(安全缓冲)

同时强制要求最小输出 token 下限为 3000,再按新预算重试。

401 / 403:Auth

清空 API key 缓存,强制刷新 OAuth token,再用新凭证重试。

网络错误:ECONNRESET / EPIPE / timeout

关闭 keep-alive socket pooling,重新建立连接后再试。

退避公式

ounter(linedelay = min(500ms × 2^attempt, 32s) + random(0, 0.25 × baseDelay)

对于无人值守会话,比如:

  • CI/CD 流水线
  • 后台 Agent

持久重试模式会对 429 和 529 无限重试

  • 最大退避 5 分钟;
  • 6 小时 reset cap;
  • 每 30 秒发一次 heartbeat,防止空闲超时被干掉。

流式层本身也有自己的可靠性机制:

  • 90 秒没有 chunk 到达,就 watchdog 中止流;
  • 45 秒时先发 warning;
  • 若首字节到达后,相邻 chunk 之间间隔超过 30 秒,会被记录为 stall;
  • 如果 streaming 完全失败,则切换到 non-streaming,同时保留连续 529 计数,避免 fallback 逻辑重复计数。

“fetch 重试三次”不叫生产级可靠性。

只有当你的系统真正理解每一种错误语义,并为每一类错误设计专门恢复路径时,它才算可靠。

Sub-Agent 架构:并行,但必须隔离

Claude Code 会生成子 Agent。
这些子 Agent 都是独立的 Agent Loop 实例,拥有各自的:

  • 上下文
  • 工具
  • 工作目录

父 Agent 一旦被终止,所有子 Agent 会被级联终止。
但子 Agent 不能直接修改父 Agent 的状态:appState 对它来说是 no-op setter。
文件状态缓存也会被克隆,防止一个 Agent 的读取污染另一个 Agent 的缓存。

Git Worktree Isolation

需要修改代码的子 Agent,会拿到自己独立的 worktree:

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linegetOrCreateWorktree(repoRoot, slug)  → 校验 slug(最长 64 字符,防路径穿越)  → 检查 worktree 是否已存在(支持快速恢复)  → git fetch(关闭交互提示)  → git worktree add(新分支:worktree-<slug>)  → 对大目录做符号链接(如 node_modules、.cache)  → 复制 CLAUDE.md、项目设置、.env 文件  → 返回 { path, branch, headCommit }

一个 Agent,一个 worktree。

如果多个并行 Agent 共用同一个 workspace,冲突迟早会发生。
而 worktree 隔离让每个 Agent 都站在自己的分支上,验证完再合并。

同时,node_modules 的 symlink 避免了磁盘膨胀:
5 个并行 Agent 不需要 5 份依赖副本。

三种 Spawn Backend

整个多 Agent 系统支持三种执行后端:

  • In-process:直接跑在 Node.js 进程内,最快,共享内存;
  • Tmux pane:通过终端多路复用器隔离,每个 Agent 在一个 pane/tab 里可见;
  • Remote:跑在 CCR 环境里,做到整机级隔离。

任务协同依赖一个磁盘持久化任务列表,路径类似:

~/.claude/tasks/<taskListId>/<taskId>.json

配合文件锁和指数退避:

  • 最多重试 30 次;
  • 每次等待 5–100ms;
  • 还会维护 high water mark,防止 reset 后 task ID 被复用。

第四层:Infrastructure

上面这些内容,仍然主要是在讲 Harness,也就是第三层。
现在请看 Claude Code 在它外面搭起来的第四层。

1)多租户(Multi-Tenancy)

CLAUDE.md 的层级本身就是一套多租户系统。

  • 企业策略:/etc/claude-code/CLAUDE.md
  • 项目策略:.claude/CLAUDE.md
  • 用户偏好:~/.claude/CLAUDE.md
  • 本地私有覆盖:CLAUDE.local.md

这实际上就是Agent 行为层面的 RBAC

  • 企业管理员设 guardrails;
  • 项目维护者设 convention;
  • 开发者设个人偏好。

每层覆盖下层,冲突按确定性规则解析。

2)跨 Session 的状态持久化

Compaction 策略本质上就是状态持久化。
Auto-compact 生成的 summary,会成为下一轮循环的新起点。

CLAUDE.md 文件负责把项目级记忆带到多个 session 之间。
Hooks 可以把任意状态持久化到磁盘。
任务协调系统则在多个 Agent 进程之间维持状态。

Claude Code 实际上把 session management 拆成了三层:

  • 单一 session 内:靠 compaction;
  • 跨 session:靠 CLAUDE.md;
  • 跨 agent:靠 task list。

3)资源隔离(Resource Isolation)

Git worktree isolation 给每个子 Agent 独立文件系统。
siblingAbortController 把工具失败控制在兄弟范围内,避免级联扩散。
企业级 deny 规则则阻止 Agent 触碰不该访问的资源。

4)分布式协同(Distributed Coordination)

任务列表上的文件锁就是典型的分布式协同。
父子 Agent 之间的 prompt cache 共享,是分布式资源优化。
持久重试模式里的 heartbeat,是 keepalive 模式。
而 worktree 管理,则是在处理多个 Agent 对同一代码仓的并发访问。

这些问题,本质上都不是“Agent 提示工程”问题。
它们是基础设施问题,必须用基础设施思维解决:

  • 协同
  • 隔离
  • 状态管理
  • 访问控制
  • 资源共享

这为什么对你的系统至关重要

这些问题,你迟早都会遇到。
区别只在于:你是用胶带和补丁临时糊上去,还是从第一天起就按系统工程去设计。

三层模型并不能让你做好准备。
因为 Harness 描述的只是:一个模型实例,在一个会话里,如何与一组工具交互。

但一旦你进入这些场景:

  • 多用户
  • 多会话
  • 多 Agent
  • 部署到你无法完全控制的环境

你就已经进入第四层了。

对于 Agent Builder 来说,分布式系统工程正在成为一项核心能力。
生产级 Agent 系统会跑在 CI 服务器上,会生成子进程,会跨 session 共享状态,会服务不同权限级别的用户。

真正理解这些问题的团队,才能做出能工作的 Agent。
停留在 Harness 层的团队,只能做出演示版。

可扩展性:四种机制,零源码修改

Claude Code 提供了四种扩展机制,而且都不要求改源码

1)Skills:把 Markdown 文件变成命令

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line---name: commitdescription: Commit staged changes with a generated messageallowed-tools: [Bash, Read, Grep]model: haikucontext: inlineuser-invocable: true---Review the staged changes and create a commit message...

这是一种带 YAML frontmatter 的 Markdown 文件。
它可以来自五个来源:

  • bundled
  • project
  • user
  • plugin
  • MCP

而且支持基于路径的发现机制。
比如 skill 指定 ["*.tsx"],那它只有在 Agent 触碰到匹配文件时才激活。

Agent 看到的是相关 skill,不是全部 skill。

2)Hooks:事件驱动自动化

支持六种类型:

  • shell commands
  • LLM evaluation
  • agentic verification
  • HTTP endpoints
  • TypeScript callbacks
  • in-memory functions

触发时机包括:

  • PreToolUse
  • PostToolUse
  • SessionStart
  • FileChanged
  • Stop

这使 Harness 能直接接入既有基础设施。
例如:

  • 任务完成后发 Slack;
  • 每次 bash 前先跑安全扫描;
  • 每次文件改动后自动触发 CI。

而 Agent Loop 本身一行都不用改。

3)MCP(Model Context Protocol)

支持五种传输方式:

  • stdio
  • SSE
  • HTTP streaming
  • WebSocket
  • in-process

并且可以在三个层级上配置:

  • enterprise managed
  • project
  • user

MCP 让 Agent 能通过统一协议,访问数据库、API、内部工具等外部系统。

4)Plugins

Plugin 本质上是包含以下内容的目录:

  • skills
  • agents
  • hooks
  • configuration

它是最高层的组合机制:
通过“新增能力”来扩展系统,而不是“修改既有文件”。

这四种机制都遵循同一条原则:

组合优于修改。

扩展时通过添加,而不是改动。
这样核心更新不会打碎扩展,扩展彼此之间也不会互相踩踏。

UI 不是装饰,而是信任机制

Claude Code 的终端 UI 跑在一个定制版 Ink(React for terminals)之上,渲染引擎大约 251KB

听起来对 CLI 来说很重。
其实一点也不。

它提供:

  • 实时逐字流式渲染;
  • 根据 stall 持续时间从正常色插值到错误红色的 spinner;
  • 带语法高亮、三行上下文、词级差异标记的 diff 渲染;
  • 显示多 Agent 层级关系的状态树;
  • 六套主题,其中包含色盲友好主题;
  • 一条状态栏,显示模型名、美元成本、上下文窗口使用率、限流使用率。

用户越能看清一个 Agent:

  • 正在做什么;
  • 调了什么工具;
  • 结果长什么样;
  • 消耗了多少上下文;
  • 花了多少钱;

他们就越愿意给这个 Agent 更高的自主权。
而更高的自主权,意味着更多真正有用的工作。

UI 是一个杠杆。

上下文窗口使用条也很关键。
它让用户不需要理解 tokenization 细节,也能直观感知剩余容量。
条快满了,他们就知道该收尾,或者该让 Agent 主动压缩上下文了。

你真正该从 Claude Code 学走什么

你不需要重写一个 Claude Code。
你需要学走的是它背后的工程决策。

1)用 Async Generator 写 Agent Loop

Streaming、Cancellation、Composability、Backpressure——这些能力都内建在这个抽象里。
如果你还是用一个返回完整结果的 while loop,那么这四样东西你就全都要自己补。

2)为工具做并发分类

只读工具并行,改状态工具串行。
这样可以拿到 2–5 倍加速,同时避免 race condition。

3)在 Streaming 过程中就启动工具执行

增量解析工具调用,只要输入 JSON 完整,就立刻开跑。
这是每一个多工具回合都能白捡到的延迟优化。

4)System Prompt 要围绕缓存边界设计

静态内容放前面,动态内容放后面,用明确边界隔开。
这可能是生产环境里杠杆最高的成本优化点。

5)压缩不要只有一种策略

先便宜后昂贵:

  • microcompact
  • snip
  • summarization
  • collapse

只有便宜方案失败时,才为昂贵方案付费。

6)把错误恢复设计成 Loop 的一级状态

Rate limit、context overflow、auth failure、network error——每一类都应该有自己的恢复路径,而且要写进状态机里,而不是糊在外围 try/catch 上。

7)从 Day 1 就考虑 Layer 4

状态跨 session 放哪?
权限怎样扩展到团队级?
加上并行后如何做协同?

这些问题如果后补,难度会高一个数量级。

8)扩展点必须做到“无需改代码”

Markdown 文件、shell 脚本、协议化工具,通常就能覆盖 95% 的扩展需求。
如果用户为了定制行为必须 fork 你的代码,那你的架构已经有缺口了。

模型正在商品化,真正决定结果的是环境

Princeton NLP 用 SWE-agent 证明了这一点:
同样的模型,更好的环境,效果就能提升 64%。

Anthropic 则在 Claude Code 上每天都在证明这一点:

一个拥有 55 个目录、331 个模块 的 TypeScript 应用,可以把同一个 Claude 模型——那个原本只是用在聊天界面里的模型——变成一个可以:

  • 连续数小时无人值守运行;
  • 在 API 故障后自动恢复;
  • 在上千轮会话中自己管理上下文;
  • 在同一代码库上协调多个并行子 Agent;

的真正生产级 Coding Agent。

真正的世界有四层。

而今天行业里大多数团队,还在拼命优化第 1 层:

  • 更大的模型
  • 更高的 benchmark 分数

但真正赢下来的团队,正在把资源投进第 3 层和第 4 层:

  • 更好的环境
  • 更强的错误恢复
  • 更成熟的权限系统
  • 更稳的上下文管理
  • 更高效的协同机制

源码就在那里。
模式都写清楚了。
设计决策也都很清晰。

先把 Harness 搭起来。
然后,把它外面的基础设施也一起搭起来。


编者按

如果你过去一直把 Agent 当成“Prompt + Tool Call + Loop”的问题来理解,那么 Claude Code 这份实现给出的最大提醒其实只有一句话:

Agent 不只是模型调用器,它本质上是一套带状态、带权限、带隔离、带协同能力的系统工程。

也就是说,真正的护城河,越来越不在模型本身,而在你为模型搭出来的那一整套执行环境里。