Coding Agent 的组成部分【译】
原文地址:https://magazine.sebastianraschka.com/p/components-of-a-coding-agent
Coding Agent 如何使用工具、记忆和代码库上下文,让 LLM 在实践中工作得更好
在这篇文章中,我想介绍 Coding Agent 和 Agent Harness 的整体设计:它们是什么、如何工作,以及不同组成部分在实践中如何配合。我的 Build a Large Language Model (From Scratch) 和 Build a Large Reasoning Model (From Scratch) 两本书的读者经常问到 Agent,所以我觉得写一篇可以引用的参考文章会很有用。
更广泛地说,Agent 已经成为一个重要话题,因为最近实用 LLM 系统的许多进展,并不只是来自更好的模型,还来自我们如何使用这些模型。在很多真实世界的应用中,围绕模型的系统,例如工具使用、上下文管理和记忆,发挥的作用和模型本身一样重要。这也有助于解释,为什么 Claude Code 或 Codex 这样的系统,会比在普通聊天界面中使用同样的模型显得能力强得多。
在这篇文章中,我会梳理 Coding Agent 的六个主要构建块。
Claude Code、Codex CLI 和其他 Coding Agent
你可能已经熟悉 Claude Code 或 Codex CLI,但为了铺垫背景,它们本质上是 Agentic Coding Tools:把一个 LLM 包在应用层,也就是所谓的 Agentic Harness 里,从而让编码任务更方便、表现更好。
图 1:Claude Code CLI、Codex CLI,以及我的 Mini Coding Agent。
Coding Agent 是为软件工作而工程化设计的。在这类工作中,值得关注的不只是模型选择,还有围绕模型的系统,包括代码库上下文、工具设计、prompt-cache 稳定性、记忆以及长会话连续性。
这个区别很重要,因为当我们讨论 LLM 的编码能力时,人们经常会把模型、推理行为和 Agent 产品混为一谈。不过,在进入 Coding Agent 的具体内容之前,我先简要补充一些背景,说明 LLM、Reasoning Model 和 Agent 这些更宽泛概念之间的区别。
LLM、Reasoning Model 和 Agent 之间的关系
LLM 是核心的 next-token 模型。Reasoning Model 仍然是 LLM,但通常是经过训练和/或提示,使其在中间推理、验证或候选答案搜索上花费更多 inference-time compute 的 LLM。
Agent 则是在其之上的一层,可以理解为围绕模型的控制循环。通常,在给定一个目标后,Agent 层(或 Harness)会决定接下来检查什么、调用哪些工具、如何更新自身状态、何时停止,等等。
粗略地说,我们可以这样理解它们之间的关系:LLM 是引擎,Reasoning Model 是强化版引擎(更强大,但使用成本更高),而 Agent Harness 帮助我们使用这个模型。这个类比并不完美,因为我们也可以把常规 LLM 和推理 LLM 作为独立模型使用(在聊天 UI 或 Python 会话中),但我希望它能传达主要意思。
图 2:常规 LLM、推理 LLM(或 Reasoning Model),以及包在 Agent Harness 中的 LLM 之间的关系。
换句话说,Agent 是在一个环境中反复调用模型的系统。
所以,简而言之,我们可以这样总结:
-
LLM: 原始模型
-
Reasoning Model: 一种经过优化的 LLM,会输出中间推理轨迹,并进行更多自我验证
-
Agent: 一个使用模型、工具、记忆和环境反馈的循环
-
Agent Harness: 围绕 Agent 的软件脚手架,负责管理上下文、工具使用、prompt、状态和控制流
-
Coding Harness: Agent Harness 的一种特殊情况,也就是面向软件工程任务的 Harness,负责管理代码上下文、工具、执行和迭代反馈
如上所列,在 Agent 和编码工具的语境中,我们还有两个常见术语:Agent Harness 和(Agentic)Coding Harness。Coding Harness 是围绕模型的软件脚手架,帮助它有效地编写和编辑代码。Agent Harness 则更宽泛一些,并不特指编码(例如可以想到 OpenClaw)。Codex 和 Claude Code 可以被视为 Coding Harness。
总之,更好的 LLM 为 Reasoning Model 提供了更好的基础(Reasoning Model 涉及额外训练),而 Harness 则能从这个 Reasoning Model 中榨取出更多能力。
当然,LLM 和 Reasoning Model 本身也能够解决编码任务(不使用 Harness),但编码工作只有一部分是 next-token generation。很大一部分工作涉及代码库导航、搜索、函数查找、应用 diff、执行测试、检查错误,以及把所有相关信息保持在上下文中。(程序员可能知道,这是很费脑力的工作,这也是为什么我们在编码时不喜欢被打断 :))。
图 3:Coding Harness 结合了三层:模型家族、Agent 循环和运行时支持。模型提供“引擎”,Agent 循环驱动迭代式问题求解,运行时支持则提供基础管道。在循环内部,“observe” 从环境中收集信息,“inspect” 分析这些信息,“choose” 选择下一步,“act” 执行它。
这里的要点是,一个好的 Coding Harness 能让 Reasoning Model 和非推理模型都显得比在普通聊天框中强大得多,因为它能帮助进行上下文管理等工作。
Coding Harness
如上一节所述,当我们说 Harness 时,通常指围绕模型的软件层,它会组装 prompt、暴露工具、跟踪文件状态、应用编辑、运行命令、管理权限、缓存稳定前缀、存储记忆,等等。
如今,在使用 LLM 时,与直接提示模型或使用网页版聊天 UI(更接近“和上传文件聊天”)相比,这一层塑造了大部分用户体验。
在我看来,如今 vanilla 版本的 LLM 能力非常相近(例如 GPT-5.4、Opus 4.6、GLM-5 等的 vanilla 版本),因此 Harness 往往可能成为让一个 LLM 比另一个 LLM 工作得更好的差异化因素。
这只是推测,但我怀疑,如果我们把最新、最强的 open-weight LLM 之一(例如 GLM-5)放进类似的 Harness 中,它很可能可以达到 Codex 中 GPT-5.4 或 Claude Code 中 Claude Opus 4.6 的水平。话虽如此,一些针对 Harness 的 post-training 通常是有益的。例如,OpenAI 历史上曾维护过单独的 GPT-5.3 和 GPT-5.3-Codex 变体。
在下一节中,我想更具体地讨论 Coding Harness 的核心组件,使用我的 Mini Coding Agent 作为例子:https://github.com/rasbt/mini-coding-agent。
图 4:Coding Agent / Coding Harness 的主要 Harness 特性,将在后续各节中讨论。
顺便说一下,在本文中,为了简化,我会有些交替地使用 “Coding Agent” 和 “Coding Harness” 这两个术语。(严格来说,Agent 是由模型驱动的决策循环,而 Harness 是围绕它的软件脚手架,提供上下文、工具和执行支持。)
图 5:极简但完整可用、从零实现的 Mini Coding Agent(用纯 Python 实现)
总之,下面是 Coding Agent 的六个主要组成部分。你可以查看我这个极简但完整可用、从零实现的 Mini Coding Agent(用纯 Python 实现)的源代码,获得更具体的代码示例。代码通过注释标出了下面讨论的六个组件:
##############################
#### Six Agent Components ####
##############################
# 1) Live Repo Context -> WorkspaceContext
# 2) Prompt Shape And Cache Reuse -> build_prefix, memory_text, prompt
# 3) Structured Tools, Validation, And Permissions -> build_tools, run_tool, validate_tool, approve, parse, path, tool_*
# 4) Context Reduction And Output Management -> clip, history_text
# 5) Transcripts, Memory, And Resumption -> SessionStore, record, note_tool, ask, reset
# 6) Delegation And Bounded Subagents -> tool_delegate1. 实时代码库上下文
这也许是最显而易见的组件,但它也是最重要的组件之一。
当用户说“修复测试”或“实现 xyz”时,模型应该知道自己是否位于 Git 仓库中、当前在哪个分支上、哪些项目文档可能包含指令,等等。
这是因为这些细节经常会变化,或者会影响正确的操作是什么。例如,“修复测试”并不是一个自包含的指令。如果 Agent 看到 AGENTS.md 或项目 README,它可能会了解到应该运行哪个测试命令,等等。如果它知道 repo root 和布局,就可以去正确的位置查找,而不是猜测。
此外,git 分支、状态和提交也可以提供更多上下文,说明当前有哪些改动正在进行,以及应该把注意力放在哪里。
图 6:Agent Harness 首先构建一个小型 workspace summary,并将其与用户请求合并,以提供额外的项目上下文。
这里的要点是,Coding Agent 会在开始任何工作之前,预先收集信息(作为 workspace summary 的“稳定事实”),这样它在每次 prompt 中都不是从零开始、毫无上下文。
2. Prompt 形状和缓存复用
一旦 Agent 获得了 repo 视图,下一个问题就是如何把这些信息提供给模型。上一张图展示了一个简化视图(“Combined prompt: prefix + request”),但在实践中,如果每次用户查询都合并并重新处理 workspace summary,会相对浪费。
也就是说,编码会话是重复性的,Agent 规则通常保持不变。工具描述通常也保持不变。甚至 workspace summary 通常也(大体)保持不变。主要变化通常是最新的用户请求、最近的 transcript,以及可能的短期记忆。
“智能”的运行时不会在每一轮都把所有内容重新构建成一个巨大的、没有区分的 prompt,如下图所示。
图 7:Agent Harness 构建一个稳定的 prompt prefix,加入不断变化的会话状态,然后把合并后的 prompt 提供给模型。
与第 1 节的主要区别在于,第 1 节关注的是收集 repo 事实。这里,我们关注的是如何高效地打包和缓存这些事实,以便重复调用模型。
“stable” 的 “Stable prompt prefix” 意味着其中包含的信息不会频繁变化。它通常包含通用指令、工具描述和 workspace summary。如果没有重要内容发生变化,我们不想在每次交互中浪费算力从头重建它。
其他组件更新得更频繁(通常每一轮都会更新)。这包括短期记忆、最近的 transcript 和最新的用户请求。
简而言之,“Stable prompt prefix”的缓存方面,指的就是智能运行时会尝试复用这一部分。
3. 工具访问和使用
工具访问和工具使用,是系统开始不像聊天、而更像 Agent 的地方。
普通模型可以用文字建议命令,但处于 Coding Harness 中的 LLM 应该做更窄、更有用的事情,并且真正能够执行命令并获取结果(而不是让我们手动调用命令,再把结果粘回聊天中)。
不过,Harness 通常不会让模型即兴生成任意语法,而是提供一个预定义的、被允许的命名工具列表,带有清晰的输入和清晰的边界。(当然,像 Python subprocess.call 这样的东西也可以是其中一部分,这样 Agent 也可以执行范围很广的任意 shell 命令。)
工具使用流程如下图所示。
图 8:模型发出结构化动作,Harness 对其进行验证,必要时请求批准,执行它,并将有边界的结果反馈回循环中。
为了说明这一点,下面是使用我的 Mini Coding Agent 时,用户通常会看到的样子。(它没有 Claude Code 或 Codex 那么漂亮,因为它非常极简,并且使用没有任何外部依赖的纯 Python。)
图 9:Mini Coding Agent 中工具调用批准请求的示意图。
在这里,模型必须选择一个 Harness 能识别的动作,比如列出文件、读取文件、搜索、运行 shell 命令、写入文件等。它还必须以 Harness 可以检查的形式提供参数。
所以,当模型请求做某件事时,运行时可以停下来,执行程序化检查,例如:
-
“这是一个已知工具吗?”
-
“参数有效吗?”
-
“这需要用户批准吗?”
-
“请求的路径真的在 workspace 内吗?”
只有这些检查通过之后,实际操作才会运行。
当然,运行 Coding Agent 会带来一定风险,但 Harness 检查也提高了可靠性,因为模型不会执行完全任意的命令。
此外,除了拒绝格式错误的动作和批准门控之外,还可以通过检查文件路径,把文件访问限制在 repo 内部。
从某种意义上说,Harness 给了模型更少的自由,但同时也提升了可用性。
4. 最小化上下文膨胀
上下文膨胀并不是 Coding Agent 独有的问题,而是 LLM 普遍存在的问题。当然,如今 LLM 支持越来越长的上下文(我最近也写过一篇关于 attention variants 的文章,介绍它们如何让计算上更可行),但长上下文仍然昂贵,并且也可能引入额外噪声(如果里面有大量无关信息)。
在多轮聊天期间,Coding Agent 比普通 LLM 更容易受到上下文膨胀的影响,因为它会反复读取文件、产生很长的工具输出、日志等等。
如果运行时以完整保真度保留所有这些内容,它很快就会耗尽可用的上下文 token。因此,一个好的 Coding Harness 通常会非常复杂地处理上下文膨胀,而不只是像普通聊天 UI 那样裁剪或总结信息。
从概念上讲,Coding Agent 中的上下文压缩可能如下图所示。具体来说,我们是在进一步放大上一节图 8 中的 clip(步骤 6)部分。
图 10:大型输出会被裁剪,较早的读取会被去重,transcript 会在重新进入 prompt 之前被压缩。
一个最小化 Harness 至少会使用两种压缩策略来管理这个问题。
第一种是裁剪,它会缩短较长的文档片段、大型工具输出、记忆笔记和 transcript 条目。换句话说,它会防止任何一段文本仅仅因为碰巧很冗长,就占据整个 prompt 预算。
第二种策略是 transcript reduction 或 summarization,它会把完整会话历史(下一节会进一步讨论)变成一个更小、可放入 prompt 的摘要。
这里的一个关键技巧是,让最近的事件保留更丰富的信息,因为它们更可能与当前步骤相关。而较旧的事件会被更激进地压缩,因为它们通常相关性较低。
此外,我们还会对较早的文件读取进行去重,这样模型不会因为某个文件在会话早些时候被读取了多次,就一遍又一遍地看到相同的文件内容。
总的来说,我认为这是优秀 Coding Agent 设计中被低估的、无聊但重要的部分之一。很多表面上的“模型质量”,实际上是上下文质量。
5. 结构化会话记忆
在实践中,本文涵盖的这 6 个核心概念都高度交织,不同章节和图示只是以不同重点或缩放层级覆盖它们。在上一节中,我们讨论了 prompt-time 的历史使用,以及如何构建紧凑 transcript。那里的问题是:过去有多少内容应该在下一轮重新放回模型中?因此重点是压缩、裁剪、去重和近因性。
现在,本节的结构化会话记忆,关注的是历史的 storage-time 结构。这里的问题是:随着时间推移,Agent 会把什么保留下来作为永久记录?因此重点在于,运行时会把更完整的 transcript 作为持久状态保存,同时维护一个更轻量的记忆层;这个记忆层更小,会被修改和压缩,而不是只做追加。
总结来说,Coding Agent 会把状态分成(至少)两层:
-
working memory:Agent 显式保留的小型、提炼后的状态
-
完整 transcript:涵盖所有用户请求、工具输出和 LLM 响应
图 11:新事件会被追加到完整 transcript 中,并被总结进 working memory。磁盘上的会话文件通常以 JSON 文件形式存储。
上图展示了两个主要会话文件:完整 transcript 和 working memory,它们通常会以 JSON 文件形式存储在磁盘上。如前所述,完整 transcript 存储整个历史,并且如果我们关闭 Agent,也可以恢复。Working memory 更像是一个提炼版本,包含当前最重要的信息,它与紧凑 transcript 有一定关联。
但紧凑 transcript 和 working memory 的职责略有不同。紧凑 transcript 用于 prompt reconstruction。它的职责是给模型一个最近历史的压缩视图,这样模型不必每一轮都看到完整 transcript,也能继续对话。Working memory 则更偏向 task continuity。它的职责是保留一个小型、显式维护的摘要,记录跨轮次真正重要的内容,例如当前任务、重要文件和最近笔记。
按照上图第 4 步,最新的用户请求,连同 LLM 响应和工具输出,会在下一轮作为“新事件”记录到完整 transcript 和 working memory 中;为了减少上图杂乱,这一步没有展示出来。
6. 通过(有边界的)Subagent 进行委派
一旦 Agent 拥有工具和状态,下一个有用能力之一就是委派。
原因在于,它允许我们通过 Subagent 把某些工作并行化为子任务,从而加快主任务。例如,主 Agent 可能正在处理一个任务,但仍然需要一个旁路答案,比如哪个文件定义了某个符号、某个配置写了什么,或者为什么某个测试失败。把这类工作拆成一个有边界的子任务很有用,而不是强迫一个循环同时承载每一条工作线索。
(在我的 Mini Coding Agent 中,实现更简单,子 Agent 仍然是同步运行的,但底层思想相同。)
Subagent 只有在继承了足够上下文、能够完成真实工作时才有用。但如果我们不限制它,现在就会有多个 Agent 重复工作、触碰相同文件,或者继续生成更多 Subagent,等等。
所以,棘手的设计问题不只是如何启动一个 Subagent,还包括如何约束一个 Subagent :)。
图 12:Subagent 会继承足够上下文以便有用,但它运行在比主 Agent 更严格的边界内。
这里的技巧在于,Subagent 继承足够上下文以便发挥作用,同时也受到约束(例如只读,并限制递归深度)。
Claude Code 很早就支持 Subagent,Codex 则是最近才加入。Codex 通常不会强制 Subagent 进入只读模式。相反,它们通常会继承主 Agent 的大部分 sandbox 和批准设置。因此,边界更多体现在任务范围、上下文和深度上。
组件总结
上面的章节试图覆盖 Coding Agent 的主要组成部分。如前所述,它们在实现上或多或少都深度交织。不过,我希望逐一介绍它们,能帮助你建立关于 Coding Harness 如何工作的整体心智模型,并理解为什么它们能让 LLM 相比简单的多轮聊天更有用。
图 13:前面章节讨论过的 Coding Harness 六个主要特性。
如果你有兴趣看这些内容如何用干净、极简的 Python 代码实现,你可能会喜欢我的 Mini Coding Agent。
这和 OpenClaw 相比如何?
OpenClaw 可能是一个有意思的对比对象,但它并不完全是同一类系统。
OpenClaw 更像是一个本地的通用 Agent 平台,也可以编码,而不是一个专门的(终端)编码助手。
它仍然和 Coding Harness 有几处重叠:
-
它使用 workspace 中的 prompt 和指令文件,例如 AGENTS.md、SOUL.md 和 TOOLS.md
-
它保留 JSONL 会话文件,并包含 transcript compaction 和会话管理
-
它可以生成 helper session 和 Subagent
-
等等。
不过,如上所述,二者的重点不同。Coding Agent 针对的是一个人在 repository 中工作,并要求编码助手高效地检查文件、编辑代码和运行本地工具。OpenClaw 则更偏向于在聊天、频道和 workspace 中运行许多长期存在的本地 Agent,编码只是其中几个重要工作负载之一。
我很高兴分享,我已经完成了 Build A Reasoning Model (From Scratch) 的写作,所有章节目前都已进入 early access。出版社目前正在处理版式,它应该会在今年夏天发布。
这可能是我迄今为止最有野心的一本书。我花了大约 1.5 年来写它,其中包含了大量实验。从时间、精力和打磨程度来说,它可能也是我最用力的一本书,我希望你会喜欢它。
《Build a Reasoning Model (From Scratch)》可在 Manning 和 Amazon 获取。
主要主题包括:
-
评估 Reasoning Model
-
inference-time scaling
-
self-refinement
-
reinforcement learning
-
distillation
围绕 LLM 中“reasoning”的讨论有很多,而我认为,理解它在 LLM 语境中到底意味着什么的最好方式,就是从零实现一个!
-
Amazon(预订)
-
Manning(完整书籍处于 early access,预最终版式,528 页)
来源:https://magazine.sebastianraschka.com/p/components-of-a-coding-agent