agent-sdk 0.2.0

Rust Agent SDK for building LLM agents
Documentation
# CLAUDE.md - Agent SDK Quick Reference

This file provides essential patterns for working with this Rust codebase.

## Essential Patterns

### Modern Rust Module System (CRITICAL)

**DO NOT use `mod.rs` files!** Use the modern Rust 2018+ module style:

```
src/
├── lib.rs              # mod llm;
├── llm.rs              # pub mod types; pub mod router; + main code
└── llm/
    ├── types.rs
    └── router.rs
```

The `foo.rs` file serves as the module root for `foo/` directory. No `mod.rs` needed!

### Error Handling

```rust
use anyhow::{Result, Context, bail, ensure};

// Standard return type
pub async fn my_function() -> anyhow::Result<Output> {
    // Add context to errors
    let data = fetch_data().await.context("failed to fetch")?;

    // Early exit with ensure
    ensure!(!data.is_empty(), "data cannot be empty");

    // Bail for errors
    if invalid {
        bail!("invalid state");
    }

    Ok(output)
}
```

### Testing Philosophy

1. **Test behavior, not coverage** - focus on user-facing functionality
2. **Never change test expectations to make tests pass** - fix the code
3. **Skip trivial tests** - Rust's type system catches many bugs at compile time

```rust
#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn feature_works() -> anyhow::Result<()> {
        // Setup
        let store = InMemoryStore::new();

        // Execute
        let result = my_function(&store).await?;

        // Assert
        assert_eq!(result.status, ExpectedStatus);

        Ok(())
    }
}
```

### Code Quality Workflow

**Before every commit:**

```bash
cargo check --all-targets    # Type check
cargo fmt                    # Format
cargo clippy -- -D warnings  # Lint (must pass)
cargo test                   # Tests (must pass)
```

### Self-Documenting Code

Functions should be self-documenting - avoid redundant comments:

```rust
// BAD: Comment restates function name
/// Find a user by their ID.
pub async fn find_by_id(...)

// GOOD: No comment needed, name is clear
pub async fn find_by_id(...)

// GOOD: Comment explains non-obvious behavior
/// Returns None if the thread was archived more than 24 hours ago.
pub async fn find_active_thread(...)
```

## Project Structure

```
src/
├── lib.rs              # Public API exports
├── agent_loop.rs       # Core agent orchestration
├── events.rs           # AgentEvent enum
├── types.rs            # Core types (ThreadId, Config, etc.)
├── tools.rs            # Tool trait and registry
├── hooks.rs            # Lifecycle hooks
├── stores.rs           # Persistence traits
├── environment.rs      # File/command abstraction
├── capabilities.rs     # Security model
├── filesystem.rs       # LocalFileSystem, InMemoryFileSystem
├── llm.rs              # LLM module root
├── llm/
│   ├── types.rs        # Message, ChatRequest, etc.
│   └── router.rs       # Model routing
├── primitive_tools.rs  # Primitive tools module root
├── primitive_tools/
│   ├── read.rs
│   ├── write.rs
│   ├── edit.rs
│   ├── bash.rs
│   ├── glob.rs
│   └── grep.rs
├── providers.rs        # Providers module root
└── providers/
    ├── anthropic.rs
    └── anthropic/
        └── data.rs
```

## Clippy Rules

**Never bypass clippy** with `#[allow(...)]` - fix the code instead:

```rust
// BAD: Bypassing clippy
#[allow(clippy::too_many_arguments)]
pub fn create(a: A, b: B, c: C, d: D, e: E, f: F) { }

// GOOD: Use a struct
pub struct CreateParams { pub a: A, pub b: B, /* ... */ }
pub fn create(params: CreateParams) { }
```

## Async Patterns

```rust
// Async function
pub async fn fetch_data(id: &str) -> Result<Data> {
    let response = client.get(url).await?;
    Ok(response.json().await?)
}

// Async trait (with async-trait crate)
#[async_trait]
pub trait DataFetcher {
    async fn fetch(&self, id: &str) -> Result<Data>;
}
```

## Key Conventions

1. **Use `anyhow::Result`** for all fallible functions
2. **Use `time` crate** (not `chrono`) for datetime handling
3. **Use `tracing`** for logging
4. **Prefer inline full paths** for clarity: `llm::Message::user("hi")`
5. **No unsafe code** - `#[forbid(unsafe_code)]` is enforced
6. **Never use `unwrap()` or `expect()`** - Always propagate errors with `?` and add context:

```rust
// BAD: Panics without context
let value = some_result.unwrap();
let value = some_result.expect("something failed");
let lock = mutex.lock().unwrap();

// GOOD: Propagate errors with context
let value = some_result.context("failed to get value")?;

// For RwLock/Mutex: convert poison error to anyhow error
let lock = self.data.read().ok().context("lock poisoned")?;
let lock = self.data.write().ok().context("lock poisoned")?;

// For Option types
let value = some_option.context("value was None")?;
```

This rule applies to both production code AND tests. In tests, return `Result<()>` and use `?`.