## 手动运行与观察

<!-- slot: 目标 -->
第 7 章发布了不可变 Plan 版本，本章让它真正跑一次。你会用精确 `planVersionId` 发起手动 Run，从回执取得 `runId`，再用命令行和 Desktop 判断它正在执行、等待、失败还是已经结束。读完后，你能沿着 Graph、Timeline、State 和业务日志找到当前步骤，并在输入错误时安全取消仍可取消的 Run。

<!-- slot: 边界 -->
本章只讲一次手动 Invocation 及其 Run。第 9 章才创建 Trigger / Binding，让外部事件或定时规则自动发起 Run；后续可靠性章节再展开分支、循环、重试和多 Agent 协作。这里也不把公共 `show` 当作完整运行明细接口：它只给 Run 摘要、当前步骤和等待摘要，完整 Activation、Effect、Event 视图在 Desktop 的运行详情页。

<!-- slot: 提示词 -->
手动运行不需要新提示词，也不要让 Agent 猜资源 ID。先从第 7 章保存 `planId` 与已批准的精确 `planVersionId`，再从当前 Workspace 选择真实的频道、分诊 Agent 和审批成员。下面的 `pln_`、`plv_`、`chn_`、`agt_`、`mem_`、`wri_` 都是脱敏示例，复制前必须替换；本章只对示例做 schema 校验，不实际启动带消息、审批或外部调用的 Run。

<!-- slot: 流程 -->
一次可观察的手动运行按以下顺序进行：

1. 按精确版本的顶层 `input` 契约准备 `destination`、`bug_summary`、`triage_agent` 和 `approver`。
2. 调用 `start`。平台先校验版本、输入与运行身份，再原子接受 Invocation 并创建初始 Run。
3. 从回执保存 `runId`、`status` 和实际冻结的 `planVersionId`，不要用 Plan 的显示名追踪。
4. 用 `show` 快速判断状态；需要理解执行路径时打开 Desktop 的 Run 详情。
5. 用 `logs` 读取有界、脱敏的业务日志；等待新进展时使用 `follow:true`，不要紧密循环调用 `show`。
6. 到达 `succeeded`、`failed` 或 `canceled` 后记录输出或错误；只有确认输入批次有误且 Run 仍非终态时才取消。

公共入口的启动命令如下。这个 Bug 分诊 Plan 若来自已批准的 Team 关联，省略 `teamId` 即会使用并冻结该关联的权威 Team：

```bash
$ printf '%s\n' '{"action":"start","planId":"pln_bug_triage","planVersionId":"plv_bug_triage_v1","input":{"destination":{"kind":"channel","scopeId":"chn_bug_ops_demo"},"bug_summary":"登录接口偶发 500","triage_agent":"agt_triage_demo","approver":"mem_approver_demo"}}' | loop exec workflow run -
```

典型回执包含 `runId`、`status`、`planId`、`planVersionId` 与 `acceptedAt`。显式版本让这次 Run 不受后来 Default 指针变化影响；若省略版本，系统只在接受 Invocation 时选择当时的默认不可变版本，接受后同样冻结。已归档 Plan 拒绝新的手动 Run，但已接受 Run 不会因之后归档或换默认版本而改变。

Desktop 的 **Run Now** 对话框只使用打开时固定的 Default 版本，并按那一版 source 生成类型化输入表单；对话框打开后，别处更新 Default 不会悄悄替换表单版本。提交失败后不改输入直接重试会复用同一逻辑调用意图；只要输入或版本改变，就会创建新意图。若要运行任意历史已发布版本，应使用上面的公共 `start` 并显式给 `planVersionId`。

<!-- slot: 配置 -->
先用 `show` 做轻量检查：

```bash
$ printf '%s\n' '{"action":"show","runId":"wri_bug_triage_demo"}' | loop exec workflow run -
```

`run.status` 的闭集是 `queued`、`running`、`waiting`、`succeeded`、`failed`、`canceled`。响应中的 `run` 还会给精确 Plan 版本、输入、终态输出或错误，以及排队、开始、完成、取消时间；有可投影步骤时，`currentStep` 给出 `stepId`、类型、状态、尝试次数和可选 deadline / error，终态 Run 也可能保留这一字段。它只是服务端从有界读取的 Activation 中选出的代表步骤，不是“全部当前活动节点”的全局真相；需要完整路径时应查看下方分页的 Graph / Timeline。Run 为 `waiting` 时还可能有 `waitingOn`，其 `kind` 可区分普通步骤、业务审批、等待 Agent callback、消息投递或其它外部效果，并附可用的 `stepId` / `deadlineAt`。

打开 Desktop 的 Run 详情后，先核顶部的状态、精确版本、`Manual` 来源与 Invocation ID，再在三个页签间切换：

- **Graph** 按 Activation 的父子关系画执行图，显示节点状态、尝试次数、选中端口和错误；它不是把事件按时间简单连线。
- **Timeline** 按时间组合 Activation、Effect 与 durable Event，适合追消息投递、callback 排队、审批和 fan-out 汇总；需要时展开详情 JSON。Activation 的 input / output 会原样进入 Timeline，Effect / Event 也只过滤有限的基础设施字段，作者业务 payload 不会自动清洗。
- **State** 显示本次 Run 的 input、当前 state、output，以及 `errorCode` / error。这里的作者输入不是秘密保险箱，不得传 token、密码或未脱敏客户数据。

三类子资源首批各取 100 条；页面出现 **Load More** 时继续分页，否则只看首屏可能漏掉长 Run 后段。Graph 和 Timeline 中看到的 callback responder、协作 scope 或队列位置是观察信息，不应拿内部 Activation / Effect ID 代替公共 `runId`。

读取精简业务日志：

