← [ BACK_TO_NOTES ]

Agent Interrupt / Resume Lifecycle

把 ask_user 一类 HITL 行为建模为 Agent run 的一等 interrupt:required → answered → resumed → completed,区分 resume input 与新聊天消息,并要求 interaction 可持久化、可回放、可刷新恢复。

#synthesis #growing #ai #architecture #frontend #agent #bodysense

[!info] related notes

Agent Interrupt / Resume Lifecycle

一句话定义

Agent Interrupt / Resume Lifecycle 把 ask_user 等需要外部输入的节点建模为同一个 Agent execution thread 的受控暂停与恢复,而不是把问题和回答伪装成两条普通聊天消息。

running
→ interaction.required
→ interrupted / waiting_user
→ interaction.answered
→ resume authorized
→ run.resumed
→ running
→ completed / failed / cancelled

为什么 HITL 是 Runtime Control Flow,不是 UI Widget

从 UI 看,ask_user 可能只是:

一个问题卡片
+ 几个按钮

但从 Agent runtime 看,它改变的是:

execution can no longer make progress
until a typed external input arrives

所以核心不是“弹一个 modal”,而是:

Running computation
→ persisted waiting state
→ external input contract
→ validated resume transition

这和普通聊天 input 完全不同。

如果只在前端展示卡片,却没有 durable interrupt state,那么:

  • refresh 后等待点丢失;
  • 双击 answer 可能触发两次 resume;
  • 网络失败后不知道回答有没有保存;
  • replay 无法解释模型为什么后面改变路径。

因此 HITL 首先是 runtime architecture,UI 只是 projection。

为什么 ask_user 不是普通 Tool Call

普通 Tool Call 常见语义:

Agent asks tool
→ tool executes immediately
→ tool result returns
→ Agent continues

而 ask_user 需要:

Agent asks human
→ run cannot continue now
→ UI renders question
→ user may answer seconds/minutes/hours later
→ answer is validated/persisted
→ same semantic execution resumes

它改变了 run lifecycle。

因此它不是简单的:

tool.call + tool.result

而是:

runtime interrupt

为什么用户回答不是新的 User Chat Message

假设 Agent 问:

“麻木是否延伸到拇指?”

用户选择:

“是”

如果前端把“是”作为新独立 chat message:

Conversation
├─ Turn 1 user symptom
├─ Turn 1 assistant asks question
└─ Turn 2 user says yes

系统会模糊:

  • 这个“是”回答哪个问题?
  • 是否属于原 run?
  • 如何关联 tool_call_id?
  • reload 后怎么知道它是 interaction answer?
  • retry 时会不会重复创建新 run?

更准确的模型:

Execution Thread T7
├─ Interaction X3 required
├─ X3 answer = yes
└─ T7 resumed with answer(X3)

所以:

Answer is resume input, not a new task intent.

为什么“同一个 Agent execution”与“同一个 public Run”要分开理解

这是 Consultation 中很容易混淆的一层。

Python/LangGraph 可能有一个长期 semantic thread/checkpoint identity:

Thread T7

Go/public runtime 可能把不同 execution segments 建模成:

Public Run R10
→ interrupted

resume command
→ Public Run R11 / resumed segment

具体项目可以选择同一个或新的 run identity,但必须明确。

所以至少要区分:

Semantic/Checkpoint Thread Identity
= 哪一条 Agent execution history 被继续

Public Durable Run Identity
= 对外业务/事件 envelope 怎样标识本次 execution segment

这两个概念不能因为都叫 “run” 就混在一起。

为什么 Identity 区分影响 Replay 和 Provenance

如果 resume 继续的是:

Thread T7

但产生新的 public Run R11,那么历史要能表达:

R11 resumes interrupted R10 / Thread T7

否则 audit 时会误以为:

R11 是一个全新无关 Agent task

反过来,如果 public run identity 保持不变,也要保证 seq/terminal lifecycle 可以表达:

interrupted → resumed → completed

核心不是哪种设计“唯一正确”,而是 identity semantics 必须稳定、可持久化、可测试。

Interaction 需要自己的 Identity

一个可恢复 interaction 至少需要:

interaction_id
thread/run identity
conversation_id
tool_call_id / origin
question
options/schema
status
answer
created_at
answered_at

如果只有自然语言 message text,就无法稳定建立控制关系。

