Skip to content

[PR] fix(pi-subagents): watchdog timeout no longer falsely reports completed; empty reply diagnostics include upstream termin | fix(pi-subagents): watchdog 超时不再误报 completed;空回复诊断带上游终态 - #4411

Open
ranxianglei wants to merge 10 commits into
masterfrom
2026-10-23_delegate-timeout-status

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Closes #4359

修了什么

1. watchdog 超时被误报为 completed(状态误判)

旧行为:异步子进程被 watchdog 杀掉(code===null、run.timedOut 置位)时,只要还有任何非空进度文本或 stderr,effectiveExitCode 就会强制为 0,随后 run.status = effectiveCode === 0 && !noOutput ? "completed" : "failed" → 部分输出 alone 就能把 run 标成 completed,父 agent 收到成功通知但没有任何交付物。

新行为:新增纯函数 classifyDelegateOutcome(code, timedOut, agentSettled, output, stderr)。completed 要求有效退出码成功且有正向完成证据:真实非 null 退出码且无 watchdog 超时,或观察到 agent_settled。watchdog kill 只在 agent 未 settle 时记 timedOut(onKill/onEofGrace 由 agentSettled 门控),因此 timeout + 未 settle = 一律 FAILED,与有无部分文本/stderr 无关。

settled 例外保留:已交付输出但进程在 teardown/EOF 挂起(code===null、agentSettled=true、timedOut 未置位)仍算 completed。agentSettled=false 时该规则与同步路径三元式(timedOut → failed)逐行投影一致,async/sync 语义对齐。

行为变更披露:未 settle 的 run 上,watchdog kill / 外部 null-code 关闭 / spawn 错误且带有部分文本或 stderr 的情形,现在报告 FAILED ⚠️(旧值:completed)。结果文件与活动日志不变,仍可人工检查。这正是 issue 报告的误判场景。

2. 空回复诊断区分上游 error/incomplete 与 completed-empty

事件 applier 捕获最后一条 assistant message_end 的终态:stopReason / rawStopReason / errorMessage(含 error 字段回退)+ text/thinking 块计数 + 该轮 usage。失败正文、结果文件回填、warn 日志各追加一行 upstream terminal state: stopReason=…[, rawStopReason=…][, errorMessage=…] , text_blocks=N, thinking_blocks=N[, , last_turn_usage(in=A, out=B)];未观测字段渲染 unknown,print 模式(无事件流)渲染 unavailable。NO_FINAL_OUTPUT_BODY 不再断言 "likely a thinking-only stop"(那只是可能原因之一),原因判断交给诊断行。响亮失败的行为本身不变。

测试

  • tests/delegate-no-output.test.ts:11 行终态矩阵(含两行此前误判)+ async/sync 一致性投影断言。
  • tests/events.test.ts:issue 中 A/B/C 三种 message_end 形状提取、terminal-only 事件、非 assistant 跳过;顺手把该文件 4 处既有坏类型断言换成收窄断言。
  • tests/delegate-event-applier.test.ts:applier last-wins 捕获、terminal-only 事件不触发 onUsage。
  • tests/delegate-tool.test.ts:formatter 渲染 + 基础文案回归。
  • 本地定向运行:改动模块相关测试全部通过;src typecheck 干净;三个改动的测试文件 strict tsc 均退出 0。全量判定以 CI 为准。

第二个 commit(顺手小修,独立标注)

做 #4359 时发现 tests/delegate-tool.test.ts 存在 6 处既有 strict-tsc 错误(测试文件从不被 CI strict typecheck,所以一直不可见):injectedWaitMessage 参数 result?: { file: string } 过窄(运行时按 truthy 安全读取,测试名就叫 "tolerates a missing file")→ 放宽为 file?: string(仅类型、向后兼容);4 个本地 run 字面量缺 usageReported → 补 false;wait-timeout 集成测试以 2 参调用 cancelTool.execute → 对齐兄弟调用补 (undefined, undefined, ctx)。该文件 strict tsc 现退出 0,108 个 delegate-tool 测试全过。

版本

pi-subagents 0.2.2 → 0.2.3(Pi-Subagents Boundary 要求);根 package-lock.json 连带一行。

范围外(另行处理)

  • 内外预算冲突(case D):README 已加提示(嵌套 sync delegate 自带 timeoutMinutes 超过外层剩余预算会被外层先杀);向子进程传递剩余 deadline 属独立变更。

@ranxianglei ranxianglei changed the title fix(pi-subagents): watchdog 超时不再误报 completed;空回复诊断带上游终态 [PR] fix(pi-subagents): watchdog timeout no longer falsely reports completed; empty reply diagnostics include upstream termin | fix(pi-subagents): watchdog 超时不再误报 completed;空回复诊断带上游终态 Oct 23, 2026
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-23_delegate-timeout-status (270ae59)

Latest build — install this exact version

npm install -g billion-context@0.0.7200-pr.4411.5ea79e8

This exact version is immutable and reproducible. The rolling pr tag tracks the newest test build across all PRs.

Recent builds (commit → version)

