6615 字
33 分钟
为什么 Coding Agent 能够长时间围绕目标稳定工作

最近一年,Coding Agent 几乎以一种令人应接不暇的速度出现:Codex、Claude Code、Qoder、Trae、CodeBuddy、OpenCode、ZCode等等,这些Coding Agent 能够读取仓库、修改代码、联网搜索、运行命令和自主排查问题。在使用的过程中,我明显感觉到Agent的能力越来越强,从去年执行任务多次失败需要人工不断介入到现在给一个总体的目标就能全自主运行完成任务,Agent的运行越来越自主,围绕目标实现效果也越来越好,也能更长时间的执行还不跑偏方向。一度让我感觉到:现在的我,已经无法发挥出这些Coding Agent的正常的能力了,瓶颈在我。

真正让我好奇的,其实是为什么这些 Agent 能够面对一个复杂而模糊的目标,连续工作很长时间,途中经历失败、改动和上下文增长,最后仍然有机会把事情做准、做好,并且尽量不破坏原有项目?我也简单的写过Agent的Demo,虽然能读写本地文件,但是一运行就发现乱调用工具,一个工具调几回,聊两轮就能忘了之前说过的话,Agent还过度探索,用着很垃圾。但是用现在的Codex + GPT5.6,Claude Code + GLM5.2,还有Qoder + Qwen3.7max,我真的感觉很惊艳。我无比好奇凭什么本事能让现在的Agent如此牛逼。现在的模型越来越强,作为人类的我,说实话我感觉现在的模型都挺好的,虽然不同模型确实用着有区别,但是我实际上许多时候是无法识别出模型的差距。我个人觉得现在的模型水平已经不相上下了,以后肯定趋于越来越相近。

如果只用“模型变强了”来解释,就会漏掉最关键的一层。模型当然决定了理解和推理能力的上限,但一个可靠的 Coding Agent,远不只是“LLM 加一个 Shell”。

它更像是把一个具有概率性、偶尔会出错的决策器,安装进一套确定性、可观测、可恢复、受约束的软件工程系统中:

Coding Agent
≈ LLM 决策器
+ 目标管理
+ 上下文管理
+ 工具系统
+ 执行循环
+ 验证系统
+ 状态持久化
+ 安全边界
+ 失败恢复
+ 人类监督

模型是发动机,Harness 则是方向盘、刹车、仪表盘、变速箱和道路规则。

这篇文章试着抛开具体产品和代码实现,从逻辑上拆解这些设计如何共同发挥作用。

一、长时间运行,不是一次思考很久#

我最初容易产生一种错觉:Agent 接到任务后,会像人一样维持一段连续而稳定的思考,从头推演到尾,然后把答案一次性做出来。

真实过程更接近很多个短闭环:

观察当前状态
↓
决定一个小动作
↓
执行动作
↓
读取真实结果
↓
验证是否取得进展
↓
更新状态并重新决策

也就是:

Observe → Think → Act → Verify → Repeat

比如 Agent 要修复一个登录超时问题,它不会只在脑中推演完整答案。它可能先搜索报错文本,找到调用链,读取相关配置,运行一个目标测试,观察失败,再修改一个函数,然后重新运行测试。每一步都不大,但每一步都会从环境中获得新的事实。

因此,所谓“自主运行两个小时”,并不是一次 LLM 调用持续两个小时,而可能是数百次这样的循环。只要系统能够保证:

  1. 每一轮仍然知道最终目标;
  2. 每一轮知道当前真实状态;
  3. 每一轮都能从上一步结果继续;
  4. 失败后能够恢复而不是失忆;

短循环就可以连接成长任务。

Agent 的长期能力首先是一种系统能力,而不是“思考时间”更长。

二、第一件重要的事:把目标变成完成条件#

用户给出的需求往往是模糊的:

帮我给登录接口增加限流。

这句话没有说明哪些行为算完成、能否改变现有接口、要不要增加基础设施、如何处理并发,以及应该用什么证据证明实现有效。

