跨 harness 对比:Agent Loop(主循环 / 何时继续何时停)

总览:这个维度到底在决定什么

所有 agent harness 的主循环骨架其实高度趋同:调模型 → 若产生工具调用则执行 → 把结果喂回 → 再调模型 → 直到不再产生工具调用。真正拉开差距的不是”怎么转圈”,而是三件事:用什么结构承载这个循环用什么信号判断”该继续还是该停”用什么机制防止它失控或过早收手。61 个 harness 在这三点上分化出几条清晰的流派。

循环的承载结构大致分五类:

  1. 朴素 while(true) / while not done ReAct 单循环——代码上就是一段直白的生成器或 for 循环(smolagentsAiderTraeAutoGPT classicOpenManusCAMELContinueAgent Zero)。
  2. 显式状态机——把”这一轮为什么结束”建模成一个带命名原因的枚举而非单一布尔量(Codex CLIChatStateAmazon QLoopStateWarpConversationStatus+CancellationReasonKimi Code 的封闭停止枚举、LettaStopReasonTypeAgentScopeReplyEndReason)。
  3. 通用图 / actor / 中间件执行引擎,agent 循环只是搭在上面的一层(LangGraph 的 Pregel BSP superstep、Pydantic AIpydantic_graph 类型化状态机、Google ADK 的图节点调度、AutoGen 的 actor 运行时、Semantic Kernel 的 auto-invoke 循环、OpenAI Agents SDKNextStep 四态机)。
  4. 把循环整体委托 / 套壳出去:自己不实现循环,交给底层 SDK、图库或另一个 harness(DeepAgentsDeerFlow 交给 LangChain/LangGraph、QwenPaw 建在 AgentScope 之上、Open Interpreter 直接用 Codex 底座、Claude Flow 起一个 claude 子进程、AutoGPT 的 Copilot 建在 Claude Agent SDK 上)。
  5. 阶段状态机 / 固定管线——不是”转圈直到收敛”,而是被建模成一串确定的阶段或一棵递归树(Plandex 的尾递归阶段机、GPT Pilot 的持久化状态纯函数路由、GPT Researcher 的固定管线 + 递归深挖树)。

“继续还是停”的权威信号是本维度最有辨识度的分歧点,大致六个流派:

  • (1) “模型这一轮不再产生工具调用就停”——绝对主流约定。但多家明确指出 stop_reason == 'tool_use' 这个 API 字段不可信(Claude Code 源码注释直说 unreliable,Kilo Code/opencode 靠”本轮是否还有未执行的 tool part”兜底),改用”本轮是否真的流出过 tool_use block”作为唯一权威信号。
  • (2) 要求模型显式调用一个终止工具才算完成(Traetask_doneSWE-agentsubmitOpenManusterminateAgent Zeroresponsebrowser-use/UI-TARSdone/finishedCopilottask_completeOpenHandsFinishToolLettais_terminal_tool)。
  • (3) 一次独立的 LLM 判官调用决定该不该继续(Gemini CLI/Qwen Code 的 next-speaker checker)。
  • (4) goal-gate / rubric-gate——用户或系统设一个自然语言完成条件,每次 agent 想收手时由一个独立判官复核是否真的达成,不达成就打回继续(MiMo Code 的 goal gate、DeepAgents 的 RubricMiddleware、QwenPaw 的 gate 流水线)。
  • (5) 有序中间件链 / gate 流水线承载全部语义——继续或停机不是一个 if,而是一整条按注册顺序执行的拦截器(DeerFlow 的 middleware 链、QwenPaw 的 StopGate 流水线)。
  • (6) 配置化的工具行为——用声明式配置决定工具结果是否回灌(OpenAI Agents SDKtool_use_behaviorAgnostop_after_tool_call)。

防失控与防过早收手几乎每家都做,深浅差别很大:从”没有任何护栏、模型说停就停”(TraeContinue 无硬 max-steps、Agent Zero/CAMEL 默认无迭代上限),到硬 turn 上限,到 doom-loop 检测(连续 N 次相同工具调用触发干预,opencode/Kilo/MiMo 阈值都是 3,Gemini 是启发式+LLM 双层,QwenPaw 是滑窗签名相似度),再到 Stop-hook 否决这一横跨极多 harness 的统一控制点——一个生命周期钩子可以在 agent 打算停下时把它拦回来强制续跑。

