ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Agent Loop的四道保险丝:Pi、OpenCode 与 Codex 如何控制失控风险

Agent Loop的四道保险丝:Pi、OpenCode 与 Codex 如何控制失控风险 Agent Loop 的难点并不在于“让模型调用工具”而在于如何处理真实运行时的边界模型会不会反复使用相同参数调用同一个工具输出被截断后工具参数还可信吗什么时候应当停止模型服务失败后重试由谁负责本文基于以下 release 源码比较 Pi、OpenCode 与 Codex 这三个开源的Coding Agent在四个关键控制面上的取舍Codex0.148.0tagrust-v0.148.0Piv0.84.2OpenCodev1.18.18本文只根据以上版本的源码做出结论。一、整体差异三种把“保险丝”放在不同层的方式比较维度Piv0.84.2OpenCodev1.18.18Codex0.148.0重复 Tool / 死循环核心 Loop 不判断预留 hooks 给 Harness 实现内建doom_loop相同工具、相同输入连续 3 次会触发 permission没有看到同类重复调用检测更依赖 Runtime 生命周期控制输出截断不执行可能不完整的 Tool Call让模型重新生成保存 provider 的 finish reason未见专门的截断恢复状态机重点处理 stream 中断后的重连与重新 sampling停止条件Loop 提供基本退出边界业务策略交给上层以 Session 是否进入终态为中心以当前 Turn 是否仍需要 follow-up 为中心服务失败重试Agent 层统一重试默认 3 次独立 Retry Policy默认最多 5 次HTTP request retry 与 stream retry 两层分离注Codex 将一次模型生成称为 sampling在 Pi、Claude Code 等项目中同一层概念通常表现为一次模型请求或一次 assistant response。先给出一个简要判断Pi更强调可组合性。核心 Loop 尽量少替开发者定义 policy。Pi相当于提供了一个基础框架这个框架的可扩展性比较强大家可以基于这个框架做更多定制化的Agent Runtime。OpenCode更强调显式控制。重复调用、步骤上限、重试与结束状态都被建模为可见规则。Codex更强调长生命周期运行。上下文、取消、恢复和重试被吸收到更厚的 Runtime 中。二、重复 Tool 调用谁来判断“这是不是死循环”这里的死循环指模型持续产生相同的 Tool Call尤其是反复以相同参数调用同一个工具且这种重复没有推动任务向前。2.1 Pi核心 Loop 只定义生命周期Pi 的核心实现位于packages/agent/src/agent-loop.tsrunLoop()负责把模型响应、工具执行、下一轮准备和 follow-up 消息串联起来。不过它没有在核心路径里维护“工具名 参数指纹”“重复调用次数”“最近 N 次工具调用”或“是否取得进展”等状态。这意味着Pi 不会在 core 层直接判定“同一个工具调用三次就是风险”。它把这个判断交给上层 Harness同时提供了足够的扩展点beforeToolCall可在调用前阻断工具。afterToolCall可观察调用结果。shouldStopAfterTurn可在当前 turn 后结束 Loop。Tool Result 中的terminate: true可结束当前工具批次。这是一种刻意的设计取舍。重复调用并不总是错误轮询后台任务状态就是典型例子。Pi 的立场更接近核心提供可控的生命周期上层应用自己决定什么算死循环OpenClaw小龙虾就是这么做的。2.2 OpenCode为重复调用提供显式 guardOpenCode 的相关实现位于packages/opencode/src/session/processor.ts它定义了一个很清晰的阈值const DOOM_LOOP_THRESHOLD 3当模型即将产生 Tool Call 时OpenCode 会检查当前 assistant message 最近三个工具 parts 是否使用了相同工具和相同输入。命中后它不会直接杀掉任务而是发出permission: doom_loop也就是说OpenCode 将“连续三次相同 Tool 相同 Input”视为一个需要权限层处理的风险信号。这个机制的边界也很清楚它检测的是调用模式不判断工具结果是否带来新的信息并且在v1.18.18中它读取的是当前 assistant message 的 parts不是跨多个 turn 的完整语义轨迹。因此这是一个低成本、易理解的 heuristic而不是通用的任务进展判断。2.3 Codex没有看到通用的重复调用阈值在 Codexrust-v0.148.0的 Runtime 主路径中没有看到与 OpenCode 类似的“同工具、同参数达到 N 次后拦截”机制。turn.rs的重心是 sampling、工具 Runtime、pending input、取消、Context budget、compaction 和 stream retry。换言之Codex 更关心“一个 Turn 如何在复杂边界下可靠地持续运行”而不是用一个通用阈值定义死循环。这也符合 Coding Agent 的实际需求。原始分析中提到Codex 的 awaiter agent instruction 明确允许重复调用工具直到后台任务完成如果采用简单的重复检测可能会误伤这类正当工作流。2.4 本节小结Pi 把死循环 policy 留给 HarnessOpenCode 直接实现了显式的重复模式检测Codex 没有看到通用 detector而是以 Runtime 生命周期控制承接风险。三、输出被截断不完整的 Tool Call 还能执行吗输出因长度达到上限而结束和生成过程中 stream 中断是两个不同问题。Pi、OpenCode 与 Codex 在这里分别优先保护工具安全、记录完成状态与恢复流式连接。3.1 Pi宁可失败也不执行可疑的工具参数Pi 会明确识别message.stopReason length当输出因 Token limit 被截断而消息中又带有 Tool Call 时Pi 不会正常执行这批调用而是将其标记为失败。这样做的原因是流式工具参数可能经由 JSON salvage parser 形成“形式上能解析、实际却缺字段”的对象。执行这类 Tool Call 可能带来不可靠的副作用。Pi 的恢复策略是不执行可能被截断的 Tool Call。将“参数可能不完整请重新发送完整调用”的 observation 返回模型。保持terminate: false让模型在下一轮重新生成工具调用。它不是从文字断点继续续写而是先保护工具执行边界再让模型生成一份完整请求。3.2 OpenCode保留 finish reason但未见专门的 length recoveryOpenCode 会将 provider 的value.reason保存到assistantMessage.finish并记录step-finish。Session Loop 会根据这些状态判断当前消息是否已完成。不过在v1.18.18的主路径中没有看到专门针对finish length的恢复状态机例如自动要求模型从断点继续、提高输出上限或将截断的 Tool Call 统一判为无效后重发。因此准确结论是OpenCode 会保存并建模 provider 的 finish reason但至少在该版本的 Session Loop 中没有看到像 Pi 一样针对 token-truncated Tool Call 的专门策略。这不等于它完全“不支持截断”。3.3 Codex重点处理 stream failureCodex 更需要区分“输出正常到达上限”和“模型尚未完成、stream 却中断”两种情况。对 stream failure源码中的策略更明确DEFAULT_STREAM_MAX_RETRIES:u645;DEFAULT_STREAM_IDLE_TIMEOUT_MS:u64300_000;也就是说流断开后最多会重连或重新 sampling 五次连续五分钟没有 stream activity则视为 stream lost。从已经检查的0.148.0代码看没有足够依据说 Codex 具备 Pi 式的“length后让所有 Tool Call 失效再让模型重发”的路径。Codex 在这一维度更明显的设计重点是把 stream error 视为可重试的 Runtime error。3.4 本节小结Pi 的原则是“不执行可能被截断的工具参数”OpenCode 会保存结束原因但没有看到专门的 length 恢复逻辑Codex 则更偏向恢复异常中断的流式生成。四、停止条件什么时候才算一个任务真正完成模型当前消息没有 Tool Call并不必然表示整个 Agent 任务已经结束。三者的区别在于停止判断放在 Loop、Session还是 Turn Runtime。4.1 Pi提供清晰的 Loop 边界Pi 的 core Loop 主要在以下情况下停止模型返回error或aborted。shouldStopAfterTurnhook 要求结束。当前工具批次通过terminate: true满足终止条件。没有待执行 Tool Call也没有 steering 或 follow-up 消息。Pi 的职责是提供可预测的生命周期边界“任务是否已经足够完成”“是否达到业务上限”等更复杂的 policy仍由上层 Harness 决定。4.2 OpenCode由 Session 是否进入终态决定OpenCode 的停止判断更像状态机。它会检查最新 assistant 是否已有finish、是否不是tool-calls、是否没有剩余工具调用以及这条消息是否对应最新 user request。满足这些条件后Session Loop 才认为用户得到了完成的响应。除了正常完成它还处理以下明确路径达到agent.steps通过MAX_STEPS_PROMPT引导最后一轮。processor 返回stop。structured output 完成。content-filter 导致当前处理结束。用户 Interrupt 被转换为AbortError并 finalize 当前 assistant。因此OpenCode 不是简单地以“没有 Tool Call”作为停止条件而是以 Session 的状态转换作为最终依据。4.3 Codex一个 Turn 可以包含多次 sampling 与 compactionCodex 的一个用户 Turn 可能经历多轮内部循环模型生成、工具执行、再次生成、必要时 compact 后继续。因此一个 Turn 的结束不等于一次 sampling 的结束。它的核心判断是needs_follow_up只要模型仍需要后续处理或仍存在 pending input当前 Turn 就不算完成。Token/Context 压力也未必导致停止Runtime 可以先 compact 再继续。取消令牌、Turn error、hooks 与 compaction 失败等则是其他可能结束 Turn 的路径。与 OpenCode 的 Session 状态机相比Codex 更像围绕 Runtime lifecycle 推进和收尾。4.4 本节小结Pi 以 Loop 边界为主OpenCode 以 Session 终态为主Codex 以 Turn 是否仍需 follow-up 为主。三者不适合硬套为同一组固定数量的“退出类型”。五、LLM API 失败重试放在 Agent、Session 还是 Runtime重试策略不仅决定重试几次也决定失败信息在哪一层可见、谁可以取消等待、谁可以统一执行策略。5.1 PiAgent 层集中重试Pi 的 Coding Agent settings 中包含retry.enabled true retry.maxRetries 3 retry.baseDelayMs 2000 retry.provider.maxRetries 0默认的 Agent-level 退避节奏是 2s、4s、8s同时 Provider SDK 默认不自行重试。这种设计让 Provider failure 回到 Agent layer再由 Agent 决定是否重试而不是让 SDK 因较长的Retry-After在内部静默等待。Provider 层仍支持timeoutMs、maxRetries与maxRetryDelayMs其中maxRetryDelayMs默认 60 秒。5.2 OpenCode独立、显式的统一 Retry PolicyOpenCode 的重试逻辑集中在packages/opencode/src/session/retry.ts它对 429、常见 5xx、服务过载、连接被拒绝或丢失、ECONNRESET、ETIMEDOUT以及 request/response/stream timeout 等情况都有分类。其默认策略是 2 秒起步的指数退避、2 倍 backoff、25% jitter最多重试 5 次如果服务端返回Retry-After或Retry-After-Ms则优先使用服务端建议。processor 通过Effect.retry(SessionRetry.policy(...))采用这一策略。三者之中OpenCode 的 retry policy 最集中也最容易直接阅读、调整和复核。5.3 Codex请求失败与流中断分两层处理Codex 把重试拆为两个范围。第一层是 HTTP request retry。默认配置的核心边界是DEFAULT_REQUEST_MAX_RETRIES4retry_429:falseretry_5xx:trueretry_transport:true也就是说在这个版本中5xx 与 transport/connection failure 会进入普通 request retry而直接 HTTP 429 不会走这条普通策略。retry_429: false是重要的版本事实不能简单套用其他 Coding Agent 中“429 必然指数重试”的判断。第二层是 stream retry。流建立后如果中断最多会进行 5 次 stream retry如果五分钟没有 stream activity则认为流已失效。这种分层意味着请求还未建立成功与流已经建立但在生成中断开由不同的机制负责。六、结语同一个 Agent Loop 问题三种不同的责任边界问题PiOpenCodeCodex模型乱循环Harness 自定义 policydoom_loopheuristicRuntime lifecycle未见通用重复检测输出截断拒绝执行不完整 Tool Call保存 finish reason重点处理 stream/error retryAgent 何时停止Loop boundary 与 hooksSession 到达终态Turn 不再需要 follow-upLLM 服务失败Agent-level bounded retry统一 Retry Policyrequest retry stream retry从功能清单看很容易得到“谁有、谁没有”的扁平结论但源码真正值得比较的是每个项目把控制策略放在了哪一层。Pi 的价值在于可组合核心不抢占 Harness 的业务判断。OpenCode 的价值在于显式重复调用、步骤数、状态和重试都形成可见规则。Codex 的价值在于 Runtime它将上下文、恢复、取消与持续执行统一进 Turn 生命周期。所以Agent Loop 的差异最终不在于“是否有一个while循环”而在于系统如何划分继续、恢复、收尾与停止的责任边界。
返回列表