JobView.kind was typed as the merge-extensible JobKind, so the job-controller program, which compiles only the merges it can see, generated a strict codec of bash | subagent for job.rows and job.observe. The first pwsh, pty-send, or workflow job failed frame validation on both ends and the browser dropped the whole roster. The projection now carries kind as a string; JobSpec.kind keeps the Host-side JobKind. A built-artifact smoke parses a pty-send row through the generated schemas, and the seam spec pins the open type.
The `start` frame and the reconnect baseline attempt carried a wall-clock
`startedTime` that no Host or Client consumer read: the Host accumulator
and the Client reconciler copied it into their attempt maps and nothing
looked at it again. Presentation frames now carry only the coordinates
consumers use (attempt, revision, turn, step, chunk index, and v1 seq
provenance); the type-equivalence block, event graph, READMEs, and the
live-stream Agent Note follow the type.
The JSONL backend rethrows storage errno failures and abort signals from the
source read of a pending migration unwrapped; a test now drives both through
the stat seam so the classification stays covered. The navigation-panes Web
e2e derives the exported archive entry from `SESSION_FORMAT_VERSION`, which
`session-log-export` uses for its `session.v<N>.jsonl` name. The module graph
records the `session-log-export` → `session` peer edge.
The merged tree carries promoteOnTimeout, the workflow run_in_background
config, and tool-jobs' pre-refactor requirement set; the settled job
goldens show the folded finished section, a static row for a job with no
retained output, and the human kill's reason in the detail.
An owned job cannot start without the agent registry, so the plugin only
needs it to resolve a notice destination; requiring it left compositions
that run unowned jobs — the PTC harness, the pwsh loader and integration
suites — without an attached controller. The inject list, catalogs, and
README return to the pre-refactor requirement set.
- docs/subsystems/jobs.md (+zh) records JobSpec, JobHandle, JobHooks,
JobOutcome, the pull-source types, JobChunk, JobOutputRead, JobView,
JobRead, VisibleJobs, and the event types as type-equivalence blocks; the
manifest and the cordis catalog link map follow
- the jobs, jobs-local, and tool-jobs READMEs (+zh) describe the ring, the
caller-bound view, the event stream, and the notice ledger; the
architecture table and the adding-a-tool cookbook drop record: true and
readOutput
- Agent Note 2026-09-03-jobs-seam-consolidation owns the decision; the
2026-09-01 record note and the 2026-08-08 web display note carry
Superseded pointers and the display note links the rows spec
- regenerated catalogs (cordis, api, inspect, client, tool, config,
persistence, doc graphs, module graph) with their Chinese mirrors; the
cordis catalog drops the jobOutput exemption and the tool catalog mounts
the agent registry for tool-jobs; the client model imports JobView from
its leaf so the typert analyzer resolves it
Record the image offload point as the core image/offload session event so the
derived surface, every adapter, and the token meter share one offloaded set.
The agent loop plans the advance from the route's declared request-image
budget before dispatch; an adapter whose exact accounting still overflows
fails with IMAGE_OFFLOAD_REQUIRED and the loop advances by the named count.
Expose a live goals/get read plus goal/activation-changed for the Web strip, reject model resume of durable paused goals, refresh snapshot and generated documentation surfaces, and record the follow-up decision.
- `JobStart` is a union discriminated by `record`: `RecordingJobStart.run`
receives `RecordingJob` (with `append`), `PlainJobStart.run` receives the
plain `RunningJob`, and the registry builds the plain face without the
method, so the former warn-and-drop path is unrepresentable.
- Remove the duplicated JSDoc opener in the registry seam and correct the
job-list slot comment (order 20 sits before the subagent catalog's 30).
- Restore the `cordis-tool-round` session fixture whose only change was a
re-recorded `createdAt`.
- Update the 2026-08-08 job-display note's stale service-disposal and slot
ordering facts; archive the superseded 2026-08-25 unified-task-list note
and point its remaining inbound links at the archived snapshot.
Review corrections on the observation record:
- `pumpJobOutput` subscribes to `done` once and keeps one pending timer;
racing the same pending promise every poll round retained a reaction
per round for the life of the job.
- The `ctx.jobOutput` effect disposer is async and awaits every open
carrier's `dispose()`, so the fiber reports unloaded only once the old
iterators are quiescent.
- `SessionJob.record: true` replaces `outputTotal`: `append` never commits
a roster change, so the mirrored count was stale while output flowed;
the live offsets ride the observation stream's `opened` anchor.
- The record is documented as a best-effort live preview whose
cross-stream reordering is bounded by the poll interval.
- The web e2e scenarios hold their background job on a test-owned barrier
file instead of a wall-clock sleep, and each is one test rather than a
chain sharing a job id.