如果 Agent 只记住一句自然语言目标,很容易在长时间执行中发生漂移:修着修着开始重构认证模块,顺便升级依赖,最后代码改了很多,却没有真正完成用户要的事情。

成熟的系统需要把目标展开成一份任务契约:

目标:
为现有登录接口增加失败限流。
约束:
- 不改变外部 API;
- 不引入新的基础设施;
- 保持旧配置兼容;
- 不修改无关模块。
验收条件:
- 同一用户连续失败 5 次后受到限制;
- 限制持续 10 分钟;
- 正确密码不能绕过限制;
- 对应单元测试通过;
- 全量测试没有新增失败;
- lint 和类型检查通过。

从系统角度看,这是在定义一个谓词:

Done(current_state) = true / false

只要 Done 仍然为 false,任务就不应该因为“已经写了不少代码”而结束。

这也是目标管理和普通待办列表的差别。待办列表只记录做过什么,目标管理还要不断追问:

  • 这些动作是否真的缩小了目标差距?
  • 原来的完成条件是否仍然成立?
  • 新发现是否要求调整计划?
  • 当前结果是“看起来完成”,还是已经有证据证明完成?

长任务能够不偏航,靠的不是模型永远记性很好,而是总目标和完成条件会被反复带回决策过程。

三、真正的记忆,是外部化的任务状态#

上下文窗口再大,也不适合承担全部长期记忆。

聊天历史混合了用户要求、模型推理、命令输出、失败尝试和大量已经过时的信息。运行时间越长,真正重要的目标越容易被淹没;一旦上下文压缩、进程中断或模型切换,纯粹依赖对话历史的任务也很难准确恢复。

因此,Agent 会把关键状态外部化。逻辑上可以理解为:

Task State
├── 原始目标
├── 当前验收条件
├── 不可违反的约束
├── 当前计划
├── 已完成子任务
├── 正在处理的子任务
├── 未解决问题
├── 已修改文件
├── 最近验证结果
├── 已知风险
└── 下一步候选动作

例如:

[完成] 复现登录超时
[完成] 确认问题来自连接池耗尽
[完成] 添加回归测试
[进行] 调整连接释放逻辑
[待办] 运行认证模块测试
[待办] 执行全量回归
[风险] Windows 下有一个既存测试不稳定

这里保存的不是一篇冗长总结,而是继续工作所需的最小世界状态。

这带来几个直接好处:

  • 上下文被压缩后仍能恢复目标;
  • 中途失败后不需要从头调查;
  • 用户插入新要求时可以判断它改变了什么;
  • 每一步的进展、证据和风险都有明确位置;
  • 系统能够发现重复劳动和无效循环。

所以,Agent 的 Memory 不应该只理解为“记住用户以前说过什么”。对于长任务,更重要的是保存此刻世界是什么状态,以及距离目标还差什么。

四、上下文工程:不是给得越多越好#

一个真实仓库可能有几十万行代码。把全部文件一次性塞进上下文,不仅昂贵,还会产生严重的注意力污染。

好的 Coding Agent 更像一名刚接手项目的工程师,采用渐进式调查:

读取项目结构与规则
↓
定位相关模块
↓
搜索符号、调用者和测试
↓
阅读少量关键文件
↓
根据新发现继续扩展范围

模型每轮真正需要看到的,通常只是一个工作集:

当前目标
+ 当前子任务
+ 项目规则
+ 相关代码
+ 最近修改
+ 测试或命令反馈
+ 必要的任务状态

这里的关键不是“上下文越长,Agent 越强”,而是:

在正确的时刻,把完成下一步所需的最小、最新、最可信的信息交给模型。

文件搜索、语言服务器、Git 历史、测试结果、项目文档乃至编译器报错,都是外部记忆的一部分。Agent 不必始终记住整个仓库,只需要知道如何重新找到事实。

