第五章:Coding Agent 与通用 Agent 学习笔记
学习来源:https://bojieli.github.io/ai-agent-book/book/chapter5/
本章的核心是说明如何把模型、上下文、工具、约束、验证、纠错与文件系统组合成一个可靠的 Coding Agent,并进一步说明为什么代码生成可以成为通用 Agent 的元能力。
一个成熟的 Coding Agent 不只是生成代码,而是能够理解项目、制定方案、修改文件、执行命令、运行测试、处理错误、回滚恢复,并最终交付可验证的结果。
一、Coding Agent 的整体架构与工作流程
1.1 Coding Agent 的基础能力
Coding Agent 的核心工作可以概括为对代码仓库进行“搜索、读取、修改、执行和验证”。基础工具通常包括 Code Interpreter、Bash Shell、文件读取、文件写入、文件编辑、Glob 文件名搜索和 Grep 内容搜索。这些工具看起来简单,但组合起来已经能够覆盖绝大多数编程任务。相比为每一种需求都提前开发专用工具,Coding Agent 可以直接通过代码和 Shell 动态解决问题,因此代码生成本身具有较强的通用性。
对于开放任务型通用 Agent,文件系统不仅用于保存文件,还承担了工作空间、长期记忆和中间状态存储的作用。Agent 可以从项目文件中读取上下文,把代码、数据、日志和生成结果写回文件,并将稳定经验沉淀为项目指令或长期记忆。也就是说,Coding Agent + 文件系统可以构成通用 Agent 的基础执行框架。
1.2 Harness 在 Coding Agent 中的作用
Coding Agent 的稳定性不能只依赖模型能力,更依赖 Harness。Harness 一方面提供上下文和工具,让 Agent 有能力完成任务;另一方面通过约束、验证和纠错机制限制错误行为。软件工程本身已经具备完善的测试、Lint、类型检查、Git、CI 和沙盒环境,这些机制天然适合作为 Agent 的自动反馈系统。因此,Coding Agent 成熟度较高的重要原因,不只是模型会写代码,而是代码任务本身具有较强的可验证性。
Harness 的核心原则可以概括为:能用程序强制执行的规则,不要只写成自然语言提示;能自动验证的结果,不要完全依赖人工审查;错误反馈应尽量快速、结构化;任何高风险修改都应具有可靠的回退机制。对 Agent 来说,“代码写完”不是任务结束,只有测试、检查和最终验证通过,才可以认为任务完成。
1.3 Coding Agent 的整体流程:按 Prompt 约束理解
这一部分可以直接理解成 Coding Agent 在执行任务时需要遵守的一组工程约束。复杂任务并不是“收到需求后直接开始写代码”,而是依次完成项目理解、需求澄清、方案设计、实现、测试、自审查和文档同步。
项目理解约束:在修改代码之前,先阅读
README、CLAUDE.md、AGENTS.md、.cursorrules等项目说明,理解目录结构、主要模块、构建方式、测试命令和禁止修改的区域。如果缺少关键文档,应先通过阅读代码建立项目结构认知,而不是直接开始修改。需求澄清约束:对简单且边界明确的任务可以直接实现;对“优化性能”“重构系统”等范围模糊的任务,应先明确目标、影响范围、允许的权衡和验收标准。在需求仍然存在关键歧义时,不应盲目编码。
设计约束:对影响多个模块或涉及重要架构变化的任务,在编码前先形成简洁设计方案,说明需要修改哪些模块、采用什么方案、是否增加依赖、可能影响哪些已有功能。复杂任务应先验证方案,再进入大规模代码修改。
实现约束:实现时优先复用项目已有抽象、工具和代码风格,避免无必要地重复造轮子。修改应尽量局部、可回滚,并避免为了让测试通过而采用破坏性捷径,例如删除已有逻辑、绕过权限或修改测试本身。
测试约束:修改完成后必须运行测试,而不是只检查代码是否生成成功。新增或修改功能应覆盖正常路径、边界情况和异常情况。如果测试失败,应分析错误、修复代码并重新测试,形成“测试—修复—再测试”的闭环。
代码审查约束:测试通过后还要检查代码可读性、重复逻辑、性能、安全问题、类型错误和代码规范。必要时运行 Linter、类型检查器或代码审查 Sub-Agent。测试通过并不意味着代码一定适合交付。
文档同步约束:如果修改影响架构、模块依赖、接口或核心行为,应同步更新项目文档。文档与代码必须共同演化,避免未来的 Agent 或开发者读取过时信息。
因此,这套流程可以压缩成一句话:先理解,再规划;修改后立即验证;验证失败继续修复;重要变更同步文档;只有验证通过才结束任务。
二、故障错误与恢复
核心问题:生产级 Harness 会遇到哪些故障?如何检测和恢复?什么时候必须终止?
2.1 常见故障分为四层
API 层故障:包括 HTTP 429 限流、服务过载、请求超时、连接中断、流式输出被截断等。这类问题通常不是任务逻辑本身导致,而是模型服务或网络基础设施的问题。
工具层故障:包括调用不存在的工具、参数格式不符合 Schema、工具执行抛出异常,以及模型面对同一个工具错误不断原样重试。其中“相同调用反复失败”尤其危险,因为它容易把 Agent 带入无限循环。
上下文层故障:包括上下文窗口溢出、压缩失败,以及消息轨迹结构损坏。例如已经出现
tool_call,却缺少对应的tool_result,会导致后续模型无法正确理解历史状态。控制流层故障:主要是死循环和死亡螺旋。死循环指 Agent 不断重复相同操作但没有产生新进展;死亡螺旋则是错误处理逻辑自身再次调用模型并继续报错,导致恢复机制不断触发自己。
2.2 如何检测故障
故障检测的第一原则不是“失败就重试”,而是先判断错误类型是否值得重试。限流、网络抖动和短时过载通常可以重试;参数错误、权限不足、工具不存在则不能原样重试,必须修改调用参数、切换策略或停止当前路径。因此,生产级 Harness 应维护“错误类型 → 恢复策略”的明确映射,而不是统一使用重试。
除了单次错误,还必须检测重复模式。系统可以对“工具名 + 参数”生成调用指纹,如果完全相同的调用连续出现且结果没有变化,就说明 Agent 可能已经进入无进展循环。同时,应维护连续失败计数器,为后续熔断提供依据。
每个长连接都需要独立的活性信号,不能只依赖连接超时。流式连接可能没有主动断开,但已经停止输出。此时 SDK 可能仍认为连接有效,所以 Harness 需要设置 watchdog timer。如果在指定时间内没有收到新 token 或事件,应主动终止当前流并触发恢复流程。对于消息轨迹,也需要完整性检查;发现工具调用缺少结果消息时,应在进入下一轮模型推理前修复结构。
2.3 如何恢复故障
故障恢复应遵循“从低成本、对用户透明的方法开始,逐步升级”的原则。
静默重试。对限流、短时过载和网络抖动使用指数退避与随机抖动进行重试,并尊重服务端给出的等待时间。主任务链路值得重试,而标题生成、输入建议等辅助功能失败时通常可以直接放弃,避免后台任务大量重试挤占配额。
降级与接续。如果单纯重试仍然失败,就改变请求方式。例如输出长度达到上限时,可以增加输出预算,或者让模型从已有内容继续生成;主模型持续不可用时,可以切换备用模型;高成本模式被限流时,可以降级到标准模式。
工具错误反馈给模型。对“工具不存在”“参数不合法”“执行异常”等问题,不应直接终止会话,而应把错误包装成结构化 Tool Result 返回模型,让模型在下一轮根据明确的错误信息自行修正。错误反馈越具体,模型越容易纠正。
最终暴露给用户。在自动重试、参数修复、降级、备用模型等方案都失败后,再把错误呈现给用户,并说明系统已经尝试过哪些恢复动作。中间恢复过程不应反复向用户暴露临时错误。
2.4 跨模型接管
当主模型持续不可用时,可以由另一个模型继续未完成的轨迹,但前提是轨迹不能完全绑定某一家模型厂商的私有消息格式。工具调用的语义通常可以转换,而模型的 reasoning、签名或厂商私有凭证可能无法跨模型复用。因此,更合理的做法是把 Agent 轨迹保存成中立格式,将可迁移的文字内容、工具名称和参数保留下来,将厂商私有凭证单独存储或在切换时丢弃。
这说明 Agent 的运行轨迹本身也是重要基础设施。中立轨迹不仅可以用于模型故障接管,还可以用于后续的轨迹重放、Agent 评估、训练样本构造和经验提取。
2.5 什么时候必须终止
恢复机制不能无限执行。以下情况应触发熔断或人工接管:同一工具调用反复出现且无进展;同一恢复路径连续失败超过阈值;达到最大迭代轮数;会话 token、时间或调用预算耗尽;恢复逻辑自身开始递归触发;继续执行可能产生不可逆的危险操作。
特别需要防止“死亡螺旋”。错误处理路径中应尽量禁止再次触发不必要的 LLM 调用,例如任务因上下文溢出结束时,不应再调用 LLM 自动生成 commit message 或记忆摘要。对于无法完全避免的递归恢复,应设置恢复深度计数器并强制终止。最终原则是:可恢复错误自动恢复,无进展错误及时熔断,高风险错误交给人工。
三、Coding Agent 的实现技巧
3.1 并行工具调用、流式执行与级联中止
传统 Agent 经常采用“生成工具调用 → 等待工具执行 → 再生成下一步”的串行方式,因此会浪费大量等待时间。更高效的实现会结合流式模型输出:当第一个工具调用的参数已经完整生成并通过校验时,就立即开始执行,不必等待模型把后续所有工具调用都生成完。对于互不依赖的多个工具调用,还可以同时并行执行,从而让模型生成与工具执行发生重叠,显著降低整体延迟。
但并行执行必须明确故障边界。工具应声明是否允许并发,默认可以采用更保守的“禁止并发”策略。一个调用失败后,只应中止同一批中依赖该结果的后续调用,不能把无关调用一起取消,更不能因为一个文件读取失败就终止整个 Agent。这里的关键思想是:故障应该局部传播,而不是无限向上扩散。
3.2 上下文精细化管理
代码仓库通常远大于模型上下文,因此不能把整个项目一次性塞给模型。文件读取工具应支持按行号范围读取,并在返回结果中保留真实行号,便于 Agent 精确定位代码。对于编译、测试等产生的大量终端输出,也不能原样全部放入上下文,应保留关键的开头和结尾,将完整日志保存为文件,在需要时再按需读取。
这类设计的重点不是简单“压缩上下文”,而是让 Agent 随时能够重新获取需要的信息。也就是说,文件系统负责保存完整状态,上下文只保留当前决策所需的最小信息。
3.3 环境信息动态注入
Coding Agent 的决策高度依赖当前运行环境,例如当前工作目录、Git 分支、最近提交、已暂存和未暂存修改。因此,这些信息应在每轮推理前以动态状态栏的方式追加到上下文,而不是长期写死在 System Prompt 中。这样既可以保持 Agent 对实时环境的准确感知,也可以避免频繁变化的状态破坏稳定前缀和 KV Cache。
3.4 命令执行状态持久化
很多开发操作依赖终端状态,例如cd切换目录、激活虚拟环境、设置环境变量和启动后台服务。如果每条命令都创建新的 Shell,会导致这些状态不断丢失。因此,Coding Agent 通常应维护一个长期存在的终端会话,在整个任务执行期间复用该 Shell。需要并行或隔离时,再额外创建独立终端。
3.5 即时语法反馈
文件修改完成后,不应等到整个任务结束才发现基础语法错误。更合理的方式是在编辑工具完成写入后自动运行语法检查、Linter 或类型检查,并把结果直接附加到工具返回值中。这样 Agent 可以在错误刚产生时立即修复,减少后期定位成本。这与 IDE 的即时错误提示类似,也是 Harness“快速反馈”原则的具体体现。
四、搜索、文件编辑与安全机制
4.1 Coding Agent 的搜索方式
代码搜索并不是只有一种方法。glob适合快速理解文件结构和定位某类文件;grep/ripgrep适合已经知道函数名、变量名、错误信息等具体文本时进行精确搜索;语义搜索适合“不知道代码具体叫什么,但知道它是做什么的”的探索任务;符号级搜索则用于追踪定义和引用关系。
实际使用时更重要的是组合,而不是追求单一搜索方案。常见策略是先快速扫目录,再通过关键词定位具体实现,必要时再追踪引用关系。近年来部分 Coding Agent 更倾向通过 Agent 自主组合glob + grep现场搜索,而不是长期维护代码向量索引,因为代码变化快,索引维护本身也有成本。
4.2 文件编辑工具
文件编辑工具的难点是如何让模型准确表达“修改哪一段、改成什么”。常见方案包括完整字符串替换、行号定位、首尾字符串匹配以及 Patch/Diff 等。当前实用性较高的方案通常强调可验证和可失败:例如 Old String → New String 要求旧文本在文件中真实存在且最好唯一,匹配失败时直接返回错误,而不是让系统猜测应该修改哪里。
对于 Agent 来说,编辑工具越确定越好。相比让模型输出模糊的自然语言修改建议,更可靠的方法是提供结构化编辑接口,让系统能够在真正落盘之前验证定位是否准确。大量编辑还应配合 Git diff、测试和回滚机制,避免一次错误修改污染整个代码库。
4.3 Coding Agent 的安全
Coding Agent 通常拥有文件读取、写入、Shell 和网络能力,因此其风险远高于普通聊天模型。真正有效的安全策略不是只过滤输入中的恶意关键词,而是限制 Agent 即使受到提示注入后也无法完成危险动作。重点包括网络出口控制、文件系统隔离、资源限制、危险命令语义分析和持久记忆安全。
代码执行环境应优先放在沙盒中,默认限制网络访问,仅放行明确需要的域名;源码、凭证和生成物应使用不同的挂载权限,SSH Key、Token 等敏感文件不应直接暴露给沙盒;CPU、内存、磁盘和运行时间都应设置上限。Shell 安全也不能只依赖rm、curl等关键字黑名单,因为危险操作可以隐藏在管道、子命令或参数组合中,应尽量根据命令真实语义进行判断。
此外,长期记忆也是安全边界。来自网页、邮件等不可信来源的内容不能未经检查直接写入MEMORY.md等长期记忆,否则一次提示注入可能跨会话持续影响 Agent。
五、代码是通用 Agent 的元能力
5.1 什么是“元能力”
普通工具解决的是预先定义好的任务,而代码生成能够在运行时创造新工具、新规则和新表达形式,因此可以被视为一种元能力。当 Agent 遇到工具箱中没有的能力时,可以临时生成脚本、API 适配器、校验器或界面,而不需要开发者提前枚举所有情况。这也是 Coding Agent 能成为开放任务型通用 Agent 核心的重要原因。
5.2 代码作为思考工具
LLM 擅长理解自然语言,但在精确计算、符号推导和复杂逻辑约束中仍可能出错。更合理的分工是让 LLM 负责理解问题和构造形式化表达,让 Python、SymPy、NumPy、SciPy 或约束求解器负责确定性计算。代码执行结果还可以直接作为验证信号,从而减少自然语言推理中的计算错误。
5.3 代码作为业务规则约束
自然语言规则容易存在歧义,而代码规则具有确定性。因此,对于退款、支付、删除数据等不可逆操作,不能只依赖 System Prompt 告诉模型“请遵守政策”,还应该在执行工具内部加入程序化校验。自然语言规则适合帮助模型理解和解释,代码校验则负责最终的强制执行,两者应同时存在。
这再次体现 Harness 的核心思想:重要规则应该编码化,而不是只文档化。对高风险操作,应以服务端真实数据作为判断依据,而不是相信模型自己传入的“我已经检查过”。
5.4 代码驱动的多媒体生成
PPT、PDF、网页、视频等内容都可以通过代码描述,因此 Agent 可以把多媒体生成转化为代码生成问题。真正困难的地方不是生成代码,而是验证最终渲染效果。因此,本章反复采用 Proposer–Reviewer 机制:一个 Agent 负责生成代码,另一个 Agent 或 Vision 模型负责查看渲染结果并提出结构化修改意见,直到结果满足要求。
代码生成和生成模型并不是互相替代。具有明确尺寸、规则和强验证要求的对象更适合代码生成;复杂度极高、难以用少量参数精确描述的视觉对象更适合生成模型。选择哪一种路线,应取决于产物是否容易形式化,以及是否需要精确验证和后续可编辑性。
5.5 代码作为系统适配器
真实系统中的 API、日志格式和数据结构会不断变化,Agent 可以读取接口文档或观察真实样本,然后临时生成解析和转换代码。这使代码成为连接不同系统的适配层。对于没有 API 的系统,也可以先通过 Computer Use 完成操作,再把成功的操作过程固化成 RPA 脚本,提高后续执行速度和稳定性。
在 Agent 运维中,这种能力还可以用于日志解析和问题诊断:读取轨迹日志,自动定位异常模块,生成问题报告、回归测试,甚至通过 MCP 创建 GitHub Issue。代码生成因此不只是“完成用户任务”,也可以成为 Agent 系统自身的维护工具。
5.6 代码作为生成式 UI
纯文本对话在复杂信息收集和数据展示场景中效率较低。Agent 可以动态生成表单、图表和交互页面,让用户一次性填写多个相关字段,或者把查询结果直接交给前端渲染。对于 SQL 等场景,更好的模式通常是让 LLM 只负责生成查询 Artifact,而不是让大量数据库结果经过 LLM 再转述,这样可以减少 token 消耗和抄写错误。
但动态代码不能直接获得无限权限。数据库应采用只读账号,限制 SQL 类型、可访问表和返回规模;前端生成代码也应运行在隔离环境中。生成式 UI 提升了交互能力,但仍然需要 Harness 在执行层完成权限和安全约束。
5.7 Agent 自举:用代码创造 Agent
代码生成的进一步应用是让 Agent 创建或修复另一个 Agent。例如 Doctor 类工具可以先用确定性规则检测 Token 过期、端口冲突、依赖缺失等常见问题,再让 LLM 处理规则无法覆盖的长尾故障。这里最合理的架构不是“全部交给 LLM”,而是确定性检查负责高频问题,LLM 负责复杂语义分析。
让 Agent 创建新 Agent 时,也不应该完全从零开始生成。更可靠的做法是提供高质量 Agent 范例,让模型复制成熟框架后进行适应性修改,包括调整 Prompt、增删工具和业务逻辑,而保留已经验证过的上下文管理、工具调用和错误恢复机制。换句话说,好的代码范例本身就是一种比长 Prompt 更强的工程约束。
六、实验
实验 5-1 ★★★:跨厂商的轨迹接管
实验目标:验证一份中立的轨迹格式能否让跑到一半的 Agent 轨迹换一家模型接着跑完,并量化“原样直传”与“一刀切剥光”两种做法的代价。
技术方案:任务需要多轮工具调用,跑到中途对当前厂商注入连续的限流与过载响应,熔断后切换到另一家继续。轨迹按中立格式保存,思考分为可移植的文字与不可移植的凭证两部分,工具调用只记录名称与参数。三种做法对照:直传把原厂商返回的消息原样搬进新厂商的结构;剥离删掉全部思考与凭证;中立丢弃凭证,把文字或厂商给出的思考摘要以普通内容的身份带入,标识符按目标厂商重新生成,遇到强制要求凭证的接收端则把历史调用改写为文字叙述。选三家线上格式互不相同的厂商,两两切换。
验收标准:切换后的首个请求都要留下原始响应,直传的失败必须是厂商真实返回的错误,不得以模拟错误代替。要求中立做法在全部厂商组合上都不出现接口错误,另外两种在哪些组合上失败、报什么错,如实记录。三者对比任务完成率、切换后重复调用同一工具的次数(按“工具名 + 参数”指纹计),以及切换后到完成所需的额外轮数与 token。若中立做法在重复调用上并未优于剥离,同样如实记录。
| 方案 | 接管成功 | 数据及总额正确 |
|---|---|---|
| 原样直传 | 3/6 | 3/6 |
| 删除思考 | 4/6 | 4/6 |
| 中立格式 | 6/6 | 6/6 |
实验 5-2 ★★:输出到一半断掉之后的接续
实验目标:比较“整轮重发”与“以半截输出为前缀续写”两种恢复方式在成本、正确性与副作用上的差异。
技术方案:在流式响应上取三个断点——思考中途、正文中途、工具调用参数中途——切断连接。三种恢复方式:丢弃半截整轮重发;把半截内容作为末尾的 assistant 消息要求模型接着写(有的厂商原生支持,有的要求显式标注这是一条待续写的消息,没有这条接口的则退回下一种);追加一条元指令说明从断点继续。半截的工具调用无法以原生结构回传,需先转成文字再让模型补完,拼接后重新解析校验。若半截输出里已有工具因流式提前执行,续写前按调用指纹去重,避免重复副作用。
验收标准:三类断点各重复若干次,报告每种方式的恢复成功率、相对整轮重发节省的输出 token、补完后参数的合法率与语义正确率(拼接处容易多出空白或重复字符,合法不等于正确),以及重复副作用次数。同时记录哪些断点在某些厂商上无法复现,以及降级路径是否可用。
七、本章总结
第五章真正想说明的是:Coding Agent 的能力来源于模型,但可靠性来源于 Harness。模型负责理解需求、生成方案和代码,Harness 通过项目指令、工具边界、测试、Lint、CI、Git、沙盒和错误恢复机制,把模型的开放生成能力限制在一个可验证、可恢复的工程闭环中。
从通用 Agent 的角度看,代码生成之所以重要,是因为它不仅能写程序,还能用于精确计算、业务规则校验、多媒体生成、系统适配、生成式 UI,以及创建和修复 Agent 自身。代码因此不是普通工具,而是一种可以继续创造工具、规则和系统的“元能力”。
本章最值得记住的是:计划先于行动,验证决定完成;错误处理要覆盖检测、恢复、接管和终止整个循环;能编码化的约束,不要只写在 Prompt 里。