DeepSeek Harness subagents, jobs, and workflows: delegate without losing ownership

Quick answer

At the source snapshot checked for this guide—default branch master, commit 528c682e061696f5a160f363f236ecbf53cbd006—DeepSeek Harness separates three concerns that a reliable multi-agent system should not collapse:

Subagent provider -> creates and owns one child execution boundary
Job registry       -> exposes a background run to its owning parent
Workflow engine    -> coordinates many bounded child calls and returns one JSON value

Choose spawn or fork when the local harness must enforce child persona, tool filtering, depth, or structured output. Choose ACP, Codex, Claude Code, or the DSH SDK only when their external runtime boundary is the requirement—and accept that those providers reject the local optional controls rather than silently approximating them. Use a Job for background collection and cancellation. Use a Workflow only for explicit, larger fan-out; its worker is not a security sandbox and its run cannot resume after a process restart.

DeepSeek Harness is still a Developer Preview. GitHub and npm currently expose dsh-v0.1.1-rc.1, but the repository warns that compatibility-breaking changes will occur. Pin the exact Profile, package versions, provider configuration, and commit you test.

Who this is for

This guide is for developers building multi-agent coding or research systems who need to decide where a child runs, what it may see, who can stop it, and what counts as completion. Read the Cordis architecture map for plugin placement, the permission and sandbox pipeline for tool authority, and the session recovery guide before relying on cold resume.

Provider selection matrix

Provider Child context Local optional controls Continuation Use it when Stop rule
spawn Fresh in-process Session; inherits cwd and model, not transcript Schema, depth, tool filter, persona Supported by the provider You need a clean specialist with local policy enforcement Dispose the run or interrupt a continuable activation
fork In-process child seeded only through the parent's last completed turn Same four controls as spawn Implemented, but shipped tools keep fork one-shot Completed parent context is essential and worth duplicating Never assume the in-flight parent turn was copied
acp Fresh subprocess and ACP session None; unsupported requests fail loud One-shot You need an ACP-compatible external agent and process boundary ACP cancel, then EOF/SIGTERM/SIGKILL teardown
codex Fresh package-local Codex process, ephemeral thread, one turn None; native Profile permission mode only One-shot Native Codex configuration and its unattended approval/sandbox modes are the product boundary Interrupt the turn, then join the process tree; no rollback
claude-code Fresh Agent SDK query and CLI process None; native permissionMode only One-shot Native Claude settings and a fail-closed unattended query are required Abort query and terminate the process tree; no rollback
dsh-sdk Fresh full Harness runtime over stdio JSON-RPC None; child cordis.yml owns policy One-shot The child must be a separate peer Harness with its own composition Protocol shutdown, EOF, then process termination

inheritsParentContext means conversation seeding only. It does not mean the child inherits the parent's tools, permission decisions, or authority. Likewise, a tool filter changes the child's registered tool surface; it is not proof that the operating-system or external account boundary is contained.

Pick Subagent, Job, or Workflow

Use a foreground Subagent when the parent's next action depends immediately on one result. In one-shot background mode, the same provider run becomes a parent-owned Job: the start returns a Job id, job_output collects its final output, and job_kill requests cancellation. The local Job registry defaults to ten running plus stopping jobs per exact owner and refuses excess work before the producer starts; it does not queue or preempt.

Use a continuable Subagent when the child needs later FIFO turns. send_message queues the next turn and never steers the current one. interrupt_agent stops only the current turn, keeps queued messages, and leaves published descendants running. A storage-only child listed as ready is resumable, not a completed result waiting to be collected. Cold resume depends on durable Session state; persistence failure can leave that state stale even after an in-process activation settled.

Use a Workflow when the user explicitly asks for larger orchestration. Its JavaScript hooks can fan out agent() calls through parallel() and pipeline(). The worker-thread engine defaults to the spawn provider, a CPU-derived concurrency ceiling, 1,000 total child calls, and 4,096 items per combinator call. Those are admission limits, not a token budget. The parent waits for the whole run, partial output is not success, and the workflow has no journal or restart resume.