下面先给对比表,再分组展开。

对比表

Harness循环承载结构”继续/停止”的权威信号硬上限(默认)突出的循环控制机制
Claude Code显式 queryLoop() while(true) + 带 transition 原因的 State”本轮是否流出过 tool_use block”(显式声明 stop_reason=='tool_use' 不可信)maxTurns;输出恢复上限 3Stop-hook 可否决停止回灌消息;prompt-too-long/max-tokens 触发压缩或升 cap 重试续跑
Codex CLI双层:外层 pending-input 驱动 + 内层 run_turn无待处理输入即停Stop hook 注入 developer message 强制继续;pre/mid-turn auto-compact;steering
OpenHands双层:LocalConversation.run() while(True) + agent.step()ConversationExecutionStatus 枚举(FINISHED 经 FinishTool)max_iteration_per_run 500Stop hook 可 veto FINISHED 弹回 RUNNING;独立 StuckDetector 扫 20 事件查 5 种重复
ClineAgentRuntime.execute() while + maxIterations无 tool call 且无完成提醒即停maxIterations完成策略:可要求必调 submit_and_exit,否则注入合成 [SYSTEM] 提醒续跑
Roo Code栈机(命名 recursively 实为迭代栈)模型没调工具就注入 noToolsUsed() 提醒consecutiveMistakeLimit源码注释明确”任务从不真正 completes”,靠 prompt 约定而非状态机强制
opencoderunLoop() while(true)finish 原因非 tool-calls 且无待执行调用即 breakagent.steps 默认 InfinityDoom-loop 阈值 3 触发权限 ask;handle.process 返回 compact/stop/continue 三态
MiMo Codeopencode fork,双层:外层 while(true) + 内层流事件状态机Goal gate + Task gate 双重把关,非单看模型信号agent.steps ?? Infinity;MAX_GOAL_REACTGoal gate 用独立 LLM judge 复核自然语言完成条件;无限循环概率 <0.5%;Max Mode 并行 N 候选选优
Kilo Codeopencode fork,外层 runLoop + 内层 processor有未执行 tool part 就继续(finish=stop 不可信)agent.steps ?? InfinityDoom-loop 阈值 3;拒绝即停(continue_loop_on_deny 可翻转);压缩重入有上限
Kimi Code独立 loop 库:runTurn + executeLoopStep封闭停止枚举(end_turn/tool_use/max_tokens/…)maxStepsshouldContinueAfterStop 钩子(Goal Mode 借此在 end_turn 后续跑);结构性 400 严格重建重试一次
Gemini CLITurn.run() + GeminiClient 外层独立 LLM 判官 next-speaker checker(非规则)MAX_TURNS 100双层 loop 检测:启发式计数 + LLM 循环判官(阈值 0.9,30 轮后启动)
Qwen CodesendMessageStream 递归 + 无头 while(true)next-speaker LLM 侧路查询MAX_TURNS 100loop 阈值 5 刻意低于 DashScope 服务端拒绝阈值,客户端先断循环
SWE-agentDefaultAgent.run() while not donesubmit 工具(魔法串 <<SWE_AGENT_SUBMISSION>>)/ exit / forfeitmax_requeries 3RetryAgent 元循环:整个 agent 重启多次再评审选优
Aider有界反射循环(非开放 ReAct)reflected_message 被设置则续跑(lint/test/解析失败)max_reflections 3状态触发式有界自动续跑,硬上限 3,不是开放工具循环
smolagentsMultiStepAgent._run_stream whileActionOutput.is_final_answer / final_answer 工具max_steps超上限强制再发一次索要 best-effort 答案;planning_interval 周期性规划步
Hermesrun_conversation() while + 独立预算纯文本响应即停(十余种 _turn_exit_reason 细分)max_turns 90;subagent 50可中断 API 调用(后台线程竞速);预算耗尽优雅停止返回摘要
GooseAgent::reply() try_stream!no_tools_called && !exit_chat + goal/grind 检查MAX_TURNS 1000;subagent 25Stop hook 可 Deny 退出(带 block cap);steer 中途注入;每轮注入剩余 turn 预算信号
MetaGPT双套:legacy SOP + RoleZero ReAct{"command_name":"end"} / Plan 完成 / 预算超支RoleZero 50;TeamLeader 3max_react_loop≥10 时触发”询问人类是否继续”安全阀;quick-think 快速路径分流
LangGraphPregel BSP superstep tick最后 AIMessage 无 tool_calls 则 ENDrecursion_limit(硬递归上限)remaining_steps 软早停给收尾留余量;interrupt_before/after
DeepAgents委托给 LangGraph(不自建循环)模型不再产工具调用即停recursion_limit 9999(靠 token 预算约束)RubricMiddleware:模型要停时独立判官评分,needs_revision 则续跑
DeerFlow委托给 LangChain create_agent(模型→工具→模型)由整条有序 middleware 链承载,注册顺序即语义TokenBudgetMiddleware per-runLoopDetectionMiddleware 打断重复调用;ClarificationMiddleware 恒最后,中断等人
Pydantic AIpydantic_graph 类型化状态机三节点(非手写 while)CallToolsNode 分支:output tool 命中/text 满足 schema → EndUsageLimits(request/token/tool_calls)EndStrategy early/graceful(v2 默认)/exhaustive;agent.iter() 可手动单步;类型化工具
OpenAI Agents SDKrun_loop while True,NextStep 四态产出 output_type 文本且无 tool call → finalmax_turns(可设 None 关闭)tool_use_behavior(run_llm_again/stop_on_first_tool/StopAtTools);handoff 换 agent 重跑;reset_tool_choice 防死循环
Google ADK双层:内层 run_async while True + 外层图节点调度last_event.is_final_response()(无未决函数调用)LoopAgent max_iterations子 agent escalate 提前跳出 LoopAgent;节点可从 session.events 重建恢复(rehydration)
AgentScope统一 Agent(非继承式),全异步事件流_check_next_action 真值表(reasoning/acting/exit)max_iters 20一等 HITL:可被 park 后用外部事件(确认/中断/外部执行结果)续跑;QwenPaw 底座
AutoGenactor 运行时 + AssistantAgent for-loop纯文本 / handoff / max_tool_iterations 耗尽max_tool_iterations 默认 1Magentic 双账本外/内层循环 + n_stalls 计数触发重规划
Semantic Kernelauto-invoke for 循环无 FunctionCallContent 即停 / filter terminatemaximum_auto_invoke_attempts 5耗尽轮数后强制一次无工具调用拿纯文本;Magentic 移植双账本
CrewAI双执行器:ReAct + Flow 状态机 Plan-and-ExecuteAgentFinish / result_as_answer / max_itermax_iter新执行器按 reasoning_effort 三档路由后处理;动态重规划 max_replans 3
QwenPawAgentScope ReAct + 自建 Gate 系统Gate 流水线首个非 None 结果生效IterationGate 20/50;BudgetGate 300k延迟停止(工具调用中途不打断);作用域隔离(Goal/Mission 替换默认 gate);DoomLoopGate
LettaLettaAgentV3 for i in range(max_steps)_decide_continuation:无 tool call → end_turn,有 → continueDEFAULT_MAX_STEPS”No heartbeats(loops happen on tool calls)“;required-before-exit 工具未调齐则注入 heartbeat 续跑;每步查余额闸门
Agno循环在 model 层Model.response while True)非 agent 层无 tool call → breaktool_call_limit(超限不抛错)超 tool_call_limit 给该工具追加”到上限了”结果让模型自停;4 类 HITL break(确认/外部执行/用户输入/team 冒泡)
CAMELchat_agent.py _step_impl while True有 tool call 就继续,无则 breakmax_iteration 默认 None(无限)external tool 命中即 break 交还调用方;response_terminators 可注入”Please continue”续跑;pause_event HITL
ContinuestreamChatResponse() while(true) 单 agent 顺序shouldContinue = validToolCalls.length > 0(纯模型决定)无硬 max-steps压缩后误停时注入”continue”自动续跑(跑完复位防死循环);每轮重取 system+重算工具
Agent Zero双层 while True(外层建 LoopData + 内层 message_loop)唯一停机=工具返回 Response(break_loop=True)response 工具)无固定 max_iterations流式提前停机:生成中途嗅探完整 JSON 工具块即截断省 token;扩展点挂满全程
OpenManusBaseAgent.run() while step<max & state!=FINISHEDterminate 工具置 FINISHEDManus 20 / ToolCall 30 / Base 10is_stuck() 检测重复 assistant 内容(阈值 2)后注入”避免重复”提示
browser-useAgent.run() while n_steps<=max_steps 感知-决策-执行LLM 调 done action(is_done)max_steps 500;max_failures 5强制收尾:末步/失败达限把工具限为仅 done;multi_act 两层 page-change guard 中断剩余动作
UI-TARS两套 GUI loop:一代 while(true) 感知-决策-执行 / 二代 Tarko 事件流 foraction finished()/call_user();二代无 toolCalls 即 finalmaxLoopCount / maxIterations截屏=下一轮观察闭环;onBeforeLoopTermination 钩子可否决终止注入 system 事件续跑
Open InterpreterCodex 底座 run_turn 外层 loopneeds_follow_up = model_needs_follow_up || has_pending_input循环内嵌 auto-compact 防溢出死循环;stop hook 可请求 continuation
Plandex尾递归阶段状态机(非 for 循环)willContinuePlan() 按阶段判定 + 未完成子任务MaxAutoContinueIterations 200阶段 Context→Tasks→Implementation;发给模型的 stop 序列 <PlandexFinish/>
GPT PilotOrchestrator while True 尾状态机外层仅 ResponseType.EXIT 才 break,DONE 则 continue无 max-iteration(状态迁移保证推进)跑哪个 agent 是持久化状态的纯函数(create_agent if/elif 阶梯);agent 内各带重试小循环
GPT Researcher固定管线 + 可选递归树(非 ReAct)标准 researcher:“子查询列表跑完”即停,无自省max_iterations 3;deep depth 2Deep research 递归树(breadth 4/depth 2,每层实例化新子研究器);停止=depth 递减到 1
TraeBaseAgent.execute_task() 线性 step 循环task_done 工具(可叠 --must-patch 要 git diff 非空)max_steps 200 / interactive 20无重试无回退;无动态步数预算;无两段式规划
AutoGPTclassic run_interaction_loop + 可插拔 prompt strategyfinish 工具触发 AgentFinishedcycle_budget 名存实亡(仅 Ctrl-C 记账)7 种可插拔 strategy 决定循环形状(one_shot/rewoo/reflexion/lats/…);Platform 侧改为图执行
Zedrun_turn_internal looptool_results.is_empty() → EndTurnRefusal 附模型回退重试;steering end_turn_at_next_boundary 中途打断
Warp显式 ConversationStatus 状态机富枚举 CancellationReason 裁决终止/非终止云端子 agent 用 BLOCKED/SUCCEEDED 状态上抛父 LLM 决策;休眠会话可被 mailbox 唤醒续跑
Amazon QChatState 枚举 + 重写版 LoopState模型返回无 tool use 的响应(UserTurnEnd每状态用 tokio::select! 与 ctrl-c 竞速;RetryModelOverload 弹性重试
GitHub Copilot闭源 CLI(SDK 纯传输)无 toolRequests → session.idle(可选 task_complete)agent_max_iterations flagAutopilot 未调 task_complete 则注入合成消息催促重启;VS Code 侧第二层小模型判完成
Augment回合制 + 子代理子循环停止枚举 end_turn/max_iterations/empty_completionagent_max_iterations flag并行工具/并行子代理都是一等公民,中断检查在每批边界
Factory Droids闭源(文档级)Stop/SubagentStop hook 控制--max-turnsStop hook 返回 block+reason 强制续跑;Mission 模式 worker/validator 角色分离
Junie闭源(hook 生命周期推断)Stop hook block / continue:falseJUNIE_STOP_HOOK_BLOCK_CAP 8Stop hook 连续 block 上限 8;StopFailure 附 9 值错误分类
Cursor闭源stop hook 返回 followup_message 触发自动继续loop_limit 5(CC 兼容模式 null)云端 agent 由 Temporal 持久化执行引擎承载
Qoder闭源Stop hook exit 2 可注入消息阻止停止--max-turnsCloud 侧 stop_reason.requires_action 暂停等客户端回传后同回合恢复
Replit闭源(假设驱动循环)未披露显式终止判据每次迭代独立小模型分类器决定是否注入微指令;doom-loop 切到不同模型的外部 agent
Claude Flow无原生循环,spawn claude 子进程扮 Queen循环归 Claude Codecontinue/stop 以 guardrail 熔断器形式存在(连续失败 WARN@3/BLOCK@5);agent_execute 单发无工具
Anthropic ResearchLeadResearcher OODA + subagent 有界循环diminishing returns 判停;subagent 5-20 工具调用上限max_turns(cookbook for _ in rangesubagent 硬性工具调用上限 5-10;lead 同步等待 subagent(官方承认的限制)

(闭源、循环内部未逐字披露、仅有文档/间接证据的还有 CodeGeeX(本地托管 Python 运行时 + Pyarmor 加密循环)、Comate(changelog 反证曾修复”工具无限循环”bug)、AutoClaw(Cluster Mode 6 阶段 SOP)、Devin(snapshot 启动 + sleep/resume + ACU 分级)、v0(task-*-v1 content part 反推)、Kiro(Autopilot/Supervised + wave DAG 调度)、Amp(教学级参考实现 + 插件 agent.end 续跑),见后文”闭源产品”一节。)

分组讨论

A. 朴素 ReAct 单循环:最早、最多、也最”没护栏”

这是数量最大、思想最老的一派:一段 while 循环,模型不再发 tool call 就停。smolagents 把这一派讲得最”教科书”——文档自称”an abstraction of ReAct framework”,_run_stream 生成器就是标准 ReAct,还内建了 planning_interval 周期性插入规划步。OpenManusCAMELAgnoContinue 都是同一骨架的不同语言实现。

这一派内部真正的分歧在**“谁来喊停”和”有没有硬上限”**:

  • 靠终止工具喊停OpenManusterminateTraetask_done(用魔法串 <<SWE_AGENT_SUBMISSION>> 识别的是 SWE-agentsubmit)。Trae 是这派里最”裸”的:线性 step 循环、无重试、无回退、无两段式规划,纯粹靠 task_done + max_steps。
  • 无硬上限、纯靠模型自停ContinueshouldContinue = validToolCalls.length > 0,明说没有 max-steps)、CAMELmax_iteration 默认 None)、Agent Zero(无固定 max_iterations,靠 response 工具 + 用户干预 + 历史压缩控上下文)。这类设计把失控风险完全押在模型和上下文预算上。