上下文工程还需要区分信息的寿命:

  • 项目规范、用户约束属于长期稳定信息;
  • 当前计划和修改范围属于任务级信息;
  • 一次命令输出和一个局部报错属于短期信息;
  • 已经被证明错误的假设应该退出活跃上下文,但保留在失败记录中。

如果不做这层治理,长上下文只会变成一间堆满旧草稿的办公室。

五、任务分解:目标稳定,计划可变#

复杂任务需要计划,但计划不能被当成不可修改的剧本。

在尚未阅读代码之前,Agent 不可能准确知道所有实现细节。如果一开始生成二十个步骤,然后机械执行到底,后续发现的真实情况就无法影响路线。

更合理的是分层规划。

战略层保持稳定:

1. 理解现状
2. 复现问题
3. 确定修改边界
4. 实现最小修复
5. 验证局部行为
6. 完成全局回归
7. 审查风险

阶段层围绕当前任务展开:

当前阶段:复现问题
1. 找到相关测试
2. 确认实际调用链
3. 构造最小复现
4. 记录修改前行为

动作层则尽量小:

运行 tests/auth/test_timeout.py

执行后,Agent 根据真实结果重新判断。测试路径可能不存在,问题可能已经修复,根因也可能与最初假设完全不同。这些都应该触发重新规划。

所以,一个可靠 Agent 的行为模式是:

目标稳定
计划动态
动作短小
证据驱动

计划的作用不是预测未来的每个细节,而是维持方向、暴露依赖、控制工作范围,并为下一步决策提供结构。

六、工具系统:模型只有提议权,系统掌握执行权#

没有工具,模型只能生成文本。有了文件读取、代码搜索、编辑、终端、浏览器、Git 和外部服务,Agent 才能感知并改变环境。

但 Tool 并不只是给 LLM 暴露几个函数。

一套真正的工具系统至少要回答:

  • 模型此刻可以发现哪些能力?
  • 每个工具在什么情况下使用?
  • 输入参数如何校验?
  • 调用会读取或改变哪些资源?
  • 是否允许执行,是否需要用户批准?
  • 超时、部分成功和结果未知如何表达?
  • 输出怎样保持紧凑、稳定、便于模型继续判断?
  • 调用如何记录和审计?

模型生成的 Tool Call 本质上只是一个行动提议:

LLM 提议动作
↓
系统校验参数
↓
策略层检查权限与风险
↓
在受限环境中执行
↓
返回结构化结果
↓
LLM 根据结果继续决策

工具返回“命令执行成功”也不等于用户任务成功。例如文件写入成功,只能证明写文件这个动作完成了,不能证明写入内容正确;部署接口返回 200,也不能证明线上服务健康。

Tool 是 Agent 的手和眼睛,但目标判断仍然属于更上层的执行循环。

七、高质量来自验证闭环,而不只来自生成质量#

普通代码生成常常停在:

我认为这段代码应该可以工作。

Coding Agent 需要走到:

我用与任务匹配的证据验证了它确实工作。

典型验证链条包括:

语法检查
→ 格式化
→ 静态分析
→ 类型检查
→ 单元测试
→ 集成测试
→ 构建
→ 运行时验证
→ Diff 审查
→ 验收条件复核

不同任务需要不同证据:

任务类型更可靠的证据
Bug 修复修改前能够复现,修改后回归测试通过
新增 API请求响应、状态码、权限与异常用例
前端页面构建成功、真实浏览器交互和视觉检查
重构外部接口不变、行为测试不变、全量回归通过
性能优化同一环境下的前后基准数据
数据库迁移dry-run、迁移测试、兼容性和回滚验证
安全修复攻击用例被阻止,合法路径仍然正常

真正可靠的路径不是:

生成 → 完成

而是:

生成
→ 验证
→ 发现差距
→ 定位原因
→ 修改
→ 再验证

这其实是一个 evaluator–optimizer 循环:一个过程产生候选结果,另一个过程按照明确标准寻找差距,然后继续优化。

