feat(workflow): show durable run records in Chat
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md
|
||||
2026-07-05-dynamic-workflows.md: 3e491478286eb77b56872fcbbdd5ebb6b62a5545
|
||||
2026-07-05-dynamic-workflows.zh.md: 7888d83f981a96ac5eb31d5ca6f1f8d0b4930ec7
|
||||
2026-07-05-dynamic-workflows.md: 287b0031a5fecaaa815befa3c7b792c3179f1dae
|
||||
2026-07-05-dynamic-workflows.zh.md: 8b63498fd7f82159bfc0cc3b5d29f3a151d84338
|
||||
|
||||
@@ -40,6 +40,8 @@ The engine exposes an in-process `MessageChannel` test path because main-process
|
||||
|
||||
A `workflow` tool mirroring `dsh-tool-subagent`'s synchronous shape: start, await, `try/finally` dispose, abort-bridge `exec.signal`, non-`completed` → `isError`. Render intent: a `generic` card titled by the call's `meta.name` parameter (presentation is a pure function of args). The tool description IS the model-facing authoring spec. The usage policy ships with the tool as its own `tool:<toolName>` prompt section (explicit-ask-only guidance — tool guidance lives in tool plugins, never in the deployment persona); the harness has no ultracode-style effort gate.
|
||||
|
||||
For a top-level tool execution, the same consumer also writes the run and actual member lifecycle into the calling parent Session as four log-only `tool-workflow/*` events. The recording path observes rather than controls execution: its first append failure disables later writes for that run and leaves a legal prefix without changing the tool result. [`ui-workflow-run`](../../../../packages/client/ui-workflow-run/README.md) rebuilds those facts through the Conversation Node engine as a separate keyed Chat row; the existing generic tool row remains its own presentation owner. The detailed persistence, replay, disclosure, and live-navigation decision lives in [durable workflow runs in Chat](2026-08-10-durable-workflow-runs-in-chat.md).
|
||||
|
||||
### The foundation: structured output on the subagent seam
|
||||
|
||||
`SubagentStartRequest.outputSchema` is implemented by `dsh-subagent-inprocess` for both in-process backends. Each structured child receives its own scoped capture tool, instruction, and enforcement registrations on `child.ctx`; concurrent children can use different schemas without sharing mutable policy, and disposing the child removes the entire attachment.
|
||||
@@ -60,7 +62,6 @@ Worker-side logic runs through an in-process `MessageChannel` so V8 coverage mea
|
||||
- **Nested `workflow()`**, **token `budget`**, and the `effort`/`isolation`/`agentType` agent options (each rejects loud with a message naming it deferred).
|
||||
- **An overall run wall-clock timeout** — cancellation always frees the caller (result settles within the grace), so a cap on total run time is a policy knob for the background redesign, not a correctness need here.
|
||||
- **Engine hardening beyond worker threads**: an isolated-vm or separate-process engine behind the same seam (actual sandboxing; memory limits).
|
||||
- **Human-interface progress UI** over the `workflow/*` events (a `/workflows`-style view); the events exist for it.
|
||||
- **ACP-backend structured output** and **`toolFilter`** (both still capability-gated `false`).
|
||||
|
||||
## Alternatives considered
|
||||
@@ -77,4 +78,4 @@ Worker-side logic runs through an in-process `MessageChannel` so V8 coverage mea
|
||||
|
||||
## Consequences
|
||||
|
||||
Fan-out plans now live in rerunnable scripts, and `outputSchema` provides authoritative structured child results. Each run pays worker startup and message-port RPC costs, but host startup stays non-blocking, cancellation can terminate the worker, and serialization enforces the value boundary. Worker threads are not a security boundary. Invalid options fail rather than degrading to Claude Code's `null`; consumers retain control through the run handle while observers receive snapshots only.
|
||||
Fan-out plans now live in rerunnable scripts, and `outputSchema` provides authoritative structured child results. Each run pays worker startup and message-port RPC costs, but host startup stays non-blocking, cancellation can terminate the worker, and serialization enforces the value boundary. Worker threads are not a security boundary. Invalid options fail rather than degrading to Claude Code's `null`; consumers retain control through the run handle while observers receive snapshots only. Top-level Web users also receive a durable, replayable workflow record without widening the execution seam or coupling the original tool card to workflow-specific UI.
|
||||
|
||||
@@ -40,6 +40,8 @@ harness 可以将一个任务委派给一个子 agent(`dsh-tool-subagent`)
|
||||
|
||||
一个 `workflow` 工具,镜像 `dsh-tool-subagent` 的同步形态:启动、await、`try/finally` dispose、abort 桥接 `exec.signal`、非 `completed` → `isError`。渲染意图:一张以调用的 `meta.name` 参数为标题的 `generic` 卡片(展示是参数的纯函数)。工具描述即面向模型的编写规范。使用策略以工具自身的 `tool:<toolName>` 提示词段落随工具发布(显式请求才使用的引导——工具引导存在于工具插件中,从不在部署 persona 中);harness 没有 ultracode 风格的 effort 门控。
|
||||
|
||||
对于顶层工具执行,同一消费方还会把运行及真正开始过的成员生命周期写入调用方父 Session,形成四类 log-only `tool-workflow/*` 事件。记录路径只观察、不控制执行:第一次 append 失败会禁用本运行后续写入并留下合法前缀,不改变工具结果。[`ui-workflow-run`](../../../../packages/client/ui-workflow-run/README.md) 通过 Conversation Node 引擎重建这些事实,形成独立 keyed Chat 行;现有 generic 工具行继续拥有自己的展示。持久化、回放、折叠与实时导航的详细决策见 [Chat 中的持久工作流运行](2026-08-10-durable-workflow-runs-in-chat.md)。
|
||||
|
||||
### 基础:subagent seam 上的结构化输出
|
||||
|
||||
`SubagentStartRequest.outputSchema` 由 `dsh-subagent-inprocess` 为两个进程内后端实现。每个结构化子 agent 在 `child.ctx` 上获得自己的作用域捕获工具、指令和强制注册;并发子 agent 可以使用不同的 schema 而不共享可变策略,dispose 子 agent 时移除整个附件。
|
||||
@@ -60,7 +62,6 @@ worker 侧逻辑通过进程内 `MessageChannel` 运行,使 V8 覆盖率能够
|
||||
- **嵌套 `workflow()`**、**token `budget`**,以及 `effort`/`isolation`/`agentType` agent 选项(每个都会明确拒绝,并在消息中注明其已延迟实现)。
|
||||
- **整体运行的挂钟超时**:取消总能释放调用方(result 在宽限期内 settle),因此总运行时间上限是后台重设计的策略旋钮,不是此处的正确性需求。
|
||||
- **超越 worker 线程的引擎加固**:在同一 seam 背后使用 isolated-vm 或独立进程引擎(真正的沙箱化;内存限制)。
|
||||
- **面向人类界面的进度 UI**(基于 `workflow/*` 事件的 `/workflows` 风格视图);事件已为此而存在。
|
||||
- **ACP(Agent Client Protocol)后端结构化输出**和 **`toolFilter`**(两者仍以能力标志 `false` 门控)。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
@@ -77,4 +78,4 @@ worker 侧逻辑通过进程内 `MessageChannel` 运行,使 V8 覆盖率能够
|
||||
|
||||
## 后果
|
||||
|
||||
扇出计划现在存在于可重运行的脚本中,`outputSchema` 提供权威的结构化子 agent 结果。每次运行付出 worker 启动和消息端口 RPC 成本,但宿主启动保持非阻塞,取消可以终止 worker,序列化强制执行值边界。worker 线程不是安全边界。无效选项会失败而非退化为 Claude Code 的 `null`;消费方通过 run handle 保持控制权,观察者仅接收快照。
|
||||
扇出计划现在存在于可重运行的脚本中,`outputSchema` 提供权威的结构化子 agent 结果。每次运行付出 worker 启动和消息端口 RPC 成本,但宿主启动保持非阻塞,取消可以终止 worker,序列化强制执行值边界。worker 线程不是安全边界。无效选项会失败而非退化为 Claude Code 的 `null`;消费方通过 run handle 保持控制权,观察者仅接收快照。顶层 Web 用户还会得到持久、可回放的工作流记录,同时不扩宽执行 seam,也不把原工具卡耦合到工作流专属 UI。
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-10-durable-workflow-runs-in-chat.md
|
||||
2026-08-10-durable-workflow-runs-in-chat.md: 791a81e9e304a11f45557197ac1f97184132ccab
|
||||
2026-08-10-durable-workflow-runs-in-chat.zh.md: e6c87f61a144cebc0282055c8ae315d9068616fd
|
||||
@@ -0,0 +1,45 @@
|
||||
# Agent Note: Durable workflow runs in Chat
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-08-10-durable-workflow-runs-in-chat.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The ordinary workflow tool row owns the model call and final tool result, but those two records do not explain which members actually started, how they were grouped, whether each member completed, failed, or was cancelled, or what remained unfinished when a process stopped. Live `workflow/*` events expose those facts only inside the current process, so a refresh or later Session open loses the run history.
|
||||
|
||||
The Web Client already assembles business-owned Conversation Nodes from durable Session events. Workflow history therefore needs a producer that can correlate one accepted run with its calling Session, a minimal durable protocol that remains meaningful as a prefix, and an independent renderer that does not take ownership away from the existing tool card.
|
||||
|
||||
## Decision
|
||||
|
||||
`dsh-tool-workflow` projects every top-level accepted run into the calling Agent's Session. `tool-workflow/run-start` records the stable `runId` and validated name; matching workflow member events record the member sequence, exact label, optional exact phase, child Session id, and outcome; `tool-workflow/run-end` records the stop reason only after the result exists and `run.dispose()` has reached quiescence. Nested transport executions run normally but write no workflow record because they do not own an independent Chat row.
|
||||
|
||||
Recording is observational. The first failed Session append disables all later writes for that run, logs one warning, and never changes cancellation, result mapping, or disposal. Each possible failure leaves either no record or a legal continuous prefix: a started run may lack later members or its ending, and a started member may lack its ending. The package invariant rejects duplicate run starts, invalid or reused positive member sequences, unpaired or repeated member endings, a run ending while members remain open, and every update after a run ending on both cold load and live append.
|
||||
|
||||
The workflow package exposes browser-safe run and observation vocabulary through `@deepseek-ai/dsh-workflow/types`; live `Agent` requests and control handles remain Host-only. `@deepseek-ai/dsh-tool-workflow/types` owns the four Session events. Client code imports only these type faces, so the Host and Client TypeScript programs share the durable contract without merging Host Cordis context.
|
||||
|
||||
`ui-workflow-run` registers one `workflow-run` Conversation Definition and one keyed Chat renderer. Every event independently yields the same `runId`; run-start initializes State, later events update it in log order, and an update-only history tail remains pending until prepend supplies the unique start. The final node keeps the engine-owned key and anchors at run-start, placing it after the original tool call while preserving one React parent from running through terminal state.
|
||||
|
||||
The renderer gives each level a distinct visual responsibility. The run uses a 32-pixel module-platform background row with persistent right/down chevrons and an inline state dot plus status text, without a badge. Phases use 32-pixel disclosure rows with title and member count in the flexible main area and a fixed precise aggregate-status tail, without another dot. Members use a 16-pixel dot slot, a truncating name area, and a fixed 64-pixel status column. Phases exist only when a member actually starts and group by the exact phase string; an omitted phase and the empty string retain distinct identities and localized names. Member settlement changes status without removing or reordering the member. A closed Turn or Step turns missing run or member endings into interrupted presentation; a durable ending remains authoritative when present.
|
||||
|
||||
Navigation is derived from two current authorities rather than persisted. A member row is interactive only while its durable member state is running and the current ordinary Session list contains the same id with `origin: 'subagent'`, `parentId` equal to the displayed parent, and `running: true`. Underlined member text is the only visible affordance; keyboard focus draws a two-pixel business-primary ring around the name area, and the fixed status label remains the lifecycle word rather than an action instruction. The renderer invokes only the injected ordinary `sessions.open(id)` callback. Addressed-only, remote, wrong-parent, and terminal members remain visible but static.
|
||||
|
||||
The [seven-state Figma reference](https://www.figma.com/design/tguwzZRmHCjbq58mfsqT0M?node-id=5-2) fixes the information hierarchy for running expanded/collapsed, completed history/expanded, failed plus cancelled, interrupted recovery, and dark narrow presentation. Repository `DisclosureRow`, `StateDot`, icons, semantic tokens, and keyed-node behavior remain the implementation authority; the reference introduces no runtime field or state owner.
|
||||
|
||||
## Verification
|
||||
|
||||
Package tests cover top-level and nested eligibility, zero-member and concurrent runs, disposal-before-ending order, all four append-failure prefixes, and cold/live invariant rejection. Conversation tests compare complete replace, update-only prepend, and live append; they cover exact phase identity, terminal and interrupted status, disclosure state, list-fact navigation, and HMR removal and re-registration. The shipped Web replay uses the existing workflow parent and child model fixtures to exercise the real worker, spawn provider, Session persistence, browser bundle, running child navigation, terminal retention, original tool-row coexistence, narrow dark tokens, and refresh reconstruction.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Append workflow content inside the existing tool card.** Rejected because `ui-tool` and the tool definition own that row's presentation and interaction. A workflow-specific appendix would couple two independently keyed business lifecycles and revive the removed post-tool attachment model.
|
||||
|
||||
**Persist a server-side projection or add a workflow wire channel.** Rejected because Session events already provide persistence, live delivery, pagination, and gap repair. Another service, cache, or transport would duplicate the same facts and create a second lifecycle owner.
|
||||
|
||||
**Render declared phases or infer a static workflow graph from script text.** Rejected because only member-start events prove work happened. `meta.phases`, `phase()` narration, branches, and script syntax do not describe one authoritative runtime topology.
|
||||
|
||||
**Keep terminal child navigation.** Rejected because the workflow record proves historical identity, not current accessibility. Cold or remote Session opening needs a separate catalog and authorization contract; this node grants no such promise.
|
||||
|
||||
## Consequences
|
||||
|
||||
Workflow progress survives refresh and process recovery in the same log as its parent conversation, while execution ownership remains with the workflow run holder and the original tool card remains unchanged. The durable protocol adds four small events and one package-owned invariant; first-write failure intentionally sacrifices later observation rather than workflow correctness. Browser State is derived per loaded window, disclosure choices remain local, and navigation can disappear as list facts change. The design shows only actual runtime members and statuses, giving up static graph visualization, outputs, logs, controls, and terminal-member opening.
|
||||
@@ -0,0 +1,45 @@
|
||||
# Agent Note: Chat 中的持久工作流运行
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-08-10-durable-workflow-runs-in-chat.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
普通工作流工具行拥有模型调用与最终工具结果,但这两条记录无法说明哪些成员真正开始、如何分组、各成员是完成、失败还是取消,也无法说明进程停止时哪些工作尚未结束。实时 `workflow/*` 事件只存在于当前进程,因此刷新或稍后重新打开 Session 会丢失运行历史。
|
||||
|
||||
Web Client 已经能够从持久 Session 事件组装由业务拥有的 Conversation Node。工作流历史因此需要:能够把一次已接受运行关联到调用 Session 的生产方、作为前缀也始终有意义的最小持久协议,以及不夺走现有工具卡所有权的独立 renderer。
|
||||
|
||||
## 决策
|
||||
|
||||
`dsh-tool-workflow` 把每个已接受的顶层运行投影到调用 Agent 的 Session。`tool-workflow/run-start` 记录稳定 `runId` 与已校验名称;匹配的工作流成员事件记录成员序号、精确标签、可选精确阶段、子 Session id 与结果;只有在结果已取得且 `run.dispose()` 完全停稳后,`tool-workflow/run-end` 才记录停止原因。嵌套 transport 执行照常运行,但不会写工作流记录,因为它不拥有独立 Chat 行。
|
||||
|
||||
记录只供观察。任一次 Session append 首次失败后,本运行会停止所有后续写入、只记录一次告警,并且绝不改变取消、结果映射或 dispose。每种失败位置都留下空记录或合法连续前缀:已开始运行可以缺少后续成员或运行终点,已开始成员也可以缺少成员终点。包 invariant 会在冷加载与实时 append 时拒绝重复运行 start、无效或复用的正成员序号、无配对或重复成员 end、仍有开放成员时结束运行,以及运行结束后的任何更新。
|
||||
|
||||
workflow 包通过 `@deepseek-ai/dsh-workflow/types` 提供浏览器安全的运行与观察词汇;包含活跃 `Agent` 的请求和控制句柄继续只属于 Host。`@deepseek-ai/dsh-tool-workflow/types` 拥有四类 Session 事件。Client 只导入这些类型 face,因此 Host 与 Client TypeScript 程序共享持久合同,而不会合并 Host Cordis Context。
|
||||
|
||||
`ui-workflow-run` 注册一个 `workflow-run` Conversation Definition 和一个 keyed Chat renderer。每条事件都能独立给出同一 `runId`;run-start 初始化 State,后续事件按日志顺序更新;只有 update 的历史尾页会保持 pending,直到 prepend 补入唯一 start。最终节点保留引擎拥有的 key,并以 run-start 锚定在原工具调用之后,从运行中到终态始终保留同一个 React 父级。
|
||||
|
||||
renderer 为每一层分配不同视觉职责。运行使用 32 像素 module-platform 背景行,常驻向右/向下 chevron,并以内联状态点加状态文字表达结局,不使用胶囊。阶段使用 32 像素 disclosure 行,在可伸缩主区显示标题与成员数,在固定尾部精确显示聚合状态且不重复状态点。成员使用 16 像素状态点槽、可省略名称区和固定 64 像素状态列。阶段只在成员真正开始时出现,并按精确阶段字符串分组;字段缺省与空字符串保留不同身份和本地化名称。成员结算只改变状态,不删除或重排成员。所属 Turn 或 Step 关闭时,缺少运行或成员终点会显示为已中断;存在持久终点时仍以它为权威。
|
||||
|
||||
导航从两个当前权威派生,不写入持久记录。只有持久成员状态仍为运行中,且当前普通 Session 列表包含同一 id、`origin: 'subagent'`、`parentId` 等于当前父 Session、`running: true` 时,成员行才可交互。带下划线的成员文字是唯一可见提示;键盘聚焦时,名称区显示 2 像素 business-primary 焦点环,固定状态列继续只表达生命周期,而不写动作说明。renderer 只调用注入的普通 `sessions.open(id)` 回调。仅地址化、远程、父级不符或终态成员继续可见,但保持静态。
|
||||
|
||||
[七状态 Figma 参考](https://www.figma.com/design/tguwzZRmHCjbq58mfsqT0M?node-id=5-2)固定运行展开/收起、完成历史/展开、失败与取消、恢复后中断以及暗色窄列的信息层级。仓库的 `DisclosureRow`、`StateDot`、图标、语义 token 和 keyed-node 行为仍是实现权威;参考稿不引入运行时字段或状态 owner。
|
||||
|
||||
## 验证
|
||||
|
||||
包测试覆盖顶层与嵌套准入、零成员与并发运行、先 dispose 后写终点的顺序、四个 append 失败前缀,以及冷/实时 invariant 拒绝。Conversation 测试比较完整 replace、只有 update 的 prepend 和实时 append,并覆盖精确阶段身份、终态与中断状态、disclosure 状态、列表事实导航、HMR 移除与重新注册。shipped Web replay 复用现有工作流父/子模型 fixture,驱动真实 worker、spawn provider、Session 持久化、浏览器 bundle、运行中子级导航、终态保留、原工具行并存、暗色窄列 token 与刷新重建。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
**把工作流内容附加到现有工具卡。** 拒绝,因为 `ui-tool` 与工具定义拥有该行的展示和交互。工作流专属 appendix 会耦合两个独立 keyed 业务生命周期,并恢复已移除的工具后附加模型。
|
||||
|
||||
**持久化服务端 projection 或新增 workflow wire 通道。** 拒绝,因为 Session 事件已经提供持久化、实时传输、分页和 gap repair。另一个 service、cache 或 transport 会复制同一事实并建立第二个生命周期 owner。
|
||||
|
||||
**展示声明阶段,或从脚本文本推断静态工作流图。** 拒绝,因为只有成员 start 事件能证明工作真正发生。`meta.phases`、`phase()` 叙述、分支和脚本语法都不是一次运行的权威拓扑。
|
||||
|
||||
**保留终态子级导航。** 拒绝,因为工作流记录证明历史身份,不证明当前可访问性。冷 Session 或远程 Session 的打开需要独立目录与授权合同;本节点不作这种承诺。
|
||||
|
||||
## 后果
|
||||
|
||||
工作流进度与父对话保存在同一日志中,能跨刷新与进程恢复;执行所有权仍属于工作流 run holder,原工具卡保持不变。持久协议增加四类小事件和一个包所有的 invariant;首次写入失败会刻意牺牲后续观察,而不是牺牲工作流正确性。浏览器 State 按已加载窗口派生,disclosure 选择保持本地,导航会随列表事实消失。设计只展示真实运行成员与状态,并放弃静态图、输出、日志、控制操作和终态成员打开。
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/rejected/simplification/2026-07-12-collapse-workflow-to-foreground-core.md
|
||||
2026-07-12-collapse-workflow-to-foreground-core.md: 5fc46584f83eb5307ff16f3353b56951b928aef3
|
||||
2026-07-12-collapse-workflow-to-foreground-core.zh.md: 0b4c73e5df973215b10166f3dc2bbd525cc8231b
|
||||
2026-07-12-collapse-workflow-to-foreground-core.md: 9151d9fb72a97aadf040fbdc13b5e0a4943f2f30
|
||||
2026-07-12-collapse-workflow-to-foreground-core.zh.md: c9eafe83e931de7aec4ec39e2471f0669c73609d
|
||||
|
||||
@@ -6,15 +6,11 @@ English | [中文](2026-07-12-collapse-workflow-to-foreground-core.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The workflow capability executes foreground JavaScript that composes subagents, but it also carries an unconsumed progress-observation system. No production listener subscribes to any of the six `workflow/*` events; listeners exist only in workflow tests. Nevertheless the seam defines run/phase/agent outcome payloads, the worker sends phase/log/agent lifecycle protocol messages, the host forwards them through a `liveAgents` pairing ledger, and the engine maintains run ids solely to correlate those notifications.
|
||||
The workflow capability carries an observe-only lifecycle beside its execution handle. That surface can look removable because the script still completes without a UI listener, but it is the only provider-neutral source of the actual members that started, their exact labels and phases, and their paired outcomes.
|
||||
|
||||
The progress vocabulary is not merely unused; it cannot serve its only named future owner without redesign. `WorkflowRunInfo` contains `{id, meta}` but no parent agent, session, or tool-call identity, while the model-facing tool never exposes the run id. A global ACP listener could not route an event to the correct client session. `meta.phases` is never consulted, `phase(title)` does not validate against it, phase `detail`/`model` and agent `label`/`phase` feed only events, and `whenToUse` is validated and copied but never rendered or selected. `phase()` and `log()` still cross the worker boundary despite having no receiver.
|
||||
The top-level `dsh-tool-workflow` consumer now uses those events to write four minimal `tool-workflow/*` facts into the calling parent Session, and `ui-workflow-run` rebuilds them into a durable Chat node. The consumer deliberately owns the projection because it alone holds the calling Agent, knows whether the tool execution is top-level, and can keep recording failure separate from workflow execution. `WorkflowRun.id` and `meta` therefore correlate live engine events with that exact durable record rather than duplicating presentation state.
|
||||
|
||||
The live handle repeats event-era data after those observers disappear. `WorkflowRun.id` has no non-event consumer, while the tool reads `run.meta.name` only to render a value it already owns as `args.meta.name`; neither belongs on the execution/cancellation handle.
|
||||
|
||||
Cancellation also has two public channels for one synchronous start. `WorkflowStartRequest.signal` is passed to the worker host, while the sole production caller separately bridges the same signal to `WorkflowRun.cancel()`. Because `start()` returns the run before control can yield, there is no readiness window that requires request-time cancellation; the duplicate signal adds host listener/disarm state without closing a race.
|
||||
|
||||
`WorkflowError.fatal` is the same speculative branch in miniature: every production construction is fatal, `fatal: false` exists only in tests, and combinators already distinguish workflow failures with `instanceof`.
|
||||
Deleting the event vocabulary, member labels or phases, or run identity would remove the current replay and navigation result rather than merely simplify unused scaffolding. The rejected proposal below remains useful as the contraction to avoid; [durable workflow runs in Chat](../../implemented/feature/2026-08-10-durable-workflow-runs-in-chat.md) owns the present consumer and boundaries.
|
||||
|
||||
## Proposal
|
||||
|
||||
@@ -24,7 +20,7 @@ Amend the implemented dynamic-workflow Agent Note and update the seam/tool/worke
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep the prebuilt observation vocabulary for a future UI.** The current shape resembles Claude Code dynamic-workflow metadata, and the host deliberately pairs each forwarded agent start with either the worker's end or a synthesized terminal end. Removing it gives up compatibility-by-shape and makes progress UI a new design task, but the existing payloads still lack routable ownership, so balanced lifecycles alone cannot make the named ACP owner viable without redesign.
|
||||
**Move durable recording into the workflow engine.** The engine knows run and member lifecycle but does not own the calling parent Session or the top-level-versus-nested tool boundary. Giving it those facts would couple a provider seam to one consumer and make recording failure part of engine execution. The tool-owned projection adds the missing ownership without widening worker messages or the service contract.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
|
||||
@@ -6,15 +6,11 @@ Status: rejected — 工作流进度是有意设计的观测接口面;应通
|
||||
|
||||
## 问题
|
||||
|
||||
工作流能力在前台执行用于编排 subagent 的 JavaScript,但它同时携带了一套无人消费的进度观测系统。没有任何生产环境的监听器订阅六个 `workflow/*` 事件中的任何一个;监听器仅存在于工作流测试中。尽管如此,seam 定义了 run/phase/agent(智能体)outcome 载荷,worker 发送 phase/log/agent 生命周期协议消息,host 通过一个 `liveAgents` 配对账本转发它们,引擎维护 run id 仅仅是为了关联这些通知。
|
||||
工作流能力在执行句柄之外还携带一套只供观察的生命周期。脚本即使没有 UI 监听器也能完成,因此这套界面看似可删除;但它是唯一与提供方无关、能够报告真正开始过的成员、精确标签与阶段以及配对结果的事实来源。
|
||||
|
||||
这套进度词汇不仅仅是未被使用;它在不经重新设计的情况下也无法服务于其唯一已命名的未来消费方。`WorkflowRunInfo` 包含 `{id, meta}` 但没有父 agent、会话或工具调用标识,而面向模型的工具也从不暴露 run id。一个全局 ACP(Agent Client Protocol)监听器无法将事件路由到正确的客户端会话。`meta.phases` 从未被查询,`phase(title)` 不对其做校验,phase 的 `detail`/`model` 和 agent 的 `label`/`phase` 仅供事件消费,`whenToUse` 被校验和复制但从未被渲染或用于选择。`phase()` 和 `log()` 仍然跨越 worker 边界,尽管没有接收方。
|
||||
顶层 `dsh-tool-workflow` 消费方现在利用这些事件,把四类最小 `tool-workflow/*` 事实写入调用方父 Session;`ui-workflow-run` 再把它们重建为持久 Chat 节点。投影由消费方拥有,因为只有它同时持有调用 Agent、知道工具执行是顶层还是嵌套,并能让记录故障与工作流执行隔离。`WorkflowRun.id` 与 `meta` 因此用于把实时引擎事件关联到该条精确持久记录,而不是复制展示状态。
|
||||
|
||||
这些观测者移除后,live handle 仍重复携带事件机制所需的数据。`WorkflowRun.id` 没有非事件消费方,而工具读取 `run.meta.name` 只是为了渲染一个它已经以 `args.meta.name` 形式持有的值;两者都不属于执行/取消 handle。
|
||||
|
||||
取消机制也为一个同步启动提供了两条公开通道。`WorkflowStartRequest.signal` 被传递给 worker host,而唯一的生产调用方另外将同一个 signal 桥接到 `WorkflowRun.cancel()`。因为 `start()` 在控制权让出之前就返回了 run,不存在需要请求时取消的就绪窗口;重复的 signal 增加了 host 的 listener/disarm 状态却没有封堵任何竞态。
|
||||
|
||||
`WorkflowError.fatal` 是同一种推测性分支的微缩版:所有生产环境的构造都是 fatal 的,`fatal: false` 仅存在于测试中,组合子已经通过 `instanceof` 区分工作流失败。
|
||||
删除事件词汇、成员标签或阶段、运行身份,会移除当前回放和导航结果,而不再只是清理未使用脚手架。下方提案继续记录应避免的收缩;[Chat 中的持久工作流运行](../../implemented/feature/2026-08-10-durable-workflow-runs-in-chat.md)拥有当前消费方与边界。
|
||||
|
||||
## 提案
|
||||
|
||||
@@ -24,7 +20,7 @@ Status: rejected — 工作流进度是有意设计的观测接口面;应通
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
**为未来 UI 保留预建的观测词汇。** 当前形态类似 Claude Code 的动态工作流元数据,host 有意地将每个转发的 agent start 与 worker 的 end 或一个合成的终止 end 配对。移除它意味着放弃形态兼容性,使进度 UI 成为一项全新的设计任务;但现有载荷仍缺少可路由的归属信息,因此仅靠平衡的生命周期也无法在不重新设计的情况下让已命名的 ACP 消费方可行。
|
||||
**把持久记录移入工作流引擎。** 引擎知道运行与成员生命周期,却不拥有调用方父 Session,也不知道顶层与嵌套工具边界。把这些事实交给引擎会让提供方 seam 耦合到单一消费方,并使记录故障进入引擎执行域。由工具拥有的投影补齐了缺失所有权,同时不扩展 worker 消息或 service 合同。
|
||||
|
||||
## 验收标准
|
||||
|
||||
|
||||
Reference in New Issue
Block a user