-
## 病症:blocked 和墙钟预算互相拆台 墙钟按 `now - spent.started_at` 算,而 started_at 是「第一次花钱的绝对时刻」。 blocked 的定义恰恰是**挂起等人工** —— 预算耗尽、节点判罚都会把 run 打到 blocked。 于是:人隔夜来处置 → 等待那段被算成「已用」→ 一起跑就报 wallclock_ms 耗尽 → 再 blocked → 再等人。**一条过夜的 run 从此永远醒不过来。** 而且报出来的原因具有误导性:写着 wallclock_ms(跑太久),实际是等人等太久。 真事:run_9168bd24f46c(e2e-demo 做官网)08-18 15:48 起跑、16:19 判 blocked, 只花了 7/100 次调用、69,961/5,000,000 tokens —— **预算用掉不到 2%,两天没能重跑**。 它正是 27-p3c-batch3-handover §五 那条「还没解决的原始问题」。 ## 修法 spent 里改记 `active_ms`(累计活跃时长),started_at 退化成「当前这一段的起点」, 不在 running 时为 null。已用 = active_ms + 当前段。 起止结算挂在 graph-run-sse.afterRunTransition 这个**单一漏斗**上,不去枚举 「哪些地方会让 run 停下来」—— 枚举漏一条就是静默少算。 ⚠️ 钩子必须放在那个 early return **之前**:进 running 既不是收敛、也不空坑, 两个条件都不满足,放后面就变成「只结算停下来、从不开始计时」, 症状是 active_ms 恒 0、墙钟形同虚设,而且照样全绿。守卫里专门有一条钉这个位置 (反向验过:把钩子注释掉,那一条立刻红)。 spendBudget 不再兼职锚墙钟 —— 一个事实一处持有者。 ## 存量行 active_ms 缺失的行:旧锚点里混着等待时间,真实活跃时长复原不出来, 按 0 起算、一笔勾销。有意为之 —— 另外两维(调用次数 / tokens)是精确的且没受影响, 真正兜住花钱的是那两维,墙钟只是跑飞保护。所以那条卡住的 run 不需要动库, 重试进 running 时自动重新锚定。 ## ponytail 只认 running 一个态。manual 模式下两次点击之间 run 也停在 running, 那段人类思考时间仍会计入 —— 上限已知,比原来跨天累计小几个数量级。 要更准得按 agent 调用的起止配对计时,那是另一套账。 ## 顺带 goal-task-acceptance 两条用例原先 deepEqual 整个 budget 对象,现在剥掉 spent 只比申报的额度 —— 它守的是「用户填的额度没被静默换成默认值」, 跟运行期账本长什么样无关;账本另有断言(起跑那一刻 agent_calls/tokens 仍是 0)。 全量:137 文件 / 1284 测试全绿。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>