22 · 执行状态该显示给谁
按来源、受众和生命周期路由计划、工具进度、后台任务与恢复摘要,减少 UI 噪声和错误回注。
本章任务
要回答的问题
计划、工具进度、后台状态和恢复摘要,应该分别显示给模型、用户、父 Agent 还是日志?
读完你能
- 按来源、受众和生命周期给执行状态分类
- 避免 UI 噪声、模型上下文污染和状态错误回注
- 用类型化事件路由可见性、持久化和聚合
- 适合现在读
- 正在设计 Agent UI、事件总线、进度反馈或父子 Agent 通信的工程师
- 先修知识
- 理解事件流、Session 与任务状态
- 实践产物
- 一张执行状态路由矩阵与事件所有权表
- 证据边界
- 可见性策略改善理解与控制,但不能替代正确性验证或权限检查
执行状态该显示给谁?
Section titled “执行状态该显示给谁?”场景:一个长工具每秒发十条 heartbeat,同一事件被写入模型上下文、用户聊天、Operator 面板和持久日志。几分钟后模型窗口被进度噪声挤满,用户看见数百条重复消息,而审计日志反而缺少最终状态。
通过标准:事件声明 source、audience、lifetime、priority 和 correlation ID;模型只接收影响决策的摘要;用户看到节流后的里程碑;Operator 可展开细节;审计保存不可变关键事件;临时心跳默认不回注。
“进度”不是一个字段。工具中间通知适合状态栏,未完成 todo 才适合恢复上下文,away summary 用来让用户回来后找回方向。把它们合并会同时增加噪声和误判。
先按来源和受众路由
Section titled “先按来源和受众路由”| 事件 | 主要受众 | 生命周期 | 是否回注模型 |
|---|---|---|---|
| approval plan | 用户 | 直到批准或拒绝 | 否 |
| execution todo | Agent 与用户 | 当前任务 | 是,只保留未完成 |
| tool progress | UI / operator | 一次工具调用 | 通常否 |
| task progress | UI / 父 session | 后台任务 | 摘要后再回注 |
| terminal status | 用户 | 短暂 chrome | 否 |
| away summary | 用户 | 返回时一条提示 | 否 |
Codex 的 MCP progress 是协议通知;Claude Code 将 hook、tool、task 事件分开;OpenClaw 把子 session 进度投影给 parent;Hermes 按平台设置 off/new/all/verbose。共同点是“显示”与“事实”分开。
设计一个不会制造噪声的 router
Section titled “设计一个不会制造噪声的 router”每条事件至少带 source、surface、audience、lifetime、thread_id、turn_id、item_id、status 和可选 parent_id。先去重,再决定投影到 UI、日志或恢复摘要。
重复 polling、子任务无输出和相同结果循环,都应触发 no-progress 检查。这里没有真实运行轨迹,不能把“有进度事件”写成“系统不会卡住”。
源码底稿:按实现展开
Section titled “源码底稿:按实现展开”源码底稿:按实现展开
四套系统怎样投影执行状态
Section titled “四套系统怎样投影执行状态”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` stepCodex 还有第三条进度通道:MCP 工具进度。app-server v2 定义 McpToolCallProgressNotification,包含 thread_id、turn_id、item_id 和 message,事件名映射为 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.rs 里 TaskProgress 会被路由到 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_progress、tool_progress、task_started、task_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.ts 里 task_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_call 和 tool_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 里进度模式是 off、new、all、verbose;new 会跳过连续重复工具名,verbose 会展示完整参数、结果和调试日志。gateway 里 /verbose 命令按平台循环并保存到 display.platforms.<platform>.tool_progress。
它还把默认值按平台分层:CLI / API 可以是 all,聊天平台可以是 new 或 off,webhook 默认 off。API server 则把 ("__tool_progress__", payload) 推进 SSE 队列。同一条 tool progress 在不同平台上的显示密度必须分开配置。
状态展示已经形成的共识
Section titled “状态展示已经形成的共识”来源决定可信度。模型写的 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 只是把卡死包装成进度。
透明度与信息噪声怎么取舍
Section titled “透明度与信息噪声怎么取舍”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 写入。
- 不要只显示进度不检测卡死;这会把故障包装成“正在努力”。
状态从事件流到界面
Section titled “状态从事件流到界面”沿着状态事件读源码
Section titled “沿着状态事件读源码”本章带走什么与下一步实验
Section titled “本章带走什么与下一步实验”同一事实不该广播到所有表面。执行状态要按谁产生、谁需要、活多久和是否影响决策来路由;可见性、持久化和回注是三个独立选择。
下一步实验:生成 1000 条混合事件,包含 heartbeat、工具开始/结束、审批、错误、恢复和最终结果。验证模型获得最小决策摘要、用户只看到里程碑、Operator 可追踪因果、审计保留关键状态,并测量上下文 Token 与 UI 消息下降比例。
附录:练习与复盘
Section titled “附录:练习与复盘”按需展开练习和十道复盘题
- 给一个现有 agent 画出 6 个状态面:approval plan、execution todo、tool progress、task progress、terminal/status、resume summary。
- 写一个 projection policy 表:每种事件是否显示给用户、是否进日志、是否回注入模型、何时过期。
- 模拟 20 次重复 polling,确认 UI 会折叠重复进度并触发 no-progress warning。
- 模拟用户离开 5 分钟后返回,生成一条 1-3 句 away summary,确认不会把 raw tool log 当总结。
- 把一个外部 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 示例包含 source、surface、audience、lifetime、thread_id、turn_id、item_id、message、status 和 parent_id?;字段集合应随投影和恢复需求调整。
有了这些字段,UI 可以按 audience 投影,resume 可以按 lifetime 过滤,日志可以按 source 审计,父子任务可以按 parent id 聚合。