【译】编码 Agent 的组成部分

原文:Sebastian Raschka, PhD,Components of A Coding Agent,发表于 2026 年 4 月 4 日。本文为中文译文,保留关键专有名词、术语、代码、原文链接及插图。

编码 Agent 如何利用工具、记忆和仓库上下文,让 LLM 在实际应用中表现得更好

在这篇文章中,我想介绍编码 Agent(Coding Agent)和 Agent Harness 的整体设计:它们是什么、如何工作,以及各个部分在实践中如何协同。我的《Build a Large Language Model (From Scratch)》和《Build a Large Reasoning Model (From Scratch)》两本书的读者经常问起 Agent,因此,我觉得写一篇可以供大家参考的文章会很有帮助。

更广泛地说,Agent 之所以成为一个重要话题,是因为近期实用 LLM 系统取得的许多进展,并不只是来自更好的模型,也来自我们使用模型的方式。在许多现实应用中,工具使用、上下文管理和记忆等外围系统,发挥的作用与模型本身同样重要。这也有助于解释,为什么 Claude Code 或 Codex 这样的系统,会让人感觉比在普通聊天界面中使用相同模型强大得多。

在这篇文章中,我会介绍编码 Agent 的六个主要构建模块。

Claude Code、Codex CLI 与其他编码 Agent

你可能已经熟悉 Claude Code 或 Codex CLI。不过,为了建立共同的背景,还是先说明一下:它们本质上都是具备 Agent 能力的编程工具,在 LLM 外面包裹了一层应用,也就是所谓的 Agent Harness,从而让模型在编程任务中用起来更方便、表现也更好。

图 1

图 1:Claude Code CLI、Codex CLI,以及我的 Mini Coding Agent。

编码 Agent 是专门为软件工作设计的。值得关注的不只是模型选择,还有它周围的系统,包括仓库上下文(repo context)、工具设计、提示词缓存稳定性(prompt-cache stability)、记忆,以及长会话的连续性(long-session continuity)。

这种区分很重要,因为当我们讨论 LLM 的编程能力时,人们经常把模型、推理行为和 Agent 产品混为一谈。不过,在深入编码 Agent 的具体细节之前,我想先简要补充一些背景,说明 LLM、推理模型和 Agent 这些更广泛概念之间的区别。

LLM、推理模型与 Agent 之间的关系

LLM 是核心的下一个 token 预测模型。推理模型(reasoning model)仍然是 LLM,但通常经过了专门训练,或者通过提示词引导,在中间推理、验证或候选答案搜索上投入更多推理时计算量(inference-time compute)。

Agent 则是位于模型之上的一层,可以理解为围绕模型建立的控制循环(control loop)。通常,在给定目标后,Agent 层或 Harness 会决定接下来检查什么、调用哪些工具、如何更新状态、何时停止等。

粗略地说,我们可以这样理解它们的关系:LLM 是引擎,推理模型是强化版引擎——更强大,但使用成本也更高——而 Agent Harness 则帮助我们使用这个模型。这个类比并不完美,因为我们也可以把常规 LLM 和推理 LLM 作为独立模型使用,例如在聊天界面或 Python 会话中使用,但我希望它能传达出主要意思。

图 2

图 2:常规 LLM、推理 LLM(或推理模型),以及包裹在 Agent Harness 中的 LLM 之间的关系。

换句话说,Agent 就是在某个环境中反复调用模型的系统。

因此,我们可以简要总结如下:

  • LLM:原始模型。
  • 推理模型(Reasoning model):一种经过优化的 LLM,能够输出中间推理轨迹(intermediate reasoning traces),并进行更多自我验证。
  • Agent:一个结合模型、工具、记忆与环境反馈的循环。
  • Agent Harness:围绕 Agent 的软件支撑框架,负责管理上下文、工具使用、提示词、状态与控制流。
  • Coding Harness:Agent Harness 的一种特殊形式,也就是面向软件工程的专用 Harness,负责管理代码上下文、工具、执行与迭代反馈。

