在本文中,我将探讨编码代理及其框架的整体设计:它们是什么、如何工作,以及各个部分在实践中是如何协同运作的。我的《从零构建大型语言模型》和《从零构建推理模型》两本书的读者经常询问关于代理的问题,因此我认为撰写一篇可作为参考的文章会很有帮助。
更广泛地说,代理已成为一个重要话题,因为近年来实际 LLM 系统的进步不仅在于更好的模型本身,更在于我们如何使用这些模型。在许多现实应用中,围绕模型的系统——如工具使用、上下文管理和记忆机制——其作用与模型本身同样关键。这也解释了为何像 Claude Code 或 Codex 这样的系统在使用体验上明显优于相同模型在普通聊天界面中的表现。
本文将阐述编码代理的六大核心构建模块。
你可能已经熟悉 Claude Code 或 Codex CLI,但为了铺垫背景,它们本质上都是将大语言模型封装在应用层内的代理式编程工具,即所谓的“代理框架”,以便更高效、便捷地完成编程任务。

编码代理专为软件开发而设计,其关键不仅在于模型选择,还包括周边系统,例如仓库上下文、工具设计、提示缓存稳定性、记忆机制和长会话连续性。
这一区别很重要,因为当人们谈论 LLM 的编码能力时,常常会将模型、推理行为和代理产品混为一谈。但在深入探讨编码代理的具体实现之前,我先简要说明一下 LLM、推理模型和代理之间的基本关系。
LLM 是核心的下一个词元预测模型。推理模型本质上仍是一个 LLM,但它通常经过专门训练或通过提示引导,在推理阶段投入更多计算资源进行中间推理、验证或对候选答案进行搜索。
代理则是在模型之上的额外一层,可理解为一个围绕模型的闭环控制系统。通常情况下,给定一个目标,代理层(或称框架)会决定下一步应检查什么、调用哪些工具、如何更新自身状态,以及何时停止等。
我们可以这样粗略类比:LLM 是引擎,推理模型是强化版引擎(性能更强,但使用成本更高),而代理框架则帮助我们更好地利用这个引擎。当然,这个类比并不完美——因为我们也可以将常规 LLM 和推理 LLM 作为独立模型使用(如在聊天界面或 Python 会话中),但我希望它仍能传达核心思想。

换句话说,代理是一个反复调用模型并运行在其环境中的系统。
简而言之,可以总结如下:
如上所述,在代理和编码工具的语境下,我们还常听到两个术语:代理框架 和(代理式的)编码框架。编码框架是指围绕模型提供代码编写与编辑支持的工具集合;而代理框架范围更广,不局限于编码场景(例如 OpenClaw 就是一个例子)。Codex 和 Claude Code 均可视为编码框架。
无论如何,更好的 LLM 为推理模型提供了更坚实的基础(这涉及额外的训练),而代理框架则能充分发挥这种推理模型的能力。
诚然,LLM 和推理模型本身也能独立完成某些编码任务(无需依赖框架),但编码工作远不止简单的词元生成。它很大程度上依赖于仓库导航、搜索、函数查找、差异应用、测试执行、错误排查,以及将所有相关信息维持在有效上下文中。(程序员们深知这需要大量脑力劳动,这也是为什么我们在编码过程中不喜欢频繁被打断的原因 😉)。

由此可见,优秀的编码框架能让推理型和非推理型模型在普通聊天框中表现出远超实际能力的水平,因为它能有效协助管理上下文并提供更多辅助功能。
正如前文所述,当我们提到“框架”时,通常指的是围绕模型构建的软件层,它负责组装提示词、暴露工具接口、跟踪文件状态、应用修改、运行命令、管理权限、缓存稳定前缀、存储记忆等。
如今,在使用 LLM 的过程中,这一层实际上塑造了大部分用户体验,相比直接提示模型或使用网页聊天界面(更接近“上传文件后聊天”的模式)而言尤为显著。
在我看来,当前主流 LLM 的基础版本(例如 GPT-5.4、Opus 4.6、GLM-5 等)在原生能力上已非常接近。因此,代理框架往往成为区分不同 LLM 实际表现的关键因素。
这属于推测性观点,但我怀疑:如果将最新最强的开源权重 LLM(如 GLM-5)放入类似的代理框架中运行,其表现很可能与 Codex 中的 GPT-5.4 或 Claude Code 中的 Claude Opus 4.6 相当。不过,某些针对特定框架的后训练优化通常仍有益处。例如,OpenAI 历史上曾维护过独立的 GPT-5.3 和 GPT-5.3-Codex 变体。
接下来,我将以我的 Mini Coding Agent 为例,深入探讨代理框架的核心组件细节:https://github.com/rasbt/mini-coding-agent。

顺便提一句,为方便叙述,本文中“编码代理”和“编码框架”这两个术语有时会被交替使用。(严格来说,代理指的是由模型驱动的决策循环,而框架则是为其提供上下文、工具和执行支持的软件脚手架。)

