agent_sdk/lib.rs
1//! # Agent SDK
2//!
3//! A Rust SDK for building AI agents powered by large language models (LLMs).
4//!
5//! This crate provides the infrastructure to build agents that can:
6//! - Converse with users via multiple LLM providers
7//! - Execute tools to interact with external systems
8//! - Stream events in real-time for responsive UIs
9//! - Persist conversation history and state
10//!
11//! ## Quick Start
12//!
13//! ```no_run
14//! use agent_sdk::{
15//! builder, AgentEvent, AgentInput, CancellationToken, ThreadId, ToolContext,
16//! providers::AnthropicProvider,
17//! };
18//!
19//! # async fn example() -> anyhow::Result<()> {
20//! // 1. Create an LLM provider
21//! let api_key = std::env::var("ANTHROPIC_API_KEY")?;
22//! let provider = AnthropicProvider::sonnet(api_key);
23//!
24//! // 2. Build the agent
25//! let agent = builder::<()>()
26//! .provider(provider)
27//! .build();
28//!
29//! // 3. Run a conversation
30//! let thread_id = ThreadId::new();
31//! let ctx = ToolContext::new(());
32//! let cancel = CancellationToken::new();
33//! let (mut events, _final_state) = agent.run(
34//! thread_id,
35//! AgentInput::Text("Hello!".to_string()),
36//! ctx,
37//! cancel,
38//! );
39//!
40//! // 4. Process streaming events
41//! while let Some(envelope) = events.recv().await {
42//! match envelope.event {
43//! AgentEvent::Text { message_id: _, text } => print!("{text}"),
44//! AgentEvent::Done { .. } => break,
45//! _ => {}
46//! }
47//! }
48//! # Ok(())
49//! # }
50//! ```
51//!
52//! ## Core Concepts
53//!
54//! ### Agent Loop
55//!
56//! The [`AgentLoop`] orchestrates the conversation cycle:
57//!
58//! 1. User sends a message
59//! 2. Agent sends message to LLM
60//! 3. LLM responds with text and/or tool calls
61//! 4. Agent executes tools and feeds results back to LLM
62//! 5. Repeat until LLM responds with only text
63//!
64//! Use [`builder()`] to construct an agent:
65//!
66//! ```no_run
67//! use agent_sdk::{builder, AgentConfig, providers::AnthropicProvider};
68//!
69//! # fn example() {
70//! # let api_key = String::new();
71//! let agent = builder::<()>()
72//! .provider(AnthropicProvider::sonnet(api_key))
73//! .config(AgentConfig {
74//! max_turns: Some(20),
75//! system_prompt: "You are a helpful assistant.".into(),
76//! ..Default::default()
77//! })
78//! .build();
79//! # }
80//! ```
81//!
82//! ### Tools
83//!
84//! Tools let the LLM interact with external systems. Implement the [`Tool`] trait:
85//!
86//! ```
87//! use agent_sdk::{DynamicToolName, Tool, ToolContext, ToolResult, ToolTier};
88//! use serde_json::{json, Value};
89//! use std::future::Future;
90//!
91//! struct WeatherTool;
92//!
93//! // No #[async_trait] needed - Rust 1.75+ supports native async traits
94//! impl Tool<()> for WeatherTool {
95//! type Name = DynamicToolName;
96//!
97//! fn name(&self) -> DynamicToolName { DynamicToolName::new("get_weather") }
98//!
99//! fn display_name(&self) -> &'static str { "Weather" }
100//!
101//! fn description(&self) -> &'static str {
102//! "Get current weather for a city"
103//! }
104//!
105//! fn input_schema(&self) -> Value {
106//! json!({
107//! "type": "object",
108//! "properties": {
109//! "city": { "type": "string" }
110//! },
111//! "required": ["city"]
112//! })
113//! }
114//!
115//! fn tier(&self) -> ToolTier { ToolTier::Observe }
116//!
117//! fn execute(
118//! &self,
119//! _ctx: &ToolContext<()>,
120//! input: Value,
121//! ) -> impl Future<Output = anyhow::Result<ToolResult>> + Send {
122//! async move {
123//! let city = input["city"].as_str().unwrap_or("Unknown");
124//! Ok(ToolResult::success(format!("Weather in {city}: Sunny, 72°F")))
125//! }
126//! }
127//! }
128//! ```
129//!
130//! Register tools with [`ToolRegistry`]:
131//!
132//! ```no_run
133//! use agent_sdk::{builder, DynamicToolName, ToolRegistry, providers::AnthropicProvider};
134//! # use agent_sdk::{Tool, ToolContext, ToolResult, ToolTier};
135//! # use serde_json::Value;
136//! # use std::future::Future;
137//! # struct WeatherTool;
138//! # impl Tool<()> for WeatherTool {
139//! # type Name = DynamicToolName;
140//! # fn name(&self) -> DynamicToolName { DynamicToolName::new("weather") }
141//! # fn display_name(&self) -> &'static str { "" }
142//! # fn description(&self) -> &'static str { "" }
143//! # fn input_schema(&self) -> Value { Value::Null }
144//! # fn execute(&self, _: &ToolContext<()>, _: Value) -> impl Future<Output = anyhow::Result<ToolResult>> + Send {
145//! # async { Ok(ToolResult::success("")) }
146//! # }
147//! # }
148//!
149//! # fn example() {
150//! # let api_key = String::new();
151//! let mut tools = ToolRegistry::new();
152//! tools.register(WeatherTool);
153//!
154//! let agent = builder::<()>()
155//! .provider(AnthropicProvider::sonnet(api_key))
156//! .tools(tools)
157//! .build();
158//! # }
159//! ```
160//!
161//! ### Tool Tiers
162//!
163//! Tools are classified by permission level via [`ToolTier`]:
164//!
165//! | Tier | Description | Example |
166//! |------|-------------|---------|
167//! | [`ToolTier::Observe`] | Read-only, always allowed | Get balance, read file |
168//! | [`ToolTier::Confirm`] | Requires user confirmation | Send email, transfer funds |
169//!
170//! ### Lifecycle Hooks
171//!
172//! Implement [`AgentHooks`] to intercept and control agent behavior:
173//!
174//! ```
175//! use agent_sdk::{AgentHooks, ToolDecision, ToolResult, ToolTier};
176//! use async_trait::async_trait;
177//! use serde_json::Value;
178//!
179//! struct MyHooks;
180//!
181//! #[async_trait]
182//! impl AgentHooks for MyHooks {
183//! async fn pre_tool_use(
184//! &self,
185//! tool_name: &str,
186//! _input: &Value,
187//! tier: ToolTier,
188//! ) -> ToolDecision {
189//! println!("Tool called: {tool_name}");
190//! match tier {
191//! ToolTier::Observe => ToolDecision::Allow,
192//! ToolTier::Confirm => ToolDecision::RequiresConfirmation(
193//! "Please confirm this action".into()
194//! ),
195//! }
196//! }
197//!
198//! async fn post_tool_use(&self, tool_name: &str, result: &ToolResult) {
199//! println!("{tool_name} completed: {}", result.success);
200//! }
201//! }
202//! ```
203//!
204//! Built-in hook implementations:
205//! - [`DefaultHooks`] - Tier-based permissions (default)
206//! - [`AllowAllHooks`] - Allow all tools without confirmation (for testing)
207//! - [`LoggingHooks`] - Debug logging for all events
208//!
209//! ### Events
210//!
211//! The agent emits [`AgentEvent`]s during execution for real-time updates:
212//!
213//! | Event | Description |
214//! |-------|-------------|
215//! | [`AgentEvent::Start`] | Agent begins processing |
216//! | [`AgentEvent::Text`] | Text response from LLM |
217//! | [`AgentEvent::TextDelta`] | Streaming text chunk |
218//! | [`AgentEvent::ToolCallStart`] | Tool execution starting |
219//! | [`AgentEvent::ToolCallEnd`] | Tool execution completed |
220//! | [`AgentEvent::TurnComplete`] | One LLM round-trip finished |
221//! | [`AgentEvent::Done`] | Agent completed successfully |
222//! | [`AgentEvent::Error`] | An error occurred |
223//!
224//! ### Task Tracking
225//!
226//! Use [`TodoWriteTool`] and [`TodoReadTool`] to track task progress:
227//!
228//! ```no_run
229//! use agent_sdk::todo::{TodoState, TodoWriteTool, TodoReadTool};
230//! use std::sync::Arc;
231//! use tokio::sync::RwLock;
232//!
233//! let state = Arc::new(RwLock::new(TodoState::new()));
234//! let write_tool = TodoWriteTool::new(Arc::clone(&state));
235//! let read_tool = TodoReadTool::new(state);
236//! ```
237//!
238//! Task states: `Pending` (○), `InProgress` (⚡), `Completed` (✓)
239//!
240//! ### Custom Context
241//!
242//! Pass application-specific data to tools via the generic type parameter:
243//!
244//! ```
245//! use agent_sdk::{DynamicToolName, Tool, ToolContext, ToolResult, ToolTier};
246//! use serde_json::Value;
247//! use std::future::Future;
248//!
249//! // Your application context
250//! struct AppContext {
251//! user_id: String,
252//! // database: Database,
253//! }
254//!
255//! struct UserInfoTool;
256//!
257//! impl Tool<AppContext> for UserInfoTool {
258//! type Name = DynamicToolName;
259//!
260//! fn name(&self) -> DynamicToolName { DynamicToolName::new("get_user_info") }
261//! fn display_name(&self) -> &'static str { "User Info" }
262//! fn description(&self) -> &'static str { "Get info about current user" }
263//! fn input_schema(&self) -> Value { serde_json::json!({"type": "object"}) }
264//!
265//! fn execute(
266//! &self,
267//! ctx: &ToolContext<AppContext>,
268//! _input: Value,
269//! ) -> impl Future<Output = anyhow::Result<ToolResult>> + Send {
270//! let user_id = ctx.app.user_id.clone();
271//! async move {
272//! Ok(ToolResult::success(format!("User: {user_id}")))
273//! }
274//! }
275//! }
276//! ```
277//!
278//! ## Modules
279//!
280//! | Module | Description |
281//! |--------|-------------|
282//! | [`providers`] | LLM provider implementations |
283//! | [`primitive_tools`] | Built-in file operation tools (Read, Write, Edit, Glob, Grep, Bash) |
284//! | [`llm`] | LLM abstraction layer |
285//! | [`subagent`] | Nested agent execution with [`SubagentFactory`] |
286//! | [`mcp`] | Model Context Protocol support |
287//! | [`todo`] | Task tracking tools ([`TodoWriteTool`], [`TodoReadTool`]) |
288//! | [`user_interaction`] | User question/confirmation tools ([`AskUserQuestionTool`]) |
289//! | [`web`] | Web search and fetch tools |
290//! | [`skills`] | Custom skill/command loading |
291//! | [`reminders`] | System reminder infrastructure for agent guidance |
292//!
293//! ## System Reminders
294//!
295//! The SDK includes a reminder system that provides contextual guidance to the AI agent
296//! using the `<system-reminder>` XML tag pattern. Claude is trained to recognize these
297//! tags and follow the instructions without mentioning them to users.
298//!
299//! ```
300//! use agent_sdk::reminders::{wrap_reminder, ReminderConfig, ReminderTracker};
301//!
302//! // Wrap guidance in system-reminder tags
303//! let reminder = wrap_reminder("Verify the output before proceeding.");
304//!
305//! // Configure reminder behavior
306//! let config = ReminderConfig::new()
307//! .with_todo_reminder_turns(5)
308//! .with_repeated_action_threshold(3);
309//! ```
310//!
311//! ## Feature Flags
312//!
313//! All features are enabled by default. The crate has no optional features currently.
314
315#![forbid(unsafe_code)]
316
317mod agent_loop;
318mod capabilities;
319pub mod context;
320mod environment;
321mod events;
322mod filesystem;
323mod hooks;
324pub mod llm;
325pub mod mcp;
326pub mod model_capabilities;
327pub mod primitive_tools;
328pub mod providers;
329pub mod reminders;
330pub mod skills;
331mod stores;
332pub mod subagent;
333pub mod todo;
334mod tools;
335mod types;
336pub mod user_interaction;
337pub mod web;
338
339pub use agent_loop::{AgentLoop, AgentLoopBuilder, builder};
340pub use capabilities::AgentCapabilities;
341pub use environment::{Environment, ExecResult, FileEntry, GrepMatch, NullEnvironment};
342pub use events::{AgentEvent, AgentEventEnvelope, SequenceCounter};
343pub use filesystem::{InMemoryFileSystem, LocalFileSystem};
344
345// Re-export CancellationToken for use with `AgentLoop::run_with_cancel`.
346pub use hooks::{AgentHooks, AllowAllHooks, DefaultHooks, LoggingHooks, ToolDecision};
347pub use llm::{ContentBlock, ContentSource, Effort, LlmProvider, ThinkingConfig, ThinkingMode};
348pub use model_capabilities::{
349 ModelCapabilities, PricePoint, Pricing, SourceStatus, get_model_capabilities,
350 supported_model_capabilities,
351};
352pub use stores::{
353 InMemoryExecutionStore, InMemoryStore, MessageStore, StateStore, ToolExecutionStore,
354};
355pub use tokio_util::sync::CancellationToken;
356pub use tools::{
357 AsyncTool, DynamicToolName, ErasedAsyncTool, ErasedListenTool, ErasedTool, ErasedToolStatus,
358 ListenExecuteTool, ListenStopReason, ListenToolUpdate, PrimitiveToolName, ProgressStage, Tool,
359 ToolContext, ToolName, ToolRegistry, ToolStatus, stage_to_string, tool_name_from_str,
360 tool_name_to_string,
361};
362pub use types::{
363 AgentConfig, AgentContinuation, AgentError, AgentInput, AgentRunState, AgentState,
364 ExecutionStatus, ListenExecutionContext, PendingToolCallInfo, RetryConfig, ThreadId,
365 TokenUsage, ToolExecution, ToolOutcome, ToolResult, ToolTier, TurnOutcome,
366};
367
368// Re-export user interaction types for convenience
369pub use user_interaction::{
370 AskUserQuestionTool, ConfirmationRequest, ConfirmationResponse, QuestionOption,
371 QuestionRequest, QuestionResponse,
372};
373
374// Re-export subagent types for convenience
375pub use subagent::{SubagentConfig, SubagentFactory, SubagentTool};
376
377// Re-export todo types for convenience
378pub use todo::{TodoItem, TodoReadTool, TodoState, TodoStatus, TodoWriteTool};
379
380// Re-export reminder types for convenience
381pub use reminders::{
382 ReminderConfig, ReminderTracker, ReminderTrigger, ToolReminder, append_reminder, wrap_reminder,
383};