Agent Zero 在这派里有两个独到点值得单拎:一是协议是 JSON 单工具(每轮只输出一个 {thoughts, tool_name, tool_args}),唯一停机机制就是工具返回 Response(break_loop=True);二是流式提前停机——在 LLM 还在生成时就用 json_parse_dirty 嗅探已完整的工具 JSON,一旦 validate_tool_request 通过就截断本轮生成省 token,这是很少见的”边生成边解析、提前掐断”手法。

Agno 的独特之处是把循环下沉到 model 层Model.responsewhile True)而非 agent 层,且超迭代上限不抛错——tool_call_limit 到顶时不是崩溃,而是给那个工具追加一条”到上限了”的结果让模型自己收尾,这是”用提示而非异常来收敛循环”的温和设计。

Aider 严格说不属于这派:它是有界反射循环而非开放 ReAct——只在 lint/test/编辑解析失败时把”反射消息”当作下一轮 user 续跑,硬上限 3 次,范围锁死在”改代码-提交-lint-test”这几件事上,不是自由工具循环。这是刻意收窄自主性的另一条路。

B. 显式状态机:把”为什么停”做成一等公民

第二派不满足于一个布尔 done,而是把”这一轮为什么结束”建成命名枚举。Warp 是最极致的——CancellationReason 枚举列出手动取消、云端 handoff、用户提交后续、用户中途跑 shell、revert、CLI-subagent 接管等近十种终止原因;Amazon Q 的重写版 LoopStateIdle→SendingRequest→ConsumingResponse→…)与 LoopEndReason 同理;Kimi Code 把停止原因收敛成封闭枚举 end_turn | tool_use | max_tokens | filtered | paused | unknown | aborted

