Sub-agents in a chat UI¶
A multi-agent run can take minutes. Users trust it when they can see it working — which specialist is on the job, what it is doing right now, in words rather than function names. Every agent a delegate runs streams on the wire as AG-UI's own sub-agent events, and kaboo-react folds them into cards.
What the client receives¶
The entry agent's stream is the chat reply: its text and tool calls arrive as ordinary AG-UI events. Each agent it delegates to, at any depth, arrives as one invocation:
SUBAGENT_STARTEDopens it, with asubagentRunId, the agent'sname, theparentToolCallIdof the delegating call and theparentSubagentRunIdof the invocation that made that call.metadataholds itstitleand thetaskit was handed.- Its text, reasoning and tool calls follow as the standard events, each
tagged with its
subagentRunId. Tool arguments stream as the model writes them, so a UI can draw a widget or a query while it is being written. TOOL_CALL_RESULTcarries each result, withmetadata.approvalwhen the call needed one (Human-in-the-loop).SUBAGENT_FINISHEDcloses it:completedwith itsresult, orsuspendedwhile it waits on a question or an approval.metadataholds its tokenusage, itscostand, for a structured output, theoutputSchemaname.SUBAGENT_ERRORcloses it when it fails.
A paused invocation keeps its subagentRunId, so the run that resumes it opens
a new segment of the same card. The events are stored and replayed like any
others, so a reload draws the same cards. RUN_FINISHED.usage counts every
agent, so a UI can show a true total.
Name the cards¶
agents:
orchestrator:
model: default
sql_analyst:
stream:
title: Analyzing SQL data
sql_visualizer:
stream:
group: sql_visualization
title: Creating SQL visualizations
Titles are what a user reads, so write them as what the agent is doing,
and in the user's language — "Analyzing SQL data", not "sql_analyst". An agent
with no title gets one derived from its name, which is rarely the phrase you
would choose. Agents that share a group render as one card, which suits a
pair that works on the same thing in turn.
Label the tools¶
mcp_clients:
web_research:
url: ${GATEWAY_URL}
transport: streamable-http
params:
tool_filters:
allowed: [web-research___search, web-research___read_pages]
tool_labels:
web-research___search: Searching the web
web-research___read_pages: Reading web pages
A sub-agent's tool call carries its label in TOOL_CALL_START.metadata.label,
and the card shows "Reading web pages" instead of web-research___read_pages.
Labels can also be set per agent, under agents.<name>.tool_labels.
Test it¶
TurnResult.groups holds the cards as a client folds them, so a test can assert
what a user would see — which agents worked, nested under whom, and with which
tools (Testing). Lab scenarios:
delegate-dashboard,
nested-dashboard
and
parallel-dashboard.