```bash
$ printf '%s\n' '{"action":"logs","runId":"wri_bug_triage_demo","tail":100}' | loop exec workflow run -
```

每项包含 `sequence`、时间、级别、事件种类与消息；当前公共日志投影不返回 `stepId`，要定位具体 Node 请转到 Timeline。响应还给 `nextCursor`、`runStatus` 和 `terminal`。继续观察时用：

```bash
$ printf '%s\n' '{"action":"logs","runId":"wri_bug_triage_demo","tail":100,"follow":true}' | loop exec workflow run -
```

`follow:true` 是有界长轮询：服务端等待事件或终态，Desktop 再沿 cursor 续取，直到终态、没有后续 cursor 或达到本次条数上限。它不是无限流，也不保证返回完整 Activation / Effect payload。

<!-- slot: 异常 -->
排障时先按状态分流：

- `queued`：Invocation 已接受但尚未开始；先确认不是正常排队，不要因没有 `startedAt` 就重复发起新 Run。
- `waiting`：查看 `waitingOn.kind` 和 `currentStep.deadlineAt`。等待审批或 Agent callback 不等于故障；只有 Plan 中 `wait.timeout` 等明确 runtime deadline 才会自动走超时端口。
- `failed`：读取 `run.errorCode` / error，并在 Graph 找失败 Activation、在 Timeline 看它之前的投递与重试。修正输入或 Plan 后应启动新 Run，不能把失败 Run 改回 running。
- 输入被拒绝：常见原因是字段缺失、类型不符、Agent 不在当前 Workspace、scope 不可见，或指定版本不可用。修复公共错误返回指出的 `$.input` 路径，不要绕过版本契约。

若误选了 Bug 批次，只在 `queued`、`running` 或 `waiting` 时取消，并写简洁原因：

```bash
$ printf '%s\n' '{"action":"cancel","runId":"wri_bug_triage_demo","reason":"发现输入批次错误，停止本次运行"}' | loop exec workflow run -
```

重复取消同一个已取消 Run 会安全重放；`succeeded` / `failed` 不能被改写成 `canceled`。取消会终止尚未提交的运行工作、释放相关租约并留下取消事件，但不能撤销已经发生的外部动作。若某个 Effect 已提交 dispatch claim，或一次投递尝试后回到 `pending` 且 `attempt_count>0`，平台会因外部写入可能已经发生而暂时拒绝取消，让带稳定幂等语义的投递先收敛；reducer 已经逻辑终态、只等待 Effect 排空的 Run 也视为已完成，不能再改写为 canceled。稍后依据最新状态重试或记录终态，而不是并发轰炸取消接口。

<!-- slot: 审批 -->
本章运行中的 `request_approval` 是业务审批，不是第 7 章的 Team Plan 发布审批。到达该节点时 Run 通常为 `waiting`，`waitingOn.kind` 为 `approval`；审批成员在原审批卡中通过或驳回后，Run 才沿对应端口继续。平台当前不会替业务审批强制一个通用超时，因此无响应时仍按第 4 章约定的业务 SLA 人工催办或升级。不要为“让流程继续”重复 start；那只会创建另一条独立 Run。

<!-- slot: 外部系统 -->
Agent callback 可能经 App / Extension 调第三方系统。公共 `logs` 只投影精简的事件摘要；Desktop 的 Effect / Event 操作详情会移除有限的基础设施身份、租约和幂等键，但作者自带的 Activation input / output、Run input / state / output 仍会按业务字段展示，不能依赖平台替你清洗其中的秘密。取消也不是第三方事务回滚：消息已经送达、工单已经创建或 API 已成功写入时，要使用外部系统自己的幂等键、补偿动作和审计记录。

Invocation 接受时会冻结 Team、Agent、scope 等身份值与执行身份快照，但不会冻结一份永久有效的权限 capability。已关联 Team 的 Plan 以批准关系中的 Team 为准，冲突的显式 `teamId` 会被拒绝；未关联 Plan 省略 `teamId` 就是普通非 Team Run，显式指定时会校验调用者的 Team 成员资格。Agent 输入会检查当前 Workspace 归属，scope 输入会做准入期的可见性与权限检查；之后消息 Effect 或 callback 真正消费 scope 时，仍会按当前资源是否 active、Workspace / kind 是否匹配，以及冻结 Human 身份此刻拥有的权限重新授权。运行期间撤销访问权可能让后续步骤失败，所以“Plan validate 通过”或“Invocation 已接受”都不代表权限会在整个 Run 中永久有效。

<!-- slot: 验收 -->
- [ ] 能用精确 `planVersionId` 和四项真实输入发起一次手动 Run，并保存回执中的 `runId`。
- [ ] 能解释省略版本、Desktop Run Now 默认版本与显式不可变版本的差异。
- [ ] 能用 `show`、Graph、Timeline、State 与 `logs` 分别回答状态、路径、事件和数据问题。
- [ ] 能区分 `waiting`、`failed` 与三个终态，并按 `waitingOn` / error 找到下一步。
- [ ] 能在非终态 Run 上带原因取消，并说明取消不能撤销已提交的外部动作。

<!-- slot: 媒体槽位 -->
<!-- media: fig-08-01-manual-run-observe.png -->
图 8-1 手动运行与观察。场景：Bug 分诊 Run 正等待业务审批；前置数据：一个已发布且显式钉定的 PlanVersion、一组脱敏输入、至少一条 Agent callback 与审批 Effect；画面同时包含顶部精确版本和 Manual 来源、Graph 当前节点、Timeline 投递事件、State 输入输出，以及 `waitingOn=approval` 的命令行摘要。脱敏要求：替换真实 Workspace、Team、成员、Agent、频道、Invocation / Run ID 和 Bug 内容，隐藏所有凭证、第三方响应及客户数据。
