Subagents
An agent a chat delegated to has a row in the thread with its task, its state and the report it ended with. Its own conversation lives in the CLI's transcript, and the views read it from there on request.
import { SubagentTimeline } from '@adecore/agents-react/chat/ui/SubagentTimeline';
import { SubagentBreadcrumb, SubagentTitleCrumb } from '@adecore/agents-react/chat/ui/SubagentControls';import { useEffect, useRef } from 'react';
import { crumbOf, openFromMain, useSubagentItem, useSubagentTrail } from '@adecore/agents-react/chat/subagent-view';
import { SubagentBreadcrumb } from '@adecore/agents-react/chat/ui/SubagentControls';
import { SubagentInfo } from '@adecore/agents-react/chat/ui/SubagentInfo';
import { SubagentTimeline } from '@adecore/agents-react/chat/ui/SubagentTimeline';
import { AgentDemo } from '../shared/agents.tsx';
import { DEMO_CHAT } from '../shared/agents-host.ts';
const CALL = 'call-subagent';
function Conversation() {
const item = useSubagentItem(DEMO_CHAT, CALL);
const { show } = useSubagentTrail(DEMO_CHAT);
const opened = useRef(false);
// The trail the thread's row sets when it is pressed, set once here so the crumbs have a way back.
useEffect(() => {
if (item !== null && !opened.current) {
opened.current = true;
show(openFromMain(crumbOf(item)));
}
});
return (
<div className="flex h-[420px] w-full flex-col overflow-hidden rounded-lg border border-border bg-surface">
<header className="flex h-10 shrink-0 items-center px-3">
<SubagentBreadcrumb chatId={DEMO_CHAT} title="Retry for fetchJson" />
</header>
<SubagentInfo chatId={DEMO_CHAT} toolUseId={CALL} />
<div className="flex min-h-0 grow flex-col">
<SubagentTimeline chatId={DEMO_CHAT} toolUseId={CALL} />
</div>
</div>
);
}
export default function SubagentsDemo() {
return (
<AgentDemo>
<Conversation />
</AgentDemo>
);
}Opening a conversation
Which conversation a chat shows is its trail: the steps down from the main agent, empty while the main agent is on screen. A subagent row's open button, or an entry of the subagent flyout over the composer, sets it. useSubagentTrail(chatId) answers { trail, show }; draw a SubagentTimeline for the last step as the timeline's overlay. The trail lives for the page only.
SubagentTimeline({ chatId, toolUseId }) reads the conversation with chat.subagent, 60 items at a time, and keeps reading while the host says it grows. It is read only; the composer under it stays for the chat's own prompts. A subagent the subagent opened goes one step further down.
SubagentInfo({ chatId, toolUseId }) is the header over it: state, time, model and what it spent, and the task it was given behind a fold.
Crumbs
SubagentBreadcrumb({ chatId, title?, className? }) is the way back up, a crumb per step and a close that goes back to the main agent; it draws nothing on the main agent. Pass title when your header does not draw the chat's title itself. SubagentTitleCrumb({ chatId, children }) makes your own title the first crumb while a subagent is on screen.
Stopping
SubagentStopButton({ chatId, item }) stops a running subagent, or draws nothing when stopping it is not on offer. A subagent the CLI opened can only be marked stopped once the turn ended (stop mark); one your app opened with a task of its own is stopped through stopTask, after the host's confirm.stopTask. Shift with the composer's Stop also ends every agent the chat opened, after confirm.stopSubagents.
Older hosts
A host without chat.subagent refuses it with unknown-request. The first refusal hides the open button on that host's rows that carry no pointer of their own (useSubagentSupport); the rows themselves still draw. history-expired reads the newest page again.
SubagentConversation is the reader behind SubagentTimeline, for a view of your own: construct it with the transport, chat id, call id and a state setter, start() it once and dispose() it on unmount, which stops the watch and leaves the subagent running. The helpers in chat/subagent-list and chat/subagent-view name, order and count subagents the way the flyout and the crumbs do.