总之,以下是编码代理的六个主要组成部分。你可以查阅我的完全从零开始实现的、功能齐全的 Mini Coding Agent 源码(纯 Python 实现),其中包含了上述六个组件的具体代码示例。代码注释已标注如下:
##############################
#### 六个代理组件 ####
##############################
# 1) 实时仓库上下文 -> WorkspaceContext
# 2) 提示结构设计与缓存复用 -> build_prefix, memory_text, prompt
# 3) 结构化工具、验证与权限控制 -> build_tools, run_tool, validate_tool, approve, parse, path, tool_*
# 4) 上下文缩减与输出管理 -> clip, history_text
# 5) 会话记录、记忆与恢复机制 -> SessionStore, record, note_tool, ask, reset
# 6) 委派与受限子代理 -> tool_delegate
这可能是最显而易见但也最重要的组件之一。
当用户说“修复测试”或“实现 xyz”时,模型必须知道当前是否处于 Git 仓库内、所在分支、项目文档是否包含相关说明等信息。
因为这些细节往往会改变或影响正确操作的选择。例如,“修复测试”并非自包含指令——若代理能看到 AGENTS.md 或项目 README,就能了解应运行哪个测试命令等。若知晓仓库根目录及布局结构,则可精准定位而非盲目猜测。
此外,git 分支、状态和历史提交也能提供更多上下文,帮助判断当前正在进行的变更及其优先级。

关键在于,编码代理会在执行任何操作前预先收集信息(称为“稳定事实”的工作区摘要),从而避免每次响应都从零开始、缺乏上下文。
一旦代理获得了仓库视图,下一个问题是:如何高效地将这些信息传递给模型?前述图示展示了一个简化版本(“组合提示:前缀 + 请求”),但在实践中,每次用户提问都重新拼接整个工作区摘要会非常低效。
也就是说,编码会话具有重复性,代理规则通常保持不变。工具描述也基本固定,工作区摘要大多也不会频繁变动(除非有重大变更)。主要变化的是最新的用户请求、最近的对话历史和短期记忆内容。
“智能”运行时不会像下图所示那样,每次都重建一个庞大的无差别提示词。

本节与第1节的区别在于:第1节关注的是获取仓库事实,而此处重点在于如何高效打包并缓存这些事实,供多次模型调用使用。
所谓“稳定提示前缀”,是指其中包含的信息不会频繁变动。它通常包括通用指令、工具描述和工作区摘要。只要没有重要更新,就不应在每次交互中都从头重建这部分内容,以免浪费计算资源。
其余组件则更频繁地更新(通常每轮对话一次),包括短期记忆、最近对话历史和最新用户请求。
简言之,“稳定提示前缀”的缓存机制就是:智能运行时尝试重用这部分内容。
工具访问和使用标志着系统从单纯聊天向真正代理行为的转变。
普通模型可以用自然语言建议命令,但置于编码框架中的 LLM 应执行更精确、实用的操作,并能实际执行命令并返回结果(而不是让用户手动运行后再粘贴回聊天窗口)。
然而,框架通常不会放任模型随意生成任意语法,而是提供一个预定义的可选工具列表,每个工具都有明确输入参数和边界限制(当然,像 Python 的 subprocess.call 这类功能仍可被纳入,使代理能够执行广泛的 shell 命令)。
工具使用流程如下图示意:

以下是我 Mini Coding Agent 中用户视角的一个示例(由于极其精简且仅用纯 Python 实现,界面不如 Claude Code 或 Codex 美观):

此时,模型需选择一个框架识别的动作,如列出文件、读取文件、搜索、运行 shell 命令、写入文件等,并提供符合框架校验格式的参数。
因此,当模型提出请求时,运行时可进行程序化检查,例如:
只有通过所有检查后,才会真正执行相应操作。
虽然运行编码代理存在一定风险,但框架的检查机制提升了可靠性,防止模型执行完全任意的命令。
此外,除了拒绝格式错误的操作和设置审批门限外,还可通过检查文件路径确保文件访问仅限于仓库内部。
某种程度上,框架限制了模型的自由度,却同时提升了可用性。
上下文膨胀并非编码代理独有的问题,而是所有 LLM 面临的普遍挑战。尽管如今 LLM 支持越来越长的上下文(我最近还撰写了关于 注意力变体 的文章,介绍使其计算上更可行的技术),但长上下文依然昂贵,并可能引入无关噪声。
现代 LLM 中的视觉注意力变体 现代 LLM 中的视觉注意力变体
现代 LLM 中的视觉注意力变体 现代 LLM 中的视觉注意力变体塞巴斯蒂安·拉斯克萨(PhD)
2025年3月22日
在多轮对话中,编码代理比常规 LLM 更容易遭遇上下文膨胀,原因包括重复的文件读取、冗长的工具输出、日志记录等。
若运行时保留所有内容的高保真版本,很快就会耗尽可用上下文 token。因此,优秀的编码框架通常采用超越普通聊天 UI 的复杂策略来处理上下文膨胀问题。
概念上,编码代理的上下文压缩机制可概括如下。具体而言,我们将进一步聚焦于前一节图8中的“clip”(裁剪)步骤。

