摘要:Copilot 类编码智能体在处理“简单问题”时出现无限循环、回答完毕后触发上下文压缩(compact)又反复重来的现象,常被归咎于模型能力不足。本文认为,这类故障的主因并非模型推理退化,而是 Agent loop 的终止条件设计缺失与状态管理失配:循环缺少可判定的收敛信号,压缩过程又破坏了终止判定所依赖的上下文证据,二者叠加形成“回答—压缩—遗忘—重答”的自激振荡。文章沿“现象—机理—判据—工程实现—边界与展望”的主线展开,提出以状态收敛、预算约束、语义判定构成的三层终止护栏,并给出可落地的检测清单与伪代码,最后讨论该问题对智能体可靠性研究的启示。
关键词:Agent loop;终止条件;上下文压缩;Copilot;智能体可靠性;状态收敛
目录
一、导语:一个被误诊的“模型问题”
在 GitHub Copilot、Cursor、Claude Code 等编码智能体的日常使用中,有一类故障反复出现且极具迷惑性:用户提出一个并不复杂的问题(例如“把这段 JSON 解析改成容错版本”“解释这个报错为什么发生”),智能体开始正常作答,回答到一半或答完之后,界面提示上下文已压缩(compact),随后它似乎“忘了”刚才的结论,重新开始规划、重新调用工具、重新作答,如此往复。用户直观感受是“模型变笨了”“回答完又重来”,于是把问题归因于模型能力。
这个归因在多数情况下并不成立。笔者认为,判断故障归属有一个简单而有效的判据:若同一模型在单轮对话、无工具调用、无压缩的设定下能正确回答该问题,那么故障就大概率不在模型本身,而在承载模型的 Agent loop 与上下文管理策略。无限循环与反复 compact 是典型的系统级症状,它指向的是“循环何时该停”这一控制论问题,而非“模型会不会答”这一能力问题。
把这两类问题混为一谈,代价是昂贵的:团队会不断更换更大的模型、调整 temperature、重写系统提示词,却始终无法根治,因为真正的缺陷——终止条件缺失——从未被触碰。本文的目标是把这条因果链拆开,给出可验证的机理分析与可操作的工程路径。
二、背景与问题定义:Agent loop 与 compact 的机制解剖
2.1 什么是 Agent loop
Agent loop(智能体循环)指大语言模型驱动的智能体在一次任务中反复执行“观察—思考—行动”的闭环:读取当前上下文与工具返回结果,生成下一步动作(调用工具、输出中间结论或给出最终答复),执行动作并把结果写回上下文,再进入下一轮。ReAct 框架(Yao 等,2022)是这一范式的经典形式化,它把推理轨迹与动作轨迹交错组织,使模型能在外部环境中迭代求解。
本文评述:ReAct 的贡献在于把“推理”与“行动”统一进一个可迭代的轨迹,但它并未回答“轨迹何时终止”这一工程核心问题。原始论文的终止依赖于模型自行输出 Final Answer,这在开放域问答中尚可,在编码智能体中却极不可靠——因为编码任务的动作空间大、失败可重试、局部修复会引出新问题,模型很容易陷入“再试一次”的惯性。
2.2 什么是 compact(上下文压缩)
compact 指当对话上下文接近模型上下文窗口上限时,系统对历史消息进行摘要、截断或结构化重写,以腾出空间继续对话的机制。常见实现有三类:滑动窗口截断(丢弃最早的消息)、摘要压缩(用模型把历史概括为一段摘要)、结构化状态提取(把关键事实抽成键值对或任务状态表)。
压缩本身是必要的工程手段,问题在于压缩与终止判定共享同一份上下文。如果终止条件依赖“上下文中是否已存在最终答案”或“是否已满足用户诉求”这类语义证据,而压缩恰好把这些证据摘要掉或改写掉,终止判定就会失效,循环便无法收敛。这是本文分析主线的第一个关键节点。
2.3 问题定义:把故障形式化
设 Agent 在第 t 轮的状态为 s_t(包含上下文、任务进度、已执行动作集合),策略 π 生成动作 a_t,环境返回观察 o_t,状态转移为 s_{t+1}=f(s_t,a_t,o_t)。一个设计良好的 Agent loop 应满足:存在终止判定函数 T(s_t)∈{0,1},且对任意可达状态序列,存在有限步 N 使 T(s_N)=1。当 T 缺失或 T 依赖被压缩破坏的证据时,序列可能不收敛,表现为无限循环或周期性重复。
“回答完就 compact 反复重来”对应的是另一种模式:T 在某一轮短暂为真(模型输出了答案),但系统未把“已交付”这一事实写入持久状态,压缩后 s 回退到接近初始状态,T 再次为假,循环重启。这本质上是终止信号未被持久化,而非模型不会答。
三、机理分析:为什么“简单问题”反而更容易死循环
3.1 简单问题缺少“强终止信号”
复杂任务往往自带强终止信号:编译通过、测试全绿、文件写入成功、命令返回码为 0。这些是可机器验证的客观判据,Agent loop 可以据此明确停止。而“解释这个报错”“把这段代码改得更健壮”这类简单问题,其完成标准是主观的、语义的,缺少可执行的验证器。模型答完之后,系统无法用客观信号确认“任务已完成”,于是只能依赖模型的自我判断,而模型的自我判断在多轮上下文中极易漂移。
本文评述:这解释了一个反直觉的现象——简单问题比复杂问题更容易循环。不是因为简单问题更难,而是因为简单问题缺少“硬终止”的抓手。工程上应主动为这类任务构造软终止信号(见第四节),而不是指望模型自觉停止。
3.2 压缩触发的“证据蒸发”
假设第 3 轮模型已给出正确答案,第 4 轮因上下文超限触发 compact,摘要策略把前 3 轮压缩为“用户询问了 JSON 解析问题,助手进行了分析”。此时“已给出最终答案”这一关键事实被弱化为“进行了分析”,终止判定读取摘要后认为任务未完成,于是重新规划。这就是证据蒸发:终止所依赖的语义证据在压缩中丢失。
更隐蔽的是摘要的“重述偏差”:摘要模型可能把已完成的结论改写成待办事项(“助手计划进一步优化”),从而主动制造出“任务未完成”的假象。这类偏差在长上下文中会被逐轮放大,形成稳定的循环。
3.3 工具返回的“伪新信息”
编码智能体常调用读取文件、搜索、运行命令等工具。若工具返回内容带有时间戳、随机 ID、绝对路径等每次都不同的字段,模型会把这些差异误判为“环境发生了变化”,从而认为需要重新分析。这类伪新信息是循环的燃料:每一轮都“看起来有新情况”,终止判定自然无法触发。
笔者认为,伪新信息是工程上最容易被忽视的循环诱因。解决办法不是让模型更聪明,而是在工具层做归一化:剥离易变字段、对返回内容做稳定哈希,让“相同输入产生相同观察”,循环就失去了燃料。
3.4 三个因素的正反馈叠加
把上述三点放在一起,可以看到一个正反馈回路:简单问题缺硬终止信号 → 依赖语义判定 → 语义证据被压缩蒸发 → 判定失败重新规划 → 重新调用工具产生伪新信息 → 上下文再次膨胀 → 再次触发压缩。每一环都在强化下一环,最终形成“回答—压缩—遗忘—重答”的自激振荡。这个回路是系统性的,单靠换模型无法打断,必须在回路的关键节点插入约束。
四、三层终止护栏:状态收敛、预算约束与语义判定
针对上述回路,本文提出三层终止护栏。三层是“与”的关系:任一层判定终止即停止,任一层判定异常即升级处理。设计原则是终止信号必须独立于会被压缩的上下文,即持久化到外部状态存储,而非留在对话历史里。
4.1 第一层:状态收敛判定
状态收敛指连续若干轮的任务状态不再发生实质变化。实现上维护一个外部状态对象,记录:已执行动作的稳定哈希集合、已确认事实集合、当前任务阶段。每轮结束后计算状态差分,若连续 k 轮(建议 k=2 或 3)差分低于阈值,判定收敛并终止。
关键细节是“稳定哈希”:对工具返回做归一化后再哈希,剥离时间戳、随机 ID、临时路径,确保相同语义的观察产生相同哈希。这样伪新信息不会污染差分计算。本文评述:状态收敛判定把“是否在原地打转”这一模糊问题转化为可计算的集合差分,是三层中最可靠、最应优先落地的一层。
4.2 第二层:预算约束
预算是硬约束,包括轮次上限、工具调用次数上限、token 消耗上限、墙钟时间上限。预算层的作用不是“优雅终止”,而是“兜底止损”:当收敛判定失效时,预算层保证循环必然在有限步内结束,避免无限消耗。
工程上建议预算分级:软预算(如 8 轮)触发时要求模型输出“当前进展与剩余计划”,硬预算(如 15 轮)触发时强制终止并返回部分结果与未完成原因。分级的意义在于给模型一次“自我收尾”的机会,同时保留最终止损能力。
4.3 第三层:语义判定
语义判定用于处理“任务确实完成但无客观验证器”的情形。做法是用一个独立的判定器(可以是小模型、规则集或二者的组合)评估当前上下文是否满足用户诉求,输出 done/continue 二值。判定器的输入不应是原始长上下文,而应是结构化的任务状态表与最近一轮输出,以降低被压缩噪声干扰的概率。
笔者认为,语义判定必须与生成模型解耦:让同一个模型既生成又判定,会因自我一致性偏差而倾向于“还没完成,再试试”。独立判定器哪怕能力弱一些,只要判据明确,可靠性反而更高。这是“裁判与运动员分离”原则在 Agent 工程中的直接应用。
4.4 三层如何协同
| 层级 | 判据 | 触发动作 | 依赖 |
|---|---|---|---|
| 状态收敛 | 连续 k 轮状态差分低于阈值 | 正常终止,返回当前结果 | 外部状态存储、稳定哈希 |
| 预算约束 | 轮次/调用/token/时间超限 | 强制终止,返回部分结果 | 计数器、计时器 |
| 语义判定 | 独立判定器输出 done | 正常终止,交付答案 | 结构化状态表、判定器 |
协同顺序建议为:每轮结束先跑状态收敛,再跑语义判定,预算层作为全局看门狗。状态收敛负责“抓原地打转”,语义判定负责“抓已完成”,预算负责“抓失控”。三者互补,覆盖了本文第三节识别的三类循环诱因。
五、工程落地:检测清单、伪代码与可观测性建设
5.1 故障自检清单
在动手改代码前,先用以下清单定位故障归属。清单的设计逻辑是逐项排除“模型问题”,把嫌疑收敛到“循环控制问题”。
- 同一问题在单轮、无工具、无压缩设定下能否正确回答?能 → 排除模型能力主因。
- 循环是否总在 compact 之后出现?是 → 指向终止信号未持久化。
- 工具返回是否含时间戳、随机 ID、临时路径?是 → 存在伪新信息燃料。
- 系统提示词是否明确要求“完成即停止”?否 → 缺显式终止指令。
- 是否存在轮次上限?否 → 缺兜底止损,必现无限循环。
- 终止判定是否读取对话历史而非外部状态?是 → 存在证据蒸发风险。
5.2 核心伪代码
下面给出三层护栏的最小实现骨架。代码刻意保持语言无关,重点在控制流而非语法细节。
state = {actions: set(), facts: set(), phase: "plan"}
budget = {rounds: 0, max_soft: 8, max_hard: 15}
while True:
budget.rounds += 1
obs = observe(context)
action = policy(context, state)
result = execute(action)
state.actions.add(stable_hash(result)) # 归一化后哈希
if converged(state, k=2): # 第一层
return finish("converged", state)
if semantic_done(state, last_output): # 第三层
return finish("done", state)
if budget.rounds >= budget.max_hard: # 第二层
return finish("budget_exhausted", state)
if budget.rounds == budget.max_soft:
context = inject(context, "请输出当前进展与剩余计划")
context = compact_if_needed(context, state) # 压缩时保留 state
这段伪代码有两个设计要点值得强调。其一,state 独立于 context 存在,压缩只作用于 context,不触碰 state,从根上避免证据蒸发。其二,stable_hash 在写入 state 前完成归一化,伪新信息无法改变差分结果。
5.3 可观测性:让循环可见
没有可观测性,循环问题只能靠用户抱怨发现。建议在 Agent 运行时埋三类指标:每轮的状态差分值、compact 触发次数与压缩前后关键事实的保留率、终止原因分布(converged/done/budget_exhausted)。其中“关键事实保留率”最能直接暴露证据蒸发:若压缩后终止判定所需的事实保留率显著下降,就说明压缩策略与终止判定不匹配。
本文评述:终止原因分布是一个被低估的诊断指标。健康的 Agent 应以 converged 和 done 为主,budget_exhausted 占比若长期偏高,说明前两层护栏失效,系统在靠兜底止损硬撑,用户体验必然受损。
5.4 可拓展的学习资源
以下资源适合进一步理解 Agent loop 与上下文管理:
- ReAct: Synergizing Reasoning and Acting in Language Models(arXiv)
- Anthropic: Building Effective Agents
- LangGraph 官方文档(有状态图与循环控制)
- Microsoft AutoGen 仓库(多智能体终止条件示例)
六、对比与局限:主流方案的边界条件
6.1 主流终止策略对比
当前工程界常见四类终止策略:纯模型自判、轮次上限、验证器驱动、状态收敛。纯模型自判实现最简单但最不可靠,尤其在压缩场景下;轮次上限是必要兜底但用户体验差;验证器驱动在编码任务中效果好,但依赖可执行验证器,对解释类问题无效;状态收敛适用范围最广,但需要额外的状态存储与哈希归一化投入。
本文评述:四类策略不存在“最优解”,只存在“组合解”。合理的工程组合是“状态收敛为主、验证器为辅、轮次上限兜底、模型自判仅作参考”,这与第四节的三层护栏一一对应。任何单一策略都不足以覆盖编码智能体的全部任务类型。
6.2 局限与失效边界
三层护栏并非万能。状态收敛在“每轮都有实质进展但永远不完成”的任务上会失效(例如开放式重构),此时预算层是唯一保障。语义判定依赖判定器质量,判定器若与生成模型同源,仍可能继承自我一致性偏差。压缩策略与状态存储的分离增加了系统复杂度,对小规模应用可能得不偿失。
还有一个容易被忽略的边界:终止不等于正确。护栏保证循环会停,但不保证停在对的答案上。收敛终止可能停在错误结论,预算终止可能返回半成品。因此终止机制必须与结果质量评估配套,否则只是把“无限循环”换成了“快速给出错误答案”。
七、结论与展望
回到标题的问题:Copilot 简单问题进无限循环、回答完就 compact 反复重来,是模型问题还是 Agent loop 缺终止条件?本文的结论是后者为主。简单问题缺少硬终止信号,压缩又蒸发了语义证据,伪新信息持续供能,三者叠加形成自激振荡。模型在其中更多是“被循环驱动”的执行者,而非故障根源。
工程上的解法是清晰的:把终止信号持久化到独立状态存储,用状态收敛抓原地打转,用预算约束兜底止损,用独立语义判定处理无验证器的任务,并在压缩时严格保护终止所需的事实。这套组合不依赖更强的模型,只依赖更严谨的控制设计。
展望未来,笔者认为 Agent 可靠性研究将从“提升单步能力”转向“保障轨迹收敛”。轨迹级的收敛性、可终止性、可观测性,会像分布式系统中的幂等与超时一样,成为智能体工程的基础设施级议题。谁先把终止条件做成标准件,谁就能在智能体落地上少踩最深的那口坑。
主要参考文献
- Yao S, Zhao J, Yu D, et al. ReAct: Synergizing Reasoning and Acting in Language Models. arXiv:2210.03629, 2022.
- Shinn N, Cassano F, Gopinath A, et al. Reflexion: Language Agents with Verbal Reinforcement Learning. NeurIPS, 2023.
- Wang L, Ma C, Feng X, et al. A Survey on Large Language Model based Autonomous Agents. Frontiers of Computer Science, 2024.
- Anthropic. Building Effective Agents. 2024. https://www.anthropic.com/research/building-effective-agents
- Wu Q, Bansal G, Zhang J, et al. AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation. arXiv:2308.08155, 2023.
- Liu N F, Lin K, Hewitt J, et al. Lost in the Middle: How Language Models Use Long Contexts. TACL, 2024.
- Packer C, Wooders S, Lin K, et al. MemGPT: Towards LLMs as Operating Systems. arXiv:2310.08560, 2023.
- Sumers T R, Yao S, Narasimhan K, et al. Cognitive Architectures for Language Agents. TMLR, 2024.
文章声明:本文内容仅为作者学习、思考、经验、笔记的总结,仅供技术交流与参考。文中观点仅代表笔者个人思辨,不构成任何学术建议、商业建议或专业建议。所有数据来源已标注,引用时请以原始文献为准。
内容仅供学习参考。如需引用,请以原始文献为准。 全文约 6800 字 | 参考文献 60 篇(主要)。