如上所列,在 Agent 和编程工具的语境中,我们还会遇到两个常见术语:Agent Harness 和(agentic)Coding Harness。Coding Harness 是围绕模型搭建的软件支撑框架,帮助模型有效地编写和编辑代码。Agent Harness 的范围则更广,并不局限于编程,例如可以想想 OpenClaw。Codex 和 Claude Code 都可以看作 Coding Harness。

总之,更好的 LLM 为推理模型提供了更好的基础——构建推理模型还需要额外训练——而 Harness 则能进一步发挥这个推理模型的能力。

当然,LLM 和推理模型本身也能解决编程任务,而不一定需要 Harness。不过,编程工作只有一部分与下一个 token 的生成有关。还有很大一部分工作涉及仓库导航、搜索、函数查找、应用 diff、执行测试、检查错误,以及把所有相关信息保留在上下文中。程序员可能知道,这些都是很费脑力的工作,所以我们才不喜欢在编程时被打断。:)

图 3

图 3:Coding Harness 结合了三个层次:模型家族(model family)、Agent 循环(agent loop),以及运行时支持(runtime supports)。模型提供“引擎”,Agent 循环推动迭代式问题求解,运行时支持提供基础支撑。在循环内部,“observe”从环境中收集信息,“inspect”分析这些信息,“choose”选择下一步,“act”执行所选操作。

这里想传达的是,一个好的 Coding Harness 能通过上下文管理等机制,让推理模型和非推理模型都显得比在普通聊天框里强大得多。

Coding Harness

正如上一节提到的,我们所说的 Harness,通常是指围绕模型的一层软件,负责组装提示词、提供工具、跟踪文件状态、应用编辑、运行命令、管理权限、缓存稳定前缀、存储记忆,以及更多其他事情。

如今,在使用 LLM 时,相比直接向模型发送提示词,或者使用更接近“带上传文件功能的聊天”的网页聊天界面,这一层软件决定了用户体验的大部分内容。

在我看来,现在各家 LLM 的基础版本已经具备非常接近的能力,例如 GPT-5.4、Opus 4.6、GLM-5 等的基础版本。因此,Harness 往往可能成为让某个 LLM 比另一个表现更好的关键差异。

这只是推测,但我怀疑,如果把 GLM-5 这样最新、能力最强的开放权重 LLM 之一放进类似的 Harness 中,它很可能达到与 Codex 中的 GPT-5.4,或 Claude Code 中的 Claude Opus 4.6 相当的表现。话虽如此,针对特定 Harness 做一些后训练(post-training),通常还是有帮助的。例如,OpenAI 过去就分别维护过 GPT-5.3 和 GPT-5.3-Codex 两个版本。

下一节中,我想结合自己的 Mini Coding Agent,更具体地讨论 Coding Harness 的核心组件:https://github.com/rasbt/mini-coding-agent。

图 4

图 4:接下来几节将讨论的编码 Agent / Coding Harness 的主要 Harness 功能。

顺便说明一下,为了简化表述,我在这篇文章中会在一定程度上交替使用“Coding Agent”和“Coding Harness”这两个术语。严格来说,Agent 是由模型驱动的决策循环,而 Harness 是它周围的软件支撑框架,负责提供上下文、工具和执行支持。

图 5

图 5:从零实现、极简但功能完整的 Mini Coding Agent,使用纯 Python 编写。

下面就是编码 Agent 的六个主要组件。如果你想看更具体的代码示例,可以查看我从零实现、极简但功能完整的 Mini Coding Agent 源码,它使用纯 Python 编写。代码通过注释标出了下文讨论的六个组件:

1
2
3
4
5
6
7
8
9
##############################
#### 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_delegate

1. 实时仓库上下文(Live Repo Context)

这可能是最显而易见的组件,但也是最重要的组件之一。

当用户说“修复测试”或“实现 xyz”时,模型应该知道自己是否位于一个 Git 仓库中、当前处于哪个分支、哪些项目文档中可能包含操作指引等。

