工作流
工作流 seam 允许 agent(智能体)运行由模型编写、会启动 subagent 的编排脚本。与 subagent 一样,它是一项可选能力,不属于 agent loop,因此其类型和操作记录在此处,而非 core.md。与 bash 一样,每个上下文只允许一个引擎实现提供 ctx.workflowEngine;没有命名提供方注册表(第二个引擎通过插件配置替换第一个,而不与它同时运行)。
Service Definition:dsh-workflow(ctx.workflowEngine + 下文词汇)。Service Provider 是 dsh-workflow-worker-thread(一个 node:worker_threads 引擎——每个 run 一个 worker,脚本的 vm 上下文位于其中);面向模型的 Consumer 是 dsh-tool-workflow。提案与设计理由见 dynamic-workflows Agent Note。
源码:浏览器安全词汇位于 packages/workflow/workflow/src/types.ts,Host 请求与活跃运行句柄位于 runtime-types.ts。
启动请求
本节定义调用方启动一次运行时提交的请求。普通工作流工具会根据模型的 { script, meta, args } 调用和发起调用的 agent 构建该请求;专用消费方还可以为本次运行选择引擎级 subagentProvider,并将 maxTotalAgents 调低,但脚本无法观察或替换这两项策略。meta 与 args 是普通 JSON 数据;引擎会用 schema 校验 meta,并在任何工作开始前明确报错并拒绝无效数据。引擎绝不会通过对脚本文本求值来获取它们。parent 是必填字段——脚本启动的每个子 agent 都归属于它,cwd、谱系与深度通过 subagent seam 传递。
/**
* What a caller asks for when starting a workflow run. `meta` and `args` are
* plain JSON data by the seam contract. `parent` is required because every
* `agent()` spawned by the script is attributed to that live Agent.
*/
interface WorkflowStartRequest {
/** The plain-JS script body (top-level await allowed; ends with `return <json-value>`). */
script: string
/** The workflow's identity block, as plain JSON data (shape-validated by the engine). */
meta: WorkflowMeta
/** Optional input exposed verbatim to the script as the `args` global. */
args?: unknown
/** Optional engine-wide child-provider override for this run. */
subagentProvider?: string
/** Optional per-run total-child ceiling. */
maxTotalAgents?: number
/** The agent on whose behalf the run executes (parent of every child). */
parent: Agent
/** Cancels the run when aborted. */
signal?: AbortSignal
}工作流的身份标识:WorkflowMeta
作为数据附在启动请求上的身份块(工具的 meta 参数;字段词汇与 Claude Code 动态工作流的 meta 块一致)。phases 仅用于进度展示:phase() 调用与标题匹配,供观察者使用;不暗示任何执行结构。
/**
* The script's identity block, provided as plain JSON data alongside the
* script body (the model-facing tool carries it as its `meta` parameter) and
* validated by the engine before the body runs. `name`/`description` are
* required; the rest is optional annotation. The field vocabulary matches the
* Claude Code dynamic-workflows meta block.
*/
interface WorkflowMeta {
/** Short kebab-case workflow name (display + persistence key). */
name: string
/** One-line description of what the workflow does. */
description: string
/** Optional guidance on when this workflow applies (shown in listings). */
whenToUse?: string
/** Optional phase declarations matched by `phase()` calls. */
phases?: WorkflowPhase[]
}终态结果:WorkflowResult
WorkflowRun.result 会兑现为一次运行的结果。value 是脚本的物化返回值——纯宿主域 JSON 数据(脚本无返回值时为 null)——仅在 completed 时有意义。stopReason 是封闭联合类型(由引擎定义;消费方可穷举):completed | cancelled | error。非 completed 的原因在 error 中携带失败信息,消费方将其映射为 isError 工具结果,而非把部分输出当作成功上报。
/**
* The outcome resolved by a live workflow run. `value` is
* the script's materialized return value (plain host-realm JSON data; `null`
* when the script returned `undefined`) — meaningful only for `completed`.
* A non-`completed` reason carries the failure in `error`; the consumer maps
* it to an `isError` tool result rather than reporting partial output.
*/
interface WorkflowResult {
/** The script's return value (host JSON data; `null` for no return). */
value: unknown
/** Why the run settled. */
stopReason: WorkflowStopReason
/** The failure message (present iff `stopReason` is not `completed`). */
error?: string
/**
* How many `agent()` calls the run accepted over its whole lifetime. On a
* graceful settlement this is the script-side count (calls still queued for
* a concurrency slot included); on a termination path (grace force-settle,
* worker death) it degrades to the host-observed count — calls queued
* inside a terminated script are unknowable then.
*/
agentsStarted: number
}活跃运行:WorkflowRun
脚本执行期间消费方持有的句柄。消费方会等待 result,可以在运行期间调用 cancel,并且必须在每条路径上调用 dispose(资源释放)。result 不会被拒绝:脚本失败会兑现为 stopReason: 'error'。运行被取消后,即使脚本本身永不结算,结果也会在引擎规定的有界宽限期内结算;引擎会强制将其结算为 cancelled,随后 worker-thread 引擎会终止脚本所在的 worker。因此,等待 result 的消费方不会在取消后无限期挂起。dispose() 会执行取消、等待有界结算并等待子 agent 完全停稳,不会因脚本卡死而挂起。
/**
* Holder-owned live workflow. `result` never rejects; consumers may cancel
* and must call idempotent `dispose()` to await script and child quiescence.
*/
interface WorkflowRun {
readonly id: WorkflowRunId
/** The validated meta block available before the script body runs. */
readonly meta: WorkflowMeta
readonly result: Promise<WorkflowResult>
/** Cancel the run and its children. */
cancel(reason?: string): void
/** Cancel if needed and await bounded settlement and cleanup. */
dispose(): Promise<void>
}失败纪律:WorkflowError.fatal
脚本内部的钩子误用:错误参数、未知或延迟的 agent() 选项、超出结构化输出子集的 schema、超出上限、seam 启动失败、取消,都会抛出 fatal: true 的 WorkflowError。parallel()/pipeline() 组合器对 fatal 错误直接重新抛出,而非将该项映射为 null:一个拼写错误的选项必须明确报错并终止脚本,绝不能消融为看似普通子 agent 失败的结果。逐项的 null 保留给子运行失败(非 completed 的 stop reason)和阶段内的普通脚本错误。
事件
workflow/* 事件(workflow/start、workflow/phase、workflow/log、workflow/agent-start、workflow/agent-end、workflow/end,见事件目录)是仅供观察的 emit,携带数据快照:每个 payload 以 WorkflowRunInfo(id + meta)开头,而非活跃的 WorkflowRun,因此订阅者无法获得 cancel/dispose;workflow/end 刻意省略 result value(观察结果的监听器不得收到调用方 result 的可变别名)。每次 emit 对每个监听器隔离:订阅者抛出的异常会被记录到日志中而不会传播,也不会阻止后续注册的监听器收到事件;每个监听器收到自己的 payload 克隆,因此修改它既不会损坏引擎也不会影响其他监听器。这种隔离方式与 subagent/start/subagent/end 一致。
持久 Chat 记录
顶层 dsh-tool-workflow 消费方把展示事实投影到调用它的父 Session,同时不改变执行所有权。运行接受后写 tool-workflow/run-start,以 runId + seq 配对成员开始与结束,并且只在结果已取得且 dispose 完全停稳后写 tool-workflow/run-end。嵌套 transport 调用不写记录。第一次 append 失败会禁用本运行后续写入,因此日志保持为空或合法连续前缀,工具结果不变。
dsh-tool-workflow/invariant 会在实时提交前和 Session 加载时校验同一协议:每个运行只有一个 start,成员序号为正且唯一,成员 end 必须配对,仍有开放成员时不能结束运行,运行结束后不能继续更新。日志尾部缺少成员 end 或 run end 是有效的中断证据,不是损坏。
dsh-client-ui-workflow-run 通过 Conversation Node 引擎把四类事件折叠为一个 workflow-run Chat 节点,以 run-start 序号锚定在原工作流工具节点之后。阶段组只来自真正开始过的成员,并保留精确字符串,包括字段缺省与 '' 的区别。Location 关闭时,缺失终点会显示为已中断。界面包 README负责定义 disclosure、状态与同父本地导航行为。
Cordis API
Generated from source by scripts/gen-cordis-catalog.ts (verified fresh by pnpm run verify-cordis-catalog in doc-sync; regenerate with pnpm run gen-cordis-catalog) — this section is byte-identical in both language sides of the page. Signature blocks use a ts cordis-catalog fence and keep the original source JSDoc; dispatch modes are defined in the primer, and the framework-inherited ctx API lives in cordis-api/inherited.md.
ctx.workflowEngine — WorkflowEngine (abstract seam)
Workflow Service Definition contract. Invalid requests throw before publication; a live run is holder-owned, its result never rejects, cancellation and disposal are bounded, and disposal waits for child cleanup within that bound. Lifecycle listener failures are contained, and workflow/end fires exactly once as the result settles.
/**
* Parse and execute a workflow script.
* @param request - the script, its `args`, the parent agent, and an
* optional cancel signal.
* @returns the live run; its `result` resolves when the script settles.
*/
abstract start(request: WorkflowStartRequest): WorkflowRunSource: packages/workflow/workflow/src/index.ts:157
workflow/* events
workflow/agent-end — emit
One agent() call settled (clean result, child failure, or run cancellation). Paired with Events['workflow/agent-start'] by agent.seq, exactly once per started call on every stop path — on an engine termination path (a worker killed past its grace) the end is engine-synthesized with outcome 'cancelled'.
/**
* One `agent()` call settled (clean result, child failure, or run
* cancellation). Paired with {@link Events['workflow/agent-start']} by
* `agent.seq`, exactly once per started call on every stop path — on an
* engine termination path (a worker killed past its grace) the end is
* engine-synthesized with outcome `'cancelled'`.
* @param info - the run's identity snapshot.
* @param agent - the call identity plus its outcome.
* @mode emit
*/
'workflow/agent-end'(info: WorkflowRunInfo, agent: WorkflowAgentEndInfo): voidSource: packages/workflow/workflow/src/index.ts:79
workflow/agent-start — emit
One agent() call established a published child run. Paired with Events['workflow/agent-end'] by agent.seq. A call that never receives a published run from the provider emits neither event in this pair.
/**
* One `agent()` call established a published child run. Paired with
* {@link Events['workflow/agent-end']} by `agent.seq`. A call that never
* receives a published run from the provider emits neither
* event in this pair.
* @param info - the run's identity snapshot.
* @param agent - the call's sequence number, label, phase, and child id.
* @mode emit
*/
'workflow/agent-start'(info: WorkflowRunInfo, agent: WorkflowAgentInfo): voidSource: packages/workflow/workflow/src/index.ts:68
workflow/end — emit
A workflow run settled (any stop reason). Fired when WorkflowRun.result resolves. Paired with Events['workflow/start'].
/**
* A workflow run settled (any stop reason). Fired when
* {@link WorkflowRun.result} resolves. Paired with
* {@link Events['workflow/start']}.
* @param info - the run's identity snapshot.
* @param result - the outcome data (stop reason, error, agent count) —
* deliberately WITHOUT the result value (see {@link WorkflowResultInfo}).
* @mode emit
*/
'workflow/end'(info: WorkflowRunInfo, result: WorkflowResultInfo): voidSource: packages/workflow/workflow/src/index.ts:89
workflow/log — emit
The script emitted a narration line (a log(message) call).
/**
* The script emitted a narration line (a `log(message)` call).
* @param info - the run's identity snapshot.
* @param message - the logged message, verbatim.
* @mode emit
*/
'workflow/log'(info: WorkflowRunInfo, message: string): voidSource: packages/workflow/workflow/src/index.ts:58
workflow/phase — emit
The script entered a phase (a phase(title) call) — progress grouping for observers; no execution semantics.
/**
* The script entered a phase (a `phase(title)` call) — progress grouping
* for observers; no execution semantics.
* @param info - the run's identity snapshot.
* @param title - the phase title, verbatim.
* @mode emit
*/
'workflow/phase'(info: WorkflowRunInfo, title: string): voidSource: packages/workflow/workflow/src/index.ts:51
workflow/start — emit
A workflow run started — the script's meta block validated, the body about to execute. Paired with Events['workflow/end'].
/**
* A workflow run started — the script's meta block validated, the body
* about to execute. Paired with {@link Events['workflow/end']}.
* @param info - the run's identity snapshot (id + meta).
* @mode emit
*/
'workflow/start'(info: WorkflowRunInfo): void