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