A new capability family at packages/workflow/ in the bash seam shape,
modeled on Claude Code's dynamic workflows: the model writes a JavaScript
orchestration script (export const meta = {...} + plain-JS body), a runtime
executes it, and the script — not the conversation — holds the loop, the
branching, and the intermediate results.
- dsh-workflow (ctx.workflows): abstract WorkflowService + run vocabulary
(WorkflowRun whose result NEVER rejects) + observe-only workflow/* events
carrying data snapshots (id + meta, never the live run), per-listener
contained like subagent/*.
- dsh-workflow-vm: in-process node:vm engine. Meta extraction via a
string/comment-aware scanner (template interpolation rejected; literal
evaluated alone in an empty timed context; statement blanked line-
preservingly so stacks keep script line numbers). Hooks: agent(prompt,
{label, phase, schema, model}) over ctx.subagents, parallel(), pipeline()
(no cross-stage barrier), phase(), log(), args. Fatal-vs-null discipline:
hook misuse (unknown/deferred options, bad arguments, unsupported
schemas, tripped caps, seam start failures, cancellation) throws fatal
WorkflowErrors the combinators RE-THROW — never dissolved into the
per-item null reserved for child failures. Realm boundary: inbound values
materialized by descriptor walks that never invoke accessors (defineProperty
copies, __proto__-safe); outbound values rebuilt in-realm via the
context's own JSON.parse. Determinism bans (Date.now/Math.random/argless
new Date) kept so future resume support cannot break scripts. Caps and
timeouts are validated Config. Every hook promise carries a no-op
rejection consumer (app-boot exits on unhandled rejections).
- dsh-tool-workflow: the model-facing workflow tool, synchronous like
dsh-tool-subagent (start → await → try/finally dispose; abort bridged;
non-completed → isError). Generic render card titled by a textual
meta.name sniff. The tool description carries the authoring contract.
Wired into examples/{coding-agent,acp-agent} with explicit-ask-only
guidance. Coverage at every tier: unit (meta scanner, materializer incl.
counting-getter and __proto__ regressions, combinator semantics,
concurrency ceiling, caps, cancellation, no-unhandled-rejection abandon),
integration over the real spawn stack, with-key e2e (real two-phase run +
the tool through the registry pipeline), and a recorded ACP snapshot
scenario (workflow-run, 1 child session). RFC:
docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md (deferred
work explicitly listed). AGENTS.md budget 1575 → 1590 for the new group's
layout line.
@deepseek-ai/dsh-workflow
The workflow seam (ctx.workflows): an abstract service defining WHAT a workflow engine does — execute a model-written orchestration script that fans out subagents — without saying HOW. The bash-shaped third of the workflow family: implementations subclass WorkflowService and register as the workflows service (one per context); dsh-workflow-vm is the first, and dsh-tool-workflow is the model-facing consumer.
Service: WorkflowService (abstract)
start(request: WorkflowStartRequest): WorkflowRun — parse and execute a script. Throws synchronously (SCRIPT_PARSE/META_INVALID) for a script that cannot begin; once a run is returned, its result NEVER rejects — every failure resolves with stopReason: 'error' (or 'cancelled'). dispose() must reach quiescence within a bounded grace (cancel → wait → abandon), never hanging its caller.
The protected emitWorkflowEvent helper dispatches the workflow/* events with PER-LISTENER containment (a throwing subscriber is logged, never propagated, and cannot starve later listeners) — the same guarantee as the subagent seam's lifecycle emits.
Vocabulary
WorkflowStartRequest—{ script, args?, parent: Agent, signal? }.parentis REQUIRED: every child the script spawns is attributed to it.argsmust be plain host-realm JSON data.WorkflowMeta/WorkflowPhase— the script's validatedexport const metablock (Claude Code format: requiredname/description, optionalwhenToUse/phases).WorkflowRun—{ id, meta, result, cancel(reason?), dispose() }; the consumer awaitsresultand MUSTdisposeon every path.WorkflowResult—{ value, stopReason: 'completed'|'cancelled'|'error', error?, agentsStarted };valueis the script's materialized return (plain JSON data;nullfor no return).WorkflowError—HarnessErrorwith aWorkflowErrorCodeand afatalflag driving the combinator discipline: a fatal error (bad hook arguments, unsupported options/schemas, tripped caps, seam start failures, cancellation) always propagates throughparallel()/pipeline()instead of dissolving into a per-itemnull.isFatalWorkflowError(error)is the catch-site predicate.
Events
All observe-only emits carrying DATA SNAPSHOTS (WorkflowRunInfo = id + meta) — never the live WorkflowRun, so a listener cannot gain cancel/dispose; control stays with the start() caller:
workflow/start(info) /workflow/end(info, resultInfo) — run lifecycle;resultInfodeliberately omits the value.workflow/phase(info, title) /workflow/log(info, message) — script narration.workflow/agent-start(info, agent) /workflow/agent-end(info, agent + outcome) — one pair peragent()call, correlated byseq.
Non-goals (this cut)
Background collection, journaling/resume, saved workflows, nested workflow(), token budgets — see the RFC's deferred section.