Files
deepseek-harness/packages/workflow/workflow

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

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.


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.