一、Coding Agent
一句话理解:
Coding能力不仅用于开发软件,也让Agent能够计算、转换数据、生成产物,并在现有工具不足时临时构造解决方案。
1.1、核心能力
一个基础Coding Agent通常需要覆盖七类操作:
| 能力 | 主要用途 |
|---|---|
| 代码解释器 | 在隔离环境中执行Python等代码 |
| Shell | 运行测试、构建项目和调用系统工具 |
| 读取文件 | 获取代码、配置、文档和日志 |
| 写入文件 | 创建文件或整体替换内容 |
| 编辑文件 | 对已有文件进行局部、可审查的修改 |
| 文件名搜索 | 按路径或模式定位文件 |
| 内容搜索 | 查找符号、调用关系和文本模式 |
这七项描述的是能力,不一定对应七个独立工具。实际系统可以使用少量通用工具组合实现,例如用Shell完成搜索和读取,再用结构化补丁工具修改文件。
工具数量不是关键,关键是能否形成完整闭环:
定位 → 读取 → 修改 → 运行 → 验证
1.2、代码是一种元能力
代码生成的价值不只在于"写程序"。它还可以用于:
- 精确计算和求解数学问题
- 清洗、聚合和可视化数据
- 将业务规则转化为可执行逻辑
- 解析新的文件或数据格式
- 临时补充工具箱中缺失的能力
- 把一次性操作固化为可重复执行的流程
与自然语言相比,代码通常更精确、可测试、可复现。但它并不天然正确:生成后的程序仍需在受控环境中运行,并通过测试、静态检查或结果核验确认有效。
1.3、典型工作方式
以整理项目中的TODO为例:
- 搜索包含TODO的文件和代码行
- 读取必要上下文,理解每项TODO的含义
- 去重并按模块、类型或优先级整理
- 写入清单或通过API创建issue
- 检查结果是否完整,避免把示例、依赖目录或已失效注释纳入其中
简单任务可能只需搜索和写入工具;复杂任务则会进一步使用解释器完成统计,用Shell运行测试,或调用外部系统创建资源。
1.4、通用Agent中的Coding内核
对于深度调研、内容生成和数据分析等开放任务,需求和产物难以预先穷举。Coding能力可以把多种任务转化为统一的可执行流程:
- 调研结果通过程序清洗和组织
- 数据通过脚本分析并生成图表
- 文档通过结构化格式或生成库创建
- 成功的操作序列被固化为可复用程序
- GUI任务在存在稳定API或代码接口时转为更可靠的调用
代码或API通常比模拟鼠标键盘更高效、稳定且易于复现,但并非所有任务都适合代码化。依赖视觉判断、没有可用接口或需要人类价值判断的任务,仍可能需要多模态模型、Computer Use或人工参与。
1.5、文件系统的作用
文件系统可以成为Agent处理信息的共享工作区:
- 输入材料和项目代码由读取工具访问
- 脚本、图表和报告作为文件产出
- 中间结果可以跨步骤复用
- 日志、知识和Skill可以通过文件维护
- Git等版本控制机制可以记录差异并支持回滚
Markdown文件具有透明、可编辑和易于版本控制的优势,但它不自动解决检索规模、并发修改、权限隔离和隐私保护问题。随着数据量增长,通常需要结合索引、数据库或专门的记忆系统。
Agent能够写文件,也不意味着它可以不受约束地修改自身知识。新发现应先作为带来源、时间和置信度的记录保存;经过验证后,才适合升级为长期记忆、规则、Skill或程序。敏感标识、凭证和个人数据不应随意写入普通记忆文件。
1.6、适用边界
Coding是否构成架构核心,取决于任务空间:
| Agent类型 | Coding的定位 |
|---|---|
| 开放任务型Agent | 用于动态构造流程和产物,通常是核心能力 |
| 软件开发Agent | 直接操作代码库,是主要工作界面 |
| 数据分析与内容生成Agent | 连接数据、计算、可视化和文档生成 |
| 流程固定的垂直Agent | 作为辅助能力,核心仍是业务工具和规则 |
对于客服、审批等封闭场景,允许模型任意生成并执行代码可能增加不必要的风险。此时更适合优先使用受约束的领域工具,只在计算、校验或数据转换等明确环节开放Coding能力。
核心结论:
Coding为Agent提供了一套通用、精确且可复现的问题解决语言;它在开放任务中可以成为能力中枢,但必须与沙盒、权限控制、版本管理和自动验证共同使用。
1.7、Coding Agent的整体流程
一句话理解:
Coding Agent应根据任务复杂度,在"理解项目---澄清需求---设计方案---实现验证---审查交付"之间形成自适应闭环。
a. 理解项目
首次接触代码库时,Agent应先建立完成当前任务所需的项目认知,包括:
- 目录结构与主要模块
- 构建、测试和运行方式
- 与任务相关的实现、调用方和测试
- 当前分支及已有未提交修改
- 项目约定和禁止修改的区域
目标是获得足够可靠的局部理解,而不是每次都完整阅读和文档化整个仓库。简单修复可以快速定位后行动;跨模块或架构任务则需要更广泛的探索。
b. 项目文档与指令文件
项目中的知识通常分为两类:
| 类型 | 主要内容 |
|---|---|
| 面向人的文档 | 项目介绍、架构、使用方法和开发指南 |
| 面向Agent的指令 | 测试命令、代码规范、修改边界和工作约定 |
AGENTS.md、CLAUDE.md等文件可以承载项目级指令,但其文件名、加载范围和优先级取决于具体Agent运行时,不能假定所有系统都会自动识别。
指令文件适合保存稳定约定,但能够通过类型系统、Linter、权限或CI强制的规则,不应只写成自然语言建议。
文档缺失不意味着Agent应自动创建大量文档。是否补充文档应取决于当前任务范围以及用户授权;必要时可先形成临时的项目理解,只有架构、接口或使用方式发生变化时才同步更新相应文档。
可以用一个简单问题评估代码库的"Agent-ready"程度:
一个不了解历史背景的新成员,能否仅依靠仓库中的代码、文档和任务记录安全地开展工作?
c. 澄清需求
Agent应先判断需求是否具备三个条件:
- 目标是否明确
- 验收标准是否明确
- 修改边界是否明确
"优化性能"之类的要求无法直接指导实现,需要进一步明确优化指标、当前基线、允许的权衡和性能瓶颈。
如果缺失信息会显著改变方案,应向用户确认;如果可以通过只读调查获得,则应先检查代码、测试和运行数据,避免把可自行查明的问题重新抛给用户。
d. 制定方案
设计深度应与风险和影响范围匹配:
| 任务 | 方案形式 |
|---|---|
| 局部、低风险修改 | 简短说明修改位置和验证方法 |
| 跨模块修改 | 列出步骤、依赖关系和潜在影响 |
| 架构或接口变更 | 编写设计文档,对比方案与迁移风险 |
| 高风险、难以回退 | 用户审批后再实施 |
设计方案通常应回答:
- 修改哪些模块,为什么
- 采用什么方案,有哪些替代方案
- 是否引入新依赖或兼容性变化
- 如何测试、回退和判断完成
并非所有设计文档都必须等待批准。只有当方案涉及重要取舍、扩大范围、破坏兼容性或产生高风险副作用时,才需要把用户确认设为执行门槛。
e. 实现与验证
实现阶段应遵循现有代码风格和抽象,尽量控制修改范围。完成修改后进入验证循环:
实现 → 运行检查 → 分析失败 → 修正 → 再次验证
验证可包括:
- 针对性测试和必要的回归测试
- 类型检查、Linter和格式检查
- 构建或运行验证
- 边界条件和异常路径检查
- Git差异与无关修改检查
"测试通过"比"代码已生成"更接近完成标准,但仍不充分。测试可能覆盖不足,也可能存在修改前就已失败的用例。Agent应区分新增失败、既有失败和因环境限制无法运行的检查,并如实说明。
f. 审查与交付
测试完成后,还应检查:
- 修改是否真正满足用户目标
- 是否引入不必要的复杂度或依赖
- 是否存在安全、性能和兼容性风险
- 是否误改了无关文件
- 文档、配置和示例是否需要同步
自我审查、静态工具和独立审查Agent可以互相补充,但不能保证发现所有问题。高风险变更仍应保留人工代码审查。
交付时应说明修改内容、验证结果、未验证事项和剩余风险,使用户能够快速判断结果是否可以合并或部署。
g. 自适应流程
不同任务需要不同程度的探索和计划:
- 快速路径:定位、修改、针对性测试、交付
- 标准路径:探索、计划、实现、测试、审查
- 审慎路径:调研、设计评审、分阶段实施、全面验证、人工审批
Agent何时从探索转向实现,既受模型行为策略影响,也受Harness中的提示词、工具、预算、权限和验收机制影响。因此应评估"模型与Harness的组合",不能把工作方式完全归因于某一方。
核心结论:
好的Coding Agent既不会在不了解项目时盲目修改,也不会无限调研而迟迟不行动;它会根据任务风险选择合适的流程深度,并以真实验证结果而不是代码生成结束作为完成依据。
1.8、Harness工程在Coding Agent中的实践
一句话理解:
模型决定Agent能想到什么,Harness决定它能看到什么、可以做什么、如何判断对错,以及失败后怎样恢复。
a. Harness的组成
在Coding Agent中,Harness可以分为两个层面:
| 层面 | 组成 | 作用 |
|---|---|---|
| 能力层 | 上下文、工具 | 让Agent理解项目并实施修改 |
| 控制层 | 约束、验证、纠正 | 限定行动边界,判断结果并处理失败 |
这些能力可落实为具体工程组件:
- 项目上下文:代码、文档、任务说明和环境状态
- 操作工具:搜索、读取、编辑、命令执行和版本控制
- 验收基线:测试、构建、类型检查和代码审查标准
- 执行边界:工作区权限、模块边界、依赖规则和审批策略
- 反馈信号:结构化的测试、Linter和编译错误
- 恢复机制:补丁回退、Git分支、快照和隔离环境
b. 为什么Coding Agent适合Harness
可以从"目标是否明确"和"结果能否自动验证"两个维度判断任务是否适合Agent:
| 结果可自动验证 | 结果主要依赖人工判断 | |
|---|---|---|
| 目标明确 | 最适合自动化,例如修复有回归测试的Bug | 可以执行,但吞吐量受审查能力限制 |
| 目标模糊 | 容易高效跑偏,验证指标可能不代表真实目标 | 应先澄清需求,再决定是否实施 |
Harness的目标是把任务尽量转化为:
明确目标 + 可执行约束 + 自动验收
软件工程已有的测试、类型系统、静态分析、CI和版本控制,为Coding Agent提供了丰富的反馈与恢复基础。这是Coding Agent较为成熟的重要原因之一。
但代码并非天然完全可验证。用户体验、架构质量、安全性和长期可维护性仍可能需要人工判断;测试也只能验证其覆盖到的行为。
c. 核心设计原则
- 强制约束优先于文字提醒
能由程序检查的规则,应尽量落实为:
- 类型和Schema约束
- Linter与格式化规则
- 模块和依赖边界
- 文件系统与网络权限
- CI中的必需检查
自然语言文档仍然适合解释设计意图和例外情况,但不应承担关键安全边界。
- 验证尽量自动化
每次修改后自动运行最相关、成本最低的检查,再按风险逐步扩大验证范围:
语法与格式 → 类型检查 → 针对性测试 → 回归测试 → 人工审查
自动化验证减少人工负担,但不能为了提高通过率而删除测试、降低规则或改变验收标准。
- 反馈快速且结构化
错误信息应尽量包含:
- 失败阶段和命令
- 文件、行号和错误类型
- 预期行为与实际行为
- 是否可能由本次修改引入
- 可继续读取的完整日志位置
反馈越接近错误发生时刻,Agent定位和修正问题的成本越低。
- 回退必须可靠
Agent应在可恢复的环境中修改代码,并在行动前了解现有工作区状态。Git、补丁和快照可以恢复文件变更,但不是"完美回退":
- 未跟踪文件可能没有进入版本记录
- 数据库迁移和外部API调用可能产生持久副作用
- 推送、发布和通知等外部动作无法通过Git撤销
因此,代码回退与外部副作用恢复需要分别设计。
d. 同时约束结果与过程
验收机制回答"结果是否正确",执行边界回答"是否以允许的方式完成"。
例如,以下结果即使通过测试也不能接受:
- 删除功能代码以消除编译错误
- 重建数据库以掩盖迁移问题
- 修改或删除测试以让CI通过
- 覆盖未读取的用户修改
- 绕过权限或关闭安全检查
这类问题说明,只奖励最终结果可能诱发破坏性捷径。生产级Harness应同时检查关键动作,对删除、覆盖、迁移和发布等操作设置专门的权限、预览或审批机制。
训练阶段可以通过过程监督让模型学习正确的工程行为;运行阶段仍需保留Harness护栏。训练形成的偏好不能替代确定性的权限控制。
e. 故障边界控制
并行调用时,错误不应无条件扩散,也不能被静默忽略。传播范围应由依赖关系决定:
- 一个文件读取失败,不影响其他独立文件的读取
- 依赖该文件内容的分析或编辑应停止
- 父任务记录部分失败,并判断是换一种方式、继续降级执行还是终止
- 有副作用的操作默认不并行,除非明确证明彼此独立
因此,更准确的原则是:
故障只传播到依赖它的操作;独立操作继续执行,父任务负责整合局部结果和失败状态。
f. 数据驱动地改进Harness
Harness不应只靠提示词经验反复调试。更可靠的方法是分析真实执行轨迹:
- 收集失败、重试和人工接管记录
- 判断问题来自模型、上下文、工具还是验证机制
- 将重复出现的问题转化为规则、测试或工具改进
- 在固定任务集上比较修改前后的成功率、成本和副作用
- 持续监控是否引入新的过度拒绝或流程僵化问题
长任务还可以将"规划"和"执行"分离,通过持久化任务清单、代码变更和验证状态跨轮次接续。不过,角色拆分会增加交接成本,只应在任务持续时间和复杂度值得时采用。
核心结论:
Coding Agent的可靠性来自模型能力与工程基础设施的共同作用。优秀的Harness不仅帮助Agent完成代码修改,还通过明确目标、限制危险路径、自动验证结果和提供可靠回退,把偶然成功转化为可重复的工程过程。
1.9、故障与错误恢复
一句话理解:
生产级Agent不能把"重试"当作统一答案,而应先识别故障类型,再选择重试、纠正、降级、接管或终止。
a. 四层故障
| 层级 | 典型故障 | 主要处理方向 |
|---|---|---|
| API层 | 限流、过载、超时、断流、输出截断 | 重试、退避、接续或切换服务 |
| 工具层 | 工具不存在、参数非法、执行异常 | 修正调用或改变方案 |
| 上下文层 | 窗口溢出、压缩失败、轨迹不完整 | 压缩、裁剪或修复结构 |
| 控制流层 | 重复调用、无进展循环、恢复逻辑递归 | 熔断、终止或人工接管 |
故障发生的位置不等于故障原因。例如,API拒绝请求可能源于暂时过载,也可能源于请求格式错误;两者需要完全不同的恢复策略。
b. 先分类,再决定是否重试
发现错误后的第一个问题应是:
如果使用相同输入再次执行,结果是否可能不同?
根据答案可以分为三类:
| 类型 | 示例 | 处理方式 |
|---|---|---|
| 暂时性故障 | 限流、过载、网络抖动 | 有上限地重试 |
| 可纠正故障 | 参数不合法、输出格式错误 | 修改输入后重试 |
| 持久性故障 | 权限不足、工具不存在、策略禁止 | 改变方案或请求协助 |
对不可重试错误原样重放,只会增加成本并形成循环。
工具错误应作为结构化结果返回Agent,包括错误代码、失败字段、约束要求和可行的修正方向。程序只能自动修复确定且不改变语义的格式问题;涉及路径、权限、收件人或金额等关键参数时,不能自行猜测。
c. 检测无进展状态
除了单次错误,Harness还需要识别跨轮次的失败模式:
- 调用指纹:记录规范化后的工具名和参数,检测完全重复的调用
- 失败计数:为不同恢复路径分别维护次数
- 进展指标:检查错误类型、文件状态或任务状态是否发生变化
- 活性监控:长连接持续无数据时,由空闲看门狗终止
- 轨迹完整性:检查工具调用、结果和消息顺序是否配对
相同调用重复出现是重要信号,但不能单独证明死循环。只读查询可能需要轮询;相反,参数略有变化的调用也可能一直没有实质进展。因此应结合任务状态和有效产出判断。
轨迹缺少工具结果时,可以插入明确标记为"未知或丢失"的占位状态,以维持产品流程,但不能伪造执行结果。用于训练的数据应隔离或剔除这类修复记录,避免把合成状态当作真实反馈。
d. 分级恢复
恢复策略可按成本和影响逐级升级:
- 静默恢复
适用于短暂且无副作用的故障:
- 指数退避并加入随机抖动
- 尊重服务端给出的重试时间
- 设置最大次数和总时长
- 辅助性后台任务失败时直接降级,避免挤占主链路资源
- 修正与接续
重试无效时,应改变请求或执行路径:
- 修正工具参数或缩小操作范围
- 压缩、裁剪或重新组织上下文
- 从结构完整的断点继续生成
- 降级到能力或成本不同的模型
- 换用等价工具或备用服务
输出被截断时,正文可以尝试接续;未完成的结构化工具参数则必须重新解析和完整校验,不能直接把半截参数交给执行器。
- 查询实际状态
如果失败发生在有副作用的操作中,首先要回答:
操作究竟没有执行、执行了一部分,还是已经成功但响应丢失?
恢复前应查询外部状态,并尽量使用幂等键或服务端去重。仅凭"工具名+参数"指纹在Agent侧去重,不能提供跨进程、跨重启或并发情况下的可靠幂等保证。
- 向用户暴露
自动恢复失败后,应向用户说明:
- 当前失败发生在哪个阶段
- 已尝试过哪些恢复方式
- 是否可能已经产生副作用
- 已完成的部分是否可以保留
- 需要用户决定或补充什么
短暂的内部重试可以不打扰用户,但错误不应从日志、审计和监控中消失。长时间恢复时也应提供进度,避免系统看起来已经卡死。
e. 跨模型接管
主模型持续不可用时,可以由备用模型接续,但不同提供方的消息、工具调用和专有状态通常不能直接互换。
因此,系统应维护厂商中立的执行轨迹,保存:
- 用户目标和有效约束
- 已完成步骤与当前任务状态
- 标准化后的工具调用和真实结果
- 可移植的模型输出或官方摘要
- 副作用记录、幂等键和待处理事项
厂商专有的签名、标识符和内部状态应与可移植内容分离,在切换时重新渲染。不要把某家模型的专有推理字段直接塞入另一家的同名字段,也不应依赖保存隐藏推理过程才能恢复任务。
如果目标模型无法接受历史工具结构,可以把已经发生的事实转换为普通的执行摘要,但必须保留"真实执行结果"和"摘要描述"的区别。
f. 流式中断恢复
输出中断可能发生在不同位置:
| 中断位置 | 推荐策略 |
|---|---|
| 分析或普通文本中途 | 从检查点接续,或重新生成当前轮 |
| 最终回答中途 | 接续并检查重复、遗漏和拼接错误 |
| 工具参数中途 | 丢弃不完整调用,重新生成并校验 |
| 工具已经提前执行 | 查询状态并去重,禁止盲目重放 |
恢复方案应比较任务完成率、额外Token、恢复轮数、参数正确率和重复副作用,而不只比较接口是否返回成功。
g. 熔断与终止
每条恢复路径都必须有独立上限:
- 最大重试次数
- 最大连续失败数
- 最大空闲时间
- 最大迭代轮数
- Token、时间和费用预算
- 最大恢复递归深度
阈值应根据生产数据和任务风险调整,而不是机械地使用统一数字。支付、删除、发布等高风险操作的自动重试阈值应明显低于只读查询。
错误处理路径还应尽量避免再次调用模型或触发新的副作用。例如,主循环因上下文溢出而失败时,不应再启动依赖同一模型的自动提交、摘要或记忆提取流程。必要的清理逻辑应尽量使用确定性程序,并通过递归深度和全局熔断器阻止死亡螺旋。
核心流程可以概括为:
发现故障 → 分类与判断副作用 → 有限恢复 → 验证实际状态 → 成功、降级或终止
核心结论:
可靠的错误恢复不是让Agent永远重试,而是让每次失败都能被分类、限制和验证;系统必须知道什么时候继续、什么时候换路,以及什么时候停止自动化并把真实状态交还给用户。
1.10、Coding Agent的实现技巧
一句话理解:
高效的Coding Agent需要在并发执行、上下文控制、环境感知、状态管理和即时验证之间取得平衡。
a. 并行调用与流式执行
传统串行流程需要等待每个工具完成后才能启动下一个工具。对于互不依赖的操作,这会产生不必要的延迟。
适合并行的任务包括:
- 搜索多个相互独立的关键词
- 读取多个无依赖关系的文件
- 查询不同的只读数据源
- 运行互不影响的静态检查
工具调用只有在参数完整、Schema校验通过、权限检查完成且模型已正式提交调用后,才可以开始执行。不能把仍在生成的半截参数直接交给工具。
并行策略不应只依赖工具自身是否"支持并发",还应检查操作之间是否访问相同资源:
| 操作组合 | 建议 |
|---|---|
| 多个只读操作 | 通常可以并行 |
| 读取不同资源 | 可以并行 |
| 修改不同且独立的文件 | 谨慎并行 |
| 修改同一文件或共享状态 | 按顺序执行 |
| 构建、迁移、发布等有副作用操作 | 默认串行 |
b. 故障边界
并行调用失败时,传播范围应由依赖关系决定:
- 独立操作继续执行
- 依赖失败结果的操作停止
- 已产生副作用的操作不能假定已经撤销
- 父任务汇总局部结果,再决定重试、降级或终止
例如,同时读取三个文件时,一个文件不存在不应取消另外两个读取;但依赖缺失文件的编辑操作必须停止。
核心原则是:
只中止依赖失败结果的分支,不让局部错误无条件扩散为整个任务失败。
c. 精细化管理上下文
代码库通常远大于模型上下文。Agent应采用"先定位、再展开"的读取方式:
目录与搜索结果 → 相关符号 → 局部代码 → 必要的调用方和测试
文件读取工具应支持:
- 按行号或字节范围读取
- 返回真实文件路径和行号
- 明确标记截断范围
- 提供继续读取的方法
- 检测文件在读取后是否已经变化
行号便于引用和诊断,但编辑后会发生漂移。因此,修改操作更适合结合原文片段、内容哈希或结构化补丁定位,而不能长期依赖旧行号。
d. 处理长输出
测试、编译和日志输出可能远超上下文预算。工具应:
- 提取结构化错误、失败用例和摘要
- 保留必要的前后文
- 明确标记省略范围
- 将完整输出保存到可继续读取的位置
- 提供筛选、搜索或按范围读取能力
简单保留头尾并不总是足够,因为关键错误可能出现在中间。能够解析测试报告、编译诊断或日志级别时,应优先返回结构化结果,而不是机械截断文本。
e. 动态环境状态
Coding Agent的决策依赖当前执行环境。常见的动态状态包括:
| 状态 | 作用 |
|---|---|
| 当前工作目录 | 解释相对路径 |
| Git分支与提交 | 判断代码基线 |
| 工作区变更摘要 | 避免覆盖已有修改 |
| 运行环境 | 确认语言、依赖和工具版本 |
| 后台任务状态 | 判断服务是否仍在运行 |
这些信息应在需要时动态读取,而不是写死在系统提示词中。为了控制上下文和缓存成本,可以常驻简短摘要,只在状态变化或任务需要时展开详细信息。
环境状态只能提高可见性,不能代替权限控制。Agent知道自己位于主分支,并不意味着它有权直接提交或推送。
f. 终端状态管理
持久化终端能够保留:
- 当前目录
- 环境变量
- 虚拟环境
- REPL状态
- 后台进程
但共享会话也会引入隐藏状态、环境污染和并发冲突,降低命令的可复现性。因此,更稳妥的策略是:
- 普通命令默认显式指定工作目录和环境
- 交互式程序、后台服务和连续调试使用持久会话
- 并行任务使用相互隔离的终端
- 会话重启后能够从显式状态恢复
- 敏感环境变量不在无关任务间继承
持久会话是一种针对状态型任务的能力,不必作为所有命令的默认执行方式。
g. 即时反馈
文件修改后应尽快运行低成本、针对性的检查:
写入 → 语法与格式检查 → 类型检查 → 针对性测试 → 完整验证
即时反馈应包含文件、行号、错误类型和修正线索,并区分本次修改引入的问题与已有基线错误。
检查范围也需要分级:
- 保存后运行语法或格式检查
- 完成局部修改后运行相关测试
- 任务完成前运行必要的回归检查
- 高成本的完整测试根据影响范围和预算执行
自动检查不能未经控制地执行仓库中的任意脚本。对于来源不可信、可能访问网络或产生副作用的构建钩子,仍需沙盒、权限限制或用户确认。
h. 即时反馈
这些技巧需要协同工作:
#mermaid-svg-n1UNo4TgEYArwpJy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-n1UNo4TgEYArwpJy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-n1UNo4TgEYArwpJy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-n1UNo4TgEYArwpJy .error-icon{fill:#552222;}#mermaid-svg-n1UNo4TgEYArwpJy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-n1UNo4TgEYArwpJy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-n1UNo4TgEYArwpJy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-n1UNo4TgEYArwpJy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-n1UNo4TgEYArwpJy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-n1UNo4TgEYArwpJy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-n1UNo4TgEYArwpJy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-n1UNo4TgEYArwpJy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-n1UNo4TgEYArwpJy .marker.cross{stroke:#333333;}#mermaid-svg-n1UNo4TgEYArwpJy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-n1UNo4TgEYArwpJy p{margin:0;}#mermaid-svg-n1UNo4TgEYArwpJy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-n1UNo4TgEYArwpJy .cluster-label text{fill:#333;}#mermaid-svg-n1UNo4TgEYArwpJy .cluster-label span{color:#333;}#mermaid-svg-n1UNo4TgEYArwpJy .cluster-label span p{background-color:transparent;}#mermaid-svg-n1UNo4TgEYArwpJy .label text,#mermaid-svg-n1UNo4TgEYArwpJy span{fill:#333;color:#333;}#mermaid-svg-n1UNo4TgEYArwpJy .node rect,#mermaid-svg-n1UNo4TgEYArwpJy .node circle,#mermaid-svg-n1UNo4TgEYArwpJy .node ellipse,#mermaid-svg-n1UNo4TgEYArwpJy .node polygon,#mermaid-svg-n1UNo4TgEYArwpJy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-n1UNo4TgEYArwpJy .rough-node .label text,#mermaid-svg-n1UNo4TgEYArwpJy .node .label text,#mermaid-svg-n1UNo4TgEYArwpJy .image-shape .label,#mermaid-svg-n1UNo4TgEYArwpJy .icon-shape .label{text-anchor:middle;}#mermaid-svg-n1UNo4TgEYArwpJy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-n1UNo4TgEYArwpJy .rough-node .label,#mermaid-svg-n1UNo4TgEYArwpJy .node .label,#mermaid-svg-n1UNo4TgEYArwpJy .image-shape .label,#mermaid-svg-n1UNo4TgEYArwpJy .icon-shape .label{text-align:center;}#mermaid-svg-n1UNo4TgEYArwpJy .node.clickable{cursor:pointer;}#mermaid-svg-n1UNo4TgEYArwpJy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-n1UNo4TgEYArwpJy .arrowheadPath{fill:#333333;}#mermaid-svg-n1UNo4TgEYArwpJy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-n1UNo4TgEYArwpJy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-n1UNo4TgEYArwpJy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-n1UNo4TgEYArwpJy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-n1UNo4TgEYArwpJy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-n1UNo4TgEYArwpJy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-n1UNo4TgEYArwpJy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-n1UNo4TgEYArwpJy .cluster text{fill:#333;}#mermaid-svg-n1UNo4TgEYArwpJy .cluster span{color:#333;}#mermaid-svg-n1UNo4TgEYArwpJy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-n1UNo4TgEYArwpJy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-n1UNo4TgEYArwpJy rect.text{fill:none;stroke-width:0;}#mermaid-svg-n1UNo4TgEYArwpJy .icon-shape,#mermaid-svg-n1UNo4TgEYArwpJy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-n1UNo4TgEYArwpJy .icon-shape p,#mermaid-svg-n1UNo4TgEYArwpJy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-n1UNo4TgEYArwpJy .icon-shape .label rect,#mermaid-svg-n1UNo4TgEYArwpJy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-n1UNo4TgEYArwpJy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-n1UNo4TgEYArwpJy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-n1UNo4TgEYArwpJy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 失败
通过
🔍 按需获取上下文
⚡ 生成并校验调用
⚙️ 按依赖关系并发执行
🌐 注入最新环境状态
✏️ 执行局部修改
🧪 立即运行相关检查
✅ 扩大验证并交付
核心结论:
Coding Agent的效率并非来自最大化并行或保留更多状态,而是让独立工作并发、相关上下文按需加载、环境变化及时可见,并让每次修改尽快获得可信反馈。
1.11、Coding Agent中的搜索工具
一句话理解:
代码搜索的目标不是返回尽可能多的结果,而是以最低成本找到足以支持下一步判断的代码及其关系。
a. 四类搜索方式
| 方式 | 最适合的问题 | 优势 | 局限 |
|---|---|---|---|
| Glob | "哪些文件可能相关?" | 快速、低成本,适合探索目录 | 不读取文件内容 |
| Grep或ripgrep | "这个名称或文本出现在哪里?" | 精确、实时,不需要索引 | 难以发现不同命名的同义实现 |
| 语义搜索 | "哪段代码实现了这个概念?" | 可以跨越命名差异召回相关代码 | 需要索引、排序和新鲜度管理 |
| 符号搜索 | "这个符号在哪里定义和使用?" | 理解定义、引用和部分类型关系 | 依赖语言解析器或语言服务器 |
这些能力互相补充,不存在适合所有任务的单一搜索方式。
b. 文件名搜索
Glob根据路径模式定位文件,例如:
/*.test.ts
src/components//Button.tsx
它适合:
- 快速了解目录结构
- 定位配置、测试和特定类型文件
- 缩小后续内容搜索范围
- 排除依赖、构建产物和生成文件
Glob速度快,但目录位置只能提供弱语义。名称相关不代表内容一定相关,因此通常需要继续读取或搜索文件内容。
c. 文本与正则搜索
Grep或ripgrep适合搜索:
- 函数名、变量名和配置项
- 错误消息和日志文本
- API路径、环境变量和常量
- 明确的代码模式
搜索工具应支持大小写、文件类型、路径范围和排除规则,并返回文件路径、行号和少量上下文。
正则表达式可以描述文本结构,但并不真正理解代码语法。复杂模式容易出现误报,也难以可靠处理嵌套结构、别名和动态调用。需要结构理解时,应使用AST或符号级工具。
d. 语义代码搜索
语义搜索适合查询用词与代码命名不一致的场景,例如用"验证用户身份"寻找check_credentials。
一个可靠的语义搜索系统通常需要:
- 结构化分块:按函数、类和方法切分,并保留文件及所属符号信息
- 混合召回:结合向量相似度与关键词匹配
- 重排序:综合相关性、路径、调用关系和代码新鲜度
- 增量更新:代码变更后及时更新或失效旧索引
- 权限过滤:在检索前限制Agent能够看到的仓库和路径
固定字符分块可能切断函数或丢失上下文;只使用向量相似度则可能漏掉精确标识符,也可能返回概念相近但无法用于当前任务的代码。
e. 是否建立语义索引
语义索引并非所有代码库都需要:
| 情况 | 更适合的方案 |
|---|---|
| 小型仓库、命名规范 | Glob与Grep通常足够 |
| 代码变化频繁、不希望维护索引 | 现场检索更简单 |
| 大型仓库、跨模块关系复杂 | 混合搜索可能明显提升召回 |
| 多语言仓库、概念与命名差异大 | 语义搜索更有价值 |
| 对精确依赖关系要求高 | 优先符号和静态分析工具 |
选择依据应是任务成功率、搜索延迟、索引成本和结果新鲜度,而不是产品形态。具体工具是否采用嵌入索引也可能随版本和架构变化,不宜视为固定路线。
f. 符号与结构搜索
符号级工具可以区分:
- 定义与引用
- 同名但不同作用域的符号
- 接口与具体实现
- 导入、继承和部分调用关系
其底层可以来自语言服务器、编译器索引或AST解析器。结构化代码查询还可用于寻找特定语法模式,例如所有未处理异常的调用或使用某类API的函数。
符号搜索通常比文本搜索更精确,但面对反射、动态加载、宏和运行时生成代码时仍可能不完整。
g. 渐进式搜索策略
搜索顺序应根据已知信息选择,而不是固定从语义搜索开始:
#mermaid-svg-XHSLyLkSUAVj6j5F{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XHSLyLkSUAVj6j5F .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XHSLyLkSUAVj6j5F .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XHSLyLkSUAVj6j5F .error-icon{fill:#552222;}#mermaid-svg-XHSLyLkSUAVj6j5F .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XHSLyLkSUAVj6j5F .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XHSLyLkSUAVj6j5F .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XHSLyLkSUAVj6j5F .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XHSLyLkSUAVj6j5F .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XHSLyLkSUAVj6j5F .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XHSLyLkSUAVj6j5F .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XHSLyLkSUAVj6j5F .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XHSLyLkSUAVj6j5F .marker.cross{stroke:#333333;}#mermaid-svg-XHSLyLkSUAVj6j5F svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XHSLyLkSUAVj6j5F p{margin:0;}#mermaid-svg-XHSLyLkSUAVj6j5F .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XHSLyLkSUAVj6j5F .cluster-label text{fill:#333;}#mermaid-svg-XHSLyLkSUAVj6j5F .cluster-label span{color:#333;}#mermaid-svg-XHSLyLkSUAVj6j5F .cluster-label span p{background-color:transparent;}#mermaid-svg-XHSLyLkSUAVj6j5F .label text,#mermaid-svg-XHSLyLkSUAVj6j5F span{fill:#333;color:#333;}#mermaid-svg-XHSLyLkSUAVj6j5F .node rect,#mermaid-svg-XHSLyLkSUAVj6j5F .node circle,#mermaid-svg-XHSLyLkSUAVj6j5F .node ellipse,#mermaid-svg-XHSLyLkSUAVj6j5F .node polygon,#mermaid-svg-XHSLyLkSUAVj6j5F .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XHSLyLkSUAVj6j5F .rough-node .label text,#mermaid-svg-XHSLyLkSUAVj6j5F .node .label text,#mermaid-svg-XHSLyLkSUAVj6j5F .image-shape .label,#mermaid-svg-XHSLyLkSUAVj6j5F .icon-shape .label{text-anchor:middle;}#mermaid-svg-XHSLyLkSUAVj6j5F .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XHSLyLkSUAVj6j5F .rough-node .label,#mermaid-svg-XHSLyLkSUAVj6j5F .node .label,#mermaid-svg-XHSLyLkSUAVj6j5F .image-shape .label,#mermaid-svg-XHSLyLkSUAVj6j5F .icon-shape .label{text-align:center;}#mermaid-svg-XHSLyLkSUAVj6j5F .node.clickable{cursor:pointer;}#mermaid-svg-XHSLyLkSUAVj6j5F .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XHSLyLkSUAVj6j5F .arrowheadPath{fill:#333333;}#mermaid-svg-XHSLyLkSUAVj6j5F .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XHSLyLkSUAVj6j5F .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XHSLyLkSUAVj6j5F .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XHSLyLkSUAVj6j5F .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XHSLyLkSUAVj6j5F .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XHSLyLkSUAVj6j5F .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XHSLyLkSUAVj6j5F .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XHSLyLkSUAVj6j5F .cluster text{fill:#333;}#mermaid-svg-XHSLyLkSUAVj6j5F .cluster span{color:#333;}#mermaid-svg-XHSLyLkSUAVj6j5F div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XHSLyLkSUAVj6j5F .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XHSLyLkSUAVj6j5F rect.text{fill:none;stroke-width:0;}#mermaid-svg-XHSLyLkSUAVj6j5F .icon-shape,#mermaid-svg-XHSLyLkSUAVj6j5F .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XHSLyLkSUAVj6j5F .icon-shape p,#mermaid-svg-XHSLyLkSUAVj6j5F .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XHSLyLkSUAVj6j5F .icon-shape .label rect,#mermaid-svg-XHSLyLkSUAVj6j5F .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XHSLyLkSUAVj6j5F .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XHSLyLkSUAVj6j5F .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XHSLyLkSUAVj6j5F :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
明确搜索目标
知道名称或文本?
Glob或Grep精确定位
语义搜索寻找候选
符号搜索追踪关系
读取实现、调用方和测试
验证是否足以支持修改
常见路径包括:
- 已知错误文本:Grep → 读取上下文 → 符号追踪
- 已知文件类型:Glob → 内容搜索 → 读取文件
- 不熟悉代码库:目录扫描 → 语义或关键词搜索 → 精确定位
- 修改公共函数:定义查找 → 引用查找 → 测试与接口检查
h. 搜索结果设计
搜索工具的返回值应包含:
- 文件路径和真实行号
- 匹配片段及必要上下文
- 匹配方式和相关性信息
- 结果总量、截断状态和继续获取方法
- 当前文件版本或索引更新时间
结果过多时应先聚类、排序或分页,避免把整个仓库的匹配项一次性塞入上下文。
仓库内容还可能包含恶意注释或提示注入文本。搜索结果应被视为待分析的数据,不能因为它出现在代码或文档中就自动获得指令效力。
核心结论:
成熟的Coding Agent应从成本最低、确定性最高的搜索方式开始,在信息不足时逐步引入语义和符号能力,并最终通过读取实现、调用方和测试来验证检索结果。
1.12、Coding Agent中的文件编辑工具
一句话理解:
文件编辑工具的核心不是"能否修改文本",而是能否让模型精确描述修改位置,并在文件变化或定位失败时安全停止。
a. 五种编辑方式
| 方式 | 优势 | 主要风险 | 适用场景 |
|---|---|---|---|
| 变更描述+Apply Model | 表达灵活,主模型可专注代码逻辑 | 合并过程不确定,可能应用到错误位置 | 复杂但可人工审查的修改 |
| 旧字符串→新字符串 | 确定、透明,失败条件清晰 | 要求原文精确匹配,大段修改成本高 | 小范围局部编辑 |
| 行号范围→新文本 | 表达紧凑,适合删除连续区域 | 编辑后行号漂移,并发修改时不可靠 | 单次、基于最新文件的修改 |
| 类Vim命令 | 操作丰富,移动和重组效率高 | 强依赖中间状态,模型容易算错位置 | 交互式、小步编辑 |
| 首尾字符串→新文本 | 兼顾内容定位与表达效率 | 边界可能重复或错误跨越代码区域 | 替换较大的连续代码块 |
工具选择不应由某个产品当前采用什么方案决定,而应根据定位可靠性、模型能力和任务类型进行评估。
b. 变更描述与Apply Model
这种方案把任务拆成两步:
主模型描述修改意图 → 应用模型合并原文件
它减少了主模型精确操作文本的负担,但引入了第二次模型判断。如果原文件存在多个相似片段,应用模型可能把正确的变更意图落到错误位置。
因此,Apply Model的输出仍需:
- 生成可审查的差异
- 检查修改范围是否符合预期
- 在定位不确定时失败,而不是猜测
- 通过语法、类型和测试验证
该方案适合作为编辑辅助,但不应直接充当高风险修改的可信边界。
c. 精确字符串替换
模型提交:
old_text: 原始文本
new_text: 修改后的文本
框架只有在old_text存在且匹配条件满足时才执行替换。
它的主要优势是确定性:
- 未找到则失败
- 要求唯一匹配时,多处匹配则失败
- 成功后可以明确展示替换前后的差异
对于重复代码,应提供更长的上下文或增加文件、符号等限定条件。工具不应在精确匹配失败后自动进行宽松模糊替换,否则会把可检测的失败变成静默误改。
d. 行号定位
行号方案可以紧凑地表达大段替换:
replace lines 120--180 with ...
但行号只是某一文件版本下的临时坐标。以下情况都会让它失效:
- 前面的编辑改变了行数
- 用户或其他Agent同时修改文件
- 格式化工具重新排列代码
- 模型使用了旧的读取结果
如果采用行号定位,应同时携带文件版本、内容哈希或边界文本,并在写入前重新验证。批量编辑还必须明确所有行号是基于初始文件,还是按顺序基于每次修改后的文件。
e. 类Vim编辑命令
类Vim命令适合复制、移动和删除等操作,但它要求模型持续跟踪光标、缓冲区和变化后的行号。
这种状态型接口更适合:
- 小步、连续并且每步可观察的修改
- 需要频繁移动代码块的交互场景
- 对相应命令语法掌握稳定的模型
如果模型倾向于一次生成多个复杂编辑,结构化补丁通常比模拟人类编辑器操作更可靠。
f. 首尾内容定位
首尾匹配只提供目标区域的开头和结尾,中间内容可以省略:
start_anchor: 区域开头
end_anchor: 区域结尾
replacement: 新内容
它适合替换较大的连续区域,但框架必须验证:
- 首尾锚点分别存在且唯一
- 两者顺序正确
- 组合后只确定一个合法区域
- 替换范围未超过允许的文件或符号边界
如果锚点之间跨越了意外的函数、类或条件分支,工具应拒绝执行。
g. 按任务选择编辑方式
| 任务 | 推荐方式 |
|---|---|
| 创建新文件 | 直接写入完整内容 |
| 修改少量代码 | 精确字符串替换或结构化补丁 |
| 替换较大连续区域 | 首尾锚点或补丁块 |
| 重命名符号 | 语言服务器或AST重构工具 |
| 批量机械修改 | AST转换、格式化器或脚本 |
| 重写小型生成文件 | 完整写入后检查差异 |
| 多文件复杂变更 | 分阶段应用,每阶段验证 |
文本工具并非所有编辑任务的最佳选择。涉及符号重命名、导入整理和语法结构变换时,语言感知工具通常比字符串替换更可靠。
h. 可靠编辑协议
无论采用哪种定位方式,生产级编辑工具都应遵循:
- 先读取:确认目标文件及相关上下文
- 校验版本:防止覆盖读取后发生的外部修改
- 唯一定位:不存在歧义时才应用
- 原子写入:避免只写入一半导致文件损坏
- 展示差异:检查实际修改是否符合预期
- 自动验证:运行语法、类型或针对性测试
- 支持回退:保留原始内容或使用版本控制
对于多文件编辑,还应明确事务边界:如果第三个文件修改失败,前两个文件是保留、回滚,还是作为部分结果等待后续处理。
核心结论:
最可靠的编辑接口不是最灵活的接口,而是能够精确定位、显式失败、检查并发变化、展示真实差异,并在验证不通过时安全回退的接口。
1.13、Coding Agent的安全
一句话理解:
Coding Agent同时接触代码、私有数据和执行环境,安全设计的重点不是识别所有恶意输入,而是让被误导的Agent也无法越过权限边界。
a. 威胁模型
Coding Agent的高风险来自三种能力同时存在:
- 读取私有数据:源码、配置、密钥和用户文件
- 接触不可信内容:仓库、网页、Issue、依赖和工具输出
- 产生外部影响:执行命令、访问网络、推送代码或调用API
三者组合后,恶意内容可能诱导Agent读取敏感信息并通过外部通道泄露。
持久记忆会进一步放大风险:恶意指令一旦进入记忆、项目指令文件或生成的Skill,可能跨会话继续生效。
因此需要保护四类边界:
| 边界 | 核心问题 |
|---|---|
| 数据边界 | Agent可以读取哪些数据 |
| 输入信任边界 | 哪些内容具有指令效力 |
| 输出影响边界 | Agent可以修改或发送什么 |
| 跨会话边界 | 哪些信息可以长期保留 |
b. 信任层级
仓库文件、代码注释、网页、终端输出和第三方工具返回值都应被视为数据,而不是自动生效的指令。
合理的优先级通常是:
系统与安全策略 >用户明确授权 >项目可信规则 >外部内容
"忠于用户"不能理解为绝对服从。Agent还必须遵守权限范围、安全策略和第三方合法权益。更准确的原则是:
Agent只代表已验证的授权主体,在明确授权范围内行动;任何外部内容都不能自行扩大这一权限。
项目指令文件本身也可能来自不可信仓库,因此需要结合仓库来源、文件位置、权限范围和上级规则判断,而不能仅凭文件名获得信任。
c. 纵深防御
安全不能依赖单一模型判断,应由多层机制共同控制:
- 最小权限:只开放当前任务需要的文件、网络和工具
- 输入隔离:明确区分指令与不可信数据
- 确定性校验:验证路径、参数、目标和权限
- 风险审批:删除、发布、推送和外部发送前单独确认
- 沙盒隔离:限制代码对宿主机和外部系统的访问
- 结果验证:检查实际修改和副作用
- 审计追踪:记录授权、调用、结果和安全决策
模型或Sidecar可以辅助风险分类,但不能替代操作系统权限、服务端授权和沙盒隔离。
d. 沙盒与资源隔离
文件系统
推荐采用:
- 工作区外默认不可写
- 敏感目录和凭证不挂载
- 必要源码只读挂载,修改在副本或受控工作区完成
- 写入前检查符号链接、路径穿越和挂载边界
- 高风险修改以补丁形式审查后落盘
只限制路径字符串并不足够,因为符号链接、绑定挂载和竞态条件都可能绕过表面检查。
网络出口
代码执行环境应默认限制网络,按任务需要放行特定目的地。仅允许某个域名仍不等于安全,因为合法站点、Webhook或上传接口也可能被用于传出数据。
网络代理可以进一步限制:
- 域名、端口和协议
- HTTP方法与路径
- 请求体大小和内容类型
- DNS及其他隐蔽通道
- 每次任务的流量与速率
下载依赖时还要考虑安装脚本和供应链风险。访问可信包源,不代表下载的所有包都可以不受限制地执行。
资源限制
沙盒应限制:
- CPU、内存和进程数量
- 磁盘空间与文件数量
- 执行时间和空闲时间
- 网络流量和并发连接
超限后应返回结构化错误及必要日志,便于Agent调整方案,但不能因此自动提升资源或权限。
e. Shell命令安全
关键词黑名单只能作为辅助规则。Shell支持管道、重定向、变量展开、子Shell和动态执行,相同效果可以通过大量不同语法实现。
更可靠的防御顺序是:
- 优先使用参数结构化的专用工具
- 限制可调用的程序和能力
- 解析命令结构及重定向、管道和子命令
- 在沙盒中执行并限制文件与网络权限
- 对高风险效果进行审批和事后验证
语义解析可以识别部分嵌套操作,但也不是完整安全边界。Shell的真实效果还取决于环境变量、文件内容、别名和被调用程序,因此必须与运行时隔离共同使用。
f. 防止破坏性捷径
安全检查不能只看最终结果。以下行为即使让测试通过,也不能接受:
- 删除代码或测试来消除错误
- 清空数据库后重新创建
- 覆盖尚未读取的用户修改
- 关闭权限检查或安全规则
- 将敏感数据写入日志、提交或错误消息
- 未经授权推送、部署或发布
Harness应同时验证"做成了什么"和"通过什么路径做成"。
g. 持久记忆安全
写入长期记忆的信息应携带:
- 来源和写入时间
- 可信级别与适用范围
- 是否包含敏感信息
- 失效条件或有效期
- 用户查看、修改和删除方式
外部内容不应直接变成长期规则。记忆中的指令性内容需要更严格的审查,凭证、密钥和未经验证的判断原则上不应进入普通记忆文件。
h. 委托方与数据层边界
多方协作时,应明确:
- 谁是授权主体
- 哪些信息可以对外披露
- 哪些操作需要再次确认
- 第三方输入只具有什么数据权限
- 授权何时过期或被撤销
对于Agent动态生成的软件,不能只依赖生成出的界面或客户端逻辑保护数据。真正的授权必须在服务端、数据库和API层重新检查。即使生成代码被篡改,底层系统也应拒绝越权访问。
i. 安全检查与用户体验
安全检查可以与进度展示、只读分析等工作并行,但受门控的危险动作必须等待检查和授权完成后才能执行。
可以提前进行:
- 展示计划
- 读取允许范围内的元数据
- 生成差异预览
- 计算风险等级
不能提前进行:
- 删除或覆盖数据
- 发送外部消息
- 推送、部署或发布
- 访问未授权的敏感资源
安全机制可以在体验上保持流畅,但不能通过"推测性执行"提前产生真实副作用。
核心结论:
Coding Agent的安全不能寄托于模型始终识别恶意内容,而应依靠最小权限、信任分层、沙盒隔离、网络控制、执行审批和数据层授权,让错误判断被限制在可观察、可恢复的范围内。