interaction_id 回答:

“用户这次回答到底在恢复哪个等待点?”

为什么 tool_call_id 和 interaction_id 都可能需要

如果 ask_user 起源于一个 tool invocation:

tool_call_id

可以关联 Agent runtime 的调用轨迹。

但 interaction 作为 durable human-input lifecycle 可能还需要自己的:

interaction_id

因为一个 tool call 的 runtime identity 和一个业务 interaction 的生命周期语义不同。

例如未来一个 interaction 可能:

  • 被重新展示;
  • 过期;
  • 被人工管理员取消;
  • 有 revision/edit policy。

所以不要默认“ToolCall ID 就是所有层的 ID”。

状态机

一个典型 interaction lifecycle:

required / pending
      ↓ user answer
answered
      ↓ resume accepted
resumed / consumed
      ↓ agent proceeds
completed

也可能:

pending
→ expired

或者 run 被取消/失败。

不同项目可以命名不同,但最重要的是:

pending、answered、run resumed、run completed 是不同事实。

用户点了选项,并不代表后端 run 已经成功 resume。

为什么 answered 和 consumed/resumed 最好分开

假设:

User answer persisted successfully
↓
server crashes before resume transition

如果状态只有:

pending / done

你无法知道:

这个答案已经保存了吗?
Agent 已经用它继续了吗?

分开后:

X3.status = answered
T7.status = interrupted

恢复流程可以继续:

find answered but unconsumed interaction
→ resume exactly once

这本质上是 durable workflow 的 repair semantics。

StreamEvent 如何表达这个控制流

BodySense public event contract 已存在类似:

state.interaction.required
state.interaction.answered
run.interrupted
run.resumed

前端 ActiveTurn reducer 可以:

interaction.required

pendingInteraction = X3
status = interrupted

interaction.answered

X3.answer = ...

run.resumed

status = streaming
pendingInteraction = null

这比用“看到某个 assistant text 就弹窗”稳定得多。

为什么 Answer 与 Resume 也要分开

考虑:

User submits answer
↓
answer persisted
↓
resume request fails due network

此时真实状态可能是:

interaction answered ✅
run resumed ❌ / unknown

如果前端把二者当成同一步,会出现:

  • 用户以为回答丢了;
  • retry 又重复提交;
  • run 被重复 resume;
  • refresh 后状态无法解释。

所以 durable model 要能区分:

Answer Fact
vs
Resume Transition

Double Submit:为什么 Resume 必须 Idempotent

用户可能:

click Answer
network spinner
click again

或者浏览器自动 retry。

如果两个请求都:

resume graph from same checkpoint

可能产生:

  • 两条分叉 execution;
  • duplicate tool side effects;
  • duplicate assistant messages;
  • inconsistent checkpoints。

所以服务端应基于:

interaction_id
answer identity/version
current interaction status
resume request id

实现收敛。

理想 property:

SubmitAnswer(X3, A)
SubmitAnswer(X3, A) again
→ one durable answer
→ at most one semantic resume transition

同一个 Interaction 提交不同 Answer 怎么办

例如:

first: yes
second: no

不能简单都当 retry。

需要明确产品语义:

Immutable answer

一旦 answered:

second different answer → conflict

Editable before consumption

如果尚未 resume,可以允许 revision:

answer_version 1 → 2

Correction after resume

通常不能“修改过去”,而应产生新的 clarification/new turn。

无论哪种,都必须显式设计,不能由 last-write-wins 偶然决定。

HITL 的真正价值不是“人工兜底”

Human-in-the-loop 常被泛化成:

“高风险就让人确认一下。”

更工程化的理解是:

在 Agent state machine 中定义一个明确 interrupt boundary,把缺失的人类输入作为有 schema、有 identity、有 durable status 的运行时依赖。

这使 HITL 可以:

  • replay;
  • retry;
  • audit;
  • time out;
  • resume;
  • display after refresh。

Interrupt Input 应该 Typed,而不是任意字符串

如果问题是选项:

Yes / No / Unsure

resume contract 应校验:

answer ∈ allowed options

如果要求数字:

pain score 0..10

应校验范围。

不要把所有 human input 都压成:

{"answer": "some string"}

然后让 LLM 猜。

Typed interaction 能:

  • 防 malformed resume;
  • 降低 ambiguity;
  • 支持 UI schema;
  • 进入 durable provenance。