验证还必须尽量独立于“我刚刚写了什么”。如果 Agent 只运行自己新增的测试,或者通过降低断言强度让测试变绿,就会出现自我证明。已有测试、编译器、运行时行为、外部接口和最终 Diff,构成了相互补充的证据。

八、为什么不容易把整个项目改崩#

任何 Agent 都无法保证绝对不破坏项目。可靠性来自多层防线,而不是一次完美判断。

1. 修改前建立基线#

先记录当前测试、构建和工作区状态:

修改前:1268 passed,3 failed
修改后:1269 passed,3 failed

这样才能区分既存问题与新增问题。否则 Agent 既可能把旧失败误认为自己造成的,也可能忽略刚刚引入的回归。

2. 遵循最小修改原则#

优先修改一个函数,而不是重写整个文件;优先复用现有抽象,而不是顺手改造架构;优先围绕问题补一条回归测试,而不是大规模整理测试体系。

动作越小,影响范围越容易判断,失败后也越容易回退。

3. 隔离工作空间#

分支、Git worktree、容器、云端 Sandbox 和临时目录,能够把 Agent 的试验与用户正在使用的环境隔离开。

隔离不是为了让 Agent 可以随意操作,而是把一次错误的最大影响限制在可恢复范围内。

4. 审查最终 Diff#

完成前还要从用户意图重新检查变更:

  • 是否修改了无关文件?
  • 是否删除了既有逻辑?
  • 是否改变公共接口?
  • 是否意外引入新依赖?
  • 是否出现密钥或敏感信息?
  • 是否通过删除测试让结果变绿?
  • 是否残留调试代码和临时文件?

修改代码不是完成任务。修改代码,并证明它实现了目标且没有造成不可接受的副作用,才算完成。

九、检查点、失败分类与恢复#

长任务一定会失败。命令会超时,依赖会下载失败,测试会卡住,最初方案会被证明错误,模型调用也可能中断。

如果系统把所有失败都当成同一种失败,只会出现两个极端:遇到错误立刻放弃,或者无休止重试。

更成熟的方式是分类处理:

临时错误:
网络抖动、服务暂时不可用
→ 有限次数重试
可修复错误:
编译失败、类型错误、测试失败
→ 分析反馈并修改
路径错误:
设计假设不成立、方案造成连锁回归
→ 回到检查点并更换方案
权限错误:
需要访问额外目录、联网或执行高风险动作
→ 暂停并请求批准
目标错误:
需求冲突、关键业务选择无法推断
→ 保留现场并请求用户决策

检查点则为恢复提供锚点:

Checkpoint 0:原始状态与基线
Checkpoint 1:问题已复现
Checkpoint 2:回归测试已建立
Checkpoint 3:核心修复完成
Checkpoint 4:全量验证通过

某条路径进入死路时,系统可以保留调查成果,但丢弃错误实现,从最近的正确状态继续,而不是在错误基础上不断打补丁。

还需要防止无效循环:

同一个错误连续出现多次
→ 停止重复原动作
→ 总结已尝试方案
→ 检查假设
→ 换路线或升级给人

长任务能力的本质不是永远不犯错,而是犯错之后能够识别、止损、保留有效进展并继续推进。

十、安全靠能力边界,而不是一句 Prompt#

在系统提示中写“不要执行危险命令”是有帮助的,但不构成真正的安全边界。

LLM 是概率性决策器,它可能误解命令,也可能低估动作的影响。安全必须在模型之外被强制执行:

模型提出动作
↓
策略引擎判断风险
↓
允许 / 拒绝 / 请求批准
↓
受限环境执行

动作可以按风险分层:

低风险:
- 读取项目文件
- 搜索代码
- 在工作区内做局部修改
- 运行本地测试
中风险:
- 联网下载依赖
- 修改大量文件
- 改变项目配置
- 访问工作区之外的目录
高风险:
- 删除重要目录
- 使用生产凭证
- 修改线上数据
- 部署生产环境
- 推送受保护分支

