DeepSeek Harness DSH plugin background jobs: ctx.jobs.start

Plugin DevelopmentPublished 2026-10-02Author: DeepSeek Plugin Market
DSH pluginDeepSeek Harnessbackground tasksrun_in_backgroundctx.jobs
DSH plugin background tasks: control run_in_background in the producer, register work with ctx.jobs.start, and return { kind: 'background', jobId }.

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:

  1. 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.
  2. 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 output source and a push channel.
  3. 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:

  1. Declare the background branch — decide inside the producer whether background execution is needed. Expect: only long-running paths enter that branch.
  2. 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.
  3. 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, a done that settles after resource cleanup and never rejects, and either a pull-based output source or pushed appends received through the JobHandle from 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

When should a DSH plugin use background tasks?

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.

How does a DSH plugin register a background task?

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.

Where does a DSH plugin background task get its jobId?

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.

Can a DSH plugin cancel a task with exec.signal after start?

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.

Who cleans up a DSH plugin background task and reads output?

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

Sources