人在环中:Propose-Then-Commit


文档摘要

人在环中:Propose-Then-Commit 本节摘要:2026 年业界对人在环中(Human-in-the-Loop, HITL)的共识很具体——它不是「Agent 问、用户点批准」那么简单,而是 propose-then-commit(先提议再提交):提议的动作带着幂等键(idempotency key)持久化到耐用存储;连同意图、数据血缘(data lineage)、触及的权限、爆炸半径(blast radius)、回滚计划呈现给评审者;只有在正向确认后才提交;执行后再验证副作用真的发生了。LangGraph 的 配 PostgreSQL 检查点、Microsoft Agent Framework 的 、Cloudflare 的 实现的都是同一个形状——API 名字不同,形状不变。

人在环中:Propose-Then-Commit

本节摘要:2026 年业界对人在环中(Human-in-the-Loop, HITL)的共识很具体——它不是「Agent 问、用户点批准」那么简单,而是 propose-then-commit(先提议再提交):提议的动作带着幂等键(idempotency key)持久化到耐用存储;连同意图、数据血缘(data lineage)、触及的权限、爆炸半径(blast radius)、回滚计划呈现给评审者;只有在正向确认后才提交;执行后再验证副作用真的发生了。LangGraph 的 interrupt() 配 PostgreSQL 检查点、Microsoft Agent Framework 的 RequestInfoEvent、Cloudflare 的 waitForApproval() 实现的都是同一个形状——API 名字不同,形状不变。典型失败模式是「橡皮图章」式批准:「批准?」被无评审地点下去。已记录的缓解是**质询-应答(challenge-and-response)**配显式清单——评审者必须对三个具体问题正向作答,批准键才亮起。EU AI Act 第 14 条要求高风险 AI 系统的「有效」人工监督,法律语言明确排除橡皮图章,而 propose-then-commit 配质询-应答正是能在第 14 条审查下存活的形状。本节是工程课:把结构化评审变成阻力最小的路径。

对应原课程:Phase 15 · Lesson 15 · propose-then-commit(原英文 phases/15-autonomous-systems/15-propose-then-commit/docs/en.md)。前置:第 12 节(持久执行)、第 14 节(三探测器)。

学习目标

阅读完本节,你应当能够:

  1. 描述 propose-then-commit 状态机的四阶段(提议/呈现/提交/验证)及每阶段必备的元数据。
  2. 解释幂等键为什么不可或缺:没有它,瞬时失败后的重试会让一个已批准动作被执行两次。
  3. 说明为什么批准候审室必须住在 Agent 不拥有的耐用存储里(关联第 12 节持久执行)。
  4. 设计一条质询-应答清单,把橡皮图章变成强制评审函数,并解释它如何满足 EU AI Act 第 14 条。
  5. 把动作分成**后果性(总是 HITL)/可逆(有时 HITL)/只读(从不用 HITL)**三档。

一、问题与直觉

Agent 要做一个动作,用户要决定:批不批。如果决定是瞬时的,那大概率不是评审;如果决定是结构化的,它慢但可信。工程问题是:怎么让结构化评审成为阻力最小的路径

2023 年代的 HITL 模式是同步提示:「Agent 想给 X 发邮件、正文 Y —— 批准吗?」用户点批准,所有人都觉得系统很安全。实践中这个界面被严重橡皮图章化:用户批准得很快,批准几乎不预测任何东西;等 Agent 出错时,审计轨迹显示一长串用户自己都记不起来的批准。

2026 年的模式——propose-then-commit——把 HITL 搬到耐用基板上,附上结构化元数据,要求正向提交。每个托管 Agent SDK 都出货一个版本:LangGraph interrupt()、Microsoft Agent Framework RequestInfoEvent、Cloudflare waitForApproval()。API 名字不同,形状不变。

⚠️ 核心张力:橡皮图章让 HITL 沦为仪式。propose-then-commit 的全部价值,在于把「评审这件事」做成结构化、可追溯、不可跳过——而不是给用户一个更花哨的「批准」键。

Propose-Then-Commit 状态机四阶段

  1. 提议(Propose):Agent 产出提议动作,持久化到耐用存储(PostgreSQL、Redis、Durable Object)。必须包含:意图(为什么做)、数据血缘(哪个来源导致这个提议)、触及的权限(哪些 scope/文件/端点)、爆炸半径(最坏情况是什么)、回滚计划(若提交了怎么撤销)、幂等键(每个提议唯一;重提交返回同一条记录)。
  2. 呈现(Surface):评审者看到提议及其全部元数据。评审者必须是(不是 Agent 评审自己)。
  3. 提交(Commit):正向确认。动作执行。
  4. 验证(Verify):执行后,把副作用读回并确认。若验证失败,系统处于已知坏状态,告警介入。