Sandbox 决定 Agent 能触达哪些资源;权限策略决定哪些动作可自动执行;审批把关键决定交还给用户;审计日志则回答“谁在何时,以什么理由,执行了什么”。

真正的安全目标不是要求 Agent 永远做出正确判断,而是:

即使某一次判断错了,它也没有足够的能力造成不可控损失。

这是一种 capability security:控制能力,而不是依赖模型“自觉”。

十一、Hook:把必须发生的事交给确定性程序#

有些工作需要理解和权衡,例如判断根因、选择修改方案、决定下一步调查什么。这些适合交给 LLM。

另一些规则不需要推理,只需要稳定执行:

  • 修改 Python 文件后必须格式化;
  • 提交前禁止包含 .env;
  • 修改 Schema 后必须生成迁移;
  • 结束前必须执行目标测试;
  • 高风险命令执行前必须检查策略。

如果只是把这些规则写进 Prompt,模型仍然可能因为上下文过长或注意力变化而忘记。

Hook 会在确定的生命周期节点自动触发:

文件修改后 → formatter
命令执行前 → 风险检查
准备提交前 → secret scan
任务结束前 → 测试与验收检查

这背后是一条很重要的工程原则:

需要语义理解、探索和权衡的事 → 交给 LLM
必须稳定发生、规则明确的事 → 交给程序

一个好的 Agent 系统不会把所有责任都压给模型。恰恰相反,它会尽量把确定性部分从模型手里拿出来。

十二、多 Agent 的真正价值#

Planner、Coder、Tester、Reviewer、Security Agent 看起来很强大,但“Agent 数量更多”并不自动带来更好的结果。

多 Agent 主要有三个实际价值。

1. 上下文隔离#

一个研究 Agent 可以阅读大量文档和代码,只把结构化结论交给主 Agent,避免把所有细节塞进主上下文。

2. 角色与权限隔离#

执行者可以修改代码,Reviewer 只读审查;研究者可以访问文档,却不能部署;测试者专注于寻找反例,而不是为自己的实现辩护。

这里的专业化不仅来自不同 Prompt,也来自不同上下文、工具和权限。

3. 并行处理独立工作#

如果一百个模块遵循同一迁移规则,可以先由主 Agent 确定规范,再让多个 Agent 处理相互独立的模块,最后统一解决冲突和执行全量验证。

但多 Agent 也会引入通信成本、状态不一致、重复劳动、文件冲突和错误扩散。对于强依赖任务,过早并行可能比单 Agent 更慢。

因此合理顺序是:

先建立可靠的单 Agent 闭环
→ 再识别可以隔离或并行的子问题
→ 明确输入、输出、权限和验收标准
→ 最后才增加 Agent

多 Agent 的价值不是模拟一间热闹的公司,而是有目的地隔离上下文、权限和工作负载。

十三、四个嵌套循环如何组成完整 Agent#

把前面的设计放在一起,可以把成熟 Coding Agent 抽象为四层循环。

1. 目标循环#

它持续回答:

最终要达到什么状态?
当前距离目标还有什么差距?
是否已经满足全部验收条件?

2. 任务循环#

它负责:

下一项子任务是什么?
依赖是否满足?
计划是否需要调整?

3. 执行循环#

它每次只推进一小步:

读取什么?
调用什么工具?
修改什么?
执行结果是什么?

4. 安全与恢复循环#

它约束每个动作:

这个动作是否允许?
是否需要审批?
执行前是否需要检查点?
失败后应该重试、回退还是暂停?

组合起来,大致是下面的逻辑:

