description: "The worker-thread workflow engine: executes model-written orchestration scripts off the host event loop, for users and maintainers choosing or configuring execution isolation."
English | 中文
Use dsh-workflow-worker-thread to run model-written workflow scripts away from the host event loop. Each run receives its own worker thread, so synchronous loops do not stall the harness and scripts that ignore cancellation can be terminated. The engine supports the workflow and ralph tools in shipped compositions and can be paired with dsh-tool-workflow to expose workflow in another composition. This isolation limits availability failures but is not a security boundary; genuinely untrusted scripts require a separate process or container.
Mount this engine when a composition needs the workflow capability: each orchestration script runs in its own worker thread, off the host event loop, and the workflow and ralph tools in the shipped composition execute on it. Do not use it as a sandbox for genuinely untrusted scripts — hostile code needs a separate-process or container engine.
Loading the engine registers ctx.workflowEngine; adding dsh-tool-workflow on top gives the model the workflow tool. Every config field is optional:
- name: '@deepseek-ai/dsh-workflow-worker-thread'
- name: '@deepseek-ai/dsh-tool-workflow'
| Field | Default | Meaning |
|---|---|---|
provider |
spawn |
Host-side subagent provider used by agent() calls. |
maxConcurrentAgents |
0 |
Concurrent agent() ceiling; 0 resolves from available CPU parallelism. |
maxTotalAgents |
1000 |
Total agent() calls one run may start — the runaway-loop backstop. |
maxItemsPerCall |
4096 |
Items accepted by one parallel() or pipeline() call. |
syncTimeoutMs |
5000 |
VM timeout for the script's initial synchronous slice, in milliseconds. |
disposeGraceMs |
5000 |
Bound before force-settlement and worker termination; also bounds dispose(). |
An owning consumer may set WorkflowStartRequest.subagentProvider and WorkflowStartRequest.maxTotalAgents for one run — engine-level policy, not script hooks; the ordinary workflow tool leaves both unset, and a per-run total-child cap may lower but never raise the configured ceiling. The generated configuration catalog is the exhaustive source for every accepted field.
When a run starts, the script body executes in the worker with top-level await and the hooks agent(), parallel(), pipeline(), phase(), and log(); meta and args arrive as plain JSON data, never evaluated code. Every agent() call starts a host-side subagent under the configured provider, with the run's parent as the parent of every child. The run settles with the script's final JSON value; an ordinary child failure resolves agent() to null so the script can handle it.
A malformed meta block, a body that does not parse, an unavailable provider route, or a per-run cap above the ceiling is rejected synchronously before a worker exists, so the caller sees a violation list and can correct the call. During execution, hook misuse and tripped caps kill the script with a fatal workflow error. Cancellation is bounded: a script that ignores it is force-settled as cancelled and its worker terminated after disposeGraceMs.
Script CPU work and synchronous spins stay off the host event loop, worker.terminate() gives disposal a real final stop, and the worker starts with a scrubbed environment — only platform temp paths and, in source mode, TSX_TSCONFIG_PATH — so ambient credentials do not cross through process.env. Host/worker messages use structured-clone data with plain-JSON validation at the script boundary.
None of this is a security boundary: no timers, filesystem API, or Node globals are intentionally injected, but escaped code can still reach Node with the worker's process authority.
Read these pages when the engine-level contract is not enough. They move from the seam contract to the model-facing consumers and the design decisions.
ctx.workflowEngine.Every script agent() call sends its prompt verbatim and optional model or structured-output schema to a subagent provider. Each child sees that provider's own context; phase and log narration stays on observer events.
Potentially many independent child contexts are paid, bounded by maxConcurrentAgents, maxTotalAgents, and maxItemsPerCall; they never join the parent history directly.
Independent of the parent request cache and of sibling children. Each child can reuse only a byte-identical prefix under its own provider, model, prompt, and schema; its later history grows append-only.
Through dsh-tool-workflow, success exposes only the materialized final JSON value and child count in that consumer's wrapper. This engine supplies stable errors including workflow script does not parse: <error>, invalid meta: <violations>, agent() requires a non-empty prompt string, agent() could not start a child: <error>, and child agent run failed: <error>, plus its exact parallel(), pipeline(), phase(), option, schema, and JSON-boundary validation messages. Intermediate child outputs are available to the script but not the parent model.
Zero direct parent tokens from this engine. Final result size is capped by the tool consumer and retained until compaction.
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
These limits define when the engine is a poor fit or needs special operational care. They are current constraints, not a task backlog.
node:vm and reach the worker's process authority; a hostile-code deployment needs a separate-process or container engine.agentsStarted excludes worker-side calls still queued behind concurrency when a forced termination makes them unknowable.instanceof Error inside scripts — workflow authors must branch on stable fields such as name and code.