daimon 0.17.0

A Rust-native AI agent framework
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
# 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

| Method | Description |
|-------|-------------|
| `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

| Pattern | Use When | Flow |
|---------|----------|-----|
| **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

| Pattern | Use When | Mutability |
|---------|----------|-------------|
| **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

| Need | Use |
|------|-----|
| 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_*` |