为什么 Refresh 后必须还能看到已回答 Interaction

如果 live 时:

Question X3
Answer: Yes

刷新页面后只剩:

assistant final answer

说明 Interaction 只存在于临时 UI,而没有成为完整历史 projection。

成熟模型应该能够从 durable events/read model 恢复:

X3 question
X3 answer
run resumed

这样历史才真正解释:

“Agent 为什么后来改变了推理路径?”

Pending Interaction 与 Interaction History 分开

当前页面需要两类信息:

Pending

当前正在阻塞 run、等待用户回答的 interaction

用于显示:

  • option card;
  • text field;
  • approval UI。

History

已经回答/结束的 interactions

用于:

  • thread timeline;
  • replay;
  • audit;
  • refresh reconstruction。

只保存 pending list 会导致 answered 后信息消失。

Interrupt 与 ActiveTurn State Machine

ActiveTurn streaming
        ↓ interaction.required
ActiveTurn interrupted
        ↓ answer request
Interaction answered
        ↓ run.resumed event
ActiveTurn streaming
        ↓
completed

注意:

interrupted
≠
failed

这决定 UI 应该显示:

“等待你补充信息”

而不是:

“请求失败,请重试”

Interrupted 状态也不应该自动算 Terminal

interrupted 表示:

execution intentionally suspended

它还可能继续。

所以 durable run lifecycle 中:

interrupted

和:

completed / failed / cancelled

不是同一类 terminal state。

这对 after_seq recovery 很重要:看到 interaction.required 后不能把 run 当“结束了”。

Interrupt 与 EvidenceGap 的联系

Diagnosis/Treatment 已学过:

user_information gap
→ generic RAG cannot resolve

Consultation 的 ask_user 正是这种 gap 的一种 acquisition channel。

因此:

EvidenceGap
(user_information)
        ↓ policy chooses human input
Interaction Required
        ↓
Answer
        ↓
Runtime resumes with new user fact candidate/input

这把前面的 Evidence Acquisition 与 L3 HITL runtime 真正连了起来。

但仍要注意:

用户回答进入 durable BodyState 的方式需要 domain validation;interaction answer 也不自动等于 accepted domain Fact。

Answer 是 Observation/Input,不一定直接成为 Durable Fact

用户回答:

“是,我有拇指麻木”

Interaction 层可以保存这个 answer。

但 BodyState domain 可能还需要:

  • source/provenance;
  • timestamp;
  • conflict handling;
  • normalization;
  • observation/fact distinction。

因此:

Interaction Answer
→ domain input
→ BodyState policy
→ accepted Observation/Fact

而不是:

Interaction Answer
= automatically final domain truth

Interrupt 与 Idempotency

用户可能因为网络卡顿连点两次提交。

服务端应该用:

interaction_id
request_id
current interaction status

确保:

same answer retry
→ converge

而不是:

resume run twice

同样,如果 interaction 已 answered,再收到同一个 request,需要明确:

  • return previous success;
  • 或 conflict;
  • 但不能重复产生业务副作用。

Resume 前是否需要重新授权当前 World

这取决于 interaction 类型和停顿时间。

例如一个 information interrupt 等了 2 天,期间:

BodyState changed
Agent configuration pointer changed
safety status changed

不能只机械把旧 checkpoint 从字节层恢复就继续。

需要区分两类 identity:

Execution Identity

同一 semantic thread 应继续使用当时 pinned configuration,不能偷偷切到 current production pointer。

Business Eligibility

当前现实状态可能已经使旧 run 不再适合继续普通路径。

因此 resume 可能需要一个 deterministic revalidation:

Can this interrupted execution still continue under current business invariants?

如果不能:

cancel/supersede old run
→ start new analysis/turn

而不是一边要求 historical execution identity 不变,一边忽略现实业务已经变化。

为什么 Resume 必须 Pin 原 Agent Configuration

假设:

09:00 Thread T7 starts with Config C15
09:02 asks user, interrupted
10:00 production pointer promoted to C16
11:00 user answers

如果 resume 直接读取:

current config = C16

就形成:

same semantic execution
first half C15
second half C16

这破坏:

  • configuration provenance;
  • replay;
  • qualification assumptions;
  • failure attribution。

所以默认应:

resume exact C15

如果业务决定 C15 已被紧急撤回不能继续,则应该显式:

