跳到主要内容

22 · 执行状态该显示给谁

按来源、受众和生命周期路由计划、工具进度、后台任务与恢复摘要,减少 UI 噪声和错误回注。

本章任务

要回答的问题

计划、工具进度、后台状态和恢复摘要,应该分别显示给模型、用户、父 Agent 还是日志?

读完你能

  • 按来源、受众和生命周期给执行状态分类
  • 避免 UI 噪声、模型上下文污染和状态错误回注
  • 用类型化事件路由可见性、持久化和聚合
适合现在读
正在设计 Agent UI、事件总线、进度反馈或父子 Agent 通信的工程师
先修知识
理解事件流、Session 与任务状态
实践产物
一张执行状态路由矩阵与事件所有权表
证据边界
可见性策略改善理解与控制,但不能替代正确性验证或权限检查

场景:一个长工具每秒发十条 heartbeat,同一事件被写入模型上下文、用户聊天、Operator 面板和持久日志。几分钟后模型窗口被进度噪声挤满,用户看见数百条重复消息,而审计日志反而缺少最终状态。

通过标准:事件声明 source、audience、lifetime、priority 和 correlation ID;模型只接收影响决策的摘要;用户看到节流后的里程碑;Operator 可展开细节;审计保存不可变关键事件;临时心跳默认不回注。

“进度”不是一个字段。工具中间通知适合状态栏,未完成 todo 才适合恢复上下文,away summary 用来让用户回来后找回方向。把它们合并会同时增加噪声和误判。

事件主要受众生命周期是否回注模型
approval plan用户直到批准或拒绝
execution todoAgent 与用户当前任务是,只保留未完成
tool progressUI / operator一次工具调用通常否
task progressUI / 父 session后台任务摘要后再回注
terminal status用户短暂 chrome
away summary用户返回时一条提示

Codex 的 MCP progress 是协议通知;Claude Code 将 hook、tool、task 事件分开;OpenClaw 把子 session 进度投影给 parent;Hermes 按平台设置 off/new/all/verbose。共同点是“显示”与“事实”分开。

每条事件至少带 sourcesurfaceaudiencelifetimethread_idturn_iditem_idstatus 和可选 parent_id。先去重,再决定投影到 UI、日志或恢复摘要。

重复 polling、子任务无输出和相同结果循环,都应触发 no-progress 检查。这里没有真实运行轨迹,不能把“有进度事件”写成“系统不会卡住”。

源码底稿:按实现展开
执行状态面:approval plan、execution todo、tool progress、task progress、terminal status、away summary 分层
Todo 只负责当前焦点;proposal、工具事实、后台任务和恢复提示要走不同路由。

Codex · 从 prompt 纪律到协议事件再到终端标题

Section titled “Codex · 从 prompt 纪律到协议事件再到终端标题”

Codex 的 base instructions 同时约束两类进度。第一类是给用户的自然语言更新:长任务需要简短说明已经做了什么、接下来做什么,写大文件前也要先说明动作。第二类是 update_plan 工具:它是可渲染的 checklist,不要求每个自然语言进度都变成 plan item。

Codex codex/codex-rs/protocol/src/prompts/base_instructions/default.md:52-60, 173-175, 267-275 Codex 同时规定自然语言进度更新和 update_plan 状态机。
Do not repeat the full contents of the plan after an `update_plan` call
...
## Sharing progress updates
...
There should always be exactly one `in_progress` step

Codex 还有第三条进度通道:MCP 工具进度。app-server v2 定义 McpToolCallProgressNotification,包含 thread_idturn_iditem_idmessage,事件名映射为 item/mcpToolCall/progress。外部 MCP 工具因此可以报告下载、索引、查询等中间状态,不必等最终 tool result。

Codex codex/codex-rs/app-server-protocol/src/protocol/v2/mcp.rs:202-207 MCP 工具进度是 app-server v2 的一等通知。
pub struct McpToolCallProgressNotification {
pub thread_id: String,
pub turn_id: String,
pub item_id: String,
pub message: String,
}