commit version build
270ae59 billion-context@0.0.7200-pr.4411.5ea79e8 build
ff7cd62 billion-context@0.0.6185-pr.4411.169ce07 build
84088f2 billion-context@0.0.6098-pr.4411.898bfb4 build
3819b3c billion-context@0.0.6002-pr.4411.e571439 build
0572473 billion-context@0.0.5310-pr.4411.70138cb build

Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr4411.tgz
npm install -g package

Install instructions are refreshed on each push.

awork added 2 commits October 23, 2026 23:08
…; empty-reply diagnostics carry upstream terminal state (#4359)

Two defects in the async delegate lifecycle:

1. Status misclassification. When the watchdog killed a non-settled child
   (code===null, run.timedOut set), partial progress text or stderr alone
   coerced effectiveExitCode to 0 and the run was marked "completed" — the
   parent got a success notification with no deliverable. New pure rule
   classifyDelegateOutcome: "completed" requires a successful effective exit
   AND positive completion evidence — a real non-null exit code without a
   watchdog timeout, OR agent_settled observed. The settled exception is kept
   (delivered output whose process hung in teardown/EOF still completes); with
   agentSettled=false the rule projects exactly onto the sync ternary, so
   async/sync semantics are aligned. Behavior change: watchdog kills, external
   null-code closes, and spawn errors of non-settled runs now report FAILED
   (artifacts kept for inspection) instead of completed when partial output or
   stderr exists.

2. Empty-reply diagnostics. Exit-0 empty replies previously got one generic
   "likely a thinking-only stop" message. The event applier now captures the
   last assistant message_end terminal state (stopReason/rawStopReason/
   errorMessage + text/thinking block census + that turn's usage); the failure
   body, result-file backfill, and warn log carry an "upstream terminal state:"
   line separating upstream error/incomplete from completed-but-empty.
   Unobserved renders as unknown; print mode says unavailable. NO_FINAL_OUTPUT_BODY
   no longer asserts a cause itself.

pi-subagents 0.2.2 -> 0.2.3 (+ root lockfile line). Regression coverage:
terminal-state matrix incl. the two misclassified rows, async/sync parity
projection, message_end extraction on issue A/B/C shapes, applier last-wins
capture, formatter rendering. README: timeout-is-failure semantics plus a
nested inner/outer budget-conflict hint.
…-tool.test.ts (found while working #4359)

Six latent type errors the test file carried before this PR (test files are
not strict-typechecked by CI, so they stayed invisible):

- injectedWaitMessage's run parameter typed result?: { file: string } while
  the runtime reads run.result?.file truthy-safely and the test named
  "tolerates a missing file" deliberately passes result: {} — loosen to
  result?: { file?: string } (type-only, backward compatible).
- Four local run literals in the buildWaitResult/buildCancelResult usage
  tests lacked usageReported, which the tests then assert flips to true —
  seed usageReported: false.
- The wait-timeout integration test called cancelTool.execute with 2 args
  against the 5-arg Tool.execute signature — pass the same trailing
  (undefined, undefined, ctx) its sibling wait call passes.

Strict tsc on the file now exits 0; all 108 delegate-tool tests pass.
@ranxianglei
ranxianglei force-pushed the 2026-10-23_delegate-timeout-status branch from ddc9d57 to 0572473 Compare October 23, 2026 15:14
ci-check and others added 8 commits October 26, 2026 10:18
…uires (#4599)

The #5140 recovery test proves forkSnapshot reconstructs a non-retained
parent's raw tail from the UNIFIED index — but it never pinned its own
storage mode. Under the CI storage baseline (preload-storage-baseline.mjs
sets BILI_STORAGE_UNIFIED=legacy for the broad corpus) no unified rows
exist, so the reconstruction has no source and forkSnapshot fails closed
with 409 SNAPSHOT_UNAVAILABLE. Deterministic, not a flake: reproduces 100%
on master and any merged branch with BILI_STORAGE_UNIFIED=legacy, passes
without it (default shadow). Pin full like the sibling persist tests and
restore the ambient value afterwards.
@ranxianglei ranxianglei added bug Something isn't working p2 pi scope/wiring labels Oct 30, 2026
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b)

查重: 不重复。 watchdog 误报 completed / 空回复诊断两条签名在 open+closed issue、PR 中均无同机制条目;最接近的 #2832(已关,preflight 摘要探针路径)与 #3745(acp_delegate_wait 展示层)子系统不同。本 PR 即 #4359 的修复 PR(floor 2 有完整分析记录)。

标签: bug / pi / scope/wiring / p2——pi 车道子代理缺陷,协议接线层,有绕行(结果文件仍可人工检查)。

现状核查(本 PR 为此前 agent 工作,未重做,只核分支与 CI):

  • 分支净变更 9 文件 389+/52-,全部在 pi-subagents/(delegate-events.ts、delegate-tool.ts + 4 个测试)+ 根 package-lock.json 一行,零 kernel/ 文件。
  • CI(head ff7cd62):40 项检查 = 23 绿 + 16 跳过 + 1 红:kernel-guard。

kernel-guard 红 = 陈旧基线假阳性,不是本 PR 改了内核:取证(job 日志 2026-10-26T08:17Z)显示触发时 master 还落后于分支经 #4599 ci-fixes sweep 带进的三处内核变更(kernel/package.json、kernel/src/compress.ts、kernel/src/sync.ts),merge ref 的 diff 非空 → 守卫索要 kernel-change: 声明。这三处现已全部落在当前 master;分支对当前 master 的净 diff 里 kernel/ 为零。因此不补声明(补了就是虚假声明),正确处置是把分支基线同步到当前 master 后重跑。

正在做:把最新 master + ci-fixes 合进分支推送重触发 CI(master 自身目前在若干 test 分片上红的在档继承家族见 #4599;合入 ci-fixes 会带入 #5140 存储模式钉,对应分片预期转绿,剩余 Windows 家族项与本 PR 无关)。完成后回帖八问结论。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b)

收工报告(八问)

  1. 是什么问题:本 PR 修 pi 子代理两个缺陷——①watchdog 杀掉异步子进程后,只要有部分文本/stderr 就把 run 误报 completed(父 agent 收到成功通知但没有任何交付物);②空回复诊断分不清上游是 error/incomplete 还是 completed-empty。本轮我的任务不是重做修复(此为既有 agent 工作,[Bug] pi-subagents: Incorrectly reports "completed" when watchdog times out with only partial output (empty-reply termin | [Bug] pi-subagents:watchdog 超时且仅有部分输出时误报 completed(附空回复终态对照) #4359 floor 2 有完整分析),而是核分支、查 CI、处理唯一红项。
  2. 什么原因导致的:CI 唯一红项 kernel-guard 的根因是陈旧基线假阳性,不是本 PR 改内核——触发时(10-26 08:17Z)master 还落后于分支经 CI Control Center: Centralized CI Failure Handling & Status Sync (Comment all CI issues here; do not open new issues) | CI 总控: CI 失败集中处理与状态同步(所有 CI 问题在此评论, 勿开新 issue) #4599 ci-fixes sweep 带进的三处内核变更(kernel/package.json、kernel/src/compress.ts、kernel/src/sync.ts),merge ref diff 非空 → 守卫索要 kernel-change: 声明。job 日志与本地 tree 级比对均已取证。
  3. 影响范围与严重程度:原缺陷影响 pi 车道所有异步 delegate 超时场景(p2,有绕行:结果文件与活动日志仍可人工检查)。本轮基线同步对 PR 内容本身零行为变更。
  4. 怎么修复的:PR 内容不动。处置 = 把分支基线同步到当前 master:merge origin/master@44067a5bc + origin/ci-fixes@67f8e8940(零冲突;ci-fixes 带入 [Bug][fork] Cold restore cannot use an intact unified raw tail for a non-retained parent (#4266 item 4) #5140 存储模式钉,tests/plugin-fork.test.ts +9/-1,舰队惯例,[PR] ci-fixes: master-CI convergence — docs surface registration + #4666 v9 stamp #5208 落地后自动退出 PR diff),推送新 head sha=270ae5941。未补 kernel-change: 声明——本 PR 对当前 master 净 diff 里 kernel/ 为零,补了就是虚假声明。
  5. 是否有回归问题:审过的既有行为——①settled 例外保留(已交付输出但 teardown/EOF 挂起仍算 completed);②agentSettled=false 时与同步路径三元式逐行投影一致;③NO_FINAL_OUTPUT_BODY 只删了一句推测性断言("likely a thinking-only stop"),响亮失败行为不变;④结果文件/活动日志写入路径未动;⑤第二个 commit 纯类型放宽 + 测试字面量补齐,无运行时变化。本地定向验证全绿:pi-subagents strict tsc 干净、改动 4 个测试文件 162/162、组件全量套件 279/279、根 typecheck(含 kernel+pi-subagents 构建)干净;kernel-guard 规则本地模拟 PASS(净 diff 零 kernel/ 文件)。
  6. 是否破坏缓存:不涉及(不触碰缓存/重试/游标/去重路径)。
  7. 是否改了设计方向:否。状态判定收敛为纯函数 classifyDelegateOutcome + 诊断信息增强,均在 pi-subagents 层内,无架构/边界转向。
  8. 是否引入新配置:否(无新 env/config/默认值)。

行为变更披露(PR 正文已载,复核确认):未 settle 的 run 上,watchdog kill / 外部 null-code 关闭 / spawn 错误且带部分文本或 stderr 的情形,现在报 FAILED ⚠️(旧值:completed)。这正是 issue 报告的误判场景,属有意变更。

CI 状态:新 head 已触发(90 checks,其中 47 queued——舰队 runner 队列积压在档见 #4599,43 cancelled 为被取代的旧 run)。预期开跑后静态门禁全绿;test 分片若命中在档继承家族(#5140 已被带入的钉覆盖;Windows 家族项待 owner 裁决,与本 PR 无关)按 #4599 协议处置。已在 #4599 留一行同步记录。

判定:分支侧就绪(kernel-guard 假阳性已消除、本地两层测试全绿),待 CI 矩阵消化完队列确认绿后即可合并——合并动作在你手里:#4411

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working p2 pi scope/wiring

Projects

None yet

1 participant