coding-agents-architecture
如何设计和优化编码智能体架构,以提升软件开发效率和代码质量?
原文链接:
Components of A Coding Agent
Components of A Coding Agent
How coding agents use tools, memory, and repo context to make LLMs work better in practice
以下为AI翻译,有润色。
在这篇文章里,我想聊聊编码智能体(Coding Agents)和智能体框架(Agent Harnesses)的整体设计:它们是什么、怎么运作,以及在实践中各个部分是如何咬合在一起的。
我的两本书《Build a Large Language Model (From Scratch)》和《Build a Large Reasoning Model (From Scratch)》的读者,时常问起关于智能体的问题,所以我觉得有必要写一篇可供参考的指南。
更宏观地看,智能体之所以成为一个重要议题,是因为近期实用型 LLM 系统的进步,很大程度上并不只来自模型本身的提升,更来自我们如何使用这些模型。在许多实际应用场景中,外围系统——工具调用、上下文管理、记忆机制——所发挥的作用,丝毫不亚于模型本身。这也解释了为什么 Claude Code 或 Codex 这类系统,用起来会比在普通聊天界面中使用同款模型强大得多。
本文将逐一拆解编码智能体的六个核心构建模块。
你可能已经接触过 Claude Code 或 Codex CLI。简单介绍一下背景:它们本质上都是代理式编码工具,将 LLM 包裹在一个应用层(即所谓的代理框架)中,从而让模型在编码任务上更顺手、表现更好。
图 1: Claude Code CLI、Codex CLI,以及我自己搭建的迷你编码智能体。
编码智能体专为软件开发工作而设计,其核心不仅在于模型的选择,更在于周边系统的质量——包括代码库上下文的注入、工具的设计、提示词缓存的稳定性、记忆能力,以及长会话的连续性。
这个区分至关重要。我们在谈论 LLM 的编码能力时,很容易把模型、推理行为和智能体产品混为一谈。所以在深入探讨编码智能体的细节之前,先简单厘清几个核心概念之间的区别:LLM、推理模型和智能体。
- 推理模型:本质上仍是 LLM,但特指那些经过训练或提示之后,能在推理、验证或搜索候选答案时投入更多计算资源的模型。
- 智能体(Agent):架设在模型之上的一层,可以理解为围绕模型运行的控制循环。给定一个目标,智能体层(或框架)负责决定下一步该检查什么、调用哪些工具、如何更新状态,以及何时停下来。
粗略地说:LLM 是引擎,推理模型是强化版引擎(能力更强,但调用成本更高),而智能体框架则是帮我们驾驭模型的系统。这个类比不完美——LLM 和推理模型当然也能独立使用(在聊天界面或 Python 环境里)——但核心思想是清晰的。
图 2: 传统 LLM、推理 LLM(推理模型),以及封装在智能体框架中的 LLM,三者之间的关系。
换句话说,智能体是一个在环境中反复调用模型的系统。
简单总结一下各概念的定义:
- 推理模型:经过优化、能输出中间推理步骤并进行更多自我验证的 LLM
- 智能体(Agent):结合了模型、工具、记忆与环境反馈的循环系统
- 智能体框架(Agent Harness):围绕智能体构建的软件脚手架,负责管理上下文、工具调用、提示词、状态与控制流
- 编码框架(Coding Harness):智能体框架的一种特例,专为软件工程设计,重点处理代码上下文、工具使用、执行与迭代反馈
如上所述,在智能体和编码工具的语境下,还有两个常见术语:智能体框架(Agent Harness)和编码框架(Coding Harness)。
编码框架是指围绕模型搭建的软件脚手架,旨在帮助模型高效地编写和编辑代码;而智能体框架的适用范围更广,不局限于编码场景(例如 OpenClaw)。Codex 和 Claude Code 都可以视为编码框架。
总结:更好的 LLM 为推理模型奠定更优的基础,而 Harness 则让推理模型发挥出更大的潜力。
当然,LLM 和推理模型本身也完全可以独立完成编码任务,无需借助框架。但编码工作远不止是生成下一个 token——它涉及代码库导航、搜索、函数查找、差异应用、测试执行、错误检查,以及在上下文中同时保持所有相关信息。(程序员应该深有体会:这是极耗脑力的工作,这也是为什么写代码时最怕被打断。)
图 3: 编码辅助框架融合了三个层级:模型系列、智能体循环和运行时支持。模型提供「引擎」,智能体循环驱动迭代式的问题解决过程,运行时支持提供底层架构。在循环中:「观察」从环境收集信息,「检查」分析信息,「选择」确定下一步行动,「执行」将其付诸实践。
核心结论:一套优秀的编码辅助框架,能让推理模型和非推理模型的表现都远超在普通聊天框中的水平——因为它承担了上下文管理等大量繁琐但关键的工作。
如上一节所述,当我们谈到「工具框架(Harness)」时,通常指的是模型周围的软件层,它负责:组装提示词、暴露工具接口、追踪文件状态、应用编辑、执行命令、管理权限、缓存稳定前缀、存储记忆……等等。
如今,使用 LLM 时,这一软件架构层所决定的用户体验,已经远超直接向模型发送提示词或使用网页聊天界面的方式(后者更接近「与上传的文件聊天」)。
在我看来,由于当今主流 LLM 的原生能力已非常接近(比如 GPT-5.4、Opus 4.6、GLM-5 等),工具框架往往才是决定某个 LLM 是否比另一个表现更好的关键因素。
这只是推测,但我怀疑:如果把目前能力最强的开源权重模型之一(比如 GLM-5)套入类似的工具框架,它在 Codex 或 Claude Code 中的表现,很可能与 GPT-5.4 或 Claude Opus 4.6 不相上下。当然,针对特定框架进行一些后期训练通常是有益的——例如,OpenAI 历史上曾维护过独立的 GPT-5.3 和 GPT-5.3-Codex 变体。
接下来,我想深入细节,以我的
Mini Coding Agent 为例,拆解编码工具的核心组件。
图 4: 后续章节将逐一介绍的编码智能体/编码工具的主要功能模块。
顺便说明:为了行文简便,本文中「编码智能体(Coding Agent)」和「编码工具(Coding Harness)」这两个术语会混用。严格来说,智能体指的是模型驱动的决策循环,而工具则是提供上下文、工具接口和执行支持的周边软件框架。
图 5: 一个极简但功能完备、从零构建的迷你编码智能体(纯 Python 实现)。
以下是编码智能体的六个主要组件。你可以查看我那个极简版迷你编码智能体的源代码,代码中通过注释标注了这六个组件的位置:
组件一:实时代码库上下文(Live Repo Context)
这或许是最显而易见的组件,却也是最重要的之一。
当用户说「修复测试」或「实现 xyz」时,模型需要知道:它是否处于一个 Git 仓库中?当前在哪个分支?哪些项目文档里可能有相关指令?
这些细节往往会直接影响正确的操作方式。「修复测试」并非一条自包含的指令。如果智能体能看到 AGENTS.md 或项目 README,它或许就能获知应该运行哪个测试命令。如果它了解仓库的根目录和目录结构,就能在正确的位置查找,而不是瞎猜。
此外,Git 分支、状态和提交记录也能帮助智能体了解当前正在进行哪些更改,以及重点应该放在哪里。
图 6: 智能体工具包会优先构建一份简短的工作区摘要,并将其与用户请求结合,提供额外的项目上下文。
核心要点:编码智能体在开始任何工作之前,会预先收集信息(作为工作区摘要的「稳定事实」),这样它在处理每个提示词时就不会从零开始,也不会陷入信息匮乏的困境。
组件二:提示词缓存——提示词结构与缓存复用(Prompt Shape & Cache Reuse)
一旦智能体掌握了代码库概览,下一个问题就是:如何高效地把这些信息喂给模型?
前面的图示展示的是简化视图(「组合提示词:前缀 + 请求」),但在实践中,每次用户查询都重新组合并处理一遍工作区摘要,显然是一种浪费。
道理很简单:编码会话具有重复性——
主要变化的只有:最新的用户请求、最近的对话记录,以及可能的短期记忆。
「智能」运行时不会在每一轮都把所有内容重新打包成一个巨大的、无差别的提示词,如下图所示。
图 7: 智能体工具框架构建一个稳定的提示词前缀,叠加上不断变化的会话状态,再将组合后的提示词输入模型。
与组件一的区别在于:组件一侧重于「收集」代码库的事实信息,组件二侧重于「打包和缓存」这些事实,以便在后续模型调用中高效复用。
「稳定提示词前缀」之所以「稳定」,是因为它包含的信息变化不大——通常包括通用指令、工具描述和工作区摘要。如果没有重要内容发生变化,我们不希望每次交互都浪费计算资源从头重建它。
其他组件则更新频率更高(通常每轮对话都会更新),包括短期记忆、最近的对话记录和最新的用户请求。
简而言之,「稳定提示词前缀」的缓存机制,本质上就是让智能运行时尽量复用这部分内容,避免重复计算。
组件三:工具——结构化工具、验证与权限(Structured Tools, Validation & Permissions)
工具访问和工具使用,是让系统从「聊天」跨越到「智能体」的关键所在。
普通模型只能用文字建议命令,但处于编码框架中的 LLM 应该做得更具体、更有用——它能实际执行命令并获取结果,而不是让我们手动复制命令、再把结果粘贴回聊天窗口。
但与其让模型即兴发挥任意语法,工具框架通常会提供一份预定义的、命名明确的允许工具列表,并附带清晰的输入要求和边界。(当然,像 Python subprocess.call 这样的通用工具也可以包含进来,让智能体能执行更广泛的 shell 命令。)
工具使用的流程如下图所示:
图 8: 模型发出结构化操作 → 工具框架进行验证 → 必要时请求批准 → 执行操作 → 将受限的执行结果反馈回循环。
以我的 Mini Coding Agent 为例,以下是用户通常看到的交互效果(它非常精简,纯 Python 实现,没有任何外部依赖,所以不像 Claude Code 或 Codex 那么美观):
图 9: Mini Coding Agent 中工具调用审批请求的示意图。
在这个框架中,模型必须选择一个运行时能够识别的操作,例如列出文件、读取文件、搜索、运行 shell 命令、写入文件等;并以运行时能够校验的格式提供参数。
当模型请求执行某项操作时,运行时会暂停并执行一系列程序化检查:
只有通过所有检查,才会真正执行操作。
运行编码智能体确实存在一定风险,但这套检查机制大幅提升了可靠性——因为模型无法执行完全随意的命令。此外,除了拒绝格式错误的操作和设置审批门控之外,文件访问权限还可以通过路径校验限制在仓库内部。
某种意义上,框架对模型自由度的约束,恰恰成就了它的可用性。
组件四:上下文管理——上下文精简与输出管理(Context Reduction & Output Management)
上下文膨胀(Context Bloat)并非编码智能体独有的问题,而是 LLM 普遍面临的挑战。尽管如今的 LLM 支持越来越长的上下文(我最近也写过关于让长上下文在计算上更可行的注意力机制变体的文章),但长上下文依然成本高昂,且可能引入额外噪声(当其中包含大量无关信息时)。
与常规 LLM 多轮对话相比,编码智能体更容易受到上下文膨胀的影响——因为它涉及反复的文件读取、冗长的工具输出、执行日志等内容。
如果运行时完整保留所有这些信息,很快就会耗尽可用的上下文 token。因此,一个优秀的编码工具框架通常会采取非常精细的手段来应对上下文膨胀,而不只是像普通聊天界面那样简单地截断或总结。
从概念上看,编码智能体中的上下文压缩(Context Compaction)工作方式如下图所示(具体来说,是对上一节图 8 中「剪辑(clip,第 6 步)」部分的深入拆解):
图 10: 过长的输出会被截断,较旧的读取内容会被去重,转录内容在返回提示词之前经过压缩。
一个最小化的工具框架至少会使用两种压缩策略:第一种:截断(Clipping)。缩短过长的文档片段、庞大的工具输出、记忆笔记和对话记录条目。换句话说,它能防止某段文本仅仅因为篇幅冗长就占用过多的提示词预算。第二种:对话记录精简或摘要(Transcript Reduction / Summarization)。将完整的会话历史(下一节会详细介绍)转化为更精简、可放入提示词的摘要。
这里有一个关键技巧:保持近期事件的丰富度,因为它们对当前步骤最有可能产生影响;而对较早的事件则进行更激进的压缩,因为它们的相关性通常较低。此外,还会对较早的文件读取记录进行去重,避免模型反复看到相同内容。
总的来说,这是优秀编码智能体设计中被严重低估的「枯燥」部分。许多人以为的「模型质量」,实际上是「上下文质量」。
组件五:会话记忆——会话记录、记忆与恢复(Transcripts, Memory & Resumption)
本文涵盖的所有六个核心概念在实践中是高度交织的,不同章节从不同角度和粒度对它们进行了阐述。
上一节讨论的是提示词阶段对历史记录的使用——我们如何构建精简的记录副本,以便模型继续对话。重点在于:压缩、截断、去重和时效性。
这一节讨论的是结构化会话记忆,聚焦于历史记录的存储结构和时间维度。核心问题变成了:智能体随时间推移保留什么作为永久记录?
运行时会维护两个层级的状态:
- 完整转录(Full Transcript):涵盖所有的用户请求、工具输出和 LLM 响应——是完整的历史归档;
- 工作记忆(Working Memory):体积更小,会被主动修改和压缩(而不只是追加),是当前任务状态的提炼。
图 11: 新事件被追加到完整记录,并在工作记忆中进行摘要更新。会话文件通常以 JSON 格式存储在磁盘上。
如图所示,两个主要的会话文件(完整记录和工作记忆)通常以 JSON 文件形式持久化存储。完整记录存储了整个历史,在关闭智能体后仍可恢复;工作记忆则更像是当前最重要信息的提炼版本。
两者的作用有所不同:
- 紧凑记录(Compact Transcript):用于提示词重构——为模型提供近期历史的压缩视图,让它无需每轮都处理完整记录即可继续对话;
- 工作记忆(Working Memory):用于任务连续性——保留一份小型的、显式维护的摘要,记录跨轮次的重要信息,如当前任务、关键文件和近期笔记。
最新的用户请求、LLM 的响应和工具输出,会在下一轮作为「新事件」同时写入完整记录和工作记忆(为了减少图示复杂度,这个过程未在图中展示)。
组件六:子智能体——任务委派与有界子智能体(Delegation & Bounded Subagents)
一旦智能体具备了工具和状态,下一个有用的扩展就是任务委派。
其原因在于:它允许我们通过子智能体将某些工作并行拆分为子任务,从而加快主任务的执行速度。
例如,主智能体正在处理某项任务,但同时需要获取一些辅助信息——某个符号在哪个文件中定义、某个配置文件的内容是什么、某个测试为什么失败。将这些需求拆分为有界的子任务,远比让单一循环同时处理所有工作线程更高效。
(在我的迷你编码智能体中,实现方式相对简单,子智能体仍然同步运行,但核心思想是一致的。)
子智能体只有在继承了足够的上下文以完成实际工作时才有价值。但如果不加约束,就会出现多个智能体重复劳动、修改相同文件,甚至生成更多子智能体的混乱局面。
因此,真正棘手的设计问题不仅在于如何生成子智能体,更在于如何约束(Bind)它们。
图 12: 子智能体继承了足够的上下文以发挥作用,但在比主智能体更严格的边界内运行。
诀窍在于:子智能体既继承了足够的上下文以发挥作用,又受到了明确的限制(例如,只读权限和递归深度上限)。
Claude Code 很早就支持了子智能体,Codex 最近也加入了这一功能。Codex 通常不强制将子智能体设为只读模式,而是让它们继承主智能体的大部分沙盒和审批设置——因此边界更多体现在任务范围、上下文和递归深度上。
以上就是编码智能体的六大核心组件。正如前文所说,它们在实现上是深度交织的。但我希望逐一介绍这些组件,有助于建立关于编码工具如何运作的整体心智模型——以及为什么与简单的多轮对话相比,它们能让 LLM 发挥出更大的价值。
图 13: 前几节讨论的编码工具六大主要功能模块。
如果你有兴趣查看这些功能在简洁、极简的 Python 代码中是如何实现的,欢迎查看我的 Mini Coding Agent。
附:OpenClaw 是coding agent吗?
OpenClaw 或许是一个有趣的对比对象,但它并不是同一种类型的系统。
OpenClaw 更像是一个本地通用智能体平台,同时具备编程能力,而不是专门的终端编码助手。
它与编码工具之间仍存在若干重叠之处:
- 在工作区中使用提示词和指令文件,例如
AGENTS.md、SOUL.md 和 TOOLS.md;
- 保留 JSONL 会话文件,包含会话记录压缩和会话管理功能;
但如上所述,侧重点有所不同:编码智能体针对的是在代码仓库中工作的开发者,要求助手高效检查文件、编辑代码、执行本地工具;而 OpenClaw 则更侧重于在聊天、频道和工作区中运行大量长期运行的本地智能体,编码只是其众多重要工作负载之一。
《从零构建推理模型》已全部完成
我很高兴地宣布,《Build A Reasoning Model (From Scratch)》所有章节目前均已开放抢先体验。出版社正在进行排版工作,预计将于今年夏天正式出版。
这可能是我迄今为止最具雄心的一本书。历时约一年半,投入了大量实验与反复打磨,希望你们会喜欢。
在 Manning 和 Amazon 上抢先体验《从零构建推理模型》。
主要议题包括:
- 推理时扩展(Inference-Time Scaling)
关于 LLM 中的「推理」,存在很多讨论。理解其真正含义的最好方式,就是从零实现一个。