> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.thatdevguy.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-Agent Orchestration with Anvil AgentRegistry

> Use Anvil AgentRegistry to register agents, wrap them as tools, and delegate work in-process — no HTTP round-trips, no separate orchestration framework.

Anvil's multi-agent system is built from the same primitives as everything else: route handlers, tools, and `LlmClient`. `AgentRegistry` lets you register named agents and invoke them from code or from inside another agent's tool loop. Sub-agents run entirely in-process — there are no HTTP round-trips, no additional servers, and no separate orchestration framework to learn.

## `AgentRegistry`

Import `AgentRegistry` from `anvil-sdk/agent`:

```ts theme={null}
import { AgentRegistry } from 'anvil-sdk/agent';

const registry = new AgentRegistry();
```

### `registry.register(name, config)`

Register a named agent with its `RunAgentOptions` (the same options `runAgent` accepts, minus `messages`):

```ts theme={null}
registry.register('summarizer', {
  client,
  system: 'You are an expert summarizer. Return a concise summary in 2–3 sentences.',
  maxIterations: 2,
});

registry.register('researcher', {
  client,
  system: 'You are a research assistant. Use tools to find accurate information.',
  tools: [searchTool, fetchPageTool],
  maxIterations: 8,
});
```

`register` returns `this`, so you can chain calls.

### `registry.call(name, input, overrides?)`

Invoke a registered agent by name. Pass a string for a single user message, or a full `ModelMessage[]` conversation. Returns `AgentRunResult`.

```ts theme={null}
const result = await registry.call('summarizer', 'Summarize the French Revolution in 3 sentences.');
console.log(result.text);
console.log(result.iterations);   // how many model turns it took
console.log(result.totalCostUsd); // accumulated cost
```

Pass `overrides` to adjust config for a specific call without modifying the registration:

```ts theme={null}
const result = await registry.call('researcher', userQuery, {
  maxIterations: 3,           // tighten the cap for this call
  model: 'claude-haiku-3-5',  // use a cheaper model
});
```

<ResponseField name="text" type="string">
  The final text response from the sub-agent.
</ResponseField>

<ResponseField name="messages" type="ModelMessage[]">
  The full conversation, including all tool calls and results.
</ResponseField>

<ResponseField name="iterations" type="number">
  The number of model turns that ran.
</ResponseField>

<ResponseField name="totalUsage" type="{ inputTokens: number, outputTokens: number }">
  Accumulated token counts across all turns.
</ResponseField>

<ResponseField name="totalCostUsd" type="number">
  Accumulated USD cost.
</ResponseField>

<ResponseField name="stoppedAtCap" type="boolean">
  `true` if the loop stopped because it hit `maxIterations` rather than finishing naturally.
</ResponseField>

### `registry.has(name)` and `registry.names()`

Check membership and list registered names:

```ts theme={null}
registry.has('summarizer'); // true
registry.names();           // ['summarizer', 'researcher']
```

## `agentAsTool`

Wrap a registered agent as an `AgentTool` so an orchestrator agent can delegate to it during its own tool loop. The sub-agent's final text becomes the tool result:

```ts theme={null}
import { agentAsTool } from 'anvil-sdk/agent';

const researchTool = agentAsTool(registry, 'researcher', {
  toolName: 'research',          // optional; defaults to `call_researcher`
  description: 'Delegate a research task to a specialist agent. Provide a detailed question.',
});
```

<ParamField body="registry" type="AgentRegistry" required>
  The registry that contains the target agent.
</ParamField>

<ParamField body="name" type="string" required>
  The name of the registered agent to wrap.
</ParamField>

<ParamField body="options.toolName" type="string">
  The tool name advertised to the parent model. Defaults to `call_<name>`.
</ParamField>

<ParamField body="options.description" type="string">
  The tool description. Defaults to a generic delegation prompt that includes the agent name.
</ParamField>