一个基础的框架至少采用两种压缩策略来解决此问题。
第一种是裁剪,即缩短长文档片段、大型工具输出、记忆笔记和转录条目。换言之,防止任何单一文本因过于冗长而独占整个提示预算。
第二种策略是转录缩减或摘要生成,即将完整会话历史(更多细节见下一节)转化为更小但可直接输入模型的摘要。
一个关键技巧是:保留近期事件更丰富的细节,因为它们更可能与当前步骤相关;而对较旧的事件则进行更激进的压缩,因其相关性较低。
另外,还需去重较早的文件读取,避免模型反复看到同一文件内容——即使该文件已在会话早期被多次读取。
总体而言,我认为这是优秀编码代理设计中常被低估且略显枯燥的部分。许多看似“模型质量”的表现,实则是上下文质量的体现。
实际上,上述六大核心概念高度交织在一起,各小节和图示也从不同角度或缩放级别进行了阐述。前一节讨论了历史在提示阶段的运用方式,以及如何构建紧凑的转录文本。那时的问题是:下一轮应将多少过去的内容送回模型?重点在于压缩、裁剪、去重和时效性。
而本节的结构化会话记忆,则关注历史的存储结构。这里的问题是:代理随时间推移应保留哪些永久性记录?重点在于运行时维护一个更完整的转录本作为持久状态,同时辅以一个更小、动态调整的记忆层,而非简单追加。
总结来说,编码代理将状态分为(至少)两层:

上图展示了通常以 JSON 格式存储在磁盘上的两个主要会话文件:完整转录本和工作记忆。如前所述,完整转录本保存全部历史,允许代理关闭后恢复;工作记忆则是提炼版本,包含当前最重要信息,与紧凑转录本有一定关联。
但紧凑转录本和工作记忆职责略有不同。紧凑转录本用于提示重构,其任务是给模型提供一个压缩后的近期历史视图,使其无需每轮都查看完整转录本即可继续对话。工作记忆则更侧重任务连续性,其任务是维护一个小型、显式管理的跨轮次摘要,例如当前任务、重要文件和最近笔记。
按照上图第4步,最新用户请求连同 LLM 响应和工具输出,将在下一轮作为“新事件”分别记录到完整转录本和工作记忆中(图中未显示以减少混乱)。
一旦代理具备工具和状态管理能力,下一步有用的功能是委派。
原因是它允许我们将某些工作并行化为子任务,通过子代理加速主任务。例如,主代理可能正在处理一项任务,但仍需获取侧面答案,比如某个符号定义在哪个文件、配置说了什么,或者为何某个测试失败。将其拆分为一个有边界的子任务比强迫单一循环承载所有线程更有效。
(在我的迷你编码代理中,实现更简单,子代理仍是同步运行的,但底层理念一致。)
子代理只有在继承足够上下文的情况下才有实际价值。但若不加限制,就会出现多个代理重复工作、操作相同文件或不断派生子代理等问题。
因此,设计的难点不仅在于如何启动子代理,更在于如何约束它。

关键在于,子代理既要有足够的上下文以完成真实工作,又要受到约束(例如只读模式、递归深度受限等)。
Claude Code 长期支持子代理,Codex 最近才加入该功能。Codex 通常不强制子代理进入只读模式,而是继承主代理的大部分沙箱环境和审批设置。因此,边界更多体现在任务范围、上下文和深度方面。
上文试图覆盖编码代理的主要组件。如前所述,它们在实现上彼此深度交织。不过,我希望逐一讲解有助于建立对编码框架整体工作机制的理解,以及为何它能比简单多轮聊天更有效地提升 LLM 的实用性。

如果你希望看到这些特性在简洁、极简的 Python 代码中实现,不妨试试我的 Mini Coding Agent。
OpenClaw 或许是个有趣的对比对象,但它并非同类型系统。
OpenClaw 更像是一个本地通用代理平台,既能编程,又不局限于终端辅助工具。
它与编码框架仍有若干重叠之处:
然而,正如上文所述,两者的侧重点不同。编码代理专为开发者在其仓库中高效检查文件、编辑代码和执行本地工具而优化;OpenClaw 则更侧重于在多个聊天、频道和工作空间中运行大量长期本地代理,编程只是众多负载之一。
我很高兴宣布,我已完稿《从零构建推理模型》,全书章节现已开放早期访问。出版社目前正在排版,预计今年夏天正式出版。
这可能是迄今为止我最有抱负的一本书。我花了约一年半时间撰写,期间进行了大量实验。这也是我投入最多精力(时间、努力和润色)的作品,希望能让你喜欢!

主要内容包括:
围绕“推理”在 LLM 中的讨论很多,我认为真正理解其含义的最佳方式是从零实现一个推理模型!