网上关于 Coding Agent 的讨论,要么神化它,要么把它贬成"不过是提示词工程"。这两种说法都不够准确。本文从真实的架构出发,拆解 Claude Code、OpenCode 等工具的实现模式,厘清它们的优缺点,以及那些被反复误解的核心问题。

一个循环统治一切

先说结论:所有 Coding Agent 的核心,都是一个 while(tool_use) 循环

通过对 Claude Code 实际 API 流量的追踪分析,其核心逻辑可以用伪代码描述如下:

1
2
3
4
5
6
7
8
while True:
response = llm.call(context)
if response.has_tool_call():
result = execute_tool(response.tool_call)
context.append(result)
else:
# 没有工具调用 = 任务完成,等待用户输入
break

没有复杂的状态机,没有多 Agent 协调框架,没有专门的"停止决策模块"。模型自己决定什么时候停——当它不再调用工具,循环就自然结束。

这个设计有一个被低估的优雅之处:模型可以通过"输出纯文本而不调用工具"来向用户提问或汇报进度,不需要任何额外的机制。

messages 数组:唯一的状态载体

上述伪代码中的 context,在所有主流 LLM API 里的实际形态是一个 messages 数组。没有其他格式——Agent 的全部状态,包括历史、推理过程、工具调用结果,都存储在这个数组里。

每条消息有四种角色,工具调用的插入位置是固定的:

1
2
3
4
5
6
7
8
[
{"role": "system", "content": "..."},
{"role": "user", "content": "用户输入"},
{"role": "assistant", "content": null,
"tool_calls": [{"id": "call_abc", "function": {"name": "read_file", "arguments": "{...}"}}]},
{"role": "tool", "tool_call_id": "call_abc", "content": "文件内容..."},
{"role": "assistant", "content": "基于文件内容,我的分析是..."}
]

每一轮循环的操作模式是:模型输出 assistant 消息(含 tool_calls)→ 框架执行工具 → 把结果以 tool 角色追加 → 再次调用模型。Agent 框架在做的核心事情,就是决定往这个数组里追加什么、以什么顺序追加、以及何时停止追加。

这个数组的增长方式,取决于框架策略:

策略 描述 适用场景
无限叠加 保留所有消息,数组持续增长 短任务、大 context 窗口
压缩摘要 用 LLM 总结历史,替换原始消息 超长任务(Claude Code 的 compaction 机制)
滑动窗口 丢弃旧消息,保留最近 N 条 长对话、有限 context 窗口

[PATTERN] 终止条件的最简实现:不需要专门的 stop 工具或终止正则,让模型的"不调用工具"行为本身成为停止信号,是最低耦合的设计。

Claude Code 的真实架构

工具集:精简而非全能

Claude Code(截至本文写作时)只有 14 个工具,分为四类:

类别 工具 设计意图
命令行 bashglobgrepls bash 需要用户审批,其余不需要
文件操作 readwriteeditmulti_editnotebook_readnotebook_edit Jupyter 单独处理,因为原始格式极长
网络 web_searchweb_fetch 基础信息获取
控制流 todo_writetask 计划管理与子 Agent 调度

没有 critic 模式,没有角色扮演,没有复杂的记忆数据库。简单是刻意的选择,不是能力不足。

TODO 列表:外化的计划

Claude Code 的第一个工具调用几乎总是 TodoWrite,创建一个结构化的任务列表:

1
2
3
4
5
6
7
{
"todos": [
{"id": "1", "content": "分析现有代码结构", "status": "completed"},
{"id": "2", "content": "实现新功能", "status": "in_progress"},
{"id": "3", "content": "编写测试", "status": "pending"}
]
}

这个设计解决了一个关键问题:模型在执行数百步操作后会"忘记"自己在做什么。TODO 列表是外化的工作记忆,不依赖模型的上下文记忆能力。

更聪明的是,工具调用的返回结果里会附带提醒文字,要求模型继续使用 TODO 列表跟踪进度。指令在工具结果里重复出现,比只放在 system prompt 里遵从率高得多。

[PATTERN] 计划外化原则:把计划写成可被工具读写的结构化文件(TODO 列表、plan.md),比依赖模型"内化"计划更确定、更可控。内化的计划会随着上下文增长而衰减,外化的计划不会。

System Reminder:动态注入的上下文

Claude Code 会在每次用户消息后附加 <system-reminder> 块,内容随状态动态变化:

  • 会话开始:注入基本行为约束(不要主动创建文件、不要做多余的事)
  • TODO 列表为空时:提醒模型考虑是否需要创建计划
  • TODO 列表更新后:把最新的任务状态注入上下文

这不是"提示词工程",这是程序化的上下文管理——根据 Agent 的运行状态,精确控制每一步模型能看到什么信息。