这是因为,这些细节往往会改变或影响什么才是正确的行动。例如,“修复测试”并不是一条自包含的指令。如果 Agent 看到了 AGENTS.md 或项目的 README,就可能从中了解到应该运行哪条测试命令等信息。如果知道仓库根目录及其布局,它就能去正确的位置查找,而不是靠猜。

此外,Git 分支、状态和提交记录,还能提供更多上下文,帮助模型理解目前正在进行哪些修改,以及应该把注意力放在哪里。

图 6

图 6:Agent Harness 首先构建一份简短的工作区摘要,再将其与用户请求结合起来,补充项目上下文。

这里的要点是,编码 Agent 会在开始工作之前,先把信息收集好,将“稳定事实”(stable facts)整理成工作区摘要。这样,它就不会在每次收到提示词时,都从毫无上下文的零点开始。

2. 提示词结构与缓存复用(Prompt Shape And Cache Reuse)

一旦 Agent 了解了仓库,接下来的问题就是如何把这些信息交给模型。上一张图对此做了简化展示:“组合提示词:前缀 + 请求”(Combined prompt: prefix + request)。但在实践中,如果每次用户提问时都重新组合、重新处理工作区摘要,就会相当浪费。

也就是说,编程会话具有重复性,而 Agent 的规则通常保持不变,工具描述通常也保持不变。即使是工作区摘要,通常也基本不会变化。主要变化的内容,往往是最新的用户请求、近期会话记录,以及可能存在的短期记忆。

“聪明”的运行时,不会在每一轮都把所有内容重新构建成一个不加区分的巨大提示词,如下图所示。

图 7

图 7:Agent Harness 构建稳定的提示词前缀,加入不断变化的会话状态,再将组合后的提示词送入模型。

这一节与第 1 节的主要区别在于:第 1 节讨论的是收集仓库事实,而这里关注的是如何高效地封装和缓存这些事实,以便反复调用模型。

“稳定提示词前缀”(Stable prompt prefix)中的“稳定”,是指其中包含的信息不会发生太大变化。它通常包含通用指令、工具描述和工作区摘要。如果没有重要变化,我们就不希望在每次交互时都从头重建它,白白消耗计算资源。

其他部分的更新则更加频繁,通常每一轮都会变化,包括短期记忆、近期会话记录和最新的用户请求。

简而言之,“稳定提示词前缀”的缓存机制,就是让一个聪明的运行时尽可能复用这部分内容。

3. 工具访问与使用(Tool Access and Use)

工具访问和工具使用,是系统开始不再那么像聊天、而更像 Agent 的地方。

普通模型可以用文字建议你运行某些命令,但 Coding Harness 中的 LLM 应该做一些范围更明确、也更有用的事情:它应该真正能够执行命令,并获取结果,而不是由我们手动运行命令,再把结果粘贴回聊天窗口。

不过,Harness 通常不会让模型随意发明语法,而是提供一组预先定义、明确命名的允许工具,每种工具都有清晰的输入和边界。当然,Python 的 subprocess.call 之类的能力也可以包含在内,让 Agent 能够执行范围非常广泛的 Shell 命令。

工具使用的流程如下图所示。

图 8

图 8:模型发出结构化操作,Harness 对其进行验证,在需要时请求批准,然后执行操作,并将有界的结果反馈到循环中。

为了说明这一点,下面展示了使用我的 Mini Coding Agent 时,用户通常会看到的界面。它没有 Claude Code 或 Codex 那么漂亮,因为它非常精简,使用纯 Python 编写,不依赖任何外部库。

图 9

图 9:Mini Coding Agent 中工具调用审批请求的示例。

在这里,模型必须选择一种 Harness 能识别的操作,例如列出文件、读取文件、搜索、运行 Shell 命令、写入文件等。它还必须按 Harness 能够检查的格式提供参数。

因此,当模型请求执行某项操作时,运行时可以先停下来,进行程序化检查,例如:

  • “这是一个已知工具吗?”
  • “参数有效吗?”
  • “这需要用户批准吗?”
  • “请求访问的路径,真的位于工作区内吗?”

只有这些检查全部通过后,操作才会真正执行。

