Files
deepseek-harness/packages/api/job-controller
Yichen Jiang e680fbc3a0 refactor(jobs): check the announced event protocol in the invariant companion
The companion validated fields of single JobView projections, which the invariant rule rejects as a check without independent observations. It now tracks each job's announced protocol (registered first, one settlement, removal last, no progress after settlement), adopting a job first seen late, and compares every announced projection with the registry's own read of the same job: identity, output total, and the settled status and time. An output event announced before registration, which the registry emitted until the previous commit, is one of the cases it rejects.
2026-09-04 12:27:16 +08:00
..

description, kind
description kind
Host and Client job streams: the roster one session can see and one job's retained output, mirrored to the browser without touching the model's consuming cursor. 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 two Remote streams are projections of ctx.jobs: job.rows mirrors the jobs one session can see as whole-set frames, and job.observe delivers one job's retained output from an absolute byte offset. The Client half installs ctx.jobs, the reference-counted service whose rosters and accumulated views the session-header job list renders. Neither stream touches the model's consuming cursor or the completion-notice ledger.

Table of Contents


Use this package

The Host controller requires the job registry (dsh-jobs-local in the shipped compositions) and fails to load without it. job.rows({ sessionId }) yields the session's visible set — its own jobs plus every unowned job — once on open and again after each coalesced burst of lifecycle commits (registration, progress, stopping, settlement, removal); output appends never refresh the roster, since a settled projection already carries the final byte count. job.observe({ sessionId?, jobId, from? }) reads through visibleTo(sessionId) — an unowned job needs no session — and yields one opened anchor carrying the job projection, coalesced output frames, then one terminal status once the job has settled and its ring is drained, after which the stream closes normally. Both reads are non-consuming: the model's job_output cursor and notice state never observe them.

The Client entry provides ClientJobs (ctx.jobs) over ClientJobsModel. watchRows(sessionId) keeps one roster stream open per watched session however many viewers hold it and drops the rows after the last release; a reconnect's first frame is already the whole truth. 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, closes the view on the terminal frame or records a stream failure on it, 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 stream (re)opens run on caller stacks whose dynamic context has not declared remote.job.

Config

Field Default Meaning
observeFlushMs 100 Coalescing window after a registry commit before the next rows or output 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 ring is a best-effort live preview, not a terminal transcript: producers' pull sources are copied per poll round, so two streams' writes inside one poll window land stdout first, and the client concatenates chunks regardless of channel.
  • Both streams are process-local: a Host restart loses every ring and roster; a resumed observation then anchors on an empty registry, and a re-opened roster starts empty.
  • Per-session fencing is enforced by the registry's visibleTo read; the Remote layer itself serves any connected browser.

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 event protocol and event-versus-read relations these streams forward.