Files
deepseek-harness/packages/api/job-controller
Yichen Jiang e8469d6f6c refactor(jobs): move the human kill to the job namespace
`session.killJob` becomes `job.kill` on `dsh-api-job-controller`, beside
`job.observe`, so the Session Controller carries only session control and
the roster while every per-job Remote lives on the job namespace. The
client `ctx.jobOutput` gains `kill(sessionId, jobId)`; `dsh-client-ui-jobs`
routes its stop control through it and no longer requires `sessions`.
Unknown or foreign jobs reject with `job/not-found`; the controller
requires `ctx.jobs` to load, so `jobs-unavailable` disappears. The two
subagent ownership-fence helpers are classified duplicate-install-safe
Host exports of the Session Controller for the cross-controller import.
2026-09-02 23:18:08 +08:00
..

description, kind
description kind
Host and Client job control: stream one background job's output record to the browser without touching the model's consuming cursor, and stop a job on a human's behalf. package-reference

Job Controller

English | 中文

Summary

@deepseek-ai/dsh-api-job-controller owns the Host ctx.jobController service and the generated Client ctx.remote.job namespace. Its Remote stream job.observe delivers a record-declaring job's retained output from an absolute byte offset, and its job.kill Remote stops one job on a human's behalf; the Client half installs ctx.jobOutput, the reference-counted per-job observation and kill service whose accumulated views the session-header job list renders. The roster itself is not here: SessionJob rows still ride the session control stream owned by dsh-api-session-controller, and a row's outputTotal says whether this controller has anything to stream for it.

Table of Contents


Use this package

The Host controller requires the live Agent registry and the job registry (dsh-jobs-local in the shipped compositions) and fails to load without them. job.observe({ sessionId?, jobId, from? }) resolves the fenced-read caller from the request's session — an unowned job needs no session — and yields one opened anchor, coalesced output frames, then one terminal status once the job has settled and its record is drained, after which the stream closes normally. Record reads are non-consuming: the model-facing job_output cursor and completion-notice state never observe them. A job the session does not own, a job without a record, or an unknown job rejects the stream. Reconnecting callers resume by passing the last frame's next as from; a from below the oldest retained byte gets a lossy first frame instead of an error.

job.kill({ sessionId, jobId }) cancels one job with the reason cancelled by the user and leaves the terminal report unclaimed, so the owning agent's completion notice stays due; it answers { outcome: 'requested' } or { outcome: 'already-finished' }, rejects an unknown or foreign job with job/not-found, and applies the Session Controller's subagent ownership fence (session/agent-busy) to a subagent-owned live session.

The Client entry provides ClientJobOutput (ctx.jobOutput) over ClientJobOutputModel. kill(sessionId, jobId) forwards to job.kill and returns the Remote result for the caller's admission verdict. observe(sessionId, jobId) opens one Gateway stream per job however many viewers expand it, keeps a bounded render tail per job with gapBefore marking eviction or resume gaps, records the terminal status or a stream failure on the view, and drops the view after the last viewer releases. The plugin resolves the Gateway stream factory and the job namespace while its own context is current, because observation (re)opens run on caller stacks that have not declared remote.job.

Config

Field Default Meaning
observeFlushMs 100 Coalescing window after new record output before an observation read, in milliseconds
observeMaxFrameBytes 65,536 Soft byte budget per observation output frame; one larger chunk ships whole

The generated configuration catalog is the exhaustive source for accepted fields and their JSDoc.


Model Experience

None, as job observation is browser and Host control state; it registers no prompt, tool, or session event. The model's own view of the same work stays with dsh-tool-jobs.

KV Cache effect

No direct effect; observation reads never touch model requests.

Known Limitations and Deferred Work

  • The stream is process-local: a Host restart loses every record, and a resumed observation then anchors on an empty registry.
  • Per-session fencing is enforced by the registry read; the Remote layer itself serves any connected browser, matching the session control stream's jobsBySession broadcast.

Dev Note

Working context for maintainers — click to expand

None.

Runtime invariant: No companion is published. The controller is a stateless projection of ctx.jobs reads; the registry's own @deepseek-ai/dsh-jobs/invariant owns the offset relations this stream forwards.