子 Agent:上下文隔离与并行

当任务复杂到单个上下文窗口装不下,Claude Code 会通过 task 工具派发子 Agent。子 Agent 是一个完整的 Claude Code 实例,接收相同的 system prompt,但有自己独立的上下文窗口。

关键设计决策:子 Agent 不知道自己是子 Agent,且在设计上被约束为不能再派发子 Agent(防止无限递归)。这保证了行为的可预测性。值得注意的是,这个约束只针对 Agent 实例的创建——Skill(通过 SKILL.md 注入的行为扩展)则可以无限递归调用,因为 Skill 是上下文内的提示词注入,不涉及新 Agent 实例的创建。

关于子 Agent 的本质,还有一个常被误解的问题:"子 Agent"是关系描述,不是能力描述。子 Agent 可以拥有与主 Agent 完全相同甚至更强的能力,它的核心价值在于上下文隔离,而非能力弱化。详细分析见《子 Agent 的本质:上下文隔离与专门化》。

子 Agent 的两个用途:

  1. 上下文管理:把大任务拆成小任务,每个子任务有干净的上下文
  2. 并行加速:多个独立子任务可以同时执行

安全检查:用小模型做守门员

一个鲜为人知的细节:Claude Code 在执行 bash 命令前,会把命令发给 Claude Haiku(最小最快的模型)做安全检查,判断命令是否读取或修改了敏感文件。

这个设计体现了一个重要的工程权衡:用便宜的小模型做确定性判断,不让安全检查拖慢主循环。Haiku 的输出是结构化的 XML,不是自然语言,降低了解析的不确定性。

[PATTERN] 异构模型协作:主循环用强模型做复杂推理,安全/分类等确定性任务用小模型,是成本与能力的最优分配。

Claude Code 的核心竞争力:上下文管理

“Claude Code 不就是提示词工程吗?”

这个问题的答案是:不是,或者说,不只是

Claude Code 的核心竞争力是广义的上下文管理,包括:

  1. 精确控制每一步模型看到什么:system reminder 动态注入、工具结果里嵌入指令
  2. 跨步骤的状态持久化:TODO 列表作为外化记忆,不依赖模型的上下文记忆(详见上文"TODO 列表"一节)
  3. 上下文窗口的生命周期管理:子 Agent 隔离、以及对应 messages 数组三种增长策略的灵活运用——短任务无限叠加、超长任务触发 compaction 压缩摘要、上下文耗尽时滑动窗口降级
  4. 工具设计的精细化:Jupyter notebook 单独处理、bash 和文件工具的权限分离

这些不是"改改 prompt 就能做到的事"。它们是对模型行为的系统性工程,需要对模型的注意力机制、上下文衰减、工具调用模式有深刻理解。

一个有力的佐证:Claude Code 的 system prompt 和工具集在每次版本更新时都会变化,这些变化直接影响模型行为。这说明 Anthropic 在持续做的是上下文工程(Context Engineering),而不是一次性的提示词设计。

OpenCode:TUI 做得好,Agent Core 一知半解

OpenCode 是一个开源的 Coding Agent,用 TypeScript 实现,以终端 TUI 为核心交互界面。它的架构比 Claude Code 更显式,也更复杂。

架构概览

OpenCode 采用 C/S 架构:TypeScript 服务端 + 多客户端(TUI、CLI、IDE 插件)。核心模块:

1
2
3
4
5
Client (TUI/CLI/IDE) → REST API + SSE → Server
├── Agent(配置模板)
├── Session(ReAct 循环)
├── Tool(工具集)
└── Event Bus(事件驱动)

会话数据持久化到本地文件系统:

1
2
3
4
~/.local/share/opencode/storage/
├── session/{projectID}/{sessionID}.json
├── message/{sessionID}/{messageID}.json
└── project/{projectID}.json

OpenCode 的设计亮点

事件驱动架构:模块间通过 Event Bus 通信,客户端通过 SSE 订阅事件流,实现实时更新。这让 TUI 的响应性非常好。

声明式 Agent 配置:OpenCode 的"Agent"本质上是配置模板,定义了工具权限、执行权限、system prompt 和模型参数。内置三个 Agent:

  • build:完整权限,适合实际开发
  • plan:只读权限,适合分析和规划
  • general:搜索优化,适合代码研究

多 LLM 提供商支持:通过统一 API 层支持 OpenAI、Anthropic、Google、Amazon 等,这是 Claude Code 不具备的灵活性。

OpenCode 的问题:Agent Core 的深度不足

OpenCode 的 TUI 做得确实好——差异渲染、低闪烁、响应迅速。但 Agent Core 的实现存在明显的工程深度不足:

