三条能终止的护栏:长任务不再被轮次墙卡死
一句话结论:一个 30 分钟长任务,曾经被轮次墙、token 预算、上下文闸、各种超时共 30 多处「终止点」同时盯着,任何一个都可能提前掐死它。我们把这些全收掉,只留下三条硬终止:用户取消、授权预算耗尽、不可恢复错误。护栏的职责从「判决」变成「降级 + 续接」。
一个长任务会被多少东西掐死
做一次全量排查,数出 37 个能让任务结束的位置:7 个合法,30 个要改。它们分好几类:
| 类别 | 例子 |
|---|---|
| 轮次上限 | 默认 12 轮硬上限 |
| token 预算 | 累计 204800 撞线 |
| 上下文闸 | 把「累计消耗」误当成「当前上下文占窗口比」 |
| 时间墙 | worker 会话 300s、节点 1800s、整链 30min、拆解 30s、评审 15s |
| 审批超时 | 用户没表态被当"拒绝",节点 FAILED |
| 异常路径 | panic recover、调度器静默跳过 |
问题不在数量,在于每道闸都是"判死":到了就写终态,任务直接结束。结果是长任务跑到第 12 轮被"轮次墙"腰斩,模型明明还在干活。
核心转变:护栏只保留三条硬终止
最终拍板的口径:
- 用户取消——必须有用户动作或明确表态。
- 授权预算耗尽——开始前就告知且可见的 token / 时长 / 算力点。
- 不可恢复错误——重试换档换模型后仍失败,且与任务无关(401、模型不存在、license、内容安全、panic、余额)。
除此之外的"墙",一律不判死。
三级护栏:从判决到降级
把原来的判决点改成三级:
| 级 | 触发 | 行为 |
|---|---|---|
| L1 提醒 | 上下文 60%、重复 1 次 | 注入提示,继续跑 |
| L2 降级 | 上下文 85% | 压缩 → 换模型 → 缩范围 → 换工具 → 打回 → 重切 |
| L3 交卷 + 自动续接 | 预算到顶 | 收工,缺的自动追加续接轮补做,不终止 |
关键是 L3:预算到顶不再写 failed,而是交卷 + 续接。正常任务能撞到的闸,就是画错的闸——这条判断线直接把绝大多数"墙"判成了设计错误。
为什么「预算耗尽」对个人版不成立
一个细节值得记:企业版有"授权额度"概念(多租户要控成本),但个人版没有。用户只会说"最多花多少钱",不会说"多少 token",系统无法量化。所以个人版的终止条件最终只定两条:用户取消、不可恢复错误。预算类卡点全部拆除,只保留"无进展 360s"作为唯一技术性判死,且必须先穷尽降级。
踩过的坑
- 上下文闸量错对象:传的是"本次循环累计消耗",语义却是"当前上下文占窗口比",累计 188k / 窗口 131072 = 143%,直接判满;单轮真实只占 8%~17%。
- A3 预算砍半回归:把预算从 204800 改成窗口×0.8=104857,等于砍半,比的是累计消耗——老错误回归。
- worker MaxTurns 显式传 12,绕过了"0=不限"的开关,导致开关从未生效于 worker。
结论
长任务的难点,不是"能不能跑久",是"该停的时候才停、不该停的时候别停"。把 30 多道判决收成三条硬终止 + 三级降级续接,任务第一次能跑满 30 分钟而不被自己画错的闸掐死。