Skip to content
预计 1 分钟右侧目录可快速定位

长 Run 什么时候必须停:Long-running Execution 与 Termination ​

前置:历史执行怎样重放和分叉:Replay、Determinism、Fork 与 Time Travel

“可以运行几天”和“让一个进程活几天”是两回事 ​

一个代码修复任务可能今天搜索仓库,晚上等待 CI,明天等人工审批,再继续发布。逻辑 Run 持续两天是合理的;要求同一个 Python coroutine、同一个 HTTP connection、同一台 Worker 两天不退出则非常脆弱。

Long-running execution 真正要求的是:

text
logical Run survives
process restart
worker replacement
context reset
human wait
external event delay

连续性来自 durable state、checkpoint、artifact/workspace identity 和外部事件记录,不来自进程内对象。

长 Run 需要两套时间概念 ​

Active execution time:当前正在调用 Model/Tool 或执行 Node 的时间。它受 operation timeout、deadline 和 worker lease 约束。

Wall-clock lifetime:从 Run 创建到最终结束的总时间,中间可能包含数小时 WAITING。它不能用一个 HTTP request timeout 或 asyncio.wait_for() 覆盖。

例如:

text
RUNNING 30s
→ WAITING_APPROVAL 18h
→ RUNNING 45s
→ SUCCEEDED

这不是“18 小时的 coroutine”,而是持久化状态机经历了两个短 execution slice 和一个 durable wait。

Termination 不是一个 max_turns ​

一个 Run 是否应停止,需要多种独立条件:

text
completion evidence
hard deadline
max turns / max node attempts
token / money / tool budget
explicit cancellation
no-progress detection
policy rejection
unrecoverable failure

这些条件职责不同。max_turns 防止模型循环;deadline 防止任务无限拖延;cost budget 控制资源;cancel 是用户/系统命令;completion evidence 证明目标确实完成。

模型可以建议“还值得继续”,但不能覆盖 hard deadline、权限拒绝或预算上限。

No-progress 比单纯计数更能抓住 Agent Loop 失控 ​

例如连续出现:

text
search(query=A) → same 20 results
search(query=A) → same 20 results
search(query=A) → same 20 results

虽然只用了 3 turns,却已经没有新 evidence。Runtime 可以维护轻量 progress signal:

text
new artifact?
new observation hash?
plan changed?
state advanced?
completion criterion closer?

连续 N 轮无进展时不一定立即失败,可以先触发 replan、要求模型解释缺口,或降低可用工具;仍无进展才终止。这里检查的是可观察系统进展,不是让模型自评“我觉得有进展”。

Termination decision 应落成 durable fact ​

如果 Runtime 发现 cost budget 已超限,只在内存里 break 不够。需要持久化为什么结束:

python
@dataclass(frozen=True)
class TerminationRecord:
    run_id: str
    reason: str          # COMPLETED / DEADLINE / BUDGET / CANCELLED / NO_PROGRESS ...
    evidence_ref: str | None
    decided_at: datetime
    policy_version: str

这样重启后不会又把已终止 Run 放回 Ready Queue,Developer Console 也能解释“为什么停”。

对 success 更要谨慎:模型说“修复完成”只是语义判断;代码 Agent 的完成证据可能是 patch persisted + required tests passed + artifact produced。具体 completion contract 属于任务/domain,但 terminal transition 必须由 Runtime 验证后提交。

长 Run 退出时还要处理正在执行的工作 ​

Cancellation/termination 不是瞬间删除所有对象。Runtime 要决定:

  • 新 Node 是否禁止启动;
  • 当前 read-only Tool 是否允许完成;
  • 有副作用 Tool 能否安全取消;
  • 已完成结果是否仍持久化;
  • Worker 是否释放 lease;
  • stream 是否发送 terminal event;
  • pending approval/timer 是否撤销。

这些决定应由 policy + operation metadata 控制,而不是粗暴 task.cancel() 后假设世界已经回滚。

🔍 源码对照:Observation owner 会枚举 subagent / shell / cloud-agent 后台任务 ​

完整源码与通用分析见 09-01 Run Lifecycle / State Machine。这里不重复代码;这里关注 long-running termination:后台 task 必须先成为可枚举的 Runtime fact,系统才能在终止时决定 drain、cancel 还是继续观察。

源码说明

仓库把 background work、terminal watch 与 turn observation 分开,说明长任务不是“把 timeout 调大”就结束。Runtime 需要持续知道后台工作是否存在、最后活动是什么、当前 Turn 是否等待,以及什么时候把无进展判断成 stall/termination condition。

工程边界

案例中的具体 stall threshold、shell watch 规则是产品选择。Shadow Harness 的 termination policy 应与预算、用户 SLA、tool semantics 和可恢复等待机制绑定。

Shadow Harness 对照

  • Run termination 采用多维 budget:turns、wall clock、tool attempts、cost、no-progress。
  • 真正长期等待转成 durable WAITING,而不是一直占住进程/Task。

验证

构造“模型不断重复同一 read tool 但没有新 Observation”的 runaway loop,验证 no-progress detector 能终止且保留可解释原因。

验证长 Run 的正确方式 ​

最有价值的测试不是让 CI 真跑两天,而是控制时间和故障:

text
Fake clock advances beyond deadline
→ Run terminates once

Scripted Model repeats identical action
→ no-progress policy triggers

process dies while WAITING
→ new process reconstructs WAITING, not RUNNING

cancel committed while Worker executing
→ no new node starts after cancel fact

长运行系统是否可靠,关键看它能否不断“停下来再继续”,而不是单次 uptime 有多长。

关联源码 ​

自测 ​

  1. Agent 一直能提出“下一步”,但没有进展。除了 turn count,还可以用哪些 termination 条件识别无效长跑?
  2. Budget exhausted、user cancel、goal satisfied 三种终止原因为什么应保留不同 final status/evidence?

Sources ​

AI Engineering · Agent · Harness · Backend Systems