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
- Record the provider, tool name, parent Session, cwd, Profile, versions, permission mode, depth policy, and all concurrency caps.
- Classify every delegation as foreground one-shot, background one-shot Job, continuable child, or foreground Workflow. Do not mix their collection APIs.
- On cancellation, stop admission first, request the owner-specific cancel operation, then wait for provider disposal or process-tree quiescence.
- Inspect terminal vocabulary. Subagents return
completed,aborted,error,max-tokens, orrefusal; Jobs endcompleted,killed, orfailed; Workflows endcompleted,cancelled, orerror. - Treat partial assistant output and safe diagnostics as evidence attached to failure, not as a successful answer.
- Before retrying, inspect workspace and external systems. Codex, Claude Code, ACP, DSH SDK, and Workflow cancellation do not roll back completed side effects.
- 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
- Selecting a provider by model brand while ignoring context, permission, and process boundaries.
- Calling
run_in_background“durable.” The shipped local Job registry is process-local. - Treating
send_messageas steering. It creates the child's next turn. - Treating
interrupt_agentas recursive cancellation. Published descendants continue. - Calling the Workflow worker or
node:vma security sandbox. The official engine explicitly rejects that claim. - Counting agents without budgeting model tokens or shared-workspace writers.
- Reporting a Job id, child id, queued message, or Workflow start as release evidence.
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
- DeepSeek Harness repository and Developer Preview boundary
- dsh-v0.1.1-rc.1 Pre-release
- npm: @deepseek-ai/dsh 0.1.1-rc.1
- Subagent provider, continuation, authority, and terminal contracts at commit 528c682
- Background Job ownership, status, access, and wait contract
- Workflow request, cancellation, event, and terminal contract
- Provider-bound delegation tool and background modes
- Spawn and fork context/capability boundaries
- ACP process and permission boundary
- Codex provider permissions, lifecycle, and limitations
- Claude Code provider permissions, lifecycle, and limitations
- DSH SDK peer-runtime provider
- Local Job admission and lifecycle
- Workflow worker limits, cleanup, and non-sandbox boundary