Agent pane: sub-session calls render as live sub-session cards, not JSON
Leo, 2026-10-24, word for word, looking at how a sub-session start renders in the chat:
"I don't know if we're, like, hand-rolling and reinventing the wheel. I don't know if we're doing it in a good way, or if we could do it in better ways. We can get some guys to research that, but look, regardless, I feel like we should be able to render this a hell of a lot better, you know?"
Root cause
The transcript already had sub-session cards (SubsessionCallCard.tsx), but subsessionCalls.ts only recognised the cmux-subsessions server. In code mode the cmux server serves that server's tools as its own, so the call arrives as mcp__cmux__subsessions_start_session (server cmux, tool subsessions_<tool>, cli/mcp/upstream.rs prefix()). Every such call fell back to the generic MCP row, which showed "Called cmux · subsessions start session" with raw Request/Response JSON.
What changes
subsessionCalls.tsreads both names (Claude Code'smcp__cmux__subsessions_*and Codex's{server: "cmux", tool: "subsessions_*"}), and now also readslist_sessions, the start'sprompt/model/harnessand each row'sharness.- A new
subagents/AgentCard.tsxdraws a sub-session as a glass card: its harness mark, name, model, live state (the working spinner, never a dot; "Waiting on you"; Done; Failed), what its turn does now, and Open chat (it opens beside this chat without taking focus). - start_session: the card, with the prompt as rendered Markdown cut to 3 lines and Show prompt.
- wait_for_sessions / list_sessions: one card per sub-session with its state; a finished one's last reply as Markdown, cut to 3 lines with Show more.
- send_message:
→ name: <first line> · <delivery>, which opens to the whole message as Markdown. - The raw JSON is only behind Raw, which reuses the generic MCP row's Request/Response view (
McpCallDetails). - Live state comes from the existing session-list store (
useSubsessionInfo,useSyncExternalStore) per card. There is no polling and no timer. A session changing redraws only its own card, and each event costs O(1) per card.
I checked with tt-workflows: their workflow card is per workflow and opens the panel, so there is no per-agent overlap with #24874.
Before / after
Gallery agent-pane.subsession-cards, variants recorded and recorded-wait. They use the exact shapes of a mcp__cmux__subsessions_start_session and a wait_for_sessions result that Claude Code recorded on 2026-10-24, with synthetic names and text. The same gallery file was rendered on the base and on this head (Linux Chromium, w420).
Gallery: agent-pane.subsession-cards/recorded, agent-pane.subsession-cards/recorded-wait
| Before (base) | After | |
|---|---|---|
| start_session (recorded), dark | ![]() | ![]() |
| start_session (recorded), light | ![]() | ![]() |
| wait_for_sessions (recorded), dark | ![]() | ![]() |
| wait_for_sessions (recorded), light | ![]() | ![]() |
Before: the start reads "Called cmux · subsessions start session", and opening it shows the raw JSON. The wait is one more generic row folded into the finished turn. After: the start is net-retries' card with its Claude mark, model, the working spinner, what it does now, Open chat and its prompt as Markdown. The wait is two cards: review done with its reply as Markdown, and net-retries still working.
Risk
Small and contained. It only changes how the transcript renders calls to the sub-session tools; no protocol, daemon or native code changes. If a call can't be read (a failure, a refusal, an unknown shape), it still falls back to the generic MCP row, as before. The model.ts height estimate for these cards grows to match.
Tests
subsessionCalls.test.ts: themcp__cmux__subsessions_*form (Claude and Codex shapes, from a recorded result),list_sessions, and other cmux tools still keeping the generic row. 40/40 pass in the touched suites.- On huge-blue:
bun test src/agent-session testgives the same 403 failures on head and base, with none only on head. Typecheck, lint, fmt, design-token, catalog-key and type-scale checks pass.
Agent session: cosmic-gray
🤖 Generated with Claude Code