old run cannot resume
→ controlled cancellation/supersession

而不是悄悄换成 C16。

Configuration Pinning 与 Current Business Revalidation 并不矛盾

两个问题:

如果继续旧 execution,用哪套行为?
→ 原 C15

当前是否仍允许继续旧 execution?
→ current deterministic business facts decide

所以可以同时做到:

execution identity stable
+
current safety/domain eligibility rechecked

不要把“重新授权”错误实现成“换最新模型再试一次”。

Interaction Expiry

有些等待点不能永久有效。

例如:

“请确认是否批准现在执行这个敏感动作”

如果用户三天后才点 Approve,context 可能完全变化。

因此 interaction 可以有:

expires_at
context_revision
approval_scope

过期后:

late answer
→ rejected / require new interaction

而不是恢复旧 action。

Information Interrupt 也可能因 BodyState 变化而需要重新问。

Interrupt 的错误场景

Interaction already answered

不能再次随意覆盖,除非产品定义 revision/edit semantics。

Interaction expired

旧 answer 不应 resume 已失效等待点。

Run already completed

不能因为迟到的 UI response 把历史 run 重新打开。

Run cancelled

迟到 answer 应保持 historical record policy,但不能恢复 cancelled run。

Network failure after submit

客户端应先查询 durable interaction/run state,再决定 retry,而不是盲目再发送。

Replay

Historical replay 应能区分:

当时 Agent 提出了什么问题
用户回答了什么
什么时候 resume

而不是只保存最终 summary。

Late Answer Race

一个实际 race:

X3 pending
↓
server timeout policy expires X3
↓
client offline still shows form
↓
user submits old answer

服务端必须验证 current interaction state:

pending? answered? expired? cancelled?

不能因为 payload 有合法 interaction_id 就接受。

这是典型的 temporal validity 问题,只不过对象从 Treatment Proposal 换成 Interaction。

Approval Interrupt 与 Information Interrupt

HITL 不只一种语义。

Information Interrupt

Agent 缺少用户事实
→ ask_user

Answer 提供新 input。

Approval Interrupt

Agent 准备执行高风险/不可逆 action
→ request approval

Answer 表达 human intent/authorization input。

二者都可使用 interrupt/resume runtime,但 authority 不同。

信息回答提供输入;审批回答授予/拒绝一个动作边界。

不要因为 UI 都是一个按钮卡片就混成同一个 domain event。

Approval 的人类“同意”仍可能不是最终 Business Authority

和 Treatment Accept 一样:

User clicks Approve

可以是重要 authority input,但服务器仍可能需要:

  • current safety check;
  • resource/permission check;
  • freshness;
  • approval scope;
  • irreversible action policy。

所以:

Human approval intent
≠
automatic unrestricted side-effect permission

HITL 是 authority chain 的一环,不等于绕过系统 policy。

Resume Tool Side Effects 为什么尤其需要谨慎

如果 resume 后第一步是:

send_message
write_record
activate_treatment

double resume 可能造成真实重复副作用。

所以:

resume idempotency
+
tool side-effect idempotency
+
interaction consumption identity

要一起设计。

只保证 UI 卡片不重复远远不够。

Checkpoint 与 Durable Interaction 为什么不是同一个东西

LangGraph checkpoint 可以保存内部执行状态:

node state
messages
interrupt continuation

但业务仍需要 durable Interaction:

question
answer
status
user-facing identity
created/answered times

原因:

  • checkpoint schema 是 framework-owned;
  • Interaction 是 product/domain control flow;
  • UI/replay/audit 不应该解析框架内部 checkpoint 才知道用户回答过什么。

所以:

checkpoint
= execution resume mechanism

Interaction
= durable business/runtime contract

Process Crash 后如何恢复

如果:

answer persisted
process crashes before resume

启动/recovery 逻辑应能识别:

Interaction X3 answered
Execution T7 still interrupted

然后安全地 resume once。

如果 checkpoint 丢失,则要明确:

cannot resume exact execution

并转到新 run / degraded recovery,而不是假装继续原语义线程。

这说明 durable HITL 真正依赖:

interaction state
+
checkpoint state
+
run/config identity

三个部分共同存在。

Refresh Recovery

页面刷新后可以:

load durable thread/read model
→ discover active interrupted run
→ load pending interaction X3
→ render question/options

如果 X3 已 answered 但 run 尚未 resume:

render answered/pending resume state

而不是重新显示可提交表单导致 duplicate answer。

UI projection 应来自 durable status,而不是组件 memory。

Current BodySense 学习重点

现有 Consultation 已经有:

  • interaction.required / answered events;
  • ActiveTurn pendingInteraction;
  • resumeInteractionStream();
  • assistant-ui resumeRun() integration;
  • durable event/recovery 基础。

历史架构计划还特别强调目标状态:

pending → answered → resumed → completed

以及 refresh/replay 后仍能显示 question + answer。

因此 L3 的重点不是“怎么做一个弹窗”,而是读懂:

一个 UI interaction 如何成为 Agent Runtime 的 durable control-flow state。

Behavioral Contracts

可以写成:

HI-01 required interaction must have stable interaction_id
HI-02 answer must validate against interaction schema
HI-03 same answer retry must not create duplicate resume
HI-04 answered != resumed
HI-05 expired/completed/cancelled interaction cannot resume old run
HI-06 resume uses original pinned configuration identity
HI-07 information answer does not automatically become durable domain Fact
HI-08 refresh reconstructs pending/answered interaction state
HI-09 replay must not re-execute real side effects
HI-10 double resume cannot duplicate action tool side effects

这些 contract 比“按钮点击成功”更接近 production HITL correctness。

测试清单

Normal path

required
→ answer
→ answered event
→ resume
→ completed

Double submit

same answer twice
→ one durable answer
→ one semantic resume

Conflicting second answer

根据产品 semantics:

conflict / revision

不能 silent last-write-wins。

Answer persisted, resume fails

refresh/retry
→ recover answered-but-unconsumed state
→ resume once

Config promotion during interrupt

started C15
production pointer now C16
resume → still C15 or explicit supersession

绝不能 silent C16 continuation。

Current state invalidates resume

BodyState/safety materially changed
→ business revalidation blocks old continuation

Expiry

late answer to expired interaction
→ no resume

Run terminal

run completed/cancelled
late answer
→ no reopen

Refresh

pending interaction 在 reload 后仍能正确恢复。

Replay

历史 interaction 的 question/answer 可展示,但 replay 不重新向真实用户发问或执行 side effect。

一个完整故障例子

T7/C15 asks X3
X3 pending
↓
user answers Yes
answer persisted
↓
HTTP response lost
↓
client retries

错误实现:

retry → resume again
→ duplicate tool execution

正确实现:

SubmitAnswer(X3, Yes)
→ sees same durable answer already exists
→ returns existing state
→ resume coordinator sees T7 already resumed / resume token consumed
→ no duplicate execution

如果第一次尚未 resume:

answer exists + T7 interrupted
→ safely resume once

这就是 durable idempotent workflow,而不是一个普通表单提交。

自测题

  1. 为什么 ask_user 是 runtime interrupt,而不只是 tool result?
  2. 为什么用户 answer 不应该自动成为一个新的 chat turn?
  3. Interaction ID、ToolCall ID、Thread ID、Public Run ID 分别可能回答什么?
  4. 为什么 answered 和 resumed 必须分开?
  5. Double submit 为什么可能造成真实 side effect 重复?
  6. Typed interaction schema 有什么价值?
  7. 为什么 resume 应 pin 原 configuration,而不能默默使用当前 production pointer?
  8. Configuration pinning 和 current business revalidation 为什么可以同时成立?
  9. Interaction Answer 为什么不一定直接成为 BodyState Fact?
  10. Approval interrupt 与 information interrupt 的 authority 有什么差别?
  11. Checkpoint 与 durable Interaction 为什么不是同一个层?
  12. Answer persisted 但 process crash 的状态应如何恢复?

最终心智模型

Chat Message
= conversation content

Interaction
= structured external-input contract

Interrupt
= runtime control state

Answer
= durable external input

Resume
= authorized/idempotent continuation transition

Checkpoint
= framework execution continuation mechanism

这六个概念不能因为都最终显示在聊天 UI 里就合并。

更重要的是:

HITL 的真正工程价值,是把人类输入从“随便发一句消息”提升成一个有 identity、有 schema、有 lifecycle、有 provenance、可恢复且受 authority 控制的 runtime primitive。

创建于 2026/8/22 更新于 2026/9/16
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本