The subagent-* scenarios pin their child tool schemas through their own
tool-schemas.1.expected.json, and the headless fixtures embed the request
header verbatim; all seven still carried the single-in_progress description.
Rebuild the todo row's parallel summary on the shared ToolRow that master
introduced: planSummary still returns the active name and the remaining
count separately, and the row hands the count to a new non-shrinking
summarySuffix slot so a narrow row clips the summary text before the count.
An error row drops the suffix, whose collapsed summary is the failure line.
Re-record the ACP todo-write transcript for the parallel prompt, regenerate
the config catalog for the required allowParallelInProgress field, and
re-record the bilingual pairing hashes.
The claimed-message refactor changed Inbox.claim(target, turn) and added
InboxNotifications.claimed, so the cordis_inspect tool output embedded in
the cordis-inspect-jsdoc replay fixture now carries the new declarations.
The row header already names the producer, so `goalChangeSummary` dropped its
redundant `goal ` prefix; the ACP and headless goal scenarios still pinned the
old string.
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.
The react-loop simplification enters the workspace baseline with the first
request instead of queueing it for a second step, and the acp-agent sandbox
scenarios now replay the branch's inbox event flow. Update the agent-spine-demo
assertions to the single-request shape (workspace rule ahead of the skill
catalog, matching registration order) and refresh the two keyless sandbox
snapshots against the merged runtime.
Injected context reached the transcript as one anonymous shape whose
expanded body was the whole message serialized as inline JSON, so the
escaping collapsed the only readable part — the model-facing prose —
into a single line.
`MessageSource` gains an optional producer-declared `form`, a small
semantic vocabulary of information shapes independent of `kind`: kind
says who produced the context, form says what shape it is, so several
producers may share one presentation. Two values ship.
`instructions` (workspace-context) lists the reconciled files above
their text and keeps the `<system-reminder>` framing verbatim, because
the framing is part of what the model read. `catalog` moves dsh-tool-skill
off the shared plugin kind onto a `skill-catalog` source carrying the
published name/description entries, and the body lists those instead of
re-parsing `<available_skills>` out of the prose. Catalog identity moves
with it: the republish digest now covers the durable entries, deleting
the text-slicing that recovered them from a logged message.
Everything else renders the opaque body — the model-facing text with its
real line breaks, then the remaining provenance as fields. That is the
documented default, not a leftover: a resumed, forked, or foreign log
must render whether or not its producer is mounted here, which is why
the classification lives in the durable source rather than a client-side
table keyed by producer.
The goal scenario asserted a paused revision-2 goal, but the pause is
appended only after cancellation reaches idle — after turn/end. Under
parallel snapshot files the subprocess could dispose before the pause
record persisted, folding the log to an active revision-1 goal.
Add a waitForEventAfterTurnEnd input step (the turn-end/title waiters'
shape, parameterized by event type) and use it in the goal scenario to
hold the subprocess open until the goal-state record lands.
The seam gains ShellDialect ('bash' | 'powershell' - concrete shells, not
families: zsh or fish would be their own values, never 'bash'); bash-local
declares bash (bash-sandbox inherits), pwsh-local declares powershell, and
both tools throw at load when the mounted executor speaks another dialect -
previously tool-pwsh over bash-local handed PowerShell text to bash -c and
the deployment error surfaced as ordinary nonzero exits. Pinned by mismatch
tests on both tools; the parity note records the contract (both languages).
Also from the review round: the tool-bash README's managed-environment
section becomes a summary linking the owning dsh-bash-env contract (the
duplicated prose carried a stale owner in its example import), the
pwshOnly JSDoc drops the stale 'on PATH' phrasing, and the task-tools
contract comment in the two pwsh compositions is indented into its block.
The boundary marker row is a 0-height hairline except at the table tail;
the marker button is absolutely positioned and visible anywhere, so the
terminal-marker e2e waits on the button. Also refresh the
cordis-inspect-jsdoc fixture to the typed error reason (LlmFailure).
TurnEndReasonMap.error now carries a single `error: LlmFailure` field:
an LlmError keeps its structured facts, any other error flattens to
errorChain text under the UNKNOWN code. Consumers read message/code
directly instead of defending against an unknown union — this also fixes
errorChain() rendering structured failures as '[object Object]' in the
TUI and ACP error paths. Document the turn-stopping contract: a
concludesTurn result never short-circuits already-submitted next-step
work (same-step additionalContexts or racing steering still runs), data
decides.
Steer, inject, and followup now land as durable user/message events on the
session surface; the steering/message event type and its ConversationNode
kind are removed from the client projection. Update tests, docs, generated
catalogs, and agent notes to match, and align the steering e2e fixture and
prompt inventory assertions with the durable user/message landing.