# Multi-Agent
URL: /docs/guides/multi-agent
Render sub-agent conversations inside tool call UIs.
In a multi-agent (orchestrator) architecture, a main agent invokes sub-agents via tool calls. Each sub-agent may produce its own conversation (user/assistant messages, tool calls, etc.). assistant-ui supports rendering these nested conversations using the `MessagePartPrimitive.Messages` primitive.
Overview \[#overview]
When a tool call includes a `messages` field (`ToolCallMessagePart.messages`), it represents a sub-agent's conversation history. `MessagePartPrimitive.Messages` reads this field from the current tool call part and renders it as a nested thread.
Key behaviors:
* **Scope inheritance** — Parent tool UI registrations are available in sub-agent messages. A `makeAssistantToolUI` registered at the top level works inside sub-agent conversations too.
* **Recursive** — Sub-agent messages can contain tool calls that themselves have nested messages. Just use `MessagePartPrimitive.Messages` again.
* **Read-only** — Sub-agent messages are rendered in a readonly context. No editing, branching, or composing.
Quick Start \[#quick-start]
Register a Tool UI for the Sub-Agent \[#register-a-tool-ui-for-the-sub-agent]
```tsx
import {
makeAssistantToolUI,
MessagePartPrimitive,
} from "@assistant-ui/react";
const ResearchAgentToolUI = makeAssistantToolUI({
toolName: "invoke_researcher",
render: ({ args, status }) => (
Researcher Agent {status.type === "running" && "(working...)"}
),
});
```
Provide the Messages from the Backend \[#provide-the-messages-from-the-backend]
Your backend must populate the `messages` field on the tool call result. For example, with the AI SDK:
```ts title="api/chat/route.ts"
tools: {
invoke_researcher: tool({
description: "Invoke the researcher sub-agent",
parameters: z.object({ query: z.string() }),
execute: async ({ query }) => {
const subAgentMessages = await runResearcherAgent(query);
return {
answer: subAgentMessages.at(-1)?.content,
// The messages field is picked up by assistant-ui
messages: subAgentMessages,
};
},
}),
},
```
The exact mechanism for populating `messages` depends on your backend
framework. The key requirement is that the tool result's corresponding
`ToolCallMessagePart` includes a `messages` array of `ThreadMessage` objects.
Register the Tool UI Component \[#register-the-tool-ui-component]
```tsx
function App() {
return (
);
}
```
Recursive Sub-Agents \[#recursive-sub-agents]
If a sub-agent's tool calls also have nested messages, the same pattern applies recursively:
```tsx
const OuterAgentToolUI = makeAssistantToolUI({
toolName: "invoke_planner",
render: () => (
Planner Agent
(
(
Researcher Agent
{/* Nested sub-agent renders recursively */}
),
},
Fallback: MyToolFallback,
},
}}
/>
),
UserMessage: MyUserMessage,
}}
/>
),
});
```
ReadonlyThreadProvider \[#readonlythreadprovider]
For advanced use cases where you have a `ThreadMessage[]` array and want to render it as a thread outside of a tool call context, use `ReadonlyThreadProvider` directly:
```tsx
import {
ReadonlyThreadProvider,
ThreadPrimitive,
type ThreadMessage,
} from "@assistant-ui/react";
function SubConversation({
messages,
}: {
messages: readonly ThreadMessage[];
}) {
return (
);
}
```
`ReadonlyThreadProvider` inherits the parent's tool UI registrations and model context through scope inheritance.
Related \[#related]
* [Generative UI](/docs/guides/tool-ui) — Creating tool call UIs
* [MessagePartPrimitive](/docs/api-reference/primitives/message-part) — API reference for message part primitives