新增的两个是这派里概念最清晰的:

  • Letta(MemGPT 后身)的 StopReasonType 是全维度最完整的停止枚举(end_turn/max_steps/tool_rule/requires_approval/insufficient_credits/context_window_overflow/…)。它更值得注意的是停止哲学对 MemGPT 原版的反叛:类 docstring 明写 “No heartbeats(loops happen on tool calls)“,系统提示结尾直接告诉模型 “To continue: call another tool. To yield control: end your response without calling a tool.”——即把”要不要续”完全绑定到”有没有再调工具”上,废掉了 MemGPT 那套 heartbeat request 机制。配合 is_terminal_tool(tool_rule 停)、required-before-exit 工具(未调齐则注入 heartbeat 强制续)和每步 _check_credits() 余额闸门,是”工具规则驱动停机”的代表。
  • AgentScope 把旧版 AgentBase/ReActAgent 收敛为单一 Agent,用 _check_next_action 真值表(reasoning/acting/exit)驱动,ReplyEndReason 做终止枚举。它最有价值的设计是一等 HITL 可恢复reply() 的输入不仅能是 Msg,还能是 UserConfirmResultEvent/ExternalExecutionResultEvent,循环可以被 park 后靠外部事件续跑——把”人类确认/外部工具执行”直接做进 loop 状态机。这一点很关键,因为 QwenPaw 正是建在 AgentScope 之上(ReAct 循环本体不在 QwenPaw 仓库里),QwenPaw 的全部贡献是在这个底座上叠了一层 Gate 系统。

C. 通用图 / 中间件引擎:循环只是引擎的一层

第三派不写循环,而是搭在一个通用执行引擎上。LangGraph 是这派的鼻祖与事实标准——Pregel 的 BSP superstep 循环(recursion_limit 是硬递归上限),ReAct 的”继续/停止”退化成 prebuilt 里一句 should_continue(最后 AIMessage 无 tool_calls 则 END)。它的下游极多:DeepAgents 完全委托 LangGraph(recursion_limit 硬编码到 9999,靠 token 预算而非步数上限约束),DeerFlowlangchain.agents.create_agent 构图。

DeerFlow 在这派里贡献了一个独特范式:“继续还是停”的全部语义由一整条有序 middleware 链承载,注册顺序即执行语义。它把 loop 检测(LoopDetectionMiddleware)、token 预算(TokenBudgetMiddleware)、安全终止(SafetyFinishReasonMiddleware)、澄清中断(ClarificationMiddleware,注释明写 “should always be last”)全部做成链上的拦截器——停机不是一个 if,而是链尾若干中间件的合力。这跟 QwenPaw 的 gate 流水线是同一思路的两种实现(一个是 LangChain middleware,一个是自建 StopGate registry)。

新增的两个把这派推向了类型化 / 可手动驱动

  • Pydantic AIpydantic_graph 类型化状态机三节点(UserPromptNode → ModelRequestNode ↔ CallToolsNode),不是手写 while。它的两个独到设计:一是 EndStrategyearly/graceful/exhaustive)精确控制 output tool 与普通 function tool 并存时何时结束(v2 起默认从 early 改为 graceful,先跑完排在 output tool 前的工具);二是因为 loop 是显式图,用户能用 agent.iter() 拿到节点流手动单步驱动——这是相对绝大多数 harness”黑盒 while 循环”的实质差异点。配合它一贯的类型化工具(工具签名即 schema),整个循环是强类型可内省的。
  • OpenAI Agents SDKrun_loop while True 每轮产出 SingleStepResultnext_stepNextStepFinalOutput/Handoff/RunAgain/Interruption 四态之一。它把”工具驱动的提前停止”做成声明式配置 tool_use_behaviorrun_llm_again 默认 / stop_on_first_tool / StopAtTools 名单 / 自定义函数),并用 reset_tool_choice 在工具调用后重置 tool_choice 防无限工具循环——这是”用配置而非硬编码控制停机”的干净范例。

