- `relay` resolves its sender in `contextBody` like every other form. It was
the one shape whose marker could claim a form the body did not render: an
unreadable sender fell back inside the body while the row still said relay,
contradicting the contract this PR's own note states.
- `recall` requires the retained, omitted, and truncated fields. Completeness
is what the card exists to report, so a reference that cannot state it is
not a readable recall — showing the label alone presents a confident card
over unknown loss.
- The snapshot body states the supersession its producer framing line carries.
That line is the one part of the model-facing text no section contains, and
unlike an instruction context's `<system-reminder>` it states the form's own
semantics rather than wrapping content.
- `GoalMessageSource` is a discriminated pair, so `{ form: 'notice' }` without
its account no longer compiles. The guarantee this PR claims now holds at
that seam too, not only through `ContextFormed` on plugin sources.
- Goal and tool-goal summaries are bounded by a shared `boundContextSummary`,
which tool-tasks now uses as well. A goal objective is unbounded caller text
in exactly the way a task label is.
- The runtime snapshot interpolates once per request: agent-loop renders the
sections and joins them through `joinContextSections`.
- Every form's fallback branch is pinned, not only the notice one.
Four values complete the vocabulary, so the opaque body is reached only by
producers that genuinely promise no shape.
`snapshot` — current state a later snapshot supersedes. system-prompt now
exposes `renderContextSections()`, the named contributions
`renderContextSnapshot()` already joins for the model, so the body attributes
each part to the subsystem that produced it instead of re-splitting joined
prose. The runtime snapshot, time-context, and tmux-context declare it.
`notice` — a one-off account of what just happened, declared by tool-tasks,
goal state changes, tool-goal wrap-up, plan-mode switches, and
repeat-tool-guard. Its `summary` rides the COLLAPSED row: these five are the
majority of shipped producers and none of them needs expanding to be read.
The task summary bounds itself because its inputs are unbounded caller text.
`relay` — a message another agent addressed to this one; both subagent
sources declare it and the body names the sender above what it said.
`recall` — material lifted from another session's log. session-reference
needed no new field: its references already record retained and omitted
counts and the truncation flag, which the body shows first, because recalled
context is bounded on the way in.
`ContextFormed` is now discriminated by `form`, so a producer cannot declare
a shape without the facts that shape is presented from — a notice without its
summary, or a snapshot without its sections, fails to compile.
Only the two hook bridges stay opaque, by design: their content is whatever
an external program printed, so no shape can be promised for it. Unknown
kinds and unreadable records land there too.
Resolve the agent-loop import conflict by retaining both durable request context and runtime policy context. Refresh the combined session fixtures and regenerate documentation catalogs. Mark PDF artifacts as binary so staged whitespace checks do not parse PDF bytes as text.
Conflict resolution notes:
- StatsLine: master redesigned the row into pipe-separated groups with LLM and
tool wall times. Kept that design and swapped only the token accounting
source, so counts and durations stay window-scoped while billing and context
occupancy read the durable projections.
- Generated artifacts (cordis catalog, module graph, event producer/consumer,
i18n pairing hashes) and web snapshots took master's side; they are
regenerated and re-recorded after this merge.
- Web e2e goldens and details-panel/timeline assertions took master's side:
that evolution is unrelated to this branch.
- ui-conversation package.json: kept master's devDependency ordering, re-adding
only the token-meter entry this branch needs.
Replace the transient `session/model-request` mux frame with ordinary durable
session state. Occupancy now rides two last-wins projection fields instead of a
non-replayable frame that needed removal tombstones and cross-stream fencing.
The frame was the only non-replayable class on the mux stream. Because host and
mux are independent SSE streams with no cross-stream order, a request emitted
before a removal could arrive after `host/session-removed`, and a legitimate
request for a new lifecycle reusing the same id could be fenced by a late
removal. Fixing that needed a lifecycle generation on every frame; the frame
itself was the problem.
Removed: the `session/model-request` frame and schema, the `agent/model-request`
core event, the ApiProxy measurement point, the client-side telemetry map and
removal tombstone, and the synthetic `cancelled` open error used to signal
reconnect through the error channel.
Added: `request/context`, a log-only session event recording the
registration-bound capacity of the route a request resolved to, appended beside
`request/header` from the lookup that already prepared the call and skipped when
the route is unchanged. Capacity stays out of `EpochHeader` because it is
adapter metadata about a route, not an input the request was built from, so it
must not join request reconstruction or header equality.
The `contextPressure` projection pairs the newest provider-reported prompt size
with the newest recorded capacity. The two are deliberately not one atomic
request observation: switching models can pair a fresh capacity with the prior
route's pressure until the next request reports usage. The figure is a
user-facing reference, and this matches how the TUI status line has always
computed occupancy.
cancel() emitted agent/cancel-requested whenever queued or steering work
existed, even under keepInbox with no active turn — a call the contract
documents as a no-op. Consumers could misread that notification as a real
cancellation. Emit only when the call actually aborts the active turn or
discards pending work, matching the "effective call" contract.
goal-session rides retry turns and survives admission failures. A
recovery policy closes a goal round's failed turn and reopens its
history under a retry trigger; the attempt now adopts that turn and
drops the failed turn's provisional reason, so the round settles from
the retry's own outcome instead of blocking an armed goal with
turn-error after a successful response. A downstream admission hook
that throws (rather than blocks) used to strand the queued reservation
forever; the listener now clears a still-turnless matching attempt on
the rejection path and reschedules the round.
agent-loop contains a persistently rejecting step close in the catch
path the same way the finally contains the turn close, so the
post-finally tail always publishes the terminal status — previously a
double veto escaped run(), leaving status at running while whenIdle()
resolved. The whenIdle catch arm is annotated as the backstop it now
is: every driver rejection path is contained today.
workspace-context folds an already-appended baseline from the session
log when the plugin is hot-remounted over a live session, instead of
injecting a duplicate from its fresh mount-local guard.
The TUI's reference-admission discard listener installs before
followup(): admission runs synchronously inside it on the common path,
so a listener installed afterwards missed its own cleanup and leaked
one callback per referenced prompt.
The public classifier existed to defend an exported reader against
arbitrary signals, but its only production caller is the loop reading
its own machine-private turn signal, where cancel() is the sole aborter
and always writes one frozen canonical cause. Delete the export and its
15-line structural validation: settle() states the slot invariant with
one cast, the boolean call sites ask signal.aborted directly, and the
retry veto drops entirely because a requested window already implies a
live signal (cancel() retires the window before aborting).
The abort(reason) channel and first-wins semantics are unchanged; only
the reader's publicness is gone, and with it the paranoia it required.