幂等键:重试安全的命脉

没有幂等键,瞬时失败后的重试可能让一个已批准动作被执行两次。具体例子:用户批准「从 A 转 100 美元到 B」。网络抖动,工作流重试。用户只批准了一次,但转账执行了两次。幂等键把批准绑定到单个、唯一的副作用;第二次执行是 no-op。

这是 Stripe 和 AWS API 用的同一套幂等模式。把它复用到 Agent 批准上,在 Microsoft Agent Framework 文档里有明确说明。

耐用性:为什么批准能比进程活得更久

批准候审室是一块 Agent 不拥有的状态。工作流被暂停(第 12 节)。当批准到达,工作流从精确那一点恢复。这就是为什么 LangGraph 把 interrupt() 配 PostgreSQL 检查点,而不是内存状态——两天后的批准仍能找到完好无损的工作流。

橡皮图章与质询-应答缓解

HITL 的默认 UI(「批准/拒绝」按钮)产出的是无真实评审的快速批准。已记录的缓解:质询-应答清单,要求评审者在对三个具体问题正向作答后,批准键才被启用。具体形状:

  • 「你理解这个动作触及什么资源吗?[ ]」
  • 「你确认爆炸半径可接受吗?[ ]」
  • 「如果失败,你有回滚计划吗?[ ]」

这不是为官僚而官僚——而是一个强制函数(forcing function)。勾不全的评审者,要么要求澄清(升级),要么拒绝(安全默认)。Anthropic 的 Agent 安全研究明确把清单驱动的 HITL 列为橡皮图章的缓解。

什么算「后果性」

不是每个动作都需要 propose-then-commit。2026 年的指南:

  • 后果性动作(总是 HITL):不可逆写入、金融交易、对外通信、生产数据库改动、破坏性文件系统操作。
  • 可逆动作(有时 HITL):本地文件编辑、staging 环境改动、有清晰回滚的可逆写入。
  • 读取与检查(从不用 HITL):读文件、列资源、调用只读 API。

动作后验证

「提交跑完了」不等于「副作用发生了」。网络分区和竞态条件可能产生一个工作流——它以为自己成功了,但后端没持久化。验证步骤在提交后重新读目标资源以确认。这与带 RETURNING 子句的数据库事务、或 PutObject 后再 GetObject 的 AWS 是同一模式。

EU AI Act 第 14 条

第 14 条要求高风险 AI 系统在欧盟内有有效的人工监督。「有效」不是装饰性的。监管语言明确排除橡皮图章模式。配质询-应答的 propose-then-commit,是在 Microsoft Agent Governance Toolkit 合规文档中能扛住第 14 条审查的形状。

二、从零实现:propose-then-commit 状态机

原课程 code/main.py 用标准库 Python 实现这台状态机。耐用存储是一个 JSON 文件。幂等键是 (thread_id, action_signature) 的哈希。驱动器模拟三个场景:干净的批准流、瞬时失败后的重试(必须不重复执行)、以及橡皮图章默认流对照质询-应答流。下面给出关键骨架。

提议记录:带全部元数据

