最近 X上大佬连续发了两篇长文,一篇讲 Claude Code 的架构与治理,一篇讲 Agent 的原理与工程实践。两篇加起来近三万字,覆盖了 Agent Loop、上下文管理、工具设计、记忆分层、多 Agent 协作、评测体系,基本把 Agent 工程里容易踩坑的地方都过了一遍。
我读完之后把两篇文章的线头接了一下,提炼出几个对工程团队比较有用的判断和做法。在用 Claude Code 或者自己搭 Agent 系统的话,可以对照着看看。
太长不看版
- Agent 主循环只有 20 行代码,新能力通过扩展工具、调整提示、外化状态接入,循环本身不动
- 上下文窗口的瓶颈是"太吵"而非"不够长",光 MCP 工具定义就能吃掉 200K 窗口的 12.5%;按使用频率分层管理,常驻层短而稳定,Skills 按需加载,确定性逻辑交给 Hooks
- 常驻层越稳定,Prompt Caching 命中率越高,边际成本越低------稳定的大系统提示比频繁变动的小提示实际更便宜
- 工具出问题多数是描述不准确,调试 Agent 应先查工具定义,再看模型能力
- 记忆是基础设施,按工作记忆、程序性记忆、情景记忆、语义记忆分层,多数场景不需要一开始就上向量数据库
- 多 Agent 先定协议和隔离边界再开并行,子 Agent 的调试过程不该进主 Agent 上下文
- 评测分数下降时,先查环境和评分器再动 Agent;Harness 对系统稳定性的影响比模型选择大
- 长任务拆成 Initializer + Coding Agent 两阶段,进度写在文件里,不写在上下文里
Agent Loop:20 行代码的主循环
很多人觉得 Agent 架构一定很复杂。把主循环抽象出来看,其实不到 20 行:
typescript
const messages: MessageParam[] = [{ role: "user", content: userInput }];
while (true) {
const response = await client.messages.create({
model: "claude-opus-4-6",
max_tokens: 8096,
tools: toolDefinitions,
messages,
});
if (response.stop_reason === "tool_use") {
const toolResults = await Promise.all(
response.content
.filter((b) => b.type === "tool_use")
.map(async (b) => ({
type: "tool_result" as const,
tool_use_id: b.id,
content: await executeTool(b.name, b.input),
}))
);
messages.push({ role: "assistant", content: response.content });
messages.push({ role: "user", content: toolResults });
} else {
return response.content.find((b) => b.type === "text")?.text ?? "";
}
}
感知、决策、行动、反馈四个阶段循环,模型返回纯文本时结束。从最小实现一路扩展到支持子 Agent、上下文压缩和 Skills 加载,主循环基本没变过。新能力接入只走三条路:扩展工具集、调整系统提示结构、把状态外化到文件或数据库。
这里有个容易被忽视的分工:模型负责推理,外部系统负责状态和边界。这个分工一旦定下来,主循环就很少需要改。很多标着 "Agent" 的产品,拆开看其实更接近 Workflow(执行路径由代码写死),两者没有高下之分,给任务找到合适的模式就行。
Anthropic 归纳了五种基本模式:提示链、路由、并行、编排器-工作者、评估器-优化器。多数 AI 系统拆开看,都是这几种的组合。
上下文治理:窗口不是不够长,是太吵了
这是两篇文章里篇幅最大的部分,也是实际使用中最容易出问题的地方。
很多人把上下文当容量问题处理,觉得窗口不够长就换个更大的模型。在用了一段时间后发现,卡住的地方通常不是容量不够,是有用的信息被大量无关内容淹没了。
先看一组数字。Claude Code 的 200K 上下文并非全部可用:
| 类别 | 消耗 | 说明 |
|---|---|---|
| 系统指令 | ~2K | 固定开销 |
| Skill 描述符 | ~1-5K | 所有启用的 Skill |
| MCP 工具定义 | ~10-20K | 最大隐形杀手 |
| LSP 状态 | ~2-5K | 固定开销 |
| CLAUDE.md | ~2-5K | 半固定 |
| Memory | ~1-2K | 半固定 |
| 动态可用 | ~160-180K | 对话、文件、工具输出 |
一个典型 MCP Server(如 GitHub)包含 20-30 个工具定义,每个约 200 tokens。接 5 个 Server,光工具定义就到了 25,000 tokens,占总上下文的 12.5%。在需要读大量代码的场景,这 12.5% 的差别很大。
动态部分的坑也不小。cargo test 一次完整输出几千行,git log、find、grep 在稍大的仓库里也能轻松塞满屏幕。Claude 不需要看全部,但只要出现在上下文里,就是实打实的 token 消耗。
怎么分层
按信息的使用频率和稳定性分层:
objectivec
常驻层 → CLAUDE.md:项目契约、构建命令、禁止事项(短、硬、可执行)
按需加载 → Skills:工作流、领域知识(描述符常驻,内容触发加载)
运行时 → 当前时间、渠道 ID、用户偏好(每轮按需拼入)
记忆层 → MEMORY.md:跨会话经验(需要时才读取)
系统层 → Hooks:确定性脚本、审计、阻断(不进上下文)
简单说:偶尔用的东西不要每次都加载进来,确定性逻辑不要让模型反复读。
几条实操建议:
- CLAUDE.md 保持短、硬、可执行。Anthropic 官方自己的 CLAUDE.md 大约 2.5K tokens
- 大型参考文档拆到 Skills 的 supporting files,不塞进 SKILL.md 正文
- 使用
.claude/rules/做路径/语言规则,不让根 CLAUDE.md 承担所有差异 - 长会话主动用
/context观察消耗,不要等系统自动压缩后再补救 - 任务切换优先
/clear,同一任务进入新阶段用/compact
压缩时架构决策容易被一起丢
默认压缩算法按"可重新读取"判断优先级,早期的 Tool Output 和文件内容会被优先删掉。问题是,架构决策和约束理由经常跟着一起丢了。两小时后再改代码,可能根本不记得两小时前定了什么。
解决方案是在 CLAUDE.md 里写明压缩保留优先级:
markdown
## Compact Instructions
When compressing, preserve in priority order:
1. Architecture decisions (NEVER summarize)
2. Modified files and their key changes
3. Current verification status (pass/fail)
4. Open TODOs and rollback notes
5. Tool outputs (can delete, keep pass/fail only)
还有一种更主动的方案:在开新会话前,让 Claude 写一份 HANDOFF.md,把当前进度、尝试过什么、哪些走通了、哪些是死路、下一步该做什么写清楚。下一个 Claude 实例只读这个文件就能接着做,不依赖压缩算法的摘要质量。
Prompt Caching 和分层直接相关
Transformer 推理时,如果当前请求的输入前缀和上次完全一致,KV 缓存可以直接复用。命中的前提是精确前缀匹配,任何一个 token 不同都会破坏匹配。
这和上下文分层直接挂钩:常驻层越稳定,前缀命中率越高,边际成本越低。所以"常驻层短而稳定"不只是为了节省 token,也在保护缓存命中。Skills 延迟加载不破坏系统提示前缀,工具定义如果频繁变动,缓存就会不断失效。
有个反直觉的结论:稳定的大系统提示,比频繁变动的小提示实际成本更低,因为写入成本只付一次,后续每次调用的读取折扣可达 90%。
工具设计:出了问题先查工具定义
两篇文章都花了不少篇幅讲工具。一个比较统一的判断:工具出问题多数是选不对、描述看不懂、返回一堆没用的、出了错 Agent 也不知道怎么改。
工具设计经历了三代演进:
| 阶段 | 做法 | 问题 |
|---|---|---|
| API 封装 | 每个 API Endpoint 对应一个工具 | 粒度过细,Agent 协调成本高 |
| ACI(Agent-Computer Interface) | 工具对应 Agent 的目标,不对应 API 操作 | 设计门槛提高 |
| Advanced Tool Use | 动态发现 + 代码编排 + 示例驱动 | 需要更完善的基础设施 |
第三代里几个数据可以记一下:
- Tool Search(动态工具发现):Agent 按需发现工具定义,上下文保留率达 95%,Opus 4 准确率从 49% 升到 74%
- Programmatic Tool Calling(代码编排):中间结果在执行环境流转不进 LLM,token 消耗从约 150,000 降到约 2,000
- Tool Use Examples(示例驱动):每个工具附 1-5 个真实调用示例,准确率从 72% 升到 90%
工具定义怎么写,直接影响 Agent 的行为质量。差的写法参数模糊、出错只返回字符串,Agent 不知道怎么修正;好的写法用结构化 schema 约束参数格式,错误信息里给出修正建议:
typescript
// 差的写法
return "Error: update failed";
// 好的写法
throw new ToolError("文章 ID 不存在", {
error_code: "POST_NOT_FOUND",
suggestion: "请先调用 list_yuque_posts 获取有效的 post_id",
});
原文里给了一个判断顺序:给 Agent 新动作能力用 Tool/MCP,给它一套工作方法用 Skill,需要隔离执行环境用 Subagent,要强制约束和审计用 Hook,跨项目分发用 Plugin。
Skills 和"保存的 Prompt"差别挺大
Skills 在两篇文章里都反复出现。它的工作方式是描述符常驻上下文,完整内容按需加载,和简单存个 Prompt 模板完全是两回事。
几个设计要点:
- 描述要让模型知道"何时该用我",而不是"我是干什么的",用 Use when / Don't use when 的写法
- 正文只放导航和约束,大资料拆到 supporting files
- 有副作用的 Skill 要显式设置
disable-model-invocation: true
Skill 描述符有两个常见陷阱。第一个是字数,长描述每个 Skill 多占几十 token,Skill 一多累积成本很可观。第二个是精度,描述太短(如 "help with backend")等于任何后端工作都能触发,路由会乱。
简短路由条件] B -->|按需加载| C[SKILL.md
任务骨架 + 约束] C -->|引用| D[supporting files
领域细节] C -->|调用| E[scripts/
确定性收集上下文] classDef default fill:#0d2f2f,stroke:#1A9090,color:#e0e0e0 classDef highlight fill:#1A9090,stroke:#0d2f2f,color:#fff class B highlight
一组实测数据:没有反例时路由准确率从基准 73% 掉到 53%,加上反例后升到 85%,响应时间还降了 18.1%。反例不是锦上添花,是 Skill 描述能不能起作用的前提。
记忆系统要提前设计
Agent 没有时间连续性,会话结束后上下文就清空了。跨会话一致性要靠单独设计的记忆层。
按 Agent 要解决的问题来分,记忆分四类:
| 类型 | 内容 | 生命周期 | 加载方式 |
|---|---|---|---|
| 工作记忆 | 当前任务最小信息 | 随会话清空 | messages[] |
| 程序性记忆 | 怎么做某件事 | 持久化 | Skills 按需加载 |
| 情景记忆 | 发生了什么 | 持久化 | JSONL 历史,支持检索 |
| 语义记忆 | Agent 认为重要的事实 | 持久化 | MEMORY.md,启动时注入 |
ChatGPT 的实现比很多人预期的更简洁:没有向量数据库,没有 RAG,四层结构(Session Metadata、User Memory、Conversation Summary、Current Session),其中 User Memory 约 33 条关键偏好事实,每次注入。
OpenClaw 的做法是混合检索:追加写日志保留原始细节,MEMORY.md 精选事实由 Agent 主动维护,搜索时 70% 向量相似度 + 30% 关键词权重。
对大多数 Agent 来说,记忆库规模不需要一开始就引入向量存储。结构化 Markdown 加关键词搜索在可调试性和可维护性上已经够用了,等规模超过几千条、确实需要语义检索时再加向量也不迟。
记忆整合也需要设计安全流程:系统只移动指针,不删除原始消息,整合失败时把原始消息写入 archive/,保留完整历史。
多 Agent 协作:先定协议,再开并行
两篇文章都提到了多 Agent 场景。一个共识是:隔离和协议要先于并行。
两种工作模式差别很大:
- 指挥者模式(同步协作):人与单个 Agent 紧密互动,session 结束 context 就没了
- 统筹者模式(异步委派):人设定目标,多个 Agent 并行工作,产出变成分支、PR 这类可持久化工件
多 Agent 真正有用的地方,在于把人的持续参与变成对工件的最终审核。
子 Agent 有一条硬规则:搜索、试错和调试过程不该污染主 Agent 的上下文。主 Agent 需要的只是结论,细节留在子 Agent 自己的消息历史里。
typescript
// 子 Agent 有独立的 messages[],跑完只回传摘要
const result = await runAgentLoop(task, { messages: [] });
return summarize(result); // 主 Agent 上下文里只有这一行
多 Agent 协作靠自然语言对齐,很快就会出问题。协议、任务图、隔离边界三样东西要先到位。消息结构要结构化(包含 request_id、from_agent、to_agent、status),通信用 append-only 的 JSONL inbox,文件修改用 Worktree 隔离。
另一个容易被低估的风险是错误放大:Agent A 先带偏,Agent B 跟着强化,Agent C 继续叠加,最后所有 Agent 收敛到同一个高置信度的错误结论。交叉验证能打断这条链,让某个 Agent 独立判断。
评测体系:分数下降时先查环境
第二篇文章有一段讲评测,我觉得很值得单独拎出来。一个经验是:评测系统出问题比 Agent 出问题更难察觉。
Agent 评测和传统 Single-turn 评测结构上完全不同:传统评测是一个 Prompt 进去看输出对不对,Agent 评测要准备工具和运行环境,Agent 多次调用工具修改状态,最后跑测试验证环境里发生了什么。
几个概念要区分清楚:
- Task(测什么)、Trial(跑多少次)、Grader(怎么打分)
- Transcript(完整执行记录)和 Outcome(环境最终结果),两类都要覆盖
- Pass@k(开发阶段,理论上能不能做到)和 Pass^k(上线前,功能有没有被改坏),混用容易误判
评分器选择有优先级:有明确正确答案用代码评分器(确定性最高),需要判断语义质量再用模型评分器,遇到拿不准的案例用人工标注校准。
评分器 bug
测试用例脱节] B -->|环境正常| D{再查评分器} D -->|评分器问题| E[正确答案被判失败
聚合分数掩盖局部退化] D -->|评分器正常| F[再动 Agent] classDef default fill:#0d2f2f,stroke:#1A9090,color:#e0e0e0 classDef warn fill:#1A9090,stroke:#0d2f2f,color:#fff class B,D warn
原文里引用了一组实测数据:随着环境资源上限放开,基础设施错误率从高位跌到接近 0,模型得分几乎不变。说明之前的"失败"不少是环境噪声,不是模型退化。
Trace 能力是前提。没有完整记录,失败案例就没法稳定复现。传统 APM 监控延迟和错误率,但 Agent 的问题往往出在模型某一轮做了错误决策,只有回看完整 Trace 才能定位。
Harness 对稳定性的影响比模型选择大
这个判断来自第二篇文章引用的一个案例:3 个工程师 5 个月写了百万行代码,近 1500 个 PR,开发速度大约是传统方式的 10 倍。速度背后不在模型,在几个工程决策:
- Agent 看不到的等于不存在------知识必须存在于代码库本身,AGENTS.md 只保留约 100 行作为索引
- 约束编码进 Linter、类型系统或 CI 规则里,而非写在文档中------写在文档里的规范太容易被忽略
- Agent 端到端自主完成任务------从复现 Bug 到开 PR、处理 Review 反馈、自主合并,全链路不需要人介入
- 测试偶发失败用重跑处理,不阻塞进度
Harness 至少包括四个部分:验收基线、执行边界、反馈信号和回退手段。按任务清晰度和验证自动化程度划分,目标明确且结果可自动验证的任务(右上角)最适合 Agent,有自动化反馈但目标模糊的任务,系统会高效地往错误方向跑。
Harness 的工作就是把任务推到"目标明确 + 结果可自动验证"的区域,让对错有机器可执行的判断标准。
长任务怎么拆
长任务最常见的失败是 session 结束时任务还没做完。更稳定的做法是拆成两个角色:
- Initializer Agent:只在第一轮运行,生成 feature-list.json、初始 commit 和 progress 文件
- Coding Agent:循环执行,每次从进度文件和 git log 恢复现场,实现一个功能,跑测试,更新状态,提交后退出
功能清单用 JSON 不用 Markdown,结构化格式更适合模型稳定修改。当所有功能都变成 passes: true,任务才算完成。即使中途崩溃,也能从文件系统的状态继续,不需要从头再来。
单个 session 内也需要外部进度锚点。同一时间只能有一个 in_progress 任务,每完成一步先更新状态再继续,连续多轮未更新时自动注入提示。
几个值得带走的判断
两篇文章信息量很大,我自己觉得最有用的几条:
- 花更多钱换更贵的模型,收益经常没有想象中大。Harness 和验证测试的质量对成功率影响更直接
- 调试 Agent 行为时先看工具定义,多数工具选择错误出在描述写得不对
- 评测分数掉了,别急着改 Agent,先排查环境和评分器
- 上下文治理是 Agent 系统能跑稳的底座,不是锦上添花的事
- 长任务的进度放在文件里,不放在上下文里
在搭 Agent 系统的话,上下文分层和工具定义质量可以作为第一件事来抓。
参考资料:
