description, kind
| description | kind |
|---|---|
| The workflow orchestration capability: run a model-written script that fans out subagents, for users and maintainers choosing or building on ctx.workflowEngine. | package-reference |
@deepseek-ai/dsh-workflow
English | 中文
Summary
dsh-workflow runs a plain-JavaScript orchestration script and gives the caller a live run whose result resolves with the script's final JSON value. The script can fan out subagents with agent(), combine independent work with parallel() and pipeline(), and narrate progress with phase() and log(); agents normally drive this through the workflow tool from dsh-tool-workflow. A run is holder-owned: its result never rejects, cancellation and disposal are bounded, and every child is attributed to the invoking agent. The package ships no execution engine — dsh-workflow-worker-thread is the current one — so a different isolation strategy can replace it without changing what callers or the model see.
Table of Contents
- Use this package
- Understand the implementation
- Further Exploration
- Model Experience
- Known Limitations and Deferred Work
- Dev Note
Use this package
Run a workflow when a task decomposes into many independent pieces that one script should coordinate — an audit across many files, a migration, multi-angle research — and the model explicitly asks for workflow-style orchestration. For one or two delegations, prefer a plain subagent call.
The model-facing path
The model reaches the capability through the workflow tool from dsh-tool-workflow, which owns the call schema and result envelope; the engine supplies the execution underneath. A tool call submits meta, script, and optional args and returns { runId, agentsStarted, result } when the run completes. The tool blocks the parent turn until the whole workflow settles, so the model sees one final outcome, never intermediate child messages.
Running a workflow script
An orchestration script is a plain JavaScript body (not TypeScript) that runs with top-level await and ends with return <json-value>. The meta identity block and any args arrive as plain JSON data — never evaluated code. During execution the script calls the provided hooks: agent(prompt, opts) starts one subagent and resolves with its final text or, with a schema, a validated structured value; parallel() and pipeline() combine independent work; phase() and log() narrate progress for observers.
// Script body — runs with top-level await, ends with a JSON return value:
const reviews = await parallel([
() => agent('Review src/a.ts for correctness'),
() => agent('Review src/b.ts for correctness'),
])
return { reviewed: reviews.length }
When the script settles, the run's result resolves with the returned value, the stop reason, and the number of children started. A script that returns nothing yields null.
Programmatic runs
Plugin consumers can start a run directly: ctx.workflowEngine.start({ script, meta, args?, parent, signal? }). parent attributes every child to the invoking agent; signal cancels the run when aborted. start() validates the meta block and parses the script before a run exists, so a malformed request fails immediately with a violation list.
A returned run exposes id, meta, result, cancel(reason?), and dispose(). The result never rejects: a script failure resolves with stopReason: 'error', cancellation with 'cancelled'. The caller owns the run — call dispose() on every path; it cancels remaining work and waits for script and children to settle within a bounded grace.
Failures and recovery
A script that does not parse, a malformed meta block, an unavailable provider route, or an unsupported per-run limit is rejected synchronously before a run exists; the workflow tool reports these as errors the model can correct from. During execution, hook misuse — bad arguments, unknown options, unsupported schemas, tripped caps — kills the script loudly rather than dissolving into a per-item null. An ordinary child failure is not an infrastructure error: agent() resolves null and the script decides how to handle it.
Understand the implementation
Implementation internals — click to expand
This section explains how the capability is split and where the contracts live; observable behavior is fully covered in Use this package.
Design concept
The package separates the script, run, result, and event contracts from execution: any engine can implement ctx.workflowEngine behind the same vocabulary, and one engine serves a context at a time — loading a second engine fails loud, so swapping engines means changing which engine plugin the composition loads. The workflow/* events are observe-only: payloads carry run identity snapshots, never the live run, so listeners cannot acquire cancellation or disposal authority.
Source map
| File | Role |
|---|---|
src/index.ts |
Service definition, workflow/* event declarations, WorkflowError and its fatal flag |
src/types.ts |
Browser-safe vocabulary: WorkflowMeta, WorkflowResult, run and agent event info |
src/runtime-types.ts |
Host-only WorkflowStartRequest and WorkflowRun handles |
src/invariant.ts |
Invariant companion: event pairing and identity checks |
Lifecycle and ownership
A run is holder-owned: engine-plugin unload prevents new starts but does not revoke accepted runs, and the caller must dispose every run it starts. dispose() cancels if needed and awaits script and child quiescence within the engine's documented bound, so a consumer awaiting result is never wedged past a cancellation.
workflow/start and workflow/end pair the run; workflow/phase and workflow/log carry script narration; workflow/agent-start and workflow/agent-end pair each child call by seq. Every listener is independently contained: a throwing listener is logged without starving peers or changing execution, and each receives its own payload clone.
Failure discipline
WorkflowError carries a machine-routable code and a fatal flag; every code is fatal, and parallel() and pipeline() re-throw fatal errors instead of mapping the item to null — a typo'd option must kill the script loudly. Codes cover start failures, contract violations, exceeded caps, provider and result faults, unserializable values, and cancellation; the exact set and meanings live in src/index.ts.
The per-item null is reserved for child-run failures and ordinary in-stage script errors, so a child that resolves normally with a non-completed stop reason is not an infrastructure exception: agent() returns null, letting the script handle an ordinary child failure.
Further Exploration
Read these pages when the package-level contract is not enough. They move from the shared workflow model to the current engine and the model-facing consumers.
- Workflow subsystem — the full type vocabulary, start request, and event payloads.
- Group map — the workflow capability family and its packages.
- workflow tool — the model-facing consumer that owns the call schema and result envelope.
- Worker-thread engine — the current execution engine and its isolation boundary.
- Dynamic workflows Agent Note — the seam design and its decisions.
Model Experience
Indirectly, through its consumer dsh-tool-workflow and a workflow engine, which render the parent tool result and the child-agent requests.
KV Cache effect
No direct invalidation; the named consumer and engine own any request-prefix changes.
Known Limitations and Deferred Work
These limits define what the capability does not yet support. They are current constraints, not a task backlog.
- Foreground collection only — the caller owns one live run and awaits it; background start/poll, spill handles, and detached collection are deferred.
- No journaling or resume — scripts, child progress, and intermediate values are not checkpointed, so a process restart cannot continue a run.
- No saved or nested workflows — the capability starts caller-supplied scripts only, and a workflow script receives no
workflow()hook for recursive orchestration. - No token-budget vocabulary — engines cap concurrency, items, and children, but neither the request nor the result accounts for model tokens across children.
- Runs are holder-owned, not service-tracked — unloading the engine does not discover independent live handles; every consumer must dispose the run it started.
Dev Note
Working context for maintainers — click to expand
This Dev Note is working context for maintainers: open directions that are not decided. It is explicitly non-authoritative — shipped behavior, limits, and accepted rationale live in the sections above, the package code, and the linked Agent Notes.
Deferred directions: a background start/poll API with spill handles and detached collection; saved and nested workflows; a token-budget vocabulary across children; and the seam's promise that a future process or sandbox engine can replace the worker-thread engine without changing the model-facing surface.