TUI 另有 terminal title / status surface。status_surfaces.rsTaskProgress 会被路由到 status line、preview item 和 terminal title。这类状态只服务人的等待感知,不回写给模型。

Claude Code · SDK 事件族、终端 progress 和 away summary

Section titled “Claude Code · SDK 事件族、终端 progress 和 away summary”

Claude Code 把进度拆得更细。SDK schema 里不只有 tool result,还有 hook_progresstool_progresstask_startedtask_progress。这些事件能覆盖 hook 执行、工具调用、后台任务启动、后台任务消耗与摘要。

claude-code/src/entrypoints/sdk/coreSchemas.ts:1616-1657, 1715-1762 Claude Code SDK 把 hook、tool、background task 的进度事件分开建模。
subtype: z.literal('hook_progress')
type: z.literal('tool_progress')
subtype: z.literal('task_started')
subtype: z.literal('task_progress')

远程 session path 也承认这些事件属于后台任务生命周期:useRemoteSession.tstask_started 注册远程 subagent,task_progress 到达时可以选择跳过渲染或折叠,print.ts 则在命令队列前 flush / drain 这些事件,避免后台 agent 进度被最终输出吞掉。

终端层另有 OSC 9;4。useTerminalNotification.ts 通过 OSC 9;4 上报 progress,terminal.ts 明确不同终端兼容性,supportedSettings.ts 给用户配置开关。这个状态面不应该进 transcript;它是 terminal chrome。

Away summary 解决的是“回来后从哪继续”。当前 Claude Code 源码在终端失焦 5 分钟、没有 turn 运行且上一轮之后没有 summary 时,用最近 30 条消息生成 1-3 句提示;这些是当前实现阈值,不是本站测得的最佳 UX。它用 small fast model、querySource: 'away_summary'skipCacheWrite: true,定位为 UI 恢复卡片而不是长期记忆。

OpenClaw · 事件投影和 hidden boundary

Section titled “OpenClaw · 事件投影和 hidden boundary”

OpenClaw 的进度核心不是模型 todo,而是 ACP runtime event。translator 在工具开始时发 tool_call 且状态 in_progress,工具结束时发 tool_call_update。auto-reply projector 默认把 tool_calltool_call_update 当 hidden boundary tag,必要时转成工具摘要;

tool_call_update 允许 edit,避免同一个工具状态在聊天平台上刷屏。

OpenClaw 的 child session relay 也属于 execution state surface:spawn 后会告诉父 session “正在把进度流回 parent”,之后把 child 的片段变成 contextPrefix:progress。这和 Todo List 不同,它是父子 session 间的事实传播。

Hermes · 显示策略是 per-platform 配置

Section titled “Hermes · 显示策略是 per-platform 配置”

Hermes 把 tool_progress 暴露成用户可配置的开关。CLI 里进度模式是 offnewallverbosenew 会跳过连续重复工具名,verbose 会展示完整参数、结果和调试日志。gateway 里 /verbose 命令按平台循环并保存到 display.platforms.<platform>.tool_progress

它还把默认值按平台分层:CLI / API 可以是 all,聊天平台可以是 new 或 off,webhook 默认 off。API server 则把 ("__tool_progress__", payload) 推进 SSE 队列。同一条 tool progress 在不同平台上的显示密度必须分开配置。

来源决定可信度。模型写的 todo、工具返回的事实、后台任务生命周期、终端 chrome、恢复摘要,不属于同一种真相;混在一起后,审计和恢复都会失真。

受众决定粒度。模型上下文通常需要短、稳定、可恢复的状态;用户需要节奏;operator 可以看细节;日志可保留完整事实;终端标题只能容纳极短文本。具体取舍要用目标渠道验证。

生命周期决定是否持久化。Approval plan 批准后过期;execution todo 完成后清空或只留审计;tool progress 进事件流;task progress 可跨进程;away summary 下一次用户输入后失效。

无进展检测不能省。Hermes 的 new 模式跳过重复工具名,OpenClaw 的 projector 可编辑同一 tool update,OpenClaw 还做 tool-loop / no-output detector。没有这层,UI 只是把卡死包装成进度。

执行状态面怎么分层

