1# Multi-agent
2
3## Overview
4
5Multi-agent lets a model spin up and coordinate subagents in parallel, synthesizing their work to provide a final response. This is especially effective for applications with complex tasks that benefit from parallel work delegation, such as codebase exploration, documentation, and implementation.
6
7Multi-agent is available as a beta feature with all GPT-5.6 models. Check the model page before enabling Multi-agent in your application.
8
9## When to use Multi-agent
10
11Tasks can often be divided into independent sections of work that a single agent would complete sequentially, but multiple agents are able to tackle in parallel. Multi-agent enables a root agent to delegate to multiple subagents that complete work concurrently. This can provide multiple benefits:
12
13- **Parallel execution.** Independent research, analysis, or implementation tasks can proceed at the same time, which can lead to faster execution.
14- **Focused context.** Each subagent receives a bounded task and maintains its own context, which reduces interference in context between unrelated lines of work and improves performance.
15- **Model-directed coordination.** The root agent can create subagents, send them additional information, wait for results, and synthesize a final answer without requiring your application to implement orchestration.
16
17Multi-agent orchestration is most useful when a task can be divided into concrete, independent workstreams, such as:
18
19- Exploring separate parts of a large codebase
20- Comparing multiple proposals, documents, or hypotheses
21- Researching several sources in parallel
22- Implementing independent components or writing independent test suites
23- Investigating different possible causes of a failure in parallel
24- Exploring separate approaches to a problem concurrently
25
26Note that adding subagents can increase token usage, and may not be as beneficial for tasks that depend on a single ordered chain of reasoning, require frequent writes to shared mutable state, or are already dominated by one slow external operation.
27
28| Use Multi-agent when | Prefer one agent when |
29| ------------------------------------------------- | ----------------------------------------------------- |
30| Work can be split into independent, bounded tasks | Each step depends directly on the previous step |
31| Separate context improves focus | The task is small enough to complete in one short run |
32| Parallel exploration can reduce wall-clock time | Agents would contend over the same mutable resource |
33| Comparing independent findings improves coverage | You require a fixed, deterministic execution graph |
34
35## Quickstart
36
37The Python and TypeScript examples use the beta Responses SDK. For HTTP
38 requests, use `client.beta.responses` and pass `responses_multi_agent=v1` in
39 the `betas` argument. For raw HTTP requests and WebSocket connections, pass
40 `OpenAI-Beta: responses_multi_agent=v1` in the request or connection headers.
41 Item schemas may change while Multi-agent is in beta.
42
43Enable Multi-agent in your Responses API request with `multi_agent.enabled`. When `multi_agent.enabled` is `true`, the root agent becomes eligible to spawn a tree of subagents. The subagents share the request’s model and available tools, while agents coordinate through collaboration primitives such as spawning, messaging, and waiting (see [How Multi-agent works](#how-multi-agent-works)). The root agent is responsible for synthesizing subagent responses and providing the final response.
44
45Review a pull request with subagents
46
47```python
48from openai import OpenAI
49
50client = OpenAI()
51
52
53def review_pull_request(diff: str) -> str:
54 response = client.beta.responses.create(
55 model="gpt-5.6-sol",
56 input=(
57 "Review the pull-request diff below with three agents: one for "
58 "correctness, one for security, and one for missing tests. "
59 "Reconcile duplicate or conflicting findings, then return a "
60 "prioritized review with file and line references.\n\n"
61 f"<diff>\n{diff}\n</diff>"
62 ),
63 multi_agent={
64 "enabled": True,
65 "max_concurrent_subagents": 3,
66 },
67 betas=["responses_multi_agent=v1"],
68 )
69
70 return "".join(
71 part.text
72 for item in response.output
73 if (
74 item.type == "message"
75 and item.agent is not None
76 and item.agent.agent_name == "/root"
77 and item.phase == "final_answer"
78 )
79 for part in item.content
80 if part.type == "output_text"
81 )
82```
83
84```typescript
85import OpenAI from "openai";
86
87const client = new OpenAI();
88
89async function reviewPullRequest(diff: string): Promise<string> {
90 const response = await client.beta.responses.create({
91 model: "gpt-5.6-sol",
92 input:
93 "Review the pull-request diff below with three agents: one for " +
94 "correctness, one for security, and one for missing tests. " +
95 "Reconcile duplicate or conflicting findings, then return a " +
96 "prioritized review with file and line references.\n\n" +
97 `<diff>\n${diff}\n</diff>`,
98 multi_agent: {
99 enabled: true,
100 max_concurrent_subagents: 3,
101 },
102 betas: ["responses_multi_agent=v1"],
103 });
104
105 return response.output
106 .flatMap((item) =>
107 item.type === "message" && item.agent?.agent_name === "/root"
108 ? item.content
109 : [],
110 )
111 .filter((part) => part.type === "output_text")
112 .map((part) => part.text)
113 .join("");
114}
115```
116
117
118`max_concurrent_subagents` sets the maximum number of subagents that can be active simultaneously across the entire agent tree. It includes all descendants—children, grandchildren, and deeper subagents—but excludes the root agent.
119
120The API does not impose a fixed upper bound on this setting. The default is `3`, which is recommended for most workloads. Multi-agent runs also have no fixed limit on tree depth or the total number of subagents created during a run.
121
122Add a developer message to tune when the root model should spawn subagents. This developer message is additive to the instructions injected for the root agent and subagents.
123
124Examples of developer messages include:
125
126- “Do not spawn subagents unless the user explicitly asks for subagents, delegation, or parallel agent work.”
127- “Proactive Multi-agent delegation is active. Use subagents when parallel work would materially improve speed or quality.”
128
129## How Multi-agent works
130
131The Responses API provides the root and subagent models with hosted orchestration actions and instructions for using them. The root agent is named `/root`. Spawned subagents use hierarchical paths such as:
132
133```text
134/root
135├── /root/researcher
136├── /root/reviewer
137└── /root/reviewer/tester
138```
139
140Multi-agent imposes no fixed limit on the total number of subagents or tree depth. For most tasks, use the default `max_concurrent_subagents` value of `3`. This setting limits the number of active subagent turns across the entire tree, including children and deeper descendants.
141
142When Multi-agent mode is enabled, the Responses API provides six hosted collaboration actions. You may see these as `multi_agent_call` items. Your application should not execute these or submit outputs for them.
143
144| Action | Purpose |
145| ----------------- | ------------------------------------------------------------------------------ |
146| `spawn_agent` | Create a subagent and assign its initial task. |
147| `send_message` | Queue a message for an existing agent without starting a new turn. |
148| `followup_task` | Assign more work to an existing non-root agent and start or resume its turn. |
149| `wait_agent` | Wait for an update in the calling agent's mailbox. |
150| `interrupt_agent` | Interrupt another agent's active turn without deleting its context. |
151| `list_agents` | Return the current agent tree, statuses, and each agent's `last_task_message`. |
152
153Handling developer-defined tool calls works in the same way as without Multi-agent enabled. Any agent in the tree may emit a `function_call`. Your application must execute the call and submit a matching `function_call_output`.
154
155Note that all agents in the tree have access to the tools configured in the API request’s model call.
156
157## Using Multi-agent in Responses API
158
159### HTTP vs. WebSocket performance
160
161HTTP and WebSocket support the same Multi-agent capabilities, but WebSocket is recommended for tool-heavy or long-running workflows. Its persistent connection lets your application return function outputs as they become available, reducing continuation overhead and allowing agents to spend less time waiting.
162
163With HTTP, the response completes once every active agent has either finished or paused to wait for a client-executed function call. Your application then executes all outstanding function calls and submits their outputs in a new Responses API request, allowing the paused agents to resume.
164
165With WebSocket, your application can inject each function output into the response as soon as it becomes available, without waiting for the active response to complete. The waiting agent can resume immediately while other agents continue working. This reduces coordination delays and avoids extra request round trips when agents finish or request tools at different times.
166
167HTTP may be sufficient for workflows that require calling multiple hosted tools, such as parallel web searches, or one-request workflows with few function calls. For most Multi-agent workflows, WebSocket is likely to provide lower latency and better end-to-end performance.
168
169### HTTP
170
171These examples require beta SDK builds that expose the beta Responses API. For HTTP streaming, call `client.beta.responses.create` and pass `responses_multi_agent=v1` with the `betas` argument; this enables beta types and autocomplete. In Python, import beta response item types from `openai.types.beta` when adding type annotations.
172
173Example client-side code:
174
175Handle HTTP streaming tool calls
176
177```python
178from __future__ import annotations
179
180import json
181import sys
182
183from openai import OpenAI
184from openai.types.beta import BetaResponseOutputItem
185
186client = OpenAI()
187ROOT = "/root"
188PROPOSALS = {
189 "alpha": {"estimated_weeks": 6, "risk": "medium"},
190 "beta": {"estimated_weeks": 8, "risk": "low"},
191}
192tools = [
193 {
194 "type": "function",
195 "name": "get_proposal",
196 "description": "Return details for a proposal that the agents should compare.",
197 "parameters": {
198 "type": "object",
199 "properties": {
200 "proposal": {
201 "type": "string",
202 "enum": ["alpha", "beta"],
203 }
204 },
205 "required": ["proposal"],
206 "additionalProperties": False,
207 },
208 "strict": True,
209 }
210]
211history = [
212 {
213 "role": "user",
214 "content": "Compare proposal alpha and proposal beta.",
215 }
216]
217
218
219def agent_name(item: BetaResponseOutputItem) -> str:
220 return item.agent.agent_name if item.agent else ROOT
221
222
223def render_to_user(delta: str) -> None:
224 print(delta, end="", flush=True)
225
226
227def log_subagent_text(agent: str, delta: str) -> None:
228 print(f"[{agent}] {delta}", end="", file=sys.stderr, flush=True)
229
230
231def process_tool_call(name: str, arguments: str) -> str:
232 if name != "get_proposal":
233 raise ValueError(f"Unknown tool: {name}")
234 parsed_arguments = json.loads(arguments)
235 return json.dumps(PROPOSALS[parsed_arguments["proposal"]])
236
237
238while True:
239 output_items = []
240 pending_calls = []
241 item_agents: dict[int, str] = {}
242
243 stream = client.beta.responses.create(
244 model="gpt-5.6-sol",
245 input=history,
246 tools=tools,
247 store=False,
248 include=["reasoning.encrypted_content"],
249 multi_agent={
250 "enabled": True,
251 "max_concurrent_subagents": 3,
252 },
253 stream=True,
254 betas=["responses_multi_agent=v1"],
255 )
256 for event in stream:
257 if event.type == "response.output_item.added":
258 item_agents[event.output_index] = agent_name(event.item)
259 elif event.type == "response.output_text.delta":
260 agent = item_agents.get(event.output_index, ROOT)
261 if agent == ROOT:
262 render_to_user(event.delta)
263 else:
264 log_subagent_text(agent, event.delta)
265 elif event.type == "response.output_item.done":
266 output_items.append(event.item)
267 if event.item.type == "function_call":
268 # Handle function calls from both the root agent and subagents.
269 pending_calls.append(event.item)
270 elif event.type == "response.completed":
271 print(f"\nUsage: {event.response.usage}", file=sys.stderr)
272 break
273 elif event.type in {
274 "error",
275 "response.failed",
276 "response.incomplete",
277 }:
278 raise RuntimeError(event)
279
280 history.extend(output_items)
281
282 for call in pending_calls:
283 history.append(
284 {
285 "type": "function_call_output",
286 "call_id": call.call_id,
287 "output": process_tool_call(call.name, call.arguments),
288 }
289 )
290
291 if not pending_calls:
292 break
293```
294
295```typescript
296import OpenAI from "openai";
297import type {
298 BetaResponseInput,
299 BetaResponseInputItem,
300 BetaResponseOutputItem,
301 BetaTool,
302} from "openai/resources/beta/responses/responses";
303
304const client = new OpenAI();
305const ROOT = "/root";
306const proposals = {
307 alpha: { estimated_weeks: 6, risk: "medium" },
308 beta: { estimated_weeks: 8, risk: "low" },
309};
310const tools: BetaTool[] = [
311 {
312 type: "function",
313 name: "get_proposal",
314 description:
315 "Return details for a proposal that the agents should compare.",
316 parameters: {
317 type: "object",
318 properties: {
319 proposal: {
320 type: "string",
321 enum: ["alpha", "beta"],
322 },
323 },
324 required: ["proposal"],
325 additionalProperties: false,
326 },
327 strict: true,
328 },
329];
330const history: Array<BetaResponseInputItem | BetaResponseOutputItem> = [
331 {
332 role: "user",
333 content: "Compare proposal alpha and proposal beta.",
334 },
335];
336
337function agentName(item: BetaResponseOutputItem): string {
338 return item.agent?.agent_name ?? ROOT;
339}
340
341function processToolCall(name: string, argumentsJson: string): string {
342 if (name !== "get_proposal") {
343 throw new Error(`Unknown tool: ${name}`);
344 }
345 const { proposal } = JSON.parse(argumentsJson) as {
346 proposal: keyof typeof proposals;
347 };
348 return JSON.stringify(proposals[proposal]);
349}
350
351while (true) {
352 const outputItems: BetaResponseOutputItem[] = [];
353 const pendingCalls: Extract<
354 BetaResponseOutputItem,
355 { type: "function_call" }
356 >[] = [];
357 const itemAgents = new Map<number, string>();
358
359 const stream = await client.beta.responses.create({
360 model: "gpt-5.6-sol",
361 // Beta output items can be replayed as input on the next request.
362 input: history as BetaResponseInput,
363 tools,
364 store: false,
365 include: ["reasoning.encrypted_content"],
366 multi_agent: {
367 enabled: true,
368 max_concurrent_subagents: 3,
369 },
370 stream: true,
371 betas: ["responses_multi_agent=v1"],
372 });
373
374 for await (const event of stream) {
375 if (event.type === "response.output_item.added") {
376 itemAgents.set(event.output_index, agentName(event.item));
377 } else if (event.type === "response.output_text.delta") {
378 const agent = itemAgents.get(event.output_index) ?? ROOT;
379 const destination = agent === ROOT ? process.stdout : process.stderr;
380 destination.write(
381 agent === ROOT ? event.delta : `[${agent}] ${event.delta}`
382 );
383 } else if (event.type === "response.output_item.done") {
384 outputItems.push(event.item);
385 if (event.item.type === "function_call") {
386 pendingCalls.push(event.item);
387 }
388 } else if (event.type === "response.completed") {
389 console.error("\nUsage:", event.response.usage);
390 break;
391 } else if (
392 event.type === "error" ||
393 event.type === "response.failed" ||
394 event.type === "response.incomplete"
395 ) {
396 throw new Error(JSON.stringify(event));
397 }
398 }
399
400 history.push(...outputItems);
401 for (const call of pendingCalls) {
402 history.push({
403 type: "function_call_output",
404 call_id: call.call_id,
405 output: processToolCall(call.name, call.arguments),
406 });
407 }
408
409 if (pendingCalls.length === 0) break;
410}
411```
412
413
414If one or more agents call developer-defined functions, execute every pending call and create a continuation request containing their outputs.
415
416### WebSocket
417
418In WebSocket mode, when an agent calls a developer-defined function, execute the function in your application and send its result to the active response with a `response.inject` event. The waiting agent can then resume without waiting for the entire Multi-agent response to complete.
419
420```json
421{
422 "type": "response.inject",
423 "response_id": "resp_123",
424 "input": [
425 {
426 "type": "function_call_output",
427 "call_id": "call_123",
428 "output": "{\"temperature\":72}"
429 }
430 ]
431}
432```
433
434For a valid `response.inject` request, the server replies with one of two events:
435
436- `response.inject.created`: the input was validated and accepted for injection
437- `response.inject.failed`: the input was not injected; inspect `error.code`
438
439```json
440{
441 "type": "response.inject.created",
442 "sequence_number": 42,
443 "response_id": "resp_123"
444}
445```
446
447```json
448{
449 "type": "response.inject.failed",
450 "sequence_number": 43,
451 "response_id": "resp_123",
452 "input": [
453 {
454 "type": "function_call_output",
455 "call_id": "call_123",
456 "output": "{\"temperature\":72}"
457 }
458 ],
459 "error": {
460 "code": "response_already_completed",
461 "message": "Response 'resp_123' has already completed."
462 }
463}
464```
465
466If a request doesn't conform to the `response.inject` schema, the server sends a generic error with status `400` and closes the WebSocket connection. Fix the request and open a new WebSocket connection before sending another event.
467
468The Python beta SDK exposes WebSocket mode through `client.beta.responses.connect`. The TypeScript beta SDK exposes it through `ResponsesWS`. Pass `OpenAI-Beta: responses_multi_agent=v1` in the connection headers; unlike HTTP streaming, the WebSocket connectors do not yet accept the `betas` argument.
469
470Save the response ID from the `response.created` event and include it in every `response.inject` event you send for that response. After sending an injection item, continue reading from the WebSocket until the response has completed and every injection has produced either a `response.inject.created` or `response.inject.failed` event.
471
472Inject tool outputs over WebSocket
473
474```python
475from __future__ import annotations
476
477import json
478
479from openai import OpenAI
480
481client = OpenAI()
482PROPOSALS = {
483 "alpha": {"estimated_weeks": 6, "risk": "medium"},
484 "beta": {"estimated_weeks": 8, "risk": "low"},
485}
486tools = [
487 {
488 "type": "function",
489 "name": "get_proposal",
490 "description": "Return details for a proposal that the agents should compare.",
491 "parameters": {
492 "type": "object",
493 "properties": {
494 "proposal": {
495 "type": "string",
496 "enum": ["alpha", "beta"],
497 }
498 },
499 "required": ["proposal"],
500 "additionalProperties": False,
501 },
502 "strict": True,
503 }
504]
505
506
507def process_tool_call(name: str, arguments: str) -> str:
508 if name != "get_proposal":
509 raise ValueError(f"Unknown tool: {name}")
510 parsed_arguments = json.loads(arguments)
511 return json.dumps(PROPOSALS[parsed_arguments["proposal"]])
512
513
514def run_multi_agent(connection):
515 previous_response_id: str | None = None
516 pending_input: list[dict[str, object]] = [
517 {"role": "user", "content": input()}
518 ]
519
520 while pending_input:
521 request = {
522 "type": "response.create",
523 "model": "gpt-5.6-sol",
524 "store": True,
525 "multi_agent": {"enabled": True},
526 "tools": tools,
527 "input": pending_input,
528 }
529 if previous_response_id is not None:
530 request["previous_response_id"] = previous_response_id
531
532 connection.send(request)
533
534 next_input: list[dict[str, object]] = []
535 completed_response = None
536 response_id: str | None = None
537 pending_injections = 0
538
539 for event in connection:
540 event_type = event.type
541
542 if event_type == "response.created":
543 response_id = event.response.id
544
545 elif event_type == "response.output_item.done":
546 item = event.item
547
548 if item.type == "function_call":
549 if response_id is None:
550 raise RuntimeError(
551 "Received a function call before response.created"
552 )
553
554 output = {
555 "type": "function_call_output",
556 "call_id": item.call_id,
557 "output": process_tool_call(item.name, item.arguments),
558 }
559 pending_injections += 1
560
561 connection.send(
562 {
563 "type": "response.inject",
564 "response_id": response_id,
565 "input": [output],
566 }
567 )
568
569 elif event_type == "response.inject.created":
570 pending_injections -= 1
571
572 elif event_type == "response.inject.failed":
573 pending_injections -= 1
574
575 if event.error.code != "response_already_completed":
576 raise RuntimeError(event.error)
577
578 next_input.extend(
579 item.model_dump(mode="json") for item in event.input
580 )
581
582 elif event_type == "response.completed":
583 completed_response = event.response
584
585 elif event_type in {
586 "error",
587 "response.failed",
588 "response.incomplete",
589 }:
590 raise RuntimeError(event)
591
592 if completed_response is not None and pending_injections == 0:
593 break
594
595 if completed_response is None:
596 raise RuntimeError("Connection ended before response.completed")
597
598 if not next_input:
599 return completed_response
600
601 previous_response_id = completed_response.id
602 pending_input = next_input
603
604
605with client.beta.responses.connect(
606 extra_headers={"OpenAI-Beta": "responses_multi_agent=v1"},
607) as connection:
608 run_multi_agent(connection)
609```
610
611```typescript
612import OpenAI from "openai";
613import type { BetaResponseInput } from "openai/resources/beta/responses/responses";
614import { ResponsesWS } from "openai/resources/beta/responses/ws";
615
616const client = new OpenAI();
617const proposals = {
618 alpha: { estimated_weeks: 6, risk: "medium" },
619 beta: { estimated_weeks: 8, risk: "low" },
620};
621const tools = [
622 {
623 type: "function" as const,
624 name: "get_proposal",
625 description:
626 "Return details for a proposal that the agents should compare.",
627 parameters: {
628 type: "object",
629 properties: {
630 proposal: {
631 type: "string",
632 enum: ["alpha", "beta"],
633 },
634 },
635 required: ["proposal"],
636 additionalProperties: false,
637 },
638 strict: true,
639 },
640];
641
642function processToolCall(name: string, argumentsJson: string): string {
643 if (name !== "get_proposal") {
644 throw new Error(`Unknown tool: ${name}`);
645 }
646 const { proposal } = JSON.parse(argumentsJson) as {
647 proposal: keyof typeof proposals;
648 };
649 return JSON.stringify(proposals[proposal]);
650}
651
652async function runMultiAgent(ws: ResponsesWS) {
653 let previousResponseId: string | undefined;
654 let pendingInput: BetaResponseInput = [
655 { role: "user", content: process.argv.slice(2).join(" ") },
656 ];
657
658 while (pendingInput.length > 0) {
659 ws.send({
660 type: "response.create",
661 model: "gpt-5.6-sol",
662 store: true,
663 multi_agent: {
664 enabled: true,
665 max_concurrent_subagents: 3,
666 },
667 tools,
668 input: pendingInput,
669 previous_response_id: previousResponseId,
670 });
671
672 const nextInput: BetaResponseInput = [];
673 let completedResponseId: string | undefined;
674 let responseId: string | undefined;
675 let pendingInjections = 0;
676
677 for await (const message of ws) {
678 if (message.type === "error") throw message.error;
679 if (message.type !== "message") continue;
680
681 const event = message.message;
682 if (event.type === "response.created") {
683 responseId = event.response.id;
684 } else if (
685 event.type === "response.output_item.done" &&
686 event.item.type === "function_call"
687 ) {
688 if (!responseId) {
689 throw new Error("Received a function call before response.created");
690 }
691 pendingInjections += 1;
692 ws.send({
693 type: "response.inject",
694 response_id: responseId,
695 input: [
696 {
697 type: "function_call_output",
698 call_id: event.item.call_id,
699 output: processToolCall(event.item.name, event.item.arguments),
700 },
701 ],
702 });
703 } else if (event.type === "response.inject.created") {
704 pendingInjections -= 1;
705 } else if (event.type === "response.inject.failed") {
706 pendingInjections -= 1;
707 if (event.error.code !== "response_already_completed") {
708 throw new Error(JSON.stringify(event.error));
709 }
710 nextInput.push(...event.input);
711 } else if (event.type === "response.completed") {
712 completedResponseId = event.response.id;
713 } else if (
714 event.type === "error" ||
715 event.type === "response.failed" ||
716 event.type === "response.incomplete"
717 ) {
718 throw new Error(JSON.stringify(event));
719 }
720
721 if (completedResponseId && pendingInjections === 0) break;
722 }
723
724 if (!completedResponseId) {
725 throw new Error("Connection ended before response.completed");
726 }
727 if (nextInput.length === 0) return;
728
729 previousResponseId = completedResponseId;
730 pendingInput = nextInput;
731 }
732}
733
734const ws = new ResponsesWS(client, {
735 headers: { "OpenAI-Beta": "responses_multi_agent=v1" },
736});
737
738try {
739 await runMultiAgent(ws);
740} finally {
741 ws.close();
742}
743```
744
745
746After sending a `response.inject` event, keep reading from the WebSocket and handle the acknowledgement:
747
748- **`response.inject.created`**: The function output was added to the active response. Continue reading events for that response.
749- **`response.inject.failed` with `response_already_completed`**: The response completed before the function output could be added. Take the `input` returned in the failure event and send it in a new `response.create` request that continues from the completed response.
750- **`response.inject.failed` with `response_not_found`**: The server could not find the response identified by `response_id`. Verify that you are using the ID received from `response.created`.
751
752A single Multi-agent run may span multiple Responses API requests. Over HTTP, when an agent calls a developer-defined function, your application executes the function and submits its output in a new `response.create` call. Over WebSocket, your application instead injects the function output into the active response.
753
754## New Multi-agent output items
755
756Multi-agent responses can include three additional output item types:
757
758- `multi_agent_call`: records a hosted Multi-agent action, such as `spawn_agent`.
759- `multi_agent_call_output`: contains the result from execution of a hosted action.
760- `agent_message`: carries an encrypted message from one agent to another.
761
762The `call_id` field links each `multi_agent_call` to its corresponding `multi_agent_call_output`.
763
764Each item also includes an `agent` attribute. For an `agent_message`, `agent.agent_name` identifies the recipient agent. Use `author` and `recipient` to trace the message direction.
765
766When your application receives a `multi_agent_call`, do not execute it as a function call or send back a result. The Responses API executes the hosted action and returns the corresponding `multi_agent_call_output`. Preserve both items if your application needs them for replay or tracing.
767
768```json
769[
770 {
771 "type": "multi_agent_call",
772 "id": "mac_123",
773 "call_id": "call_spawn_a",
774 "action": "spawn_agent",
775 "arguments": "{\"task_name\":\"agent_a\",\"fork_turns\":\"all\",\"message\":\"enc_...\"}",
776 "agent": { "agent_name": "/root" }
777 },
778 {
779 "type": "multi_agent_call_output",
780 "id": "maco_123",
781 "call_id": "call_spawn_a",
782 "action": "spawn_agent",
783 "output": [
784 {
785 "type": "output_text",
786 "text": "{\"task_name\":\"/root/agent_a\"}",
787 "annotations": [],
788 "logprobs": []
789 }
790 ],
791 "agent": { "agent_name": "/root" }
792 },
793 {
794 "type": "agent_message",
795 "id": "amsg_123",
796 "author": "/root/agent_a",
797 "recipient": "/root",
798 "content": [
799 {
800 "type": "encrypted_content",
801 "encrypted_content": "enc_..."
802 }
803 ],
804 "agent": { "agent_name": "/root" }
805 }
806]
807```
808
809Agent-attributed SSE events include a top-level `agent` attribute. For an `agent_message` event, `agent.agent_name` identifies the recipient agent. Response lifecycle events such as `response.created` and `response.completed` describe the overall response rather than an individual agent, so they do not include an `agent` attribute.
810
811```json
812{
813 "type": "response.output_item.done",
814 "agent": { "agent_name": "/root" },
815 "item": {
816 "type": "agent_message",
817 "id": "amsg_123",
818 "author": "/root/agent_a",
819 "recipient": "/root",
820 "content": [
821 {
822 "type": "encrypted_content",
823 "encrypted_content": "enc_..."
824 }
825 ],
826 "agent": { "agent_name": "/root" }
827 }
828}
829```
830
831## Limitations
832
8331. Compaction:
834 1. The `/responses/compact` endpoint is not supported when Multi-agent is enabled.
835 2. When `multi_agent.enabled` is set to `true`, automatic server-side compaction is enabled implicitly, even if the request does not configure `context_management`. Compaction is applied independently to the root agent and each subagent, preserving their separate contexts. Users can still override `compact_threshold` by setting an explicit `context_management.compact_threshold` in the request.
8362. `reasoning.summary` is not supported when Multi-agent is enabled.
8373. `max_tool_calls` is not supported when Multi-agent is enabled.
8384. `max_concurrent_subagents` defaults to `3`, which is the recommended setting.
839
840## Prompt guidance
841
842When Multi-agent is enabled, our systems automatically append these instructions to the root agent and subagents as a new developer message. You cannot edit or remove these instructions, but you should frame your developer instructions as additive to these automatically injected instructions.
843
844### Root agent
845
846````text
847You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals.
848
849At the start of your turn, you are the active agent.
850You can spawn sub-agents to handle subtasks, and those sub-agents can spawn their own sub-agents.
851All agents in the team, including the agents that you can assign tasks to, are equally intelligent and capable, and have access to the same set of tools.
852
853You can use `spawn_agent` to create a new agent, `followup_task` to give an existing agent a new task and trigger a turn, and `send_message` to pass a message to a running agent without triggering a turn.
854Child agents can also spawn their own sub-agents.
855You can decide how much context you want to propagate to your sub-agents with the `fork_turns` parameter.
856
857You will receive messages in the form:
858```
859Message Type: MESSAGE | FINAL_ANSWER
860Task name: <recipient>
861Sender: <author>
862Payload:
863<payload text>
864```
865They may be addressed as to=/root
866
867There are {max_concurrent_subagents + 1} available concurrency slots, meaning that up to {max_concurrent_subagents + 1} agents can be active at once, including you.
868````
869
870### Subagent
871
872````text
873You are an agent in a team of agents collaborating to complete a task.
874
875You can spawn sub-agents to handle subtasks, and those sub-agents can spawn their own sub-agents. All agents in the team, including the agents that you can assign tasks to, are equally intelligent and capable, and have access to the same set of tools.
876
877You can use `spawn_agent` to create a new agent, `followup_task` to give an existing agent a new task and trigger a turn, and `send_message` to pass a message to a running agent.
878Child agents can also spawn their own sub-agents.
879
880When you provide a response in the final channel, that content is immediately delivered back to your parent agent.
881
882You will receive messages in the form:
883```
884Message Type: NEW_TASK | MESSAGE | FINAL_ANSWER
885Task name: <recipient>
886Sender: <author>
887Payload:
888<payload text>
889```
890You may also see them addressed as to=/root/..., which indicates your identity is /root/...
891
892There are {max_concurrent_subagents + 1} available concurrency slots, meaning that up to {max_concurrent_subagents + 1} agents can be active at once, including you.
893````
894
895## Related guides
896
897- [Function calling](https://developers.openai.com/api/docs/guides/function-calling)
898- [WebSocket mode](https://developers.openai.com/api/docs/guides/websocket-mode)
899- [Compaction](https://developers.openai.com/api/docs/guides/compaction)