Subagent transcript in the expanded tool card
Overview
A subagent tool card keeps the delegated conversation in its expanded body. The nested transcript is rendered with the same rows, bubbles, role labels and colours as the main chat transcript, so a delegated run reads as a conversation instead of a separate widget.
What the expanded card shows
| Nested turn | Rendered as |
|---|---|
system | The collapsed System prompt · N lines card the main transcript uses for the active system prompt. Its role label names the agent the run dispatched (Search), or agent for a generic delegation. Expanding it wraps the prompt inside the card at the transcript's own width — see Wrapping. |
user | A user chat bubble, right-aligned, with the user role label. |
assistant | An assistant chat bubble with markdown, labelled with the model that ran the delegated call. |
tool | A compact nested tool row (verb label, one-line arguments, per-tool result summary, status dot, per-tool preview) — not a chat bubble, so the assistant→tool→assistant loop stays readable. |
While the run is in flight, streamed answer deltas land in a single assistant bubble under the Subagent is working… line; the card is re-rendered into the final transcript above once the run settles.
Wrapping
The system prompt row is a shared component — buildSystemPromptRow renders both the top-level prompt card and the nested one inside an expanded subagent card — and its body is a <pre class="chat-msg__system-body"> that wraps with white-space: pre-wrap.
The prompt is plain text, not a fenced code block: its own line breaks and blank lines survive, but a long line wraps instead of running off the card. On a phone this is the difference between reading the instructions the model was given and guessing at a clipped fragment of them — an unwrapped prompt renders as one ~2000 px line inside a horizontally scrolling box, and the sideways scroll is invisible.
The markdown renderer's code-block rule (chat-markdown.css, … .chat-msg__body pre) deliberately excludes .chat-msg__system-body. That rule sets white-space: pre and, at one point of extra specificity over the prompt's own rule in chat-transcript.css, used to win the cascade: the prompt lost its wrapping in both the top-level and the nested card. Fenced code blocks in assistant replies are unaffected — they keep white-space: pre and scroll horizontally, which is what a code block should do.
Nested tool rows
A tool row inside the subagent card is rendered with the same classes the main transcript's tool card head uses — .tool-card__name for the verb label, .tool-card__args for the one-line arguments, .tool-card__result-summary for the collapsed summary and .tool-card__pill for the status dot. A delegated call therefore reads at the main card's type scale, casing, argument budget (220 characters) and dot size, instead of on a private style scale.
One row per call. The live stream emits a
tool_calland later itstool_result; the persisted nested chat stores them as two separate messages (an assistant turn carryingtool_calls, then arole: "tool"turn). Both paths render one row: the status dot flips fromrunningtook/errorand the summary and preview fill in when the result lands.Rows sit next to the bubbles. Tool rows are appended at the same level as the chat bubbles, which is where the live container puts them, so a call keeps its position when the run settles.
A direct
@agentdispatch settles into its own card. The@<agent> <task>composer command opens the top-levelsubagentcard before the server has answered, so it is keyed with a placeholder id; oncePOST /api/tools/subagentreturns the id it actually minted, the card is re-keyed to it before the result is appended. Both sides then agree ondata-tool-idand the one card flips fromrunningtook/errorin place, instead of leaving a stranded Subagent is working… card beside a duplicate result card. A request that fails outright has no id to adopt, so the placeholder card is retired.No empty assistant bubble. An assistant turn that only asked for tools carries no text of its own; it renders as its tool rows, matching the main transcript, which skips empty assistant turns.
Rows fold. Each nested row has its own chevron, a real
<button>witharia-expanded. The result preview is hidden until the row is opened, so a run that read five files lists five compact rows (label, arguments, summary, status dot) instead of pasting five files into the nested transcript. Tap the chevron, or anywhere on the row line, to open or fold it; taps inside the revealed output don't fold it, so you can select text.Same policy as a top-level card. A clean result is folded, a failed one opens so the error is visible, and a nested shell command that is still streaming opens on its first output chunk so you can watch it run, then folds when it succeeds. Once you have tapped a row, your choice wins over all of these defaults, including when the settled render replaces the live rows and when the transcript is rebuilt: the choice is carried over by the nested call id (
scripts/test-subagent-transcript-parity.js).
Naming the agent
A delegated run names the agent it dispatched, in two places:
a
.tool-card__agentchip in the card head, beside the genericSubagentlabel;the role label of the nested system row.
The name comes from the
subagentcall'sagentargument, which the server echoes back on the result asresult.agent. It has to ride the result payload: the nested transcript does not contain it (an agent's system message is its instructions, not its name), so without it a@reviewerdispatch and a model-driven generic delegation render identically. A run with noagentargument shows no chip and labels its system rowagent.
Usage
Enable the Subagent tool for the project in Settings → Project and approve a delegation (or use an
@agentmention in the composer).Read the delegated conversation directly under the card head — a settled
Subagentcard opens itself (is-expanded), because the nested transcript IS its body. Tap the header to collapse or re-expand it;is-expandedis authoritative, so a collapsed card really hides its transcript.Read the delegated conversation top to bottom: the subagent's prompt, the delegated task, its tool calls, and its final answer.
To see which agent ran, read the chip in the head or the nested system row's role label.
Expand and collapse
A settled card opens itself.
appendToolResultCardleaves a successfulsubagentcardis-expanded(unless the user collapsed it), because the nested transcript IS its body — a collapsed card would render as a bareSubagent · task · okheader with the conversation hidden behind an undiscoverable tap.A running card opens itself too. The live body is created while the run is in flight so nested activity streams into a card the user can already see.
Collapsing works.
is-expandedis the single source of truth for a subagent card's body visibility, exactly as for every other card. Tapping the header collapses the card and hides the transcript; tapping again re-expands it.The user's collapse wins over auto-expand. The header tap records
card._userCollapsed, and both the settle path and the rebuild path honor it, so a card the user closed does not pop back open when its result lands or when the transcript reconciles.