当然,运行编码 Agent 仍然存在一定风险,但 Harness 的这些检查也提高了可靠性,因为模型不会完全任意地执行命令。

此外,除了拒绝格式错误的操作,以及通过审批控制执行之外,还可以通过检查文件路径,将文件访问限制在仓库内部。

从某种意义上说,Harness 给了模型更少的自由,但也同时改善了可用性。

4. 尽量减少上下文膨胀(Minimizing Context Bloat)

上下文膨胀(context bloat)并不是编码 Agent 独有的问题,而是 LLM 普遍面临的问题。如今,LLM 确实支持越来越长的上下文;我最近也写过一篇关于注意力机制变体(attention variants)的文章,介绍它们如何让长上下文在计算上更可行。但长上下文仍然昂贵,而且如果其中包含大量无关信息,也会引入更多噪声。

现代 LLM 中注意力机制变体的可视化指南(A Visual Guide to Attention Variants in Modern LLMs)

Sebastian Raschka, PhD · 3 月 22 日

阅读全文

在多轮交互中,编码 Agent 比普通 LLM 更容易出现上下文膨胀,因为它们会反复读取文件,并产生冗长的工具输出、日志等内容。

如果运行时把所有这些内容都原封不动地保留下来,就会很快耗尽可用的上下文 token。因此,一个好的 Coding Harness 在处理上下文膨胀方面,通常会有相当精细的设计,而不只是像普通聊天界面那样截断或总结信息。

从概念上看,编码 Agent 的上下文压缩(context compaction)可能如下图所示。具体来说,我们是在进一步放大上一节图 8 中 clip 部分,也就是第 6 步,看看里面发生了什么。

图 10

图 10:较大的输出会被截断,较早读取的内容会被去重,会话记录也会在重新进入提示词之前被压缩。

即使是一个极简的 Harness,也至少会使用两种压缩策略来管理这个问题。

第一种是截断(clipping),用来缩短较长的文档片段、大型工具输出、记忆笔记和会话记录条目。换句话说,它可以防止某一段文字仅仅因为内容冗长,就占据整个提示词预算。

第二种策略是会话记录缩减或摘要(transcript reduction or summarization),把完整的会话历史——下一节会进一步介绍——转化成一份更短、可以放入提示词的摘要。

这里有一个关键技巧:保留近期事件的更多细节,因为它们更可能与当前步骤有关;对更早的事件则进行更激进的压缩,因为它们可能没那么相关。

此外,我们还会对较早读取的文件内容进行去重,避免仅仅因为同一个文件之前在会话中被多次读取,模型就不断看到相同内容。

总体而言,我认为,这是优秀编码 Agent 设计中一个被低估、也有些乏味的部分。许多看起来像是“模型质量”的差异,其实是上下文质量的差异。

5. 结构化会话记忆(Structured Session Memory)

在实践中,本文讨论的这六个核心概念都高度交织在一起。不同章节和插图,只是在不同的重点或缩放层次上讨论它们。上一节介绍了如何在构建提示词时使用历史,以及如何构建压缩后的会话记录。那里的问题是:在下一轮中,应该把多少过去的信息重新送回模型?因此,重点是压缩、截断、去重和时效性。

而这一节的结构化会话记忆,关注的是历史信息在存储时的结构。这里的问题是:随着时间推移,Agent 会保留哪些内容作为永久记录?因此,重点是运行时会保留更完整的会话记录,作为持久状态;与此同时,还会维护一层更轻量的记忆。后者更小,会被修改和压缩,而不只是不断追加内容。

概括来说,编码 Agent 至少会把状态分成两层:

  • 工作记忆(working memory):Agent 显式维护的一小份提炼后的状态。
  • 完整会话记录(full transcript):涵盖全部用户请求、工具输出和 LLM 回复。

图 11

图 11:新事件会追加到完整会话记录中,并被概括进工作记忆。磁盘上的会话文件通常以 JSON 文件的形式存储。