Google ADK 介于图派和状态机派之间:内层 run_asyncwhile Trueis_final_response() 停),外层把每个 agent 当图节点调度(ADK 2.0 的 BaseAgent(BaseNode)),LoopAgentmax_iterations 或子 agent 的 escalate 事件停——escalate 冒泡跳出是很多编排框架都借鉴的”子节点向上喊停”模式。AutoGenmax_tool_iterations 默认 1,几乎是”单步”)和 Semantic Kernel(auto-invoke 默认 5,耗尽后强制一次无工具调用拿纯文本)则代表”基础循环刻意做浅、把多轮自治推给上层 Magentic/GroupChat 编排器”的企业级分层思路。

D. LLM 判官 / gate 判停:不信模型自己说的”我做完了”

这一派是本维度最有含金量的分支:不把”模型说停”当权威,而是引入一个独立判断来复核

  • 最早把它做成产品级机制的是 Gemini CLInext-speaker checker 用一次独立小模型调用(固定 rubric prompt)判断”该让用户接话还是模型继续”,循环检测也是启发式计数 + LLM 循环判官双层(阈值 0.9,跑满 30 轮后启动)。Qwen Code fork 自它并沿用/加强了这套(loop 阈值 5 还刻意调到低于 DashScope 服务端拒绝阈值,让客户端先断)。
  • MiMo Code(opencode fork)把它推到量化落地:goal gate 让用户用 /goal 设自然语言停止条件(如”所有测试通过且已提交”),每当 agent 想终止就发起独立 LLM judge 复核,judge prompt 明写 “assistant claiming the goal is impossible is evidence, not proof”,博客给出无限循环概率 <0.5% 的实测数字。它还有个”算力换质量”的 Max Mode(每轮并行生成 N=5 候选方案再 judge 选优,SWE-Bench Pro +1020%、代价 45 倍 token)。
  • DeepAgentsRubricMiddleware 利用”模型未返回工具调用”这个事件做钩子,用独立判官评分,needs_revision 就续跑——本质同构。
  • QwenPawGate 系统是这派工程化最完整的:StopGate 抽象基类按优先级排成流水线,首个非 None 结果生效(IterationGate/BudgetGate/DoomLoopGate/RubricGate);两个精巧设计是延迟停止语义(STOP 信号若发生在工具调用中途,暂存到下一轮才消费,确保工具永不被打断)和作用域隔离(Goal/Mission 模式激活时完全替换默认 gate 集合,会话结束自动恢复)。
  • 闭源里 Replit 走得最远:每一次 agent 迭代都有个跑在独立小模型上的分类器检视轨迹决定是否注入微指令;doom-loop 时甚至切换到不同模型的外部 agent 提方案(显式理由是降低”自我偏好偏差”、利用”生成器-判别器差距”)。Copilot 的 VS Code 侧也叠了第二层小模型在每 turn 后判断原始请求是否满足。

