Two mechanisms for agent-to-agent communication — choose based on whether the agents share conversation history.
## `delegate` — Pass the Baton
`delegate` hands the current request to another agent, including all conversation history. The delegated agent sees the same messages and continues the conversation as if it were the original agent.
Use this for **routing** — when one agent decides who should handle a request.
```typescript
// Create Hook — router
export function Create(
ctx: agent.Context,
messages: agent.Message[]
): agent.Create {
const input = messages[messages.length - 1]?.content || "";
if (input.toLowerCase().includes("code")) {
return {
delegate: {
agent_id: "yao.code-agent",
messages,
},
};
}
if (input.toLowerCase().includes("report")) {
return {
delegate: {
agent_id: "yao.report-agent",
messages,
options: { metadata: { source: "router" } },
},
};
}
return { messages }; // handle locally
}
```
`delegate` can also be returned from `Next` Hook — after seeing the LLM's response.
## `ctx.agent.Call` — Call as a Function
`ctx.agent.Call` runs another agent in an **independent context** with its own conversation history. The result is returned to the calling Hook. Use this for **sub-tasks** — parallel or sequential processing where isolation is important.
```typescript
// Create Hook — fan out to multiple specialist agents
export function Create(
ctx: agent.Context,
messages: agent.Message[]
): agent.Create {
const query = messages[messages.length - 1]?.content || "";
// Call in parallel
const results = ctx.agent.All([
{
agent: "yao.web-searcher",
messages: [{ role: "user", content: query }],
},
{
agent: "yao.kb-searcher",
messages: [{ role: "user", content: query }],
},
]);
// Merge results into a single context message
const context = results
.filter((r) => !r.error)
.map((r) => r.content)
.join("\n\n");
const enriched = [
{ role: "system" as const, content: `Research results:\n${context}` },
...messages,
];
return { messages: enriched };
}
```
## Comparison
| | `delegate` | `ctx.agent.Call` |
|---|---|---|
| Conversation history | Shared | Independent |
| When to use | Routing | Sub-tasks, parallel work |
| Return to caller | No | Yes (result returned) |
| Available in | Create, Next Hook | Create, Next Hook |
| Streaming to client | Yes (auto) | Opt-in via `onChunk` |
## Streaming from Sub-Agent to Client
```typescript
const result = ctx.agent.Call("yao.sub-agent", messages, {
onChunk: (msg) => {
ctx.Send(msg); // Forward chunks as they arrive
return 0; // 0 = continue, non-zero = stop
},
});
```
## Sequential Pipeline
Chain agents where each feeds into the next:
```typescript
export function Create(
ctx: agent.Context,
messages: agent.Message[]
): agent.Create {
// Step 1: Extract intent
const intentResult = ctx.agent.Call("yao.intent-agent", messages);
const intent = intentResult.content;
// Step 2: Execute based on intent
const actionResult = ctx.agent.Call("yao.action-agent", [
...messages,
{ role: "assistant", content: intent },
{ role: "user", content: "Execute the above." },
]);
// Inject both results as context
const enriched = [
{
role: "system" as const,
content: `Intent: ${intent}\nAction result: ${actionResult.content}`,
},
...messages,
];
return { messages: enriched };
}
```
## Real Example: Robot Pipeline
The `robot-host` assistant routes to specialized agents for each phase (inspiration → goals → tasks → validation → delivery). Each phase agent runs independently via `ctx.agent.Call`, with its result passed to the next:
```typescript
// Simplified from yaobots/assistants/yao/robot-host
export function Create(
ctx: agent.Context,
messages: agent.Message[]
): agent.Create {
const phase = ctx.memory.chat.Get("phase") as string || "inspiration";
if (phase === "inspiration") {
return {
delegate: {
agent_id: "yao.robot-inspiration",
messages,
},
};
}
// ... more phases
return { messages };
}
```
## What's Next
You've covered all core mechanisms. Time to build something real.
→ **[Build: Bookmark Agent](./build-assistant)**