上图展示了两个主要的会话文件:完整会话记录与工作记忆。它们通常以 JSON 文件的形式保存在磁盘上。如前所述,完整会话记录保存整个历史,因此,即使关闭 Agent,也可以恢复会话。工作记忆则更像一个提炼后的版本,包含当前最重要的信息,与压缩后的会话记录有一定关联。

不过,压缩后的会话记录(compact transcript)和工作记忆,承担的职责略有不同。压缩后的会话记录用于重建提示词(prompt reconstruction):它的任务是让模型获得近期历史的压缩视图,从而不必在每一轮都查看完整记录,也能继续对话。工作记忆则更侧重任务连续性(task continuity):它的任务是维护一份简短、显式更新的摘要,保存跨轮次的重要内容,例如当前任务、重要文件和近期笔记。

在上图第 4 步之后,最新的用户请求、LLM 回复和工具输出,会在下一轮作为一个“新事件”,同时记录进完整会话记录和工作记忆中。为了避免上图过于杂乱,这一步没有画出来。

6. 使用有边界的子 Agent 进行委派(Delegation With (Bounded) Subagents)

当 Agent 具备工具和状态之后,接下来一个有用的能力就是委派(delegation)。

原因在于,我们可以借助子 Agent(subagents),把某些工作拆成子任务并行处理,从而加快主任务。例如,主 Agent 正在处理某项任务,但同时还需要知道一个旁支问题的答案:某个符号定义在哪个文件里、某个配置写了什么,或者某个测试为什么失败。把它拆成一个有明确边界的子任务很有帮助,而不必强迫同一个循环同时承载所有工作线索。

在我的 Mini Coding Agent 中,实现更简单,子 Agent 仍然是同步运行的,但基本思路相同。

子 Agent 只有继承足够的上下文,才能真正开展工作,因而才有价值。但如果不对其加以限制,就可能出现多个 Agent 重复劳动、修改相同文件,或继续生成更多子 Agent 等情况。

所以,棘手的设计问题不只是如何创建子 Agent,还包括如何给它设定边界。:)

图 12

图 12:子 Agent 继承足够多的上下文,以便发挥作用,但它运行时受到的边界约束,比主 Agent 更严格。

这里的技巧是,让子 Agent 继承足够的上下文,能够派上用场;同时,也对其施加限制,例如只读访问,以及限制递归深度。

Claude Code 支持子 Agent 已经很久了,Codex 则是在更近的时候加入了这项能力。Codex 通常不会强制子 Agent 进入只读模式;相反,它们往往会继承主 Agent 的大部分沙箱和审批设置。因此,这里的边界更多体现在任务范围、上下文和深度上。

组件总结

前面几节尝试介绍了编码 Agent 的主要组件。如前所述,在实现中,它们或多或少都会深度交织在一起。不过,我希望逐个介绍这些组件,有助于大家建立整体的心智模型,理解 Coding Harness 如何工作,以及为什么相比简单的多轮聊天,它能让 LLM 更有用。

图 13

图 13:前面几节讨论的 Coding Harness 的六项主要功能。

如果你有兴趣看看,如何用干净、极简的 Python 代码实现这些组件,也许会喜欢我的 Mini Coding Agent。

与 OpenClaw 相比如何?

OpenClaw 可能是一个有意思的对比对象,但它并不完全属于同一类系统。

OpenClaw 更像是一个本地运行、同时也能编程的通用 Agent 平台,而不是一个专门的终端编程助手。

它与 Coding Harness 仍然有一些重叠:

  • 它会使用工作区中的提示词和指令文件,例如 AGENTS.md、SOUL.md 和 TOOLS.md。
  • 它会保存 JSONL 会话文件,并提供会话记录压缩和会话管理功能。
  • 它可以创建辅助会话和子 Agent。
  • 等等。

不过,如前所述,两者的侧重点不同。编码 Agent 针对的是这样一种场景:一个人在仓库中工作,要求编程助手高效地检查文件、编辑代码和运行本地工具。而 OpenClaw 更侧重于跨聊天、渠道和工作区,运行许多长期存续的本地 Agent;编程只是其中一项重要工作负载。