Expand description
§Agent SDK
A Rust SDK for building AI agents powered by large language models (LLMs).
This crate provides the infrastructure to build agents that can:
- Converse with users via multiple LLM providers
- Execute tools to interact with external systems
- Persist turn events for downstream consumers and UIs
- Persist conversation history and state
For task-oriented recipes — tools, typed tools, structured output,
streaming, MCP (local + remote HTTP), durable serving, and
human-in-the-loop — see the
cookbook
and the runnable
examples/.
§Quick Start
Ask a question and print the answer — the whole 30-second path:
use agent_sdk::{builder, ThreadId, providers::AnthropicProvider};
let agent = builder::<()>()
.provider(AnthropicProvider::from_env()) // reads ANTHROPIC_API_KEY
.build();
let answer = agent.ask(ThreadId::new(), "What is the capital of France?").await?;
println!("{answer}");ask builds the ToolContext and CancellationToken
internally and returns the assembled assistant text. When you need
application context, a confirmation flow, explicit cancellation, or the raw
AgentRunState, drop down to run. The runnable example
below uses a tiny stub provider so it compiles and runs under
cargo test --doc with no network and no API key:
use std::sync::Arc;
use agent_sdk::{
builder, AgentEvent, AgentInput, CancellationToken, EventStore, InMemoryEventStore,
ThreadId, ToolContext,
};
use agent_sdk::llm::{
ChatOutcome, ChatRequest, ChatResponse, ContentBlock, LlmProvider, StopReason, Usage,
};
use async_trait::async_trait;
// A stub provider that always replies with a fixed line. Keeps the
// quickstart key-free and offline. Swap for `AnthropicProvider` (below)
// for real conversations.
struct StubProvider;
#[async_trait]
impl LlmProvider for StubProvider {
async fn chat(&self, _request: ChatRequest) -> anyhow::Result<ChatOutcome> {
Ok(ChatOutcome::Success(ChatResponse {
id: "stub".to_string(),
content: vec![ContentBlock::Text { text: "Paris.".to_string() }],
model: self.model().to_string(),
stop_reason: Some(StopReason::EndTurn),
usage: Usage {
input_tokens: 0,
output_tokens: 0,
cached_input_tokens: 0,
cache_creation_input_tokens: 0,
},
}))
}
fn model(&self) -> &str { "stub-model" }
fn provider(&self) -> &'static str { "stub" }
}
// 1. Build the agent.
let event_store = Arc::new(InMemoryEventStore::new());
let agent = builder::<()>()
.provider(StubProvider)
.event_store(event_store.clone())
.build();
// 2. Run a conversation.
let thread_id = ThreadId::new();
let final_state = agent.run(
thread_id.clone(),
AgentInput::Text("What is the capital of France?".to_string()),
ToolContext::new(()),
CancellationToken::new(),
);
let _ = final_state.await?;
// 3. Read persisted events.
let mut reply = String::new();
for envelope in event_store.get_events(&thread_id).await? {
match envelope.event {
AgentEvent::Text { text, .. } => reply.push_str(&text),
AgentEvent::Done { .. } => break,
_ => {}
}
}
assert_eq!(reply, "Paris.");§Run with a real key
For a live conversation, depend on the anthropic provider and read your
key from the environment — the only change is the provider line:
use agent_sdk::{builder, InMemoryEventStore, providers::AnthropicProvider};
use std::sync::Arc;
let api_key = std::env::var("ANTHROPIC_API_KEY")?;
let event_store = Arc::new(InMemoryEventStore::new());
let agent = builder::<()>()
.provider(AnthropicProvider::sonnet(api_key))
.event_store(event_store)
.build();§Core Concepts
§Agent Loop
The AgentLoop orchestrates the conversation cycle:
- User sends a message
- Agent sends message to LLM
- LLM responds with text and/or tool calls
- Agent executes tools and feeds results back to LLM
- Repeat until LLM responds with only text
Use builder() to construct an agent:
use agent_sdk::{builder, AgentConfig, providers::AnthropicProvider};
let agent = builder::<()>()
.provider(AnthropicProvider::from_env())
.config(AgentConfig {
max_turns: Some(20),
system_prompt: "You are a helpful assistant.".into(),
..Default::default()
})
.build();§Tools
Tools let the LLM interact with external systems. The lowest-ceremony way
is the SimpleTool trait — a &'static str name, no ToolName type:
use agent_sdk::{SimpleTool, ToolContext, ToolResult};
use serde_json::{json, Value};
use std::future::Future;
struct WeatherTool;
impl SimpleTool<()> for WeatherTool {
fn name(&self) -> &'static str { "get_weather" }
fn description(&self) -> &'static str { "Get current weather for a city" }
fn input_schema(&self) -> Value {
json!({ "type": "object", "properties": { "city": { "type": "string" } } })
}
fn execute(
&self,
_ctx: &ToolContext<()>,
input: Value,
) -> impl Future<Output = anyhow::Result<ToolResult>> + Send {
async move {
let city = input["city"].as_str().unwrap_or("Unknown");
Ok(ToolResult::success(format!("Weather in {city}: Sunny, 72°F")))
}
}
}Register it with ToolRegistry::register_simple:
use agent_sdk::{builder, ToolRegistry, providers::AnthropicProvider};
let mut tools = ToolRegistry::new();
tools.register_simple(WeatherTool);
let agent = builder::<()>()
.provider(AnthropicProvider::from_env())
.tools(tools)
.build();For full control over the serialized tool name, implement the Tool
trait directly with a strongly-typed ToolName (e.g. a #[derive(Serialize, Deserialize)] enum or the built-in DynamicToolName).
§Tool Tiers
Tools are classified by permission level via ToolTier:
| Tier | Description | Example |
|---|---|---|
ToolTier::Observe | Read-only, always allowed | Get balance, read file |
ToolTier::Confirm | Requires user confirmation | Send email, transfer funds |
§Lifecycle Hooks
Implement AgentHooks to intercept and control agent behavior:
use agent_sdk::{AgentHooks, ToolDecision, ToolInvocation, ToolResult, ToolTier};
use async_trait::async_trait;
struct MyHooks;
#[async_trait]
impl AgentHooks for MyHooks {
async fn pre_tool_use(&self, invocation: &ToolInvocation) -> ToolDecision {
println!("Tool called: {}", invocation.tool_name);
match invocation.tier {
ToolTier::Observe => ToolDecision::Allow,
ToolTier::Confirm => ToolDecision::RequiresConfirmation(
"Please confirm this action".into()
),
}
}
async fn post_tool_use(&self, tool_name: &str, result: &ToolResult) {
println!("{tool_name} completed: {}", result.success);
}
}Built-in hook implementations:
DefaultHooks- Tier-based permissions (default)AllowAllHooks- Allow all tools without confirmation (for testing)LoggingHooks- Debug logging for all events
§Events
The agent emits AgentEvents during execution for real-time updates:
| Event | Description |
|---|---|
AgentEvent::Start | Agent begins processing |
AgentEvent::Text | Text response from LLM |
AgentEvent::TextDelta | Streaming text chunk |
AgentEvent::ToolCallStart | Tool execution starting |
AgentEvent::ToolCallEnd | Tool execution completed |
AgentEvent::TurnComplete | One LLM round-trip finished |
AgentEvent::Done | Agent completed successfully |
AgentEvent::Error | An error occurred |
§Task Tracking
Use TodoWriteTool and TodoReadTool to track task progress:
use agent_sdk::todo::{TodoState, TodoWriteTool, TodoReadTool};
use std::sync::Arc;
use tokio::sync::RwLock;
let state = Arc::new(RwLock::new(TodoState::new()));
let write_tool = TodoWriteTool::new(Arc::clone(&state));
let read_tool = TodoReadTool::new(state);Task states: Pending (○), InProgress (⚡), Completed (✓)
§Custom Context
Pass application-specific data to tools via the generic type parameter:
use agent_sdk::{DynamicToolName, Tool, ToolContext, ToolResult, ToolTier};
use serde_json::Value;
use std::future::Future;
// Your application context
struct AppContext {
user_id: String,
// database: Database,
}
struct UserInfoTool;
impl Tool<AppContext> for UserInfoTool {
type Name = DynamicToolName;
fn name(&self) -> DynamicToolName { DynamicToolName::new("get_user_info") }
fn display_name(&self) -> &'static str { "User Info" }
fn description(&self) -> &'static str { "Get info about current user" }
fn input_schema(&self) -> Value { serde_json::json!({"type": "object"}) }
fn execute(
&self,
ctx: &ToolContext<AppContext>,
_input: Value,
) -> impl Future<Output = anyhow::Result<ToolResult>> + Send {
let user_id = ctx.app.user_id.clone();
async move {
Ok(ToolResult::success(format!("User: {user_id}")))
}
}
}§Workspace Architecture
The SDK is split into focused crates. This agent-sdk crate is the
public façade — it re-exports everything you need so downstream
users only depend on agent-sdk.
| Crate | Purpose |
|---|---|
agent_sdk_foundation | Data-only contract types (IDs, events, LLM messages) |
agent_sdk_tools | Tool traits, registry, hooks, stores, environment |
agent_sdk_providers | LLM provider trait and first-party implementations |
agent-server | Server-side orchestration (internal, not published) |
agent-sdk | This crate — façade with agent loop, examples, and convenience re-exports |
§Modules
| Module | Description |
|---|---|
providers | LLM provider implementations |
primitive_tools | Built-in file operation tools (Read, Write, Edit, Glob, Grep, Bash) |
llm | LLM abstraction layer |
subagent | Nested agent execution with SubagentFactory |
mcp | Model Context Protocol client (stdio + streamable-HTTP/SSE, resources/prompts) |
todo | Task tracking tools (TodoWriteTool, TodoReadTool) |
user_interaction | User question/confirmation tools (AskUserQuestionTool) |
web | Web search and fetch tools |
skills | Custom skill/command loading |
reminders | System reminder infrastructure for agent guidance |
§System Reminders
The SDK includes a reminder system that provides contextual guidance to the AI agent
using the <system-reminder> XML tag pattern. Claude is trained to recognize these
tags and follow the instructions without mentioning them to users.
use agent_sdk::reminders::{wrap_reminder, ReminderConfig, ReminderTracker};
// Wrap guidance in system-reminder tags
let reminder = wrap_reminder("Verify the output before proceeding.");
// Configure reminder behavior
let config = ReminderConfig::new()
.with_todo_reminder_turns(5)
.with_repeated_action_threshold(3);§Feature Flags
Providers and the heavier tool families are gated behind cargo features so
a minimal consumer only compiles (and only pulls the transitive
dependencies of) what it uses. The common case — an Anthropic agent — works
out of the box because anthropic is the only default feature.
| Feature | Default | Pulls | Description |
|---|---|---|---|
anthropic | Yes | — | Anthropic Messages API provider |
openai | No | — | OpenAI Chat Completions + Responses providers |
openai-codex | No | tokio-tungstenite | OpenAI Codex / ChatGPT WebSocket provider |
gemini | No | — | Google Gemini provider |
vertex | No | — | Google Vertex AI provider (implies anthropic + gemini) |
cloudflare | No | — | Cloudflare AI Gateway proxy (implies anthropic + openai + gemini) |
web | No | html2text | web search + fetch tools |
mcp | No | — | mcp Model Context Protocol client (stdio + streamable-HTTP/SSE) |
skills | No | serde_yaml_ng | skills markdown skill loader |
otel | No | opentelemetry | OpenTelemetry tracing instrumentation |
A minimal Anthropic-only build pulls no WebSocket, HTML, or YAML crates:
agent-sdk = { version = "0.8", default-features = false, features = ["anthropic"] }When otel is enabled, the SDK emits OpenTelemetry spans for agent
invocations, turns, LLM requests, tool execution, subagent runs, MCP
operations, and context compaction. See the observability module for details.
Re-exports§
pub use reminders::ReminderConfig;pub use reminders::ReminderTracker;pub use reminders::ReminderTrigger;pub use reminders::ToolReminder;pub use reminders::append_reminder;pub use reminders::wrap_reminder;pub use subagent::METADATA_MAX_SUBAGENT_DEPTH;pub use subagent::METADATA_SUBAGENT_DEPTH;pub use subagent::SubagentConfig;pub use subagent::SubagentFactory;pub use subagent::SubagentTool;pub use todo::TodoItem;pub use todo::TodoReadTool;pub use todo::TodoState;pub use todo::TodoStatus;pub use todo::TodoWriteTool;pub use user_interaction::AskUserQuestionTool;pub use user_interaction::ConfirmationRequest;pub use user_interaction::ConfirmationResponse;pub use user_interaction::QuestionOption;pub use user_interaction::QuestionRequest;pub use user_interaction::QuestionResponse;pub use observability::CaptureDecision;otelpub use observability::CaptureKind;otelpub use observability::CaptureResult;otelpub use observability::ObservabilityStore;otelpub use observability::PayloadBundle;otel
Modules§
- advanced
- Server- and host-facing contract types.
- builtin_
tools - Bundled registration for the full set of built-in SDK tools.
- context
- Context compaction for long-running conversations.
- llm
- LLM provider trait, streaming types, and message data types.
- mcp
mcp - Model Context Protocol (MCP) client support.
- model_
capabilities - Model capability metadata and pricing.
- observability
otel - OpenTelemetry observability module.
- prelude
- The common imports for building an in-process agent.
- primitive_
tools - Primitive tools that work with the Environment abstraction.
- providers
- LLM Provider implementations.
- reminders
- System reminder infrastructure for agent guidance.
- skills
skills - Skills system for loading agent behavior from markdown files.
- subagent
- Subagent support for spawning child agents.
- todo
- TODO task tracking for agents.
- user_
interaction - User interaction types and tools.
- web
web - Web tools for search and fetching.
Macros§
- tool
- Define a tool inline, expanding to a fresh zero-sized struct plus a
SimpleToolimpl — the lowest-ceremony way to add a one-off tool in an example, test, or script.
Structs§
- Agent
Capabilities - Capabilities that control what the agent can do.
- Agent
Config - Configuration for the agent loop
- Agent
Error - Error from the agent loop.
- Agent
Event Envelope - Envelope wrapping every
AgentEventwith idempotency metadata. - Agent
Handle - Handle to a persistent agent thread.
- Agent
Loop - The main agent loop that orchestrates LLM calls and tool execution.
- Agent
Loop Builder - Builder for constructing an
AgentLoop. - Agent
Loop Compaction Config - Configuration bundle for constructing an
AgentLoopwith compaction. - Agent
State - Snapshot of agent state for checkpointing
- Allow
AllHooks - Hooks that allow all tools without confirmation
- Cancellation
Token - A token which can be used to signal a cancellation request to one or more tasks.
- Content
Source - Source data for image and document content blocks.
- Default
Context Factory - Default factory that delegates to
crate::tools::ToolContext::from_seed. - Default
Hooks - Default hooks implementation that uses tier-based decisions
- Dynamic
Tool Name - Dynamic tool name for runtime-created tools (MCP bridges, subagents).
- Exec
Result - Result from command execution
- External
Tool Result - A tool result provided by the external runtime for a specific tool call.
- File
Entry - Entry in a directory listing
- Grep
Match - Match result from grep operation
- InMemory
Event Store - In-memory implementation of
EventStore. - InMemory
Execution Store - In-memory implementation of
ToolExecutionStore. - InMemory
File System - In-memory filesystem for testing
- InMemory
Store - In-memory implementation of
MessageStoreandStateStore. Useful for testing and simple use cases. - Local
File System - Local filesystem implementation using
std::fs - Logging
Hooks - Hooks that log all events (useful for debugging)
- Model
Capabilities - Noop
Audit Sink - Default sink that discards every record.
- Null
Environment - A null environment that rejects all operations. Useful as a default when no environment is configured.
- Pending
Tool Call Info - Information about a pending tool call that was extracted from the LLM response.
- Price
Point - Pricing
- Redaction
Policy - Configurable redaction rules for tool audit records and observability payloads.
- Response
Format - Requests that the model constrain its final answer to a JSON Schema.
- Retry
Config - Configuration for retry behavior on transient errors.
- RunOptions
- Per-run trace metadata applied to every span emitted by the agent loop.
- Sequence
Counter - Monotonically increasing per-run counter for event ordering.
- Simple
Tool Adapter - Adapter that turns any
SimpleToolinto a fullToolwithName = DynamicToolName. - Stored
Turn Events - Stored event data for a single turn.
- Structured
Config - Bounds for the structured-output re-prompt loop.
- Structured
Output - A successfully validated structured output and the response that produced it.
- Thinking
Config - Configuration for extended thinking.
- Thread
Id - Unique identifier for a conversation thread
- Token
Usage - Token usage statistics
- Tool
Context - Context passed to tool execution
- Tool
Context Seed - Durable inputs needed to reconstruct a
crate::tools::ToolContext. - Tool
Execution - Record of a tool execution for idempotency.
- Tool
Invocation - Structured input passed to the
pre_tool_usehook for policy evaluation. - Tool
Registry - Registry of available tools.
- Tool
Result - Result of a tool execution
- Turn
Options - Options that control how a single
run_turninvocation behaves. - Typed
Tool Adapter - Adapter that turns any
TypedToolinto a fullTool.
Enums§
- Agent
Event - Events emitted by the agent loop during execution. These are streamed to the client for real-time UI updates.
- Agent
Input - Input to start or resume an agent run.
- Agent
RunState - Outcome of running the agent loop.
- Content
Block - Effort
- Effort level for adaptive thinking via
output_config. - Execution
Status - Status of a tool execution for idempotency tracking.
- Primitive
Tool Name - Tool names for SDK’s built-in primitive tools.
- Redaction
Level - How aggressively to redact a given field category.
- Source
Status - Structured
Output Error - Errors from the structured-output runner.
- Structured
Output Support - How a provider satisfies a
ResponseFormatstructured-output request. - Thinking
Mode - The mode of extended thinking.
- Tool
Decision - Decision returned by pre-tool hooks
- Tool
Outcome - Result of tool execution - may indicate async operation in progress.
- Tool
Runtime - How tool calls should be handled during a turn.
- Tool
Status - Status update from an async tool operation.
- Tool
Tier - Permission tier for tools
Constants§
- REDACTED_
MARKER - Redaction marker used for wholesale redaction (sensitive key
match or full-string secret prefix). Entity-level masks use
[REDACTED:<category>]— seecrate::privacy.
Traits§
- Agent
Hooks - Lifecycle hooks for the agent loop. Implement this trait to customize agent behavior.
- Async
Tool - A tool that performs long-running async operations.
- Environment
- Environment abstraction for file and command operations.
- Event
Store - Trait for storing and retrieving turn-scoped event streams.
- LlmProvider
- Message
Store - Trait for storing and retrieving conversation messages. Implement this trait to persist messages to your storage backend.
- Progress
Stage - Marker trait for tool progress stages (type-safe, like
ToolName). - Simple
Tool - An ergonomic
Toolwhose name is a plain string. - State
Store - Trait for storing agent state checkpoints. Implement this to enable conversation recovery and resume.
- Tool
- Definition of a tool that can be called by the agent.
- Tool
Audit Sink - Async sink that receives one
ToolAuditRecordper tool-call lifecycle transition. - Tool
Execution Store - Store for tracking tool executions (idempotency).
- Tool
Logic - The
execute-only half of a tool, used as the target of the#[derive(Tool)]/#[derive(TypedTool)]ergonomics macros. - Tool
Name - Marker trait for tool names.
- Typed
Tool - A tool whose model-emitted arguments are validated against a typed,
deserializable
Inputbeforeexecuteruns.
Functions§
- builder
- Create a new builder for constructing an
AgentLoop. - get_
model_ capabilities - invalid_
tool_ input_ result - Synthesise the structured validation-error
ToolResultreturned to the model when its arguments fail to deserialize into aTypedTool::Input. - redact_
error - Apply redaction rules to an error string based on the given policy’s
error level. Same semantics as
redact_string, but gated byRedactionPolicy::error_level. - redact_
for_ observability - Combined helper for the SDK observability boundary.
- redact_
string - Apply redaction rules to a string value based on the given policy’s output level.
- redact_
value - Apply redaction rules to a JSON value based on the given policy’s input level.
- run_
structured - Run a bounded, schema-validated structured-output exchange against
provider. - stage_
to_ string - Helper to get string representation of a progress stage via serde.
- supported_
model_ capabilities - tool_
name_ from_ str - Parse a tool name from string via serde.
- tool_
name_ to_ string - Helper to get string representation of a tool name via serde.
- validate_
tool_ input - Deserialize raw model args into a typed
Input, or synthesise the structured validation-error result.
Derive Macros§
- Tool
- Derive an
agent_sdk::SimpleToolimpl from struct-level#[tool(...)]attributes. - Tool
Name - Derive
agent_sdk::ToolName(plus theSerialize/Deserializeit requires) for an enum, removing the marker-impl boilerplate. - Typed
Tool - Derive an
agent_sdk::TypedToolimpl (typedInput+ runtime validation) from struct-level#[tool(...)]attributes.