DeepSeek Harness DSH plugin background jobs: ctx.jobs.start
A DeepSeek Harness (DSH) plugin runs tool work in the background by letting the producer configuration control run_in_background, calling ctx.jobs.start({ kind, label, owner: exec.agent, run }) inside the branch to register the task, and returning the typed handle { kind: 'background', jobId } on success. This suits long-running work; once the id is published you must switch to the task's own cancellation signal instead of exec.signal. It is the background branch of the same tool model whose foreground contract is covered in tool plugin development.
How a DSH plugin starts a background task: the producer controls run_in_background
Background capability is not the tool spawning its own thread; it is the producer configuration controlling run_in_background and handing the work to the task runtime (source). The deciding criteria are straightforward:
- Is the work long-running? — the case where a long wait is expected and the caller still needs a handle right away. Expect: go through the background task rather than blocking the foreground call.
- Do you need to inspect output while it runs? — you want to pull or receive output appends during execution. Expect: the background task provides an
outputsource and a push channel. - Does it need a lifetime independent of the call? — the work should continue after the call is cancelled. Expect: the background task lifecycle belongs to
job_kill, owner dispose, and service teardown, not to the caller.
The registry fails a call that was already aborted before entering the producer body: there is no task in that case, and its id cannot satisfy the success output schema (source).
How a DSH plugin registers a background task: ctx.jobs.start and the typed handle
Register the task with ctx.jobs.start({ kind, label, owner: exec.agent, run }), and let the success branch return the typed canonical handle (source). Take these three steps:
- Declare the background branch — decide inside the producer whether background execution is needed. Expect: only long-running paths enter that branch.
- Call
ctx.jobs.start— pass{ kind, label, owner: exec.agent, run }. Expect: the runtime validates that owner and the task controller are available, then starts the work. - Return the typed handle — return
{ kind: 'background', jobId }. Expect: the caller gets an id, not text that a human has to read.
Why the typed handle matters: Native renderers may keep human-readable prose such as started background job bash-1, but PTC mode must never parse that text to obtain the id — programs read the jobId field, and a tool body must not force callers to parse ids and fields out of natural language.
How a DSH plugin manages background task ids, cancellation, and output
After ctx.jobs.start() publishes the id, use the task's own cancellation signal instead of exec.signal (source). The key conventions:
- Cancellation semantics change: cancelling the outer call afterwards only stops waiting for that call and does not terminate the already-published work.
- Lifetime ownership: the task lifecycle belongs to
job_kill, owner dispose, and service teardown. - What the spec provides: a synchronous
cancel, adonethat settles after resource cleanup and never rejects, and either a pull-basedoutputsource or pushed appends received through theJobHandlefrom the starter. - Output reads: model-facing consumable reads are rendered from the output ring by
dsh-tool-jobs. - Foreground still couples to the signal: foreground work remains coupled to
exec.signal, unchanged.
Three self-checks before you finish: does the background branch return a jobId field rather than relying on text; after publishing the id, did you switch to the task's own cancellation signal; and is the owner set to exec.agent so cleanup works correctly. For a ready-made task-style tool to compare against, look for similar plugins in DSH Plugin Hub.
FAQ
DSH plugin background tasks are for long-running work: the producer configuration controls run_in_background and hands the work to the task runtime, so the foreground call gets a typed handle immediately instead of waiting for a result. Foreground work stays coupled to exec.signal, while background work switches to the task's own cancellation signal once the task publishes its id.
A DSH plugin registers a background task inside the producer branch by calling ctx.jobs.start({ kind, label, owner: exec.agent, run }). The runtime validates that owner and the task controller are available before run() starts, then provides the id, a session fence, a generic control tool, notifications, and owner cleanup.
A DSH plugin takes the jobId from the typed canonical handle { kind: 'background', jobId } returned by the successful background branch. Native renderers may keep human-readable text such as started background job bash-1, but PTC mode must never parse that text to obtain the id.
A DSH plugin must stop using exec.signal to cancel work once ctx.jobs.start() publishes the id, and switch to the task's own cancellation signal instead. Cancelling the outer call then only stops waiting for that call and does not terminate the already-published work; the task lifecycle belongs to job_kill, owner dispose, and service teardown.
A DSH plugin background task spec provides a synchronous cancel, a done that settles after resource cleanup and never rejects, and either a pull-based output source or pushed appends delivered through the JobHandle. Model-facing consumable reads are rendered from the output ring by dsh-tool-jobs, while the runtime owns owner cleanup.
Related Terms
- run_in_background
- run_in_background is the producer configuration of a DSH plugin that hands long-running tool work to the task runtime; when enabled, the success branch returns a typed background handle instead of a final result.— https://deepseek-harness.github.io/deepseek-harness/en/reference/cookbook/adding-a-tool
- ctx.jobs.start
- ctx.jobs.start is the entry point a DSH plugin uses to register a background task; it accepts { kind, label, owner, run }, publishes the task id, and provides a session fence, a control tool, notifications, and owner cleanup.— https://deepseek-harness.github.io/deepseek-harness/en/reference/cookbook/adding-a-tool
- JobHandle
- JobHandle is the typed handle of a DSH plugin background task, returned by the successful background branch as { kind: 'background', jobId }; through the starter it also receives pushed output appends.— https://deepseek-harness.github.io/deepseek-harness/en/reference/cookbook/adding-a-tool
- producer
- producer is the branch of a DSH plugin tool that produces the canonical return value; in the background form it is controlled by the run_in_background configuration, and the registry fails a call that was already aborted before entering its body.— https://deepseek-harness.github.io/deepseek-harness/en/reference/cookbook/adding-a-tool