# Daimon Multi-Agent Guide
This document covers all multi-agent patterns in the Daimon Rust AI agent framework: Agent-as-Tool, Supervisor, Handoff Network, Fork Builder, and Hot-Swap Agent. Each pattern serves a distinct purpose; choose based on your delegation model, flow control, and runtime requirements.
---
## Table of Contents
1. [Agent-as-Tool](#agent-as-tool)
2. [Supervisor](#supervisor)
3. [Handoff Network](#handoff-network)
4. [Fork Builder](#fork-builder)
5. [Hot-Swap Agent](#hot-swap-agent)
6. [Patterns & Best Practices](#patterns--best-practices)
---
## Agent-as-Tool
`AgentTool` wraps an agent as a `Tool`, so another agent can invoke it by name. The caller sends an `input` string; the inner agent runs its ReAct loop; the caller receives the response text as the tool result.
### How It Works
- The wrapped agent receives the `"input"` field from the tool arguments as its prompt.
- The agent runs `agent.prompt(input)` and returns `response.final_text` as the tool output.
- If the inner agent fails, the tool returns an error message (not a panic) so the caller can react.
### API
```rust
AgentTool::new(agent: Arc<Agent>, name: impl Into<String>, description: impl Into<String>)
```
- **agent** — The agent to wrap (shared via `Arc`)
- **name** — Tool name the calling agent uses when invoking
- **description** — Description the model sees when deciding to call this tool
### Example: Research Agent + Writing Agent
A writer agent delegates research to a specialist. The writer calls the `research` tool with a topic; the researcher returns findings; the writer composes the final output.
```rust
use daimon::prelude::*;
use std::sync::Arc;
#[tokio::main]
async fn main() -> daimon::Result<()> {
let model = Arc::new(daimon::model::openai::OpenAi::new("gpt-4o"));
let researcher = Arc::new(
Agent::builder()
.shared_model(Arc::clone(&model))
.system_prompt(
"You are a research specialist. Search for facts and summarize findings concisely.",
)
.build()?,
);
let writer = Agent::builder()
.shared_model(model)
.system_prompt(
"You are a content writer. Use the research tool to gather facts, \
then write polished prose. Always research before writing.",
)
.tool(AgentTool::new(
researcher,
"research",
"Perform research on a topic. Input: the research question or topic.",
))
.build()?;
let response = writer
.prompt("Write a short paragraph about Rust's ownership model.")
.await?;
println!("{}", response.text());
Ok(())
}
```
### Tool Schema
`AgentTool` exposes a JSON Schema with a single required `input` string:
```json
{
"type": "object",
"properties": {
"input": {
"type": "string",
"description": "The task or question to send to the agent"
}
},
"required": ["input"]
}
```
The calling agent passes `{"input": "research question"}` when invoking the tool.
---
## Supervisor
A **Supervisor** is a coordinator agent whose tools *are* sub-agents. The LLM decides which sub-agent to invoke based on the task. Use for request-response delegation: one input, one routed call, one aggregated result.
### How It Works
- Each sub-agent is wrapped as an `AgentTool` and registered with the coordinator.
- The coordinator runs the ReAct loop; when it needs help, it calls a sub-agent tool.
- Sub-agents run independently; their responses are fed back as tool results.
- The coordinator can call multiple sub-agents in sequence or combine their outputs.
### API
```rust
Supervisor::builder()
.model(M)
.system_prompt("...")
.agent(name, agent, description)
.tool(extra_tool) // optional
.max_iterations(25)
.build()?
```
### Example: Customer Service Supervisor
A supervisor routes to billing, technical support, or general inquiry agents based on the user's message.
```rust
use daimon::prelude::*;
use std::sync::Arc;
#[tokio::main]
async fn main() -> daimon::Result<()> {
let model = Arc::new(daimon::model::openai::OpenAi::new("gpt-4o"));
let billing = Arc::new(
Agent::builder()
.shared_model(Arc::clone(&model))
.system_prompt(
"You handle billing questions: invoices, payments, refunds. \
Be precise and reference account details when available.",
)
.build()?,
);
let technical = Arc::new(
Agent::builder()
.shared_model(Arc::clone(&model))
.system_prompt(
"You handle technical support: bugs, integrations, API usage. \
Provide step-by-step guidance.",
)
.build()?,
);
let general = Arc::new(
Agent::builder()
.shared_model(Arc::clone(&model))
.system_prompt(
"You handle general inquiries: product info, policies, FAQs. \
Be friendly and helpful.",
)
.build()?,
);
let supervisor = Supervisor::builder()
.shared_model(model)
.system_prompt(
"You are a customer service coordinator. Route the user's request \
to the appropriate specialist: billing, technical, or general. \
Call the right agent tool based on the query.",
)
.agent("billing", billing, "Handles billing, invoices, payments, refunds")
.agent("technical", technical, "Handles technical support and integrations")
.agent("general", general, "Handles general inquiries and FAQs")
.build()?;
let response = supervisor
.run("I was charged twice for my subscription last month")
.await?;
println!("{}", response.text());
Ok(())
}
```
---
## Handoff Network
A **HandoffNetwork** lets agents transfer control to each other mid-conversation. Each agent has synthetic `transfer_to_<name>` tools; when invoked, the conversation switches to that agent while preserving message history. Use for conversational flows where control passes between specialists.
### How It Works
- The network runs its own loop, starting with the entry agent.
- Each agent receives its normal tools plus `transfer_to_<other_agent>` for every other agent.
- When the model calls `transfer_to_billing`, the network switches the active agent, updates the system prompt, and continues with the same message history.
- Context is preserved across handoffs; the new agent sees the full conversation.
### API
```rust
HandoffNetwork::builder()
.entry(name) // first agent to receive input
.agent(name, agent)
.max_handoffs(10)
.max_iterations_per_agent(25)
.build()?
```
### Example: Triage → Specialist → Escalation
A triage agent classifies the request; if needed, it hands off to a specialist; the specialist can escalate to a senior agent.
```rust
use daimon::prelude::*;
use std::sync::Arc;
#[tokio::main]
async fn main() -> daimon::Result<()> {
let model = Arc::new(daimon::model::openai::OpenAi::new("gpt-4o"));
let triage = Arc::new(
Agent::builder()
.shared_model(Arc::clone(&model))
.system_prompt(
"You are a triage agent. Classify the request: billing, technical, \
or escalation. Use transfer_to_billing, transfer_to_technical, \
or transfer_to_escalation when the user needs a specialist.",
)
.build()?,
);
let billing = Arc::new(
Agent::builder()
.shared_model(Arc::clone(&model))
.system_prompt(
"You handle billing. Resolve invoices, payments, refunds. \
Use transfer_to_escalation if the issue requires a manager.",
)
.build()?,
);
let technical = Arc::new(
Agent::builder()
.shared_model(Arc::clone(&model))
.system_prompt(
"You handle technical support. Use transfer_to_escalation \
for complex or unresolved issues.",
)
.build()?,
);
let escalation = Arc::new(
Agent::builder()
.shared_model(Arc::clone(&model))
.system_prompt(
"You are a senior agent. Handle escalated cases. \
Provide final resolution or next steps.",
)
.build()?,
);
let network = HandoffNetwork::builder()
.entry("triage")
.agent("triage", triage)
.agent("billing", billing)
.agent("technical", technical)
.agent("escalation", escalation)
.max_handoffs(10)
.build()?;
let response = network
.run("I need help with my bill and a bug in the API")
.await?;
println!("Final agent: {}", response.final_agent);
println!("Handoffs: {}", response.handoff_count);
println!("{}", response.text());
Ok(())
}
```
### HandoffResponse
```rust
pub struct HandoffResponse {
pub messages: Vec<Message>,
pub final_text: String,
pub final_agent: String,
pub handoff_count: usize,
pub iterations: usize,
pub usage: Usage,
}
```
---
## Fork Builder
Use `agent.fork_builder()` to create a mutated copy of an agent. The builder starts with the parent's config; you override specific fields, then `build()` to produce an independent agent. Use for A/B testing, specialized variants, or branching configurations.
### How It Works
- `fork_builder()` returns a `ForkBuilder` pre-populated with the agent's model, tools, memory, hooks, etc.
- Mutate with `.system_prompt()`, `.tool()`, `.remove_tool()`, `.model()`, `.memory()`, etc.
- `build()` creates a new `Agent` with fresh memory (unless you set `.memory()` explicitly).
- The forked agent is independent; changes do not affect the original.
### API
```rust
agent.fork_builder()
.system_prompt("...")
.tool(my_tool)
.remove_tool("old_tool")
.model(new_model)
.memory(custom_memory)
.hooks(my_hooks)
.max_iterations(10)
.build()
```
### Example: A/B Testing Prompts
```rust
use daimon::prelude::*;
let base = Agent::builder()
.model(my_model)
.tool(search_tool)
.build()?;
let variant_a = base
.fork_builder()
.system_prompt("You are concise. Answer in 1-2 sentences.")
.build();
let variant_b = base
.fork_builder()
.system_prompt("You are thorough. Provide detailed explanations.")
.build();
let (resp_a, resp_b) = tokio::join!(
variant_a.prompt("What is async in Rust?"),
variant_b.prompt("What is async in Rust?"),
);
```
### Simple Fork Variants
| `agent.fork()` | Clone with fresh memory; same config |
| `agent.fork_from_checkpoint(run_id, checkpoint)` | Resume from checkpoint; same config |
| `agent.fork_with_memory(mem)` | Same config, different memory backend |
```rust
// Simple clone: independent memory, same everything else
let copy = agent.fork();
// Resume from saved state
let resumed = agent.fork_from_checkpoint("run-123", &checkpoint).await?;
// Switch to persistent memory
let persistent = agent.fork_with_memory(SqliteMemory::open("./db").await?);
```
---
## Hot-Swap Agent
`HotSwapAgent` wraps an agent behind a `RwLock`, allowing runtime reconfiguration without rebuilding. Concurrent prompts hold a read lock; swap operations hold a write lock. Use for feature flags, gradual rollout, or live experimentation.
### How It Works
- All prompt operations acquire a read lock; they can run concurrently.
- Swap operations acquire a write lock; they block only during the brief update.
- Ongoing prompts complete with their original configuration; new prompts use the updated config.
### API
```rust
let hot = HotSwapAgent::new(agent);
// Swaps (write lock)
hot.swap_model(new_model).await;
hot.swap_system_prompt(Some("...".into())).await;
hot.add_tool(my_tool).await;
hot.remove_tool("name").await;
hot.swap_memory(new_memory).await;
hot.swap_hooks(my_hooks).await;
hot.swap_middleware(stack).await;
hot.add_input_guardrail(guard).await;
hot.add_output_guardrail(guard).await;
hot.clear_input_guardrails().await;
hot.clear_output_guardrails().await;
hot.set_max_iterations(20).await;
hot.set_temperature(Some(0.5)).await;
hot.replace(new_agent).await;
// Reads (no lock conflict with prompts)
let prompt = hot.system_prompt().await;
let count = hot.tool_count().await;
let names = hot.tool_names().await;
```
### Example: Feature Flag Model Swap
```rust
use daimon::prelude::*;
let agent = Agent::builder()
.model(OpenAi::new("gpt-4o"))
.system_prompt("You are helpful.")
.build()?;
let hot = HotSwapAgent::new(agent);
// 90% of traffic uses gpt-4o; 10% uses gpt-4o-mini for cost testing
if rand::random::<f32>() < 0.1 {
hot.swap_model(OpenAi::new("gpt-4o-mini")).await;
}
let response = hot.prompt("Explain recursion").await?;
```
### Example: Gradual Prompt Rollout
```rust
let hot = HotSwapAgent::new(agent);
// Admin endpoint: update system prompt without restart
async fn update_prompt(hot: &HotSwapAgent, new_prompt: String) {
hot.swap_system_prompt(Some(new_prompt)).await;
}
```
---
## Patterns & Best Practices
### Supervisor vs Handoff: When to Use Which
| **Supervisor** | Request-response delegation; coordinator decides and aggregates | User → Supervisor → sub-agent tool call → Supervisor → final response |
| **Handoff** | Conversational flow; control passes between agents mid-dialog | User → Agent A → transfer_to_B → Agent B → transfer_to_C → Agent C → final response |
- **Supervisor**: The coordinator stays in control. It calls sub-agents as tools, gets results, and produces the final answer. Good for: routing, parallel sub-tasks, aggregation.
- **Handoff**: The active agent changes. Each agent "owns" the conversation until it hands off. Good for: triage → specialist → escalation, multi-step support flows, collaborative dialogue.
### Fork vs Hot-Swap: When to Use Which
| **Fork** | Parallel variants, A/B tests, branching configs | Creates new agent; original unchanged |
| **Hot-Swap** | Live updates, feature flags, runtime experimentation | Mutates wrapped agent in place |
- **Fork**: Use when you need multiple independent copies (e.g., run variant A and B in parallel, or create specialized agents from a template). No shared state.
- **Hot-Swap**: Use when one agent must be reconfigured at runtime (e.g., swap model for cost testing, update prompt via admin UI). All callers see the same agent; updates apply to future calls.
### Composing Multi-Agent with Orchestration
You can use agents, supervisors, and handoff networks as nodes in Chains, Graphs, and DAGs.
```rust
// Supervisor as a Graph node
let supervisor = Arc::new(Supervisor::builder()
.model(model)
.agent("researcher", research_agent, "Research")
.agent("writer", writer_agent, "Writing")
.build()?);
let supervisor_node = FnNode::new(move |ctx| {
let sup = Arc::clone(&supervisor);
Box::pin(async move {
let input = ctx.get_str("input").unwrap_or("").to_string();
let response = sup.run(&input).await?;
ctx.set("output", serde_json::json!(response.final_text));
Ok(NodeOutcome::Continue)
})
});
// Handoff network as a Chain step
let handoff = HandoffNetwork::builder()
.entry("triage")
.agent("triage", triage_agent)
.agent("specialist", specialist_agent)
.build()?;
let chain = Chain::builder()
.transform(|mut ctx| {
let h = handoff.clone();
Box::pin(async move {
let resp = h.run(&ctx.text).await?;
ctx.text = resp.final_text;
Ok(ctx)
})
})
.build()?;
```
### Memory Isolation Between Agents
- **AgentTool**: The inner agent uses its own memory. Each call is a fresh prompt from the caller's perspective; the inner agent's memory accumulates across calls within the same coordinator run.
- **Supervisor**: Sub-agents have independent memory. The coordinator has its own memory (conversation with the user + tool results).
- **Handoff**: All agents share the same message list. The network maintains one `messages` vec; only the system prompt and active agent change on handoff. No per-agent memory isolation.
- **Fork**: Each fork has independent memory by default. Use `fork_with_memory` to share or customize.
### Cost Tracking Across Multi-Agent Systems
- **AgentTool**: Cost is incurred by the inner agent. The coordinator's cost tracker does not automatically include sub-agent spend. Attach a `CostModel` to each agent and aggregate manually if needed.
- **Supervisor**: The coordinator's `AgentResponse` includes its own cost. Sub-agent costs are not rolled up. Use hooks or middleware to aggregate.
- **Handoff**: `HandoffResponse.usage` aggregates token usage across all agents. Cost is not computed unless each agent has a `CostModel`; you must sum manually.
- **Best practice**: Use a shared `CostTracker` or a hook that records cost per agent, then aggregate in your application layer.
### Quick Reference
| One agent calls another as a tool | `AgentTool::new(agent, name, description)` |
| Coordinator routes to specialists | `Supervisor::builder().agent(...).build()` |
| Agents pass conversation to each other | `HandoffNetwork::builder().entry(...).agent(...).build()` |
| Create mutated copy for A/B testing | `agent.fork_builder().system_prompt(...).build()` |
| Simple clone with fresh memory | `agent.fork()` |
| Resume from checkpoint | `agent.fork_from_checkpoint(run_id, checkpoint)` |
| Different memory, same config | `agent.fork_with_memory(mem)` |
| Runtime model/prompt/tool swap | `HotSwapAgent::new(agent)` + `swap_*` / `add_*` |