Plan-first

  • 适合高风险执行前审批
  • 用户能先看方案
  • proposal 可流式展示
  • 不能代表真实执行进度
  • 批准后应转入 todo 或 task
  • 容易和 checklist 混用

Todo-first

  • 适合单 agent coding CLI 的当前焦点
  • 模型有明确当前焦点
  • 压缩恢复简单
  • 工具细节不完整
  • 后台任务语义不足
  • 容易过早标完成

Event-first

  • 直接记录工具和子 agent 事实
  • 适合 operator UI
  • 失败和卡死可观测
  • 剩余语义工作不一定清楚
  • 需要聚合和去重
  • 不能全量塞回 prompt

Task-board-first

  • 适合 IDE / 团队 / 后台任务
  • owner、blocker、claim 可审计
  • 在状态、锁和事件都持久化时支持跨进程恢复
  • 复杂度高
  • 需要锁和迁移
  • 不适合替代轻量 todo

Status-card-first

  • 用户体验好
  • 不会污染主 transcript
  • 适合终端和 resume 场景
  • 不是事实源
  • 不能承担恢复真相
  • 要避免总结幻觉
状态面按职责拆分;每一层都要写清来源、受众和失效条件。

路由原则:先展示用户能采取行动的信息

Section titled “路由原则:先展示用户能采取行动的信息”

最小实现:状态面先保持可行动

Section titled “最小实现:状态面先保持可行动”

Execution State Router 复刻方案

最小可行

  • 定义统一事件 envelope:`source`、`surface`、`audience`、`lifetime`、`thread_id`、`turn_id`、`item_id`、`message`。
  • 把 approval plan、execution todo、tool progress 三类事件先分开,不允许互相覆盖。
  • UI 侧做 projection policy:哪些显示给用户、哪些只进日志、哪些允许编辑同一条消息。
  • 上下文恢复只注入 pending / in_progress 的 execution todo,不注入 raw tool progress。
  • 给长任务保留自然语言 progress update 纪律,但不要把它当持久状态。

进阶

  • 加 MCP progress notification,外部工具可报告中间状态。
  • 加 background task 事件:`task_started`、`task_progress`、`task_completed`、`task_failed`。
  • 加 terminal/status surface:标题、状态栏、OSC 9;4 或等价进度提示,并提供关闭开关。
  • 加 away / resume summary:用户离开后只总结高层目标和下一步,跳过 commit recap。
  • 加 no-progress detector:重复 polling、同工具同参数无变化、子 session 无输出都要报警。

一开始别做

  • 不要把 Plan、Todo、Task、Tool event 合并成一张全局表。
  • 不要把所有 tool progress 原样回注入模型;摘要即可。
  • 不要让聊天平台默认显示 verbose 级别工具参数。
  • 不要把 away summary 当 long-term memory 写入。
  • 不要只显示进度不检测卡死;这会把故障包装成“正在努力”。
执行状态路由矩阵:事件来源、投影面、持久化、上下文回注入策略
execution-state router 的价值在于按目标投影:每个事件只去指定的 surface。

同一事实不该广播到所有表面。执行状态要按谁产生、谁需要、活多久和是否影响决策来路由;可见性、持久化和回注是三个独立选择。

下一步实验:生成 1000 条混合事件,包含 heartbeat、工具开始/结束、审批、错误、恢复和最终结果。验证模型获得最小决策摘要、用户只看到里程碑、Operator 可追踪因果、审计保留关键状态,并测量上下文 Token 与 UI 消息下降比例。

按需展开练习和十道复盘题
  1. 给一个现有 agent 画出 6 个状态面:approval plan、execution todo、tool progress、task progress、terminal/status、resume summary。
  2. 写一个 projection policy 表:每种事件是否显示给用户、是否进日志、是否回注入模型、何时过期。
  3. 模拟 20 次重复 polling,确认 UI 会折叠重复进度并触发 no-progress warning。
  4. 模拟用户离开 5 分钟后返回,生成一条 1-3 句 away summary,确认不会把 raw tool log 当总结。
  5. 把一个外部 MCP 工具改成支持 progress notification,确认前端能显示中间状态但最终 prompt 只拿摘要。
