从 Claude Code 到 Agent 工程:两篇万字长文里的架构共识

最近 X上大佬连续发了两篇长文,一篇讲 Claude Code 的架构与治理,一篇讲 Agent 的原理与工程实践。两篇加起来近三万字,覆盖了 Agent Loop、上下文管理、工具设计、记忆分层、多 Agent 协作、评测体系,基本把 Agent 工程里容易踩坑的地方都过了一遍。

我读完之后把两篇文章的线头接了一下,提炼出几个对工程团队比较有用的判断和做法。在用 Claude Code 或者自己搭 Agent 系统的话,可以对照着看看。

太长不看版

  1. Agent 主循环只有 20 行代码,新能力通过扩展工具、调整提示、外化状态接入,循环本身不动
  2. 上下文窗口的瓶颈是"太吵"而非"不够长",光 MCP 工具定义就能吃掉 200K 窗口的 12.5%;按使用频率分层管理,常驻层短而稳定,Skills 按需加载,确定性逻辑交给 Hooks
  3. 常驻层越稳定,Prompt Caching 命中率越高,边际成本越低------稳定的大系统提示比频繁变动的小提示实际更便宜
  4. 工具出问题多数是描述不准确,调试 Agent 应先查工具定义,再看模型能力
  5. 记忆是基础设施,按工作记忆、程序性记忆、情景记忆、语义记忆分层,多数场景不需要一开始就上向量数据库
  6. 多 Agent 先定协议和隔离边界再开并行,子 Agent 的调试过程不该进主 Agent 上下文
  7. 评测分数下降时,先查环境和评分器再动 Agent;Harness 对系统稳定性的影响比模型选择大
  8. 长任务拆成 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 加载,主循环基本没变过。新能力接入只走三条路:扩展工具集、调整系统提示结构、把状态外化到文件或数据库。

graph LR A[收集上下文] --> B[模型决策] B --> C{需要工具调用?} C -->|是| D[执行工具] D --> E[验证结果] E --> A C -->|否| F[输出结果] classDef default fill:#0d2f2f,stroke:#1A9090,color:#e0e0e0 classDef decision fill:#1A9090,stroke:#0d2f2f,color:#fff class C decision

这里有个容易被忽视的分工:模型负责推理,外部系统负责状态和边界。这个分工一旦定下来,主循环就很少需要改。很多标着 "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 logfindgrep 在稍大的仓库里也能轻松塞满屏幕。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")等于任何后端工作都能触发,路由会乱。

graph TD A[系统提示] -->|常驻| B[Skill 描述符
简短路由条件] 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(上线前,功能有没有被改坏),混用容易误判

评分器选择有优先级:有明确正确答案用代码评分器(确定性最高),需要判断语义质量再用模型评分器,遇到拿不准的案例用人工标注校准。

graph TB A[评测分数下降] --> B{先查环境} B -->|环境问题| C[资源不足/进程被杀
评分器 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 倍。速度背后不在模型,在几个工程决策:

  1. Agent 看不到的等于不存在------知识必须存在于代码库本身,AGENTS.md 只保留约 100 行作为索引
  2. 约束编码进 Linter、类型系统或 CI 规则里,而非写在文档中------写在文档里的规范太容易被忽略
  3. Agent 端到端自主完成任务------从复现 Bug 到开 PR、处理 Review 反馈、自主合并,全链路不需要人介入
  4. 测试偶发失败用重跑处理,不阻塞进度

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 系统的话,上下文分层和工具定义质量可以作为第一件事来抓。


参考资料:

相关推荐
甲维斯2 小时前
手搓 CodingPlan 照妖镜,TOKEN 燃烧器!
ai编程
多厘2 小时前
Claude Code 入门: 建立信任感
claude
sin°θ_陈2 小时前
前馈式3D Gaussian Splatting 研究地图(路线一):像素对齐高斯的起点——pixelSplat 与 latentSplat 在解决什么
python·深度学习·3d·aigc·webgl·3dgs·空间智能
chushiyunen2 小时前
rag检索增强生成-概念版
ai编程
小凡同志2 小时前
Cursor 和 Claude Code:AI 编程的两种哲学
人工智能·claude·cursor
Miku162 小时前
开源项目 superpowers 深度解读:把 AI Coding Agent 变成遵守工程流程的协作伙伴
agent·ai编程·claude
AI_Ming2 小时前
程序员转行学习 AI 大模型: 第一次如何调用大模型API | 附完整可运行代码
aigc·openai·ai编程
甜城瑞庄的核桃2 小时前
Claude Code 工程化实战:从工具使用者到 Agent 构建者的进阶之路
人工智能·机器学习·aigc·ai编程
超爱柠檬2 小时前
工作流(Workflow)—— 可视化 AI 应用编排
openai·ai编程