Authority and concurrency budget

Write this budget before exposing a delegation tool:

Budget Decision to record Fail-closed default
Provider Named static provider/tool binding No per-call dynamic provider selection
Workspace Exact child cwd and allowed roots Disposable worktree or read-only fixture
Tools Child-visible tools and external credentials Minimal tool filter plus no ambient credential assumption
Mutation Whether file, network, account, or deploy writes are allowed Read-only task; explicit approval for expansion
Depth Absolute local depth or child-owned recursion policy Numeric cap for in-process; provider-managed only when verified
Parallelism Parent tool-call pool, Job cap, Workflow concurrent and total caps Start with one writer per shared target
Deadline Operator timeout and cancellation trigger Cancel, then verify quiescence before retry
Evidence Required terminal state, output, diff, and external release evidence Non-completed or unknown means failure/unknown, never success

The critical ownership rule is simple: a returned id proves admission, not completion. A Job id says the registry accepted the run. A continuable child id says the first message reached its inbox. A Workflow id says the run was published. None proves the child's side effects succeeded.

Cancel, resume, and terminal-state contract

  1. Record the provider, tool name, parent Session, cwd, Profile, versions, permission mode, depth policy, and all concurrency caps.
  2. Classify every delegation as foreground one-shot, background one-shot Job, continuable child, or foreground Workflow. Do not mix their collection APIs.
  3. On cancellation, stop admission first, request the owner-specific cancel operation, then wait for provider disposal or process-tree quiescence.
  4. Inspect terminal vocabulary. Subagents return completed, aborted, error, max-tokens, or refusal; Jobs end completed, killed, or failed; Workflows end completed, cancelled, or error.
  5. Treat partial assistant output and safe diagnostics as evidence attached to failure, not as a successful answer.
  6. Before retrying, inspect workspace and external systems. Codex, Claude Code, ACP, DSH SDK, and Workflow cancellation do not roll back completed side effects.
  7. Resume only a continuable child with a valid durable descriptor and Session state. Restart a one-shot run or Workflow as a new execution, with duplicate-side-effect checks.

Six delegation canaries

Run these in a disposable repository with harmless external fixtures.

Canary Expected evidence Stop condition
1. Provider boundary Spawn/fork accept configured local controls; external providers reject unsupported controls A provider silently ignores persona, schema, tool filter, or depth
2. Context boundary Spawn and external providers see only the standalone task; fork sees completed parent turns but not the in-flight turn Child receives an unbalanced tool call or unintended parent secret
3. Owner fence A sibling cannot read or kill another owner's Job; only valid ancestry can interrupt a continuable child Predictable id grants control without owner validation
4. Capacity fence The 11th local Job and an over-cap Workflow child fail before work starts Excess work is queued or starts without a collectible controller
5. Cancellation Cancellation reaches a terminal state and process-backed providers prove whole-tree exit Status says stopped while the child can still mutate the fixture
6. Recovery Continuable child accepts a later FIFO turn after interrupt/cold resume; one-shot and Workflow runs require new ids A retry is called resume or repeats an unknown side effect

Common mistakes

FAQ

Should I use Codex or Claude Code through a Workflow?

Only if the Workflow engine is configured to that named provider and you accept a fresh one-shot product process per child. Provider selection is deployment policy, not a model-call option inside the script.

Can I resume a background Codex or Claude Code Job?

No. The current providers create a fresh ephemeral Codex thread or fresh Claude SDK query per run. A Job can collect or cancel that run; it does not add product-session persistence.

Does a completed Workflow mean every child completed?

It means the script returned successfully. Ordinary failed child runs can become null for the script to handle, while fatal startup, contract, cap, infrastructure, or cancellation errors stop the run. Make acceptance criteria explicit in the returned JSON.

Sources