while not goal_satisfied:
refresh_world_state()
task = choose_next_task()
while not task_satisfied:
context = gather_relevant_context()
action = decide_small_reversible_action(context)
permission = evaluate_risk(action)
if permission_requires_human:
pause_for_approval()
checkpoint_if_needed()
result = execute(action)
evidence = verify(result)
if evidence_confirms_progress:
update_task_state()
elif error_is_recoverable:
retry_or_replan()
else:
rollback_or_escalate()
evaluate_global_goal()

这里最重要的并不是伪代码本身,而是控制权的分配:

  • LLM 负责理解、判断和提出动作;
  • 工具负责读取事实与改变环境;
  • 验证器负责产生可信反馈;
  • 状态系统负责记住进展;
  • 权限系统负责限制能力;
  • 恢复系统负责处理失败;
  • 目标循环负责决定何时真正结束。

十四、从控制论看 Coding Agent#

到这里,我觉得理解 Coding Agent 最贴切的方式,不是“一个会自主写代码的聊天机器人”,而是一个反馈控制系统:

控制系统概念Coding Agent 中的对应物
目标值用户目标与验收条件
被控对象当前代码库与运行环境
控制器LLM
控制动作工具调用与代码修改
传感器编译器、测试、运行结果、Diff
状态估计计划、任务状态与上下文
安全约束Sandbox、权限与审批
恢复机制Git、检查点、重试与回滚

一个差的 Agent 接近开环控制:

收到目标 → 生成大量代码 → 希望它正确

一个好的 Agent 则是闭环控制:

收到目标
→ 执行一点
→ 测量结果
→ 比较目标差距
→ 修正方向
→ 再执行一点
→ 直到误差足够小

这也解释了为什么 Coding Agent 看起来能够处理远超单次生成能力的复杂任务。它不是一开始就知道完整答案,而是能够借助真实环境,一边行动,一边获得证据,一边纠偏。

十五、真正精妙的地方:让“不可靠”变得可用#

抛开模型能力后,Agent 设计最精妙的地方,并不是隐藏了某个神奇 Prompt,而是做了大量看似朴素的工程工作:

  • 用明确验收条件约束目标;
  • 用外部状态抵抗遗忘与中断;
  • 用按需检索保持上下文准确;
  • 用动态计划适应未知环境;
  • 用小步行动降低错误影响;
  • 用真实验证替代模型自信;
  • 用检查点和分类恢复处理失败;
  • 用 Sandbox、权限和审批限制破坏;
  • 用 Hook 保证确定性规则必然发生;
  • 用多 Agent 隔离上下文、权限和独立工作。

这些机制叠加在一起,才让一个概率性模型能够稳定工作很久。

可以把整体可靠性粗略理解为:

Agent 可靠性
≈ 模型能力
× 目标清晰度
× 上下文质量
× 工具质量
× 验证强度
× 状态恢复能力
× 安全边界

它更像乘法,而不是加法。任何一项接近零,整体体验都会明显崩塌。

所以评价一个 Coding Agent,不应该只看它一次能生成多少代码,也不应该只看它接入了多少工具、拥有多少个子 Agent。更值得观察的是:

  • 它如何定义任务完成;
  • 如何在长时间运行中保持目标;
  • 如何获取此刻真正需要的上下文;
  • 如何证明每一步产生了有效进展;
  • 如何区分失败并恢复;
  • 如何限制高风险行为;
  • 如何证明最终结果可信。

我现在更愿意这样理解 Coding Agent 的“自主”:

高级感不来自它可以不受控制地运行很久,而来自它自主运行很久以后,结果仍然围绕目标、可以验证、能够解释,并且在出错时可控、可停、可恢复。

这不是一次性推理的胜利,而是反馈控制、软件工程与模型能力共同作用的结果。


本文部分内容与整理由 Codex 协助完成,并结合作者自己的理解整理。

为什么 Coding Agent 能够长时间围绕目标稳定工作
https://jiqingjiang.github.io/posts/tech/agent/为什么coding-agent能够长时间围绕目标稳定工作/
作者
erode
发布于
2026-07-23
许可协议
CC BY-NC-SA 4.0