第八章把审批放在安全体系里观察:当 agent 想越过既有边界时,人可以决定是否授权。 但 Human in the loop 远不止“危险命令弹个确认框”。用户还会补充缺失信息、在执行中改变方向、拒绝某条路线、紧急叫停,以及在中断后决定如何继续。 本章把这些看似分散的交互放回同一张图里:模型提出下一步,harness 管理控制权,人只在机器无法独立作出正确决定的边界上介入。
9.1 Human in the loop 不是“人盯着每一步”
如果把 agent 想成一辆自动驾驶汽车,最保守的设计是:每到一个路口都问驾驶员“左转可以吗?直行可以吗?”。它确实很安全,却也失去了自动驾驶的意义。
另一个极端是:驾驶员只说一次目的地,之后方向盘、油门和刹车全部失效。它很自主,但一旦目标理解错了,错误会沿着 LOOP 连续放大。
Human in the loop 要解决的,不是“要不要人”,而是三个更精确的问题:
- 何时交出控制权? 是信息不足时、动作越权时、方向偏离时,还是每个步骤都问?
- 交出什么控制权? 是请人补充事实、选择方案、授权副作用,还是终止整个轮次?
- 拿回控制权后如何继续? 人的回答怎样进入历史,等待中的工具怎样被唤醒,被拒绝的路线怎样让模型真正放弃?
Codex 的答案可以概括为:
默认让 agent 连续工作,只在语义边界、权限边界和生命周期边界上请人介入。
这意味着人并不住在 LOOP 的每一圈里。大多数时候,模型与工具自行完成“采样 → 行动 → 回灌”;只有遇到不能靠推理消除的不确定性,或者准备跨越既有授权时,控制权才短暂交还给人。
flowchart LR
U["用户<br/>目标 / 偏好 / 授权"] -->|"开始任务"| L["agent LOOP"]
L -->|"信息不足"| Q["提问<br/>补全决策"]
L -->|"越过权限边界"| A["审批 / 权限申请<br/>授权动作"]
U -->|"方向需要调整"| S["steer<br/>追加新输入"]
U -->|"必须立刻停止"| I["interrupt<br/>取消当前轮次"]
Q -->|"答案回灌"| L
A -->|"批准 / 拒绝"| L
S -->|"下个步骤边界"| L
I -->|"保留历史,可 recover"| U
这四个入口看起来不同,背后却共享同一个思想:人不是模型的逐步执行器,而是目标、事实与授权的最终来源。
9.2 三方分工:模型提议,harness 仲裁,人负责意图
Human in the loop 经常被画成“模型 → 人 → 模型”两方关系,但生产级 harness 里至少有三方:
| 参与者 | 最擅长的事 | 不应该独自决定的事 |
|---|---|---|
| 模型 | 理解任务、探索方案、提出下一步行动 | 自己扩大权限、把外部内容当成用户授权 |
| harness | 维护状态、执行策略、冻结快照、挂起与恢复、记录事件 | 替用户猜业务偏好,凭空创造授权 |
| 人 | 定义目标、提供缺失事实、权衡业务取舍、承担授权责任 | 逐条理解所有底层命令和运行时细节 |
这是一种刻意的不对称:
- 模型有提议权:它最接近任务语义,知道自己缺什么信息、想采取什么行动;
- harness 有程序化的裁决权:规则、沙箱和审批策略决定哪些提议可以自动执行,哪些必须上交;
- 人有意图与授权权:只有人能回答“我真正想要什么”以及“我愿意承担哪种副作用”。
第八章已经说明,用户点了批准也不意味着所有硬边界自动消失。反过来也一样:模型说“用户应该会同意”不构成授权。三方各守一段边界,任何一方都不能代替另外两方。
关键区别:模型可以判断“这个方案技术上更好”,harness 可以判断“这个动作命中了审批规则”,但只有用户能判断“这是不是我愿意接受的取舍”。
9.3 四种介入,不要混成一个“确认框”
Human in the loop 至少包含四种语义不同的介入。它们都需要用户输入,但不能共用一个含糊的“确定/取消”。
| 介入形式 | 谁发起 | 回答的问题 | 对当前轮次的影响 |
|---|---|---|---|
| steer | 用户主动 | “目标或约束变了吗?” | 不打断在途采样,在下个步骤边界注入 |
| 提问 | 模型主动 | “缺失的事实或偏好是什么?” | 当前工具调用等待,答案作为结果回灌 |
| 审批 / 权限申请 | harness 或模型 | “这个动作或权限是否被授权?” | 批准后执行,拒绝后换路或停止 |
| interrupt | 用户主动 | “当前工作是否必须立刻停下?” | 协作式取消当前轮次,保留已发生的历史 |
它们分别对应四种不同的控制问题:
- steer 改的是方向;
- 提问补的是信息;
- 审批授的是权力;
- interrupt 控的是生命周期。
如果把四者混在一起,语义会迅速变坏。例如把“我不喜欢这个实现方案”表达成“拒绝命令”,模型可能只换一条命令继续走原路线;把“这次不要执行”表达成 interrupt,又会让整个轮次停掉,连本来可以继续的分析也一起丢失。
好的 harness 不只收集一个布尔值,而是让用户表达拒绝什么、是否继续、接下来该往哪里走。
9.4 Steer:不中断工作,也能改变方向
Steer 是最轻量的人工介入。用户看到 agent 正在工作,补充一句:
“不要改公共 API,兼容旧配置。”
这条消息不会立刻切断正在进行的模型请求。它先进入待处理输入队列,等本轮采样和已经形成的工具调用走到安全边界,再作为一条新的用户输入写入历史。下一次采样时,模型看到新约束并调整路线。
sequenceDiagram
participant U as 用户
participant H as harness
participant M as 模型
participant T as 工具
H->>M: 采样 #1(旧上下文)
U->>H: steer:"保留旧 API"
Note over H: 输入排队,不修改在途请求
M-->>H: 工具调用
H->>T: 执行并回灌结果
Note over H: 到达步骤边界
H->>H: 把 steer 追加进历史
H->>M: 采样 #2(已包含新约束)
为什么不立即把消息塞进正在流式生成的上下文?因为那次采样已经基于旧快照开始了。中途修改输入,会让“请求到底基于什么上下文”失去答案,也会破坏第五章反复强调的可重试、可缓存和可重放。
因此 steer 的语义不是“抢过模型的话筒”,而是:
让当前原子步骤完成,然后在下一个可观察边界改变后续决策。
这也解释了 steer 与 interrupt 的分界:
- 只是补充约束、改变优先级、提醒漏项,用 steer;
- 当前动作继续一秒都可能造成错误,用 interrupt。
9.5 提问:把不可推断的决定交还给人
模型遇到不确定性时,有三种选择:
- 从代码、文档或环境中继续调查;
- 做一个可逆、低风险的合理假设;
- 停下来问用户。
并不是所有不确定性都值得提问。一个 agent 若每发现两个可能的文件名就请示一次,用户很快会变成它的搜索引擎;但如果“迁移后保留兼容层还是直接删除旧接口”会决定整个实现方向,擅自假设的返工成本可能远高于一次提问。
可以用一个简单标准判断:
当继续调查也无法得到答案,并且不同答案会显著改变成本、风险或最终产品行为时,才问人。
Codex 把这类提问做成结构化工具,而不是让模型在普通 assistant 文本里随口问一句。一次提问包含:
- 稳定的问题 ID,保证回答能对应回原问题;
- 简短标题和单句问题;
- 两到三个互斥选项;
- 每个选项的影响或取舍;
- 一个明确的推荐项;
- 前端自动提供的自由填写入口。
结构化的价值不只是 UI 更好看。它迫使模型在开口前完成一半决策工作:把问题缩小、列出可行选项、说明差异、给出建议。用户负责选择,不负责替 agent 从零分析。
差的提问:
"这里怎么做?"
好的提问:
"旧配置字段需要保留多久?"
- 保留一个版本(推荐):兼容现有用户,下个大版本删除
- 立即删除:实现更简单,但现有配置会失效
- 长期保留:无迁移风险,但维护两套语义
提问工具还有一条重要的权限边界:只有根 agent 直接向用户提问。 子 agent 有疑问时先写信给父 agent,由父 agent 汇总、去重,判断是否真的需要打扰用户。这避免一支 agent 队伍同时弹出多个互相重叠的问题。
9.6 审批与权限申请:批准动作,不是批准理由
第八章已经从安全角度拆过审批。本章换一个控制权视角,区分两类容易混淆的请求。
9.6.1 动作审批:这一次可以做吗
动作审批绑定的是一个已经具体化的行动,例如:
- 执行某条命令;
- 修改某组文件;
- 访问某个网络目标;
- 调用一个需要确认的 MCP 工具。
用户看到的应该是即将发生什么,而不只是模型为什么想做。理由可以帮助理解,但不能代替动作本身。恶意网页也能诱导模型生成一段听起来合理的理由,真正需要审查的是命令、目标、文件范围和副作用。
9.6.2 权限申请:接下来一段时间允许做什么
request_permissions 表达的不是某条具体命令,而是一份最小权限增量,例如:
- 本轮允许访问网络;
- 本轮允许写某个额外目录;
- 本会话允许一组文件系统能力。
权限申请的回答必须同时包含授权内容和作用域。用户同意联网,不代表同意任意写盘;同意本轮,不代表后续轮次仍然有效。harness 还应把最终授权与原请求求交集,不能让前端误传一个更宽的响应就扩大权限。
动作审批和权限申请的差别,可以类比为:
- 动作审批是“这张报销单可以签”;
- 权限申请是“这个项目在本月有多少预算额度”。
两者都需要人点头,但授权对象完全不同。
9.7 等待不是停机:统一的挂起与唤醒协议
提问、命令审批、补丁审批、权限申请、MCP 表单看起来是五套功能,在运行时里却可以收敛成一个模式:
发出请求事件
→ 为请求登记一个 pending waiter
→ 当前工具 future 挂起
→ 前端展示交互
→ 用户回答形成应答 Op
→ 按请求 ID 找到 waiter
→ 唤醒工具,继续 LOOP
sequenceDiagram
participant Tool as 工具 future
participant TurnState as turn 状态
participant Host as 前端
participant User as 用户
Tool->>TurnState: 登记 pending request(请求 ID)
Tool->>Host: Event:需要人工输入
Note over Tool: future 挂起,不占 CPU
Note over TurnState: 提交循环仍可处理<br/>steer / interrupt / 其他应答
Host->>User: 展示动作、理由、作用域和选项
User-->>Host: 作出决定
Host->>TurnState: Op:请求 ID + 应答
TurnState-->>Tool: oneshot 唤醒
Tool->>Tool: 执行 / 拒绝 / 取消
这里有五个工程约束。
1. 请求必须可关联。 每个请求都带 turn、item/call 和请求 ID。回答只能唤醒原来的等待者,不能靠“当前屏幕上正好有个弹窗”猜归属。
2. 等待必须可取消。 用户按下 interrupt、turn 结束或连接失效时,pending waiter 要被清理。等待通道关闭应解释为“请求已取消”,不能合成一次普通拒绝后让旧轮次继续执行。
3. 控制通道不能被等待堵住。 工具 future 可以等人,但提交循环仍要处理 interrupt 和应答 Op。否则系统会陷入“要处理批准才能继续,但负责处理批准的循环也被卡住”的死锁。
4. 交互请求不能静默丢失。 普通进度通知漏一条,最终 item 还能重建;审批请求漏掉,turn 会永远等待。网络前端宁可明确报错或中止请求,也不能假装发送成功。
5. 等人的时间与算力时间要分开。 用户可能去开一个小时的会。等待期间没有模型推理,也没有工具执行,不应被误算成“工具超时”或“agent 运行过慢”。可观测性需要分别记录等待时长和实际处理时长。
统一协议的意义在于,新增一种 Human in the loop 交互时,不必再给 LOOP 增加新分支。只要它能表达成“发请求、挂起、应答、唤醒”,就可以复用取消、路由、审计和前端传输。
9.8 一个决定不只有“是”和“否”
成熟的 Human in the loop 协议,至少要表达三个维度:
| 维度 | 典型选项 | 回答的含义 |
|---|---|---|
| 决定 | 批准 / 拒绝 | 这件事做不做 |
| 作用域 | 一次 / 本 turn / 本 session / 持久规则 | 这个决定能复用多久 |
| 控制流 | 继续换路 / 中止当前 turn | 拒绝后 agent 还要不要工作 |
因此“拒绝”至少有两种:
- Decline:不要做这个动作,但 turn 继续。模型收到明确的拒绝观察,应当换一条路线;
- Cancel / Abort:不要做,而且暂停当前 turn,等待用户下一步指令。
“批准”也有不同强度:
- 仅这一次:最窄,适合高风险或上下文敏感动作;
- 本 turn / 本 session:减少重复打扰,但不跨越明确的生命周期边界;
- 沉淀为规则:跨会话信任,必须比临时批准有更严格的可解释性和覆盖范围校验。
这里有一个重要原则:
扩大授权范围必须是用户显式选择,不能由“用户连续点了三次同意”自动推断。
行为重复不等于永久信任。尤其是 shell 前缀、解释器和网络外发,同一句命令在不同工作目录、不同输入数据下可能有完全不同的风险。
9.9 人的回答如何进入上下文
Human in the loop 不只是运行时唤醒问题,还是上下文问题。用户回答之后,模型必须知道发生了什么,否则它可能再次提出同一动作。
不同回答进入历史的方式也不同:
- steer 是新的用户输入,直接改变任务约束;
- 提问答案是工具结果,和原问题按 call ID 成对出现;
- 审批拒绝是一次结构化失败观察,告诉模型“不是执行故障,而是用户不允许”;
- interrupt 留下一条中断标记,提醒下次采样检查部分执行的副作用;
- 权限变化属于世界状态,下一步通过权限差分片段告诉模型“现在能做什么”。
这几种内容的信任等级也不同。用户亲自给出的业务答案可以确立偏好和授权;网页、工具输出、子 agent 来信只能提供证据,不能假装成用户同意。即使它们最终都出现在模型上下文里,harness 和 guardian 仍要保留来源。
这说明“把回答拼成一段文本塞回去”远远不够。Human in the loop 的结果至少需要保存:
- 谁作出的决定;
- 针对哪个请求;
- 决定内容和作用域;
- 当时展示给人的动作与理由;
- 后续是继续、拒绝还是中断;
- 是否改变了权限或持久规则。
第十章会看到,这些事实也是恢复与审计的重要输入。不过要区分两类状态:已经作出的决定应当持久化,进程内等待应答的临时通道不能被当成可持久化状态。 恢复时要依据事件重新判断是取消、重新询问还是继续,不能盲目复活一个已经过期的弹窗。
9.10 多 agent:业务问题归口,安全审批不绕过
一支 agent 队伍会把人工注意力放大成新的并发瓶颈。三个分身可以同时工作,也就可能同时遇到三个问题、四个审批和两个权限申请。
Codex 用两条不同的规则处理。
业务提问归口到根 agent。 子 agent 不直接向用户问“产品希望怎么做”。它把问题写信给父 agent,父 agent 可以结合其他分身的结果去重、合并,最后只向用户提出真正阻塞全局的决策。
安全审批沿原链路上交。 子 agent 不能因为“只是分身”就绕开审批。它执行危险命令、修改越界文件时仍走同样的策略、沙箱与审批关卡,最终授权来源仍是用户或受管审查者。
flowchart TD
C1["子 agent A:业务歧义"] --> P["父 / 根 agent 汇总"]
C2["子 agent B:相同歧义"] --> P
P -->|"一个结构化问题"| U["用户"]
C3["子 agent C:危险动作"] --> G["统一审批关卡"]
G -->|"需要真人授权"| U
G -->|"规则 / guardian 可决"| R["自动放行或拒绝"]
这形成了一条清晰的治理原则:
问题可以汇总,授权不能转借。
父 agent 可以替子 agent 整理问题,但不能因为自己曾获批某个无关动作,就把权限泛化给整棵树。会话级缓存可以减少同类重复审批,但命中条件和作用域必须由 harness 控制。
9.11 无人值守:没有人回答时,边界必须更硬
CI、批处理和后台 agent 没有一个随时在线的用户。此时最危险的设计是:
“既然没人能点批准,那就默认批准。”
正确语义正好相反:无法获得人工授权,就不能跨越需要授权的边界。
这就是审批策略 never 的含义:
- 沙箱内已允许的低风险动作照常执行;
- 本来需要询问的动作直接变成禁止;
- agent 收到明确拒绝,可以选择受限方案或报告阻塞;
- 不留下永远等待的交互请求。
同样,前端如果不支持结构化提问,也必须把“不支持”作为工具失败明确回灌,不能吞掉请求继续假设答案。
因此 Human in the loop 不是自主运行的前提,而是一种可选的升级通道。无人值守模式关闭了升级通道,保留的自动能力反而必须被更窄的硬边界包住。
9.12 交互设计:人的注意力也是一种预算
模型 token、工具并发和运行时长都有预算,人的注意力也应该有。
一次弹窗的成本不只是点击两秒。用户需要切换注意力、理解上下文、判断影响;弹窗过多后会出现审批疲劳:用户不再阅读内容,只机械地选择第一个按钮。此时看似更严格的审批系统,实际安全性反而下降。
减少打扰不能靠简单地“少问”,而要从四个位置做设计。
9.12.1 先自动调查,再问不可推断的部分
能从代码、配置和文档查到的事实不问用户;只有业务偏好、风险承受和外部承诺这类机器无法知道的内容才上交。
9.12.2 一次只问一个决策主题
结构化提问可以容纳多个问题,但默认应优先一个。把五个互相依赖的决定塞进同一表单,看似减少弹窗,实际上把决策树甩给了用户。
9.12.3 展示差异,而不只展示对象
审批框不能只有一条原始命令。用户至少需要知道:
- agent 想做什么;
- 会影响哪些资源;
- 为什么现有权限不够;
- 批准一次、批准本会话、拒绝分别意味着什么;
- 是否存在更窄的替代方案。
对补丁应展示文件范围和 diff,对网络应展示目标域名,对权限申请应展示新增能力,而不是让用户从底层参数里自行推理。
9.12.4 自动机制过滤低价值请求
安全规则、会话缓存、沙箱内试跑和 guardian 的共同目标,不是替人拥有最终意图,而是过滤掉机器能够可靠处理的低风险决定,把人的注意力留给真正高价值的边界。
可以把介入强度画成一条阶梯:
flowchart LR
O["Human out of the loop<br/>规则内自动执行"] --> G["Human on the loop<br/>可观察、可 steer / interrupt"]
G --> H["Human in the loop<br/>关键点必须回答"]
H --> X["硬禁止<br/>即使确认也不放行"]
不是所有动作都应该进入同一档。优秀的 harness 会根据风险、可逆性和授权状态把它们分层,而不是拿一个全局开关决定“全自动”或“全手动”。
9.13 一个完整例子:控制权如何来回交接
假设用户说:
“把项目的鉴权配置迁移到新格式,并运行验证。”
一次合理的 Human in the loop 流程可能是:
sequenceDiagram
participant U as 用户
participant A as agent
participant H as harness
participant E as 执行环境
U->>A: 提交迁移任务
A->>E: 读取配置、搜索调用点
E-->>A: 同时存在"兼容一版"和"立即切换"两种产品语义
A->>U: 提问:旧格式保留多久?<br/>给出选项、影响与推荐
U-->>A: 保留一个版本
A->>E: 修改工作区内文件并运行本地测试
U->>A: steer:还要补迁移告警
Note over A: 下一步骤边界吸收新约束
A->>H: 申请访问外部测试环境
H->>U: 权限申请:仅本 turn 开放指定网络
U-->>H: 批准本 turn
H->>E: 执行远端验证
E-->>A: 验证通过
A-->>U: 汇总改动、验证结果与剩余风险
这段流程里,人只出现了三次:
- 决定机器无法推断的兼容策略;
- 主动补充一个新要求;
- 授权一次超出现有边界的访问。
文件搜索、方案分析、代码修改和测试都没有逐步请示。介入点少,但每一次都改变了决策空间。 这比“每条命令都点允许”更符合 Human in the loop 的本意。
9.14 常见失败模式
| 失败模式 | 表面现象 | 根因 | 更好的做法 |
|---|---|---|---|
| 问得太早 | agent 还没调查就问用户文件在哪 | 把搜索成本转嫁给人 | 先穷尽低成本、只读调查 |
| 问得太晚 | 已经大改一轮才确认产品方向 | 把不可逆决策留到执行后 | 在高分叉成本之前设语义检查点 |
| 只给原始命令 | 用户看不懂,只能盲批 | 展示了实现,没有展示影响 | 同时展示目的、对象、范围和替代方案 |
| 拒绝语义含糊 | 模型被拒后换个写法反复重试 | 没区分执行失败与用户禁止 | 回灌结构化拒绝原因,必要时中止 turn |
| 授权范围偷偷扩大 | 一次批准变成长期白名单 | 把重复行为误当成永久信任 | 作用域必须由用户显式选择 |
| 多个分身同时提问 | 弹窗风暴、问题重复 | 没有根 agent 归口 | 业务问题向上汇总,审批统一排队 |
| 旧弹窗仍可提交 | turn 已结束,批准却落到新任务 | 请求没有生命周期和坐标 | 应答按 ID 关联,turn 结束即作废 |
| 把人当唯一安全边界 | 用户点错一次就能造成灾难 | 没有纵深防御 | 审批之外仍保留规则、沙箱和硬禁止 |
| 中断被当成回滚 | 用户以为停下就什么都没发生 | 忽略工具可能已产生部分副作用 | 明确中断只停止后续工作,并保留审计记录 |
最值得警惕的是第一行和最后一行之间的张力:问得太多,用户会机械批准;问得太少,agent 会在错误方向上走太远。Human in the loop 的质量,最终取决于介入点是否选在真正改变结果的边界上。
9.15 更深一层:Human in the loop 是控制权协议
把本章所有机制放在一起,会得到三个更深的结论。
9.15.1 它不是 UI 功能,而是分布式状态机
一个审批框跨越模型流、harness 内核、前端进程和真人,任何一层都可能断开。请求要有 ID、状态、取消语义和超时语义;应答要幂等地落到正确 turn;旧请求不能污染新轮次。
所以“弹窗长什么样”只是最后一公里,真正困难的是等待期间谁拥有状态、断线后谁负责收尾、重复应答如何处理。
9.15.2 它像两阶段提交,但没有真正回滚
高风险动作先进入“准备”阶段:展示将要发生的事并等待授权;批准后才进入“提交”阶段执行。等待期间环境可能变化,因此执行前仍应重新确认请求绑定的对象和权限没有漂移。
但 agent 操作通常没有数据库事务那样的 rollback。命令跑到一半被 interrupt,文件和外部系统可能已经部分改变。Human in the loop 能控制下一步是否发生,不能保证过去从未发生。
9.15.3 人工注意力应投向不可替代的判断
规则擅长处理已知模式,guardian 擅长筛查风险,模型擅长分析方案,harness 擅长执行硬约束。只有业务意图、价值取舍和最终授权必须由人提供。
因此衡量 Human in the loop 设计好坏,不应该看“一天弹了多少审批”,而应该看:
- 错误方向能否被及时纠正;
- 高风险动作能否获得与风险匹配的授权;
- 用户是否理解自己批准了什么;
- 被拒绝后 agent 能否理性换路;
- 没有人在线时系统是否安全地退化;
- 一次人工决定是否在恰当范围内复用。
9.16 小结:Human in the loop 的六条设计原则
-
人只在边界上介入。 默认让 LOOP 连续运行;信息不可推断、方案出现重大分叉、动作需要越权或用户主动纠偏时,才交还控制权。Human in the loop 不是逐步遥控。
-
四种介入,四种语义。 steer 改方向,提问补信息,审批授权限,interrupt 管生命周期。协议和 UI 必须区分它们,不能把所有决定压成一个“确定/取消”。
-
请求统一挂起,应答精确唤醒。 交互请求都是 Event → pending waiter → 应答 Op → oneshot 唤醒;请求带完整坐标、不可静默丢失、可被中断,等待不堵塞提交循环。
-
决定必须带作用域和后续控制流。 批准一次、本 turn、本 session、持久规则不是同一件事;拒绝并继续与拒绝并中止也不是同一件事。扩大作用域必须由用户显式选择。
-
业务问题归口,安全边界不转借。 子 agent 的业务疑问由根 agent 汇总后再问人,避免弹窗风暴;危险动作仍逐一经过统一策略和审批,父 agent 不能替整棵树创造授权。
-
注意力有预算,无人值守则边界更硬。 自动调查、规则、缓存和 guardian 过滤低价值请求,把人留给不可替代的判断;没人能回答时,需要审批的动作应被禁止,而不是默认放行。
留给读者思考的几个问题:
- steer 在步骤边界才生效。如果当前工具是一个持续十分钟、具有外部副作用的部署命令,“等待边界”是否仍然合理?是否需要工具级暂停或更细的安全点?
- 用户批准的是屏幕上展示的动作,但从批准到执行之间环境可能变化。哪些字段应该成为不可变的审批指纹,才能避免“批准 A,执行成了 B”?
request_user_input的回答被视为可信用户输入。如果问题本身是被网页提示注入诱导出来的,结构化提问是否可能成为“洗白外部指令”的通道?前端应该展示多少提问来源?- 会话级审批缓存减少了打扰,却可能跨越工作目录、环境或子 agent 身份复用。缓存键里应该纳入多少上下文,才能在命中率和安全性之间取得平衡?
- 人工等待期间,模型没有消耗 token,但系统资源、外部锁和任务时限仍可能流逝。哪些资源应该释放,哪些状态必须保留?
- 如果一次任务需要十次人工介入,这是任务本身高风险,还是 agent 缺少工具、上下文或策略?可观测性应该怎样区分二者?(→ 第 12 章)
下一章我们进入持久化与可恢复性:人的决定、被中断的 turn、权限变化和完整事件流怎样落盘;进程重启后,哪些状态可以安全恢复,哪些等待中的交互必须重新确认。