
opencode 我们为什么删除了看门狗机制本文基于 opencode-goal 插件(开源,SDK 1.18)真实实现编写。主题:旧版独立 watchdog 定时器的删除理由,以及卡死救护如何内聚进 TICK 门链。1. 旧架构旧版存在两套后台定时器:TICK(1 秒):驱动自动续推;独立 watchdog(按配置时限):自动中止卡死的子代理。watchdog 的删除(2026-08-29 TICK 重构)不是能力削减,而是职责重分配:卡死救护仍存在,但决策移入 TICK 内部。2. 删除理由2.1 多驱动路径 多故障源watchdog 的 abort 是独立于 TICK 的派发路径。会话存在两条驱动路径(TICK 续推 watchdog 中止)时,日志、竞态、错误处理全部翻倍。单一驱动器原则下,任何自动动作都应当由同一个循环决策。2.2 计时与事实脱节watchdog 按自身计时器计数,而会话活动由事件刷新。两者不同步时,看似卡死、实则刚活动过的会话会被误中止。误杀子代理的代价(上下文丢失)远高于等待。2.3 定时器生命周期watchdog 需要按会话启动/重置/取消,并与压缩、重启门、中止判定交错。多定时器时序难以穷举验证,边界 bug 集中于此。2.4 与既有原则冲突系统原则为事件只记录状态,TICK 才做决策。watchdog 是事件/时间驱动的兜底,违背该原则,保留即持续引入复杂度。3. 替代设计:卡死救护内聚 TICK删除 watchdog 后,救护逻辑成为drive_session门链的一环:快照显示 busy/retry? → session_busy_since 记录首次出现时刻(单调 TICK 时钟) → 超过 STUCK_RESCUE_STALL_MS(默认 90 秒,env GOAL_STUCK_STALL_MS) 且期间无任何事件(逐事件刷新窗口) 且无进行中工具(tool_inflight 非空则豁免) → session.abort 一次解除卡死;每会话每停滞窗口至多一次 → 否则跳过关键性质:判定基于权威快照 事件印记,而非独立计数器(无脱节);单一时钟、单一循环、单一状态源;工具豁免:长工具调用(如 5 分钟 bash)期间不误杀;合法长生成持续产生part.updated事件,自动续刷窗口,不会触发。3.1 救护指纹归属救护 abort 产生的错误与用户 ESC 同为MessageAbortedError/Aborted。救护发起时记录时间戳;指纹 5 秒窗口内到达归因救护、不暂停目标。完整判定链见《opencode 用户操作中断取消判定》。3.2 子代理边界主会话卡死:TICK 自动 abort;子代理:不设自动中止(自动杀子代理的进度损失风险大于收益),由 TUI 伴侣插件提供手动入口(命令面板 → Poke session → Abort)。4. 收益与边界维度删除前删除后驱动路径TICK watchdog单一 TICK卡死判定依据独立计时器(可与事实脱节)权威快照 事件窗口定时器多套交错一套,dispose 一次清理子代理救护定时自动中止仅手动(TUI)边界:若会话在 90 秒内不产生事件且无进行中工具,会被视为停滞(合法静默场景罕见,且每停滞窗口仅 abort 一次,不无限重试)。指纹归因窗口 5 秒,救护 abort 事件本地即时到达,窗口内可收。约束要点:任何自动动作(续推、压缩、救护中止)都必须由单一循环决策,禁止为单个动作引入独立定时器或事件触发路径;卡死判定必须以权威快照与事件窗口为据,禁止以独立计数器代替。5. 验证goal-noprogress-test.mjs/goal-user-abort-test.mjs:停滞救护与指纹归属;goal-fault-injection-test.mjs:SDK 挂起/重放注入后救护触发且目标不暂停;goal-regression-gates-test.mjs(13 断言):门链回归。6. 结论删除 watchdoog 的实质:救护不是TICK 之外的另一个看守,而是TICK 自己决策该救什么。单一驱动循环 权威状态 确定性门序,使卡死处理成为可验证的一环,而非散布的补丁。