这一派的共识很清楚:模型对”我做完了”的自我判断不可靠,用一个更便宜的独立判断来把关,是把无限循环概率压到 <1% 的关键工程手段。

E. 阶段状态机 / 固定管线:把”循环”换成”流程”

第五派干脆不做开放循环,而是把任务建模成确定的阶段序列或递归树。

  • Plandex 的主循环是尾递归的阶段状态机Context → Tasks → Implementation 三阶段,每阶段结尾模型必须吐 <PlandexFinish/>(这也是发给模型的 stop 序列),willContinuePlan() 按阶段和未完成子任务判定是否 iteration+1 再递归,硬上限 200。
  • GPT Pilot 的 Orchestrator 是 while True没有 max-iteration——推进完全由持久化状态迁移保证,create_agent() 是”跑哪个 agent 是当前状态的纯函数”(一条很长的 if/elif 阶梯映射 epics/spec/architecture/tasks/steps)。这是”用状态机而非计数器控制终止”的代表。
  • GPT Researcher 明确不是 ReAct:标准 researcher 是固定管线(选 persona → 规划固定条数子查询 → 并发检索 → 返回),停止条件就是”子查询列表跑完”,无自省;deep research 才是真正的迭代——递归树(breadth 4 / depth 2,每层为每个子查询实例化一个全新子研究器跑完整 research),停止=depth 递减到 1。
  • 闭源的 AutoClaw Cluster Mode(6 阶段 SOP)、Kiro 的 spec 任务 wave DAG 调度也属这类”流程化而非循环化”。

F. GUI / computer-use 感知循环:动作空间不是文本工具而是屏幕

新增的两个 GUI harness 给本维度带来了”观察 = 截屏、动作 = 点击/键入”的循环变体:

  • browser-useAgent.run() 是感知-决策-执行循环:每步取 browser state(DOM + 截图)→ LLM → multi_act 执行动作,停机靠 LLM 调 done action。它有两个 GUI 特有的循环控制:一是 multi_act 一步多动作但带两层 page-change guard(静态标记 terminates_sequence 的 navigate/search 执行后跳过剩余;运行时对比动作前后 URL+focus 变了就中断),因为页面一变后续基于旧 DOM 的动作就失效了;二是强制收尾——末步或失败达上限时把可用工具限制为仅 done 做最后一次回复。
  • UI-TARS 有两代 loop:一代是 GUI 专用 while(true) 感知-决策-执行(截屏 → VLM → 解析 Thought/Action → 执行,停机靠 finished()/call_user());二代 Tarko 是事件流驱动的 for(iteration<maxIterations),停机靠”最新 assistant 事件无 toolCalls”。二代的独特点是 onBeforeLoopTermination 钩子可否决终止(高阶 agent 能拦截”想收尾”这一步、注入 system 事件让 loop 继续),以及新 GUI SDK 用 onAfterToolCall 在每次操作后 doScreenshot() 把新截图作为 environment_input 回灌——把一代的”截屏=下一轮观察”闭环移植进二代事件流内核