Q1 · 概念:Execution State Surface 和 Todo List 有什么区别?

Todo List 是 execution state surface 的一种,负责当前执行焦点。Execution State Surface 是更大的集合,还包括审批计划、工具进度、后台任务进度、MCP progress、终端状态和恢复摘要。

判断标准是来源和受众:Todo 通常由模型维护并回注入上下文;tool progress 来自工具事实,主要给 UI / operator 看;away summary 给用户回来时恢复认知,不是任务真相。

Q2 · 设计:为什么不能把 Plan、Todo、Task 合成一张表?

因为三者生命周期不同。Plan 是批准前 proposal,用户可能拒绝;Todo 是当前执行焦点,完成后应清空或只留审计;Task 是后台或团队协调,需要 owner、blocker、锁和重试。

合并后最常见的错误是:把没批准的 proposal 当执行进度,把已完成 todo 重新注入上下文,或者为单人 CLI 引入过重的 task-board 复杂度。

Q3 · 协议:MCP progress 和 tool result 的关系是什么?

Tool result 是终态输出,MCP progress 是中间通知。Codex app-server v2 给 MCP progress 单独定义 McpToolCallProgressNotification 和事件名 item/mcpToolCall/progress

这允许慢工具在运行中报告“正在下载、正在索引、正在查询”,UI 能及时显示;模型通常不需要看到所有原始 progress,只需要最终 result 或摘要。

Q4 · UX:终端 progress 为什么不应该进 transcript?

终端标题、状态栏、OSC 9;4 是 chrome,不是对话内容。它们的作用是降低用户等待焦虑,而不是给模型提供事实。

如果写进 transcript,压缩和 resume 会把短暂 UI 状态误当任务语义,长期会造成上下文噪声。

Q5 · Resume:away summary 和 compaction summary 有什么区别?

Away summary 面向用户回来时恢复认知,通常 1-3 句,强调高层目标和下一步。Compaction summary 面向模型继续执行,需要保存未完成任务、关键约束、工具结果和最近意图。

Claude Code 的 away summary 用最近 30 条消息、小模型、skipCacheWrite;这些选择把它限制在 UI 恢复卡片这一层,不是长期记忆。

Q6 · Operator:为什么多通道系统需要 per-platform progress 配置?

终端和 API 可以承受更密的工具进度,聊天平台会被刷屏,webhook 往往只需要最终结果。Hermes 用 off/new/all/verbose 和 per-platform 默认值处理这个差异。

一个通用默认值会同时伤害两边:给 CLI 太少信息,或者给聊天平台太多噪声。

Q7 · Subagent:子代理进度应该走 Todo 还是 Task Progress?

子代理本身的运行态应该走 task progress:started、progress、completed、failed、cancelled。父 agent 的“我还在等哪个子任务”可以反映成 execution todo。

这两个层次要分开。否则子代理每个工具调用都会污染父 todo,或者父 todo 完成了但 child 迟到事件又把最终回复打乱。

Q8 · 故障:为什么“显示进度”还不够?

进度 UI 可能忠实显示系统卡死。例如同一个 tool 用同样参数反复 polling、子 session 没输出、工具返回完全不变。

所以 progress surface 必须配 no-progress detector:重复事件折叠、长时间无输出报警、相同结果 circuit breaker。

Q9 · Prompt:哪些状态可以回注入模型?

通常优先回注入当前未完成 execution todo、关键 task terminal result、经过摘要的工具事实和用户最新目标;raw tool progress、终端 chrome、已完成 todo、未批准 plan 通常留在各自 surface。具体白名单要按恢复实验确定。

原则是:模型只需要继续执行所需的稳定状态,不需要 UI 噪声。

Q10 · 实现:最小 Execution State Router 需要哪些字段?

一个可测试的 envelope 示例包含 sourcesurfaceaudiencelifetimethread_idturn_iditem_idmessagestatusparent_id?;字段集合应随投影和恢复需求调整。

有了这些字段,UI 可以按 audience 投影,resume 可以按 lifetime 过滤,日志可以按 source 审计,父子任务可以按 parent id 聚合。