上下文管理缺失:OpenCode 没有类似 Claude Code 的动态 system reminder 机制,也没有成熟的上下文压缩策略。当会话变长,模型行为会退化,但框架层面没有对应的补偿机制。

工具调用依赖第三方库:OpenCode 使用 Vercel AI SDK 处理工具调用,这个库对自托管模型的兼容性很差,导致使用非 Anthropic/OpenAI 模型时工具调用经常失败。

"Agent"是配置,不是真正的多 Agent:OpenCode 的 Agent 系统本质上是 system prompt 的切换,没有真正的多 Agent 协调能力。子会话(parent-child session)的实现也相对简单,缺乏 Claude Code 那种精细的上下文隔离。

sysprompt 过长:OpenCode 的 system prompt 包含大量"你是一个工程师,你应该……"之类的角色设定,这类内容会占用宝贵的上下文空间,并且可能干扰模型的原生推理能力。

[PATTERN] sysprompt 的反模式:过长的角色设定 prompt 不会让模型"更像工程师",反而会压缩模型处理实际任务信息的注意力空间。

pi:一个人的极简主义实验

Mario Zechner 独立开发的 pi 是一个有趣的对照实验。他刻意做减法:

  • 没有内置 TODO 列表:认为这是不必要的复杂性
  • 没有 plan 模式:不需要专门的规划阶段
  • 没有 MCP 支持:认为 MCP 增加了不必要的抽象层
  • 没有子 Agent:单一上下文,简单可控
  • 默认 YOLO 模式:不需要用户逐步确认

pi 的核心贡献是 pi-ai,一个统一的多 LLM API 层,支持跨提供商的上下文切换(把 Claude 的 thinking trace 转换成 <thinking> 标签传给 GPT),以及结构化的工具结果(LLM 看到的内容和 UI 显示的内容分离)。

pi 的存在证明了一件事:一个人用几周时间,可以做出在某些场景下不输主流工具的 Coding Agent。这既说明了 Coding Agent 的核心架构确实不复杂,也说明了"让循环好好跑起来"的工程细节才是真正的难点。

行业的真实问题:缺乏有意义的 Benchmark

当前 Coding Agent 领域最大的问题不是技术,而是没有有意义的 benchmark

现有的 benchmark(如 SWE-bench)测试的是特定类型的 GitHub issue 修复,与真实开发场景差距很大。这导致:

  • 工具的"优化"可能是针对 benchmark 的过拟合,而非真实能力提升
  • 无法量化"sysprompt 变长"对实际任务的影响
  • 不同工具之间的比较缺乏客观基准

一个有价值的 benchmark 应该模拟真实的开发任务:需求理解、代码修改、测试验证、回归检测。在这样的 benchmark 下,很多看起来"功能丰富"的工具可能并不比极简实现更好。

统一的底层逻辑:Programmatic Prompt Engineering

回到最开始的问题:COT、tool calling、RAG、memory 系统、workflow,这些技术的本质是什么?

它们都是通过编程手段控制模型上下文的方式。

  • COT:让模型在上下文里写出推理过程,影响后续输出
  • Tool calling:把外部信息注入上下文,扩展模型的感知范围
  • RAG:把相关文档注入上下文,补充模型的知识
  • Memory 系统:把历史信息压缩后注入上下文,突破单次上下文限制
  • Workflow:控制信息注入的顺序和时机

这个统一视角有一个重要推论:评估任何 Agent 技术的标准,应该是"它如何改变了模型的上下文,以及这种改变是否让模型做出了更好的决策",而不是技术本身的复杂程度。

一个 50 行的 while 循环加上精心设计的上下文管理,可以比一个有 20 个模块的复杂框架更有效。

Aone Copilot:同一套逻辑的另一个实现

这个统一视角也解释了为什么不同产品的 Coding Agent 在架构上高度趋同。以 Aone Copilot 为例,它与 Claude Code 在核心机制上几乎是镜像关系:

机制 Claude Code Aone Copilot
任务追踪 todo_write 工具 todo_write 工具
文件读取 read 工具 read_file 工具
文件写入 write 工具 create_file 工具
文件编辑 edit / multi_edit file_replace 工具
子 Agent task 工具 task 工具
动态上下文注入 system reminder system prompt 动态注入

这不是巧合,而是收敛到同一个最优解的结果:当你要解决"让模型在长任务中保持状态、安全操作文件、可控地调用子任务"这个问题时,TODO 列表 + 文件读写工具 + 子 Agent 调度几乎是唯一合理的工具集组合。

[PATTERN] 工具集收敛定律:不同团队独立设计的 Coding Agent,最终工具集会高度相似——这说明工具集的形态是由任务结构决定的,而非由实现者的偏好决定的。

参考资料