这两个证明了:GUI/computer-use agent 的循环骨架跟文本 coding agent 完全同构(感知-决策-执行 = 调模型-执行工具-喂回),差别只在动作空间(屏幕坐标/键鼠 vs shell/edit)和一个额外约束——页面状态一变,队列里基于旧观察的后续动作必须作废

G. Stop-hook 否决:横跨全场的统一”续跑”控制点

最后一条不是循环结构,而是一个横切机制:让一个生命周期钩子在 agent 打算停下时把它拦回来强制续跑。这几乎是 Claude Code 生态的事实标准,被极广泛地复制:Claude CodehandleStopHooks(blockingErrors 强制再跑)、Codex CLI(Stop hook 注入 developer message)、OpenHands(veto FINISHED 弹回 RUNNING,仓库 AGENTS.md 还记录了这里的已知竞态)、Goose(Stop hook 可 Deny,带 block cap 防无限拒绝)、KimishouldContinueAfterStopFactory/Junie(block cap 8)/Qoder/Cursor(followup_message)/Amp(插件 agent.end 返回 continue)无一例外都暴露了这个点。共性是都配了一个 block cap / stop_hook_active 防护,防止 hook 无限拦停自己——这本身说明”暴露 continue/stop 给外部钩子”已成范式,且大家都踩过它导致死循环的坑。UI-TARS 二代的 onBeforeLoopTerminationAgno 的 team HITL 冒泡本质也是同一控制点在不同语境下的实现。

闭源产品的循环披露

无源码但有间接证据的一批:CodeGeeX 会在用户机器上安装并托管一个本地 Python agent 运行时(conda 建环境 + FastAPI 监听 localhost:11435),真正的循环在 Pyarmor 加密的 converse_agent.py 里不可读。Comate 的 changelog 直接反证了循环的存在与不完善——v3.19.0 明确”修复了循环调用工具及过度主动触发的问题”。Devin 只暴露外部可观察行为(snapshot 启动、sleep/resume、按 ACU 把 session 分 XS–XL 级、L/XL 标 unhealthy),主循环终止逻辑未披露。v0 只能从 task-*-v1 content part 序列反推出任务粒度的可中断/可恢复循环。Amp 反而给了教学级参考实现(how-to-build-an-agent.md),坦言”inner loop 就这么点,产品在此之上叠的是 elbow grease”。

结尾:这个维度最值得借鉴的设计

第一,独立判官 / goal-gate 复核”是否真的做完了”(D 组)。 本维度所有 harness 的共同软肋是”模型对自己是否完成任务的判断不可靠”——它会过早收手(漏做验收项)或死循环(在同一个 bug 上反复)。MiMo Code 用 goal gate + 独立 LLM judge 把无限循环概率压到实测 <0.5%Gemini CLI 用 next-speaker checker 把”该不该继续”从规则升级成判断,Replit 更进一步用不同模型的外部 agent 打破自我偏好偏差。理由很硬:判官模型比主模型便宜得多,而”用一次廉价复核换掉一次过早交付或一场死循环”的性价比极高;这也是唯一被官方拿出量化数字证明有效的循环控制手段。QwenPaw 的 gate 流水线 + DeerFlow 的 middleware 链则证明这套判停逻辑可以做成可插拔、可排序、可作用域隔离的一等架构,而非散落的 if。

第二,把停止建成命名枚举 + 把 continue/stop 暴露给外部钩子(B 组 + G 组)。 LettaStopReasonTypeWarpCancellationReasonKimi 的封闭枚举证明:把”为什么停”从布尔量升级成枚举,直接换来了可观测性(Junie 的 StopFailure 9 值错误分类)、可恢复性(AgentScope 靠外部事件 park/resume)和可编程性(Stop-hook 让用户在不改内核的前提下注入”你还没做完”逻辑)。代价极小(一个枚举 + 一个钩子点 + 一个 block cap),收益贯穿调试、审计、扩展全流程——这也是为什么它从 Claude Code 一路扩散成了几乎全行业的默认范式。