import hashlib, json def make_proposal(thread_id, action, intent, lineage, perms, blast, rollback): sig = json.dumps(action, sort_keys=True) key = hashlib.sha256(f"{thread_id}:{sig}".encode()).hexdigest() return { "idempotency_key": key, # 重提交同一提议 → 返回同一条记录 "action": action, "intent": intent, # 为什么做 "data_lineage": lineage, # 哪个来源导致 "permissions_touched": perms, "blast_radius": blast, # 最坏情况 "rollback_plan": rollback, "state": "proposed", # proposed → committed → verified }

幂等提交:重试不重复执行

def commit(proposal, durable_store, approver): rec = durable_store.get(proposal["idempotency_key"]) if rec and rec["state"] == "committed": return rec # 已提交过, 第二次执行是 no-op if not approver.positive_ack(proposal): raise Pending("等待人工正向确认") execute(proposal["action"]) proposal["state"] = "committed" durable_store.put(proposal["idempotency_key"], proposal) return proposal

设计要点:幂等键把「批准」绑定到「唯一副作用」。execute 失败可以重试,但绝不会因为重试而把同一副作用执行两次。

验证 + 自动回滚

def verify(proposal, read_back): # 「提交跑完」 ≠ 「副作用发生」: 网络分区/竞态可致假成功 if not read_back(proposal["action"]): run_rollback(proposal["rollback_plan"]) # 验证失败 → 自动回滚 + 告警 alert("verify 失败, 已进入已知坏状态") return False proposal["state"] = "verified" return True

质询-应答:把橡皮图章改成强制函数

CHALLENGE = [ "你理解这个动作触及什么资源吗?", "你确认爆炸半径可接受吗?", "如果失败你有回滚计划吗?", ] def gated_approve(proposal, reviewer): # 批准键默认禁用; 三个问题都正向作答才启用 answered = all(reviewer.acknowledge(q) for q in CHALLENGE) if not answered: return "要求澄清(升级) 或 拒绝(安全默认)" return commit(proposal, durable_store, reviewer)

三、框架对比:同一个形状的不同实现

框架 HITL 原语 耐用存储 形状是否一致
LangGraph interrupt() PostgreSQL 检查点 是:提议-呈现-提交-验证
Microsoft Agent Framework RequestInfoEvent 框架托管状态 是:带结构化元数据的耐用 HITL 请求
Cloudflare Agents waitForApproval() Durable Objects 是:批准候审室在 Agent 之外
裸 stdlib 实现 自建状态机 JSON 文件(教学) 同形状, 仅规模不同

关键观察:API 名字不同,形状不变——这说明 propose-then-commit 是领域共识,不是某家厂商的发明。选型时看的是耐用存储的可用性、元数据字段的完整度、是否内建质询-应答门控,而不是 API 拼写。

💡 三家都把批准候审室放在 Agent 不拥有的状态上。这是设计层面的硬要求:如果候审室是 Agent 内存的一部分,Agent 重启就丢了;如果是 Agent 能写的资源,Agent 就能自己批准自己。

四、可复用产物

原课程 outputs/skill-hitl-design.md 评审一条拟议的 HITL 工作流,检查它是否符合 propose-then-commit 形状,并标出缺失项:缺幂等键、缺验证步骤、缺质询-应答层、元数据不全(没有爆炸半径或回滚计划)。它把「枚举后果性动作 → 为每个配提议记录 → 检查幂等与验证 → 检查门控」串成可重复清单。

code/main.py 是零依赖状态机,改耐用存储后端(JSON 换 Redis/PostgreSQL)即可贴近产线;状态机逻辑本身无需改动。

五、练习

  1. code/main.py:确认已批准提议的重试用的是耐用记录、不重复执行。然后把幂等键改成包含时间戳,展示重试会重复执行——体会幂等键绑定的对象是什么。

  2. 加回滚字段:给提议记录扩展 rollback 字段。模拟一次验证失败的执行,展示回滚自动触发。

  3. 读 Microsoft Agent Framework RequestInfoEvent 文档:找出一个玩具引擎缺失的元数据字段,加上它,解释它防御什么。

  4. 设计质询-应答清单:针对一个具体动作(如「在公开 Twitter 账号发帖」),评审者必须回答哪三个问题?为什么是这三个?

  5. 找一个同步「批准?」就够的案例(不需要耐用存储):解释为什么,并说出你接受的风险类别。

本节要点回顾

  1. 2026 HITL 共识是 propose-then-commit, 不是「问一句点批准」——提议持久化+幂等键+正向提交+执行后验证。
  2. 四阶段状态机:提议(含意图/血缘/权限/爆炸半径/回滚/幂等键)、呈现(人评审)、提交(正向确认)、验证(读回副作用)。
  3. 幂等键把批准绑定到唯一副作用:瞬时失败重试不会让「转 100 美元」变成「转 200 美元」——复用 Stripe/AWS 的幂等模式。
  4. 批准候审室必须住 Agent 不拥有的耐用存储:LangGraph 配 PostgreSQL、Cloudflare 用 Durable Objects,两天后的批准仍能找到工作流。
  5. 橡皮图章是默认失败模式:缓解是质询-应答清单——三个具体问题正向作答,批准键才启用,这是强制函数而非官僚。
  6. 动作分三档:后果性(总是 HITL)/可逆(有时 HITL)/只读(从不用 HITL)。
  7. 「提交跑完」≠「副作用发生」:网络分区与竞态可致假成功,验证步骤必须读回确认。
  8. EU AI Act 第 14 条要求「有效」监督, 明确排除橡皮图章:配质询-应答的 propose-then-commit 是能存活于审查的形状。

下一节,我们把「单步可恢复」升级为「跨多步可恢复」——检查点与回滚(checkpoints and rollback),看长程 Agent 如何在任意一步崩溃后,从已知良好的检查点重启而非从头来过。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U