为什么需要回程闲置保护?

为什么需要回程闲置保护?

对象:opencode-goal 插件的子代理静默死亡巡检(sweep_idle_children + prompt_sent_at 登记)。本文讲清它为什么存在、为什么只能是现在的形状------每一个"只能",背后都是一次真机事故。

0. 导语:两种死法,一种看不见

2026-09-06 下午,il2cpp R* 主代理下十几个子代理同时遭遇 provider 熔断(APIError: temporarily unavailable,17:06--17:11 波及十几个会话)。熔断过去后,问题来了:有些子再也没说话。父等回程,等到天荒地老。

子代理有两种"消失":

  • 异常死:模型报错、会话终结------有错误事件,可走异常上报(首错即报、同签名去重),父立刻知道;
  • 静默死 / 完工不报 :无错误事件,转 idle,不回程------父没有任何信号,只能干等

第二种是本巡检存在的全部理由:派发后子代理既无回程又无响应,只能判定挂了。没有这个巡检,静默死的子= permanently lost,父的调度闭环永远等不到它的那一块。

1. 第一次实现错在哪:对所有空闲子挨个发消息

第一版巡检的逻辑是朴素的:转空闲超窗(idle_nag_ms,默认 120000)且无 relay 回程 → 通知父。结果真机炸了:同一子"空闲未回程"x10、"已挂"x13,每 2 分钟一轮,几十条通知淹没父会话。根因两条,都是"把不该盯的也盯了":

  1. 从未派发的裸空闲子也被盯:重启遗留、已完成、abort 过的子,全在 children 列表里,全被当成"未回程";
  2. busy 观察重置计时:复活消息会让死孩子短暂 busy,旧代码 busy 即清零重计时------"复活→忙→再闲→再报"无限循环。

教训:提示的权力必须有边界。知道≠打扰,能看见≠该说话。

2. 现在的形状:成对 1:1,有派才有盯

重写后的巡检(src/server.ts,sweep_idle_children)核心只有三行门禁:

ts 复制代码
const sent_at = prompt_sent_at.get(kid);
if (sent_at === undefined) continue;      // 从未派发:零打扰
if (now - sent_at < nag_ms) continue;     // 窗内:等
if (idle_nag_state.get(kid)?.revived) continue; // 本集已复活过:额度用完
  • 登记点只有一处 :manage_subagent prompt 派发成功时 prompt_sent_at.set。没派发就没记录,没记录就永不打扰------重启/kill 后内存清空,旧子自动退出视线;
  • 清除点三处:relay 回程(成对完成)、abort 人工接管、新一集重派(重置);
  • busy 只跳过不重置:工作中(含消化复活激活)不打扰,也不重计时。

3. 动作:复活一次,上报一次,用完即停

超窗未回程只做两件事,每派发集各一次:

  1. 向子发存活检查与唤醒(要求它经 relay 回程报告状态);
  2. 向父发"已挂":该子代理派发后超窗零回程零响应(1:1 未应答=挂了) + 复活结果 + 处置(重派或 task 新建承接)。

之后无论子回不回程,插件不再重复打扰------额度用完。父要救,自己重派(重派=新一集,新额度)。复活投递失败(会话已终结)直接提示父新建承接,不空等。

4. 真机证据:两边都验证过

  • 反面(旧逻辑):同一子"空闲未回程"x6--11、"已挂"x3--13,时间戳每 ~2 分钟一轮,父会话通知刷屏------这就是改写的直接动因;
  • 正面 (新逻辑):provider 熔断波下,日志连发 dead child rescue ... revived: true,父会话收到 181 次异常上报/已挂通知------每子一次,不多不少;重启后旧子零打扰,洪水消失;
  • 边界 :send_prompt 超时有界(10 秒),失败返回 false,不冻结 TICK;巡检整体 try/catch,失败静默,绝不阻断主循环。

5. 如何验证

scripts/goal-idle-nag-test.mjs(10/0),每条都是一个行为契约:

  • F1 裸空闲超窗零通知;F2 派发超窗判挂上报 1 次+复活 1 次;F3 无新证据不重复;
  • F4 忙→闲不新开集;F5 回程后零打扰;F6 重派=新额度可再判挂;F7 abort 后零打扰。

线上:搜 dead child rescue 看复活记录;数父会话"已挂"通知(应每子每集一次,多一次即回归)。

6. 一句话总结

回程闲置保护不是"盯着所有空闲子",而是"只盯自己派出去没回来的":有派必有应,超窗唤醒一次、上报一次,额度用完即停。知道的权力,配上不打扰的义务。

相关推荐
七牛开发者1 小时前
HarnessDev:让 LLM 自己创建并迭代 Agent Harness
chatgpt·llm·agent
spencer_tseng2 小时前
AI coding 2026.09.07
ai·coding
梦想不只是梦与想2 小时前
大模型系列(三):提示工程、RAG 与 Agent
大模型·agent·rag
智码看视界2 小时前
Day68-结构化Prompt设计:XML标签法/Markdown法/JSON Schema
prompt·markdown·json schema·spring ai·结构化prompt·xml标签
桃西西呀2 小时前
红酒标签上的 87 分是怎么算出来的?我拿 1599 瓶真酒把线性回归和逻辑回归拆开讲
人工智能·机器学习·llm
知无涯者2 小时前
Agent 007 - Parallel Tool Use
agent
DevOps老兵3 小时前
AI Infra实战11:模型部署Pipeline——CI/CD自动化
人工智能·ci/cd·ai·自动化·gitlab
python零基础入门小白3 小时前
LangGraph智能体实战:如何用Langfuse构建AI运行时全链路可观测系统?
人工智能·学习·ai·chatgpt·程序员·大模型·智能体
面向Google编程3 小时前
Kafka 成了 Agent 的「共享内存」,Flink 成了它的「大脑」
flink·kafka·agent