agents-core 0.0.12

Core traits, data models, and prompt primitives for building deep agents.
Documentation
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
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
# Rust Deep Agents SDK

[![Crates.io](https://img.shields.io/crates/v/agents-runtime.svg)](https://crates.io/crates/agents-runtime)
[![Documentation](https://docs.rs/agents-runtime/badge.svg)](https://docs.rs/agents-runtime)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)

High-performance Rust framework for composing reusable "deep" AI agents with custom tools, sub-agents, and prompts. This repository contains the SDK workspace, AWS integration helpers, documentation, and deployment scaffolding.

## Workspace Layout
- `crates/agents-core` – Domain traits, message structures, prompt packs, and state models.
- `crates/agents-runtime` – Tokio-powered runtime glue between planners, tools, and state stores.
- `crates/agents-toolkit` – Built-in tools (mock filesystem, todo management) and utilities.
- `crates/agents-aws` – AWS adapters (Secrets Manager, DynamoDB, CloudWatch) behind feature flags.
- `examples/` – Reference agents; `getting-started` provides the echo smoke test.
  - `agents-example-cli` provides a local CLI harness using OpenAI.
- `deploy/` – Terraform modules and IaC assets for AWS environments.
- `docs/` – Roadmap, ADRs, playbooks, and reference material.

## Installation

Add the unified SDK to your `Cargo.toml`:

```toml
# Simple installation (includes toolkit by default)
[dependencies]
agents-sdk = "0.0.1"

# Or choose specific features:
# agents-sdk = { version = "0.0.1", default-features = false }  # Core only
# agents-sdk = { version = "0.0.1", features = ["aws"] }       # With AWS
# agents-sdk = { version = "0.0.1", features = ["redis"] }     # With Redis persistence
# agents-sdk = { version = "0.0.1", features = ["postgres"] }  # With PostgreSQL persistence
# agents-sdk = { version = "0.0.1", features = ["dynamodb"] }  # With DynamoDB persistence
# agents-sdk = { version = "0.0.1", features = ["full"] }      # Everything
```

### Individual Crates (Advanced)

If you prefer granular control, you can also use individual crates:

```toml
[dependencies]
agents-core = "0.0.1"      # Core traits and types
agents-runtime = "0.0.1"   # Agent runtime and builders
agents-toolkit = "0.0.1"   # Built-in tools (optional)
agents-aws = "0.0.1"       # AWS integrations (optional)
```

## Quick Start

### Basic Agent with Tools

```rust
use agents_sdk::{ConfigurableAgentBuilder, OpenAiConfig};
use agents_macros::tool;
use std::sync::Arc;

// Define a tool using the #[tool] macro - it's that simple!
#[tool("Adds two numbers together")]
fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Configure OpenAI
    let config = OpenAiConfig::new(
        std::env::var("OPENAI_API_KEY")?,
        "gpt-4o-mini"
    );

    // Build an agent with tools
    let agent = ConfigurableAgentBuilder::new("You are a helpful math assistant.")
        .with_openai_chat(config)?
        .with_tool(AddTool::as_tool())  // Tool name is auto-generated
        .build()?;

    // Use the agent
    use agents_sdk::state::AgentStateSnapshot;
    
    let response = agent.handle_message(
        "What is 5 + 3?",
        Arc::new(AgentStateSnapshot::default())
    ).await?;
    
    println!("{}", response.content.as_text().unwrap_or("No response"));

    Ok(())
}
```

### Defining Tools

The `#[tool]` macro automatically generates the schema and wrapper code:

```rust
use agents_macros::tool;

// Simple tool
#[tool("Multiplies two numbers")]
fn multiply(a: f64, b: f64) -> f64 {
    a * b
}

// Async tool
#[tool("Fetches user data from API")]
async fn get_user(user_id: String) -> String {
    // Make API call...
    format!("User {}", user_id)
}

// Tool with optional parameters
#[tool("Searches with optional filters")]
fn search(query: String, max_results: Option<u32>) -> Vec<String> {
    let limit = max_results.unwrap_or(10);
    // Perform search...
    vec![]
}

// Use the tools:
let tools = vec![
    MultiplyTool::as_tool(),
    GetUserTool::as_tool(),
    SearchTool::as_tool(),
];
```

### Using Persistence Backends

Choose the persistence layer that fits your infrastructure:

```rust
use agents_sdk::{ConfigurableAgentBuilder, InMemoryCheckpointer};
use std::sync::Arc;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // InMemory (default, no external dependencies)
    let checkpointer = Arc::new(InMemoryCheckpointer::new());
    
    // Redis (requires redis feature)
    #[cfg(feature = "redis")]
    let checkpointer = Arc::new(
        agents_sdk::RedisCheckpointer::new("redis://127.0.0.1:6379").await?
    );
    
    // PostgreSQL (requires postgres feature)
    #[cfg(feature = "postgres")]
    let checkpointer = Arc::new(
        agents_sdk::PostgresCheckpointer::new("postgresql://user:pass@localhost/agents").await?
    );
    
    // DynamoDB (requires dynamodb feature)
    #[cfg(feature = "dynamodb")]
    let checkpointer = Arc::new(
        agents_sdk::DynamoDbCheckpointer::new("agent-checkpoints").await?
    );

    let agent = ConfigurableAgentBuilder::new("You are a helpful assistant")
        .with_checkpointer(checkpointer)
        .build()?;

    // Save and load state across sessions
    let thread_id = "user-123";
    agent.save_state(thread_id).await?;
    agent.load_state(thread_id).await?;
    
    Ok(())
}
```

See [`examples/checkpointer-demo`](examples/checkpointer-demo) for a complete working example.

### Human-in-the-Loop (HITL) Tool Approval

The HITL middleware allows you to require human approval before executing specific tools. This is essential for:
- **Critical Operations**: Database modifications, file deletions, API calls with side effects
- **Security Review**: Operations that access sensitive data or external systems
- **Cost Control**: Expensive API calls or resource-intensive operations
- **Compliance**: Operations requiring audit trails or manual oversight

#### Quick Start - HITL in 3 Steps

**Step 1: Configure HITL Policies**

```rust
use agents_sdk::{ConfigurableAgentBuilder, HitlPolicy, persistence::InMemoryCheckpointer};
use std::sync::Arc;

// Step 1: Define HITL policies
let checkpointer = Arc::new(InMemoryCheckpointer::new());

let mut agent_builder = ConfigurableAgentBuilder::new(
    "You are a helpful assistant. When users request operations, call the appropriate tools immediately."
)
.with_model(get_default_model()?)
.with_tools(vec![/* your tools */]);

// Add HITL policy for each critical tool
agent_builder = agent_builder.with_tool_interrupt(
    "delete_file",
    HitlPolicy {
        allow_auto: false,  // Requires approval
        note: Some("File deletion requires security review".to_string()),
    }
);

let agent = agent_builder
    .with_checkpointer(checkpointer)  // Required for HITL!
    .build()?;
```

**Step 2: Handle Interrupts**

```rust
use agents_sdk::{hitl::HitlAction, state::AgentStateSnapshot};

// Agent will pause when it tries to call a restricted tool
match agent.handle_message("Delete the old_data.txt file", Arc::new(AgentStateSnapshot::default())).await {
    Ok(response) => {
        // Check if execution was paused
        if let Some(text) = response.content.as_text() {
            if text.contains("paused") || text.contains("approval") {
                // HITL was triggered!
                if let Some(interrupt) = agent.current_interrupt() {
                    // Show interrupt details to human
                    println!("Tool: {}", interrupt.tool_name);
                    println!("Args: {}", interrupt.tool_args);
                }
            }
        }
    }
    Err(e) => println!("Error: {}", e),
}
```

**Step 3: Resume with Approval**

```rust
// After human reviews and approves
agent.resume_with_approval(HitlAction::Accept).await?;

// Or modify the arguments
agent.resume_with_approval(HitlAction::Edit {
    tool_name: "delete_file".to_string(),
    tool_args: json!({"path": "/safe/path/file.txt"}),
}).await?;

// Or reject
agent.resume_with_approval(HitlAction::Reject {
    reason: Some("Operation not authorized".to_string()),
}).await?;
```

**Important**: HITL requires a checkpointer to persist interrupt state. If no checkpointer is configured, HITL will be automatically disabled with a warning.

#### HITL Policy Structure

The `HitlPolicy` struct controls tool execution behavior:

```rust
pub struct HitlPolicy {
    /// If true, tool executes automatically without approval
    /// If false, execution pauses and waits for human response
    pub allow_auto: bool,
    
    /// Optional note explaining why approval is needed
    /// Shown to humans when reviewing the interrupt
    pub note: Option<String>,
}
```

#### Handling Interrupts

When a tool requires approval, the agent execution pauses and creates an interrupt:

```rust
use agents_sdk::{AgentMessage, MessageContent, MessageRole};
use std::sync::Arc;

// Agent encounters a tool requiring approval
let result = agent.handle_message(
    "Delete the old_data.txt file",
    Arc::new(AgentStateSnapshot::default())
).await;

// Execution pauses with an interrupt error
match result {
    Err(e) if e.to_string().contains("HITL interrupt") => {
        println!("Tool execution requires approval!");
        
        // Check the current interrupt
        if let Some(interrupt) = agent.current_interrupt().await? {
            match interrupt {
                AgentInterrupt::HumanInLoop(hitl) => {
                    println!("Tool: {}", hitl.tool_name);
                    println!("Args: {}", hitl.tool_args);
                    println!("Note: {:?}", hitl.policy_note);
                    println!("Call ID: {}", hitl.call_id);
                }
            }
        }
    }
    _ => {}
}
```

#### Responding to Interrupts

Use `HitlAction` to respond to interrupts:

```rust
use agents_sdk::HitlAction;

// 1. Accept - Execute with original arguments
agent.resume_with_approval(HitlAction::Accept).await?;

// 2. Edit - Execute with modified arguments
agent.resume_with_approval(HitlAction::Edit {
    tool_name: "delete_file".to_string(),
    tool_args: json!({"path": "/safe/path/file.txt"}),  // Modified path
}).await?;

// 3. Reject - Cancel execution with optional reason
agent.resume_with_approval(HitlAction::Reject {
    reason: Some("Operation not authorized".to_string()),
}).await?;

// 4. Respond - Provide custom message instead of executing
agent.resume_with_approval(HitlAction::Respond {
    message: AgentMessage {
        role: MessageRole::Agent,
        content: MessageContent::Text(
            "I cannot delete that file. Please use the archive tool instead.".to_string()
        ),
        metadata: None,
    },
}).await?;
```

#### Complete HITL Example

```rust
use agents_sdk::{
    ConfigurableAgentBuilder, HitlPolicy, HitlAction, AgentInterrupt,
    InMemoryCheckpointer, AgentMessage, MessageContent, MessageRole,
};
use std::collections::HashMap;
use std::sync::Arc;
use serde_json::json;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Configure HITL policies
    let mut policies = HashMap::new();
    policies.insert(
        "execute_command".to_string(),
        HitlPolicy {
            allow_auto: false,
            note: Some("Shell commands require security review".to_string()),
        }
    );

    // Build agent with HITL and checkpointer
    let checkpointer = Arc::new(InMemoryCheckpointer::new());
    let agent = ConfigurableAgentBuilder::new(
        "You are a system administrator assistant."
    )
        .with_tool_interrupts(policies)
        .with_checkpointer(checkpointer)
        .build()?;

    // User request triggers a tool requiring approval
    let result = agent.handle_message(
        "Run 'rm -rf /tmp/cache' to clear the cache",
        Arc::new(AgentStateSnapshot::default())
    ).await;

    // Handle the interrupt
    if result.is_err() {
        if let Some(interrupt) = agent.current_interrupt().await? {
            match interrupt {
                AgentInterrupt::HumanInLoop(hitl) => {
                    println!("⚠️  Approval Required");
                    println!("Tool: {}", hitl.tool_name);
                    println!("Command: {}", hitl.tool_args);
                    
                    // Human reviews and decides
                    let user_decision = get_user_approval(); // Your UI logic
                    
                    match user_decision {
                        "approve" => {
                            agent.resume_with_approval(HitlAction::Accept).await?;
                            println!("✅ Command executed");
                        }
                        "modify" => {
                            // Safer alternative
                            agent.resume_with_approval(HitlAction::Edit {
                                tool_name: "execute_command".to_string(),
                                tool_args: json!({"command": "rm -rf /tmp/cache/*.tmp"}),
                            }).await?;
                            println!("✅ Modified command executed");
                        }
                        "reject" => {
                            agent.resume_with_approval(HitlAction::Reject {
                                reason: Some("Too dangerous".to_string()),
                            }).await?;
                            println!("❌ Command rejected");
                        }
                        _ => {}
                    }
                }
            }
        }
    }

    Ok(())
}

fn get_user_approval() -> &'static str {
    // Your approval UI logic here
    "approve"
}
```

**See Complete Example**: [`examples/hitl-financial-advisor`](examples/hitl-financial-advisor) - Full working demo with real OpenAI integration, showing transfer approvals, sub-agents, and all HITL actions.

#### HITL Best Practices

1. **Always use a checkpointer**: HITL requires state persistence to work correctly
2. **Provide clear policy notes**: Help humans understand why approval is needed
3. **Handle all action types**: Support Accept, Edit, Reject, and Respond in your UI
4. **Log interrupt decisions**: Maintain audit trails for compliance
5. **Test interrupt scenarios**: Verify your approval workflow handles edge cases
6. **Consider timeout policies**: Decide how long to wait for human response
7. **Use appropriate granularity**: Not every tool needs approval - focus on critical operations

### Development Setup (From Source)

```bash
git clone https://github.com/yafatek/rust-deep-agents-sdk.git
cd rust-deep-agents-sdk

# Format, lint, and test
cargo fmt
cargo clippy --all-targets --all-features
cargo test --all

# Run examples
cargo run --example simple-agent
cargo run --example deep-research-agent
cargo run --example checkpointer-demo
```

## Features

### ✅ Core Features (Python Parity Achieved)

**Agent Builder API**
- `ConfigurableAgentBuilder` with fluent interface matching Python's API
- `.with_model()` method supporting OpenAI, Anthropic, and Gemini models
- `.get_default_model()` function returning pre-configured Claude Sonnet 4
- Async and sync agent creation: `create_deep_agent()` and `create_async_deep_agent()`

**Middleware Stack**
- **Planning Middleware**: Todo list management with comprehensive tool descriptions
- **Filesystem Middleware**: Mock filesystem with `ls`, `read_file`, `write_file`, `edit_file` tools
- **SubAgent Middleware**: Task delegation to specialized sub-agents
- **HITL (Human-in-the-Loop)**: Tool execution interrupts with approval policies
  - Configurable per-tool approval requirements
  - Support for Accept, Edit, Reject, and Respond actions
  - Automatic state persistence with checkpointer integration
  - Policy notes for human reviewers
- **Summarization Middleware**: Context window management
- **AnthropicPromptCaching**: Automatic prompt caching for efficiency

**State Management**
- **State Reducers**: Smart merging functions matching Python's `file_reducer` behavior
- **Persistence**: `Checkpointer` trait with multiple backend implementations
- **Thread Management**: Save/load/delete agent conversation threads

**Persistence Backends**
- **InMemory**: Built-in, zero-config persistence (development)
- **Redis**: High-performance in-memory data store with optional durability
- **PostgreSQL**: ACID-compliant relational database with full SQL support
- **DynamoDB**: AWS-managed NoSQL database with auto-scaling

**Provider Support**
- **Anthropic**: Claude models with prompt caching support
- **OpenAI**: GPT models integration  
- **Gemini**: Google's Gemini Chat models

**Built-in Tools**
- **Todo Management**: `write_todos` with detailed usage examples
- **File Operations**: Full CRUD operations on mock filesystem
- **Task Delegation**: `task` tool for spawning ephemeral sub-agents

### 🚧 Future Features (Planned)

#### Custom SubAgent Support
Enable users to define completely custom execution graphs beyond simple prompt/tool configurations:

```rust
// Future API design
let custom_subagent = CustomSubAgent {
    name: "data-processor".to_string(),
    description: "Processes complex data with custom logic".to_string(),
    graph: Box::new(MyCustomGraph::new()), // Custom execution graph
};

let agent = ConfigurableAgentBuilder::new("main instructions")
    .with_custom_subagent(custom_subagent)
    .build()?;
```

**Benefits:**
- Full control over sub-agent execution flow
- Custom state management within sub-agents  
- Complex branching and conditional logic
- Integration with external systems and APIs

#### Dict-based Model Configuration
Allow models to be configured via dictionary/struct configs in addition to instances:

```rust
// Future API design
let agent = ConfigurableAgentBuilder::new("main instructions")
    .with_model_config(ModelConfig {
        provider: "anthropic".to_string(),
        model: "claude-sonnet-4".to_string(),
        max_tokens: 64000,
        temperature: 0.1,
        // ... other provider-specific options
    })
    .build()?;
```

**Benefits:**
- Simplified configuration management
- Easy serialization/deserialization of agent configs
- Runtime model switching without code changes
- Better integration with configuration management systems

#### Advanced State Features
- **Distributed State Stores**: Redis, DynamoDB backends for multi-agent systems
- **State Migrations**: Schema evolution support for long-running agents
- **State Encryption**: Automatic encryption for sensitive data
- **Custom Reducers**: User-defined state merging logic beyond built-in reducers

#### Enhanced Tool System  
- **Dynamic Tool Registration**: Runtime tool addition/removal
- **Tool Composition**: Combining multiple tools into workflows
- **Tool Validation**: Schema-based input/output validation
- **Tool Metrics**: Performance and usage analytics

## Support the Project

If you find this project helpful, consider supporting its development:

[![PayPal](https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white)](https://paypal.me/yafacs)

Your support helps maintain and improve this open-source project. Thank you! ❤️

## Next Steps
Follow the [roadmap](docs/ROADMAP.md) to implement planners, runtime orchestration, AWS integrations, and customer-ready templates.