# 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