<ParamField body="options.overrides" type="Partial<AgentConfig>">
  Config overrides applied every time the parent model delegates to this sub-agent.
</ParamField>

The returned tool uses a fixed `message` input schema — the parent model passes a plain string describing the subtask, which becomes the sub-agent's first user message.

## `withAgents` middleware and `callAgent` helper

Use `withAgents` to attach a registry to `ctx.state.agents`, then use `callAgent` anywhere in your handler tree:

```ts theme={null}
// server/routes/_middleware.ts
import { withAgents } from 'anvil-sdk/agent';
import { registry } from '../agents';

export default [withAgents(registry)];
```

```ts theme={null}
// server/routes/report/post.ts
import { callAgent } from 'anvil-sdk/agent';
import type { Context } from 'anvil-sdk';

export default async function handler(ctx: Context) {
  const body = await ctx.body<{ topic: string }>();
  const result = await callAgent(ctx, 'researcher', body.topic);
  return { summary: result.text };
}
```

`callAgent(ctx, name, input)` is sugar for `getAgents(ctx).call(name, input)`. It throws a clear error if no registry is attached.

## Full orchestration example

This example registers a researcher and a summarizer, then wires them together in a parent orchestrator agent that the client talks to:

```ts theme={null}
// server/agents.ts
import { AgentRegistry } from 'anvil-sdk/agent';
import { LlmClient, AnthropicDriver } from 'anvil-sdk/llm';
import { searchTool } from './tools/search';

const client = new LlmClient({
  drivers: [new AnthropicDriver({ apiKey: process.env.ANTHROPIC_API_KEY })],
  defaultModel: 'claude-opus-4-8',
});

export const registry = new AgentRegistry()
  .register('researcher', {
    client,
    system: 'You are a research assistant. Use the search tool to find accurate, up-to-date information.',
    tools: [searchTool],
    maxIterations: 6,
  })
  .register('summarizer', {
    client,
    system: 'You are an expert summarizer. Produce a concise 2–3 sentence summary from the provided text.',
    maxIterations: 2,
  });
```

```ts theme={null}
// server/routes/report/agent.ts
import { defineAgent, agentAsTool } from 'anvil-sdk/agent';
import { registry } from '../../agents';
import { client } from '../../llm';

export default defineAgent({
  client,
  system: `You are an orchestrator. For each user question:
1. Use the research tool to gather information.
2. Use the summarize tool to condense it.
3. Return the final summary to the user.`,
  maxIterations: 6,
  tools: [
    agentAsTool(registry, 'researcher', {
      toolName: 'research',
      description: 'Research a question using live search. Provide a detailed question.',
    }),
    agentAsTool(registry, 'summarizer', {
      toolName: 'summarize',
      description: 'Summarize a block of text into 2–3 sentences. Provide the full text.',
    }),
  ],
});
```

The `POST /report` route now orchestrates the two sub-agents. The orchestrator model decides when to call `research` and `summarize`; each sub-agent runs to completion in-process and returns its text as the tool result.

<Note>
  Sub-agents run synchronously in the parent's event loop via `runAgent` — no network calls, no spawned processes. The parent agent's `maxIterations` counts its own turns; sub-agent turns are independent.
</Note>

## Sub-agent execution model

`agentAsTool` calls `registry.call(name, message)` inside `execute`, which calls `runAgent` directly. The entire sub-agent loop runs to completion before `execute` returns. This means:

* **No HTTP round-trips** — the sub-agent never makes an HTTP request to itself.
* **No shared streaming** — the parent stream does not include the sub-agent's intermediate events; only its final text appears as a tool result.
* **Nested cost tracking** — each sub-agent call uses the client's internal cost accumulator, so `client.totalCostUsd` reflects the total across all agents sharing that client.

<Tip>
  Register sub-agents with their own `LlmClient` instances to use cheaper or faster models for specific tasks, while reserving your most capable model for the top-level orchestrator.
</Tip>
