claude_agent/lib.rs
1//! # claude-agent-rs
2//!
3//! A lightweight AI coding agent in Rust that runs Claude on your Max subscription for $0.
4//!
5//! ## Quick Start
6//!
7//! ```rust,no_run
8//! use claude_agent::{Agent, AgentConfig};
9//!
10//! #[tokio::main]
11//! async fn main() -> anyhow::Result<()> {
12//! let agent = Agent::new(AgentConfig::default()).await?;
13//! let response = agent.chat("what is 2+2?").await?;
14//! println!("{}", response.text());
15//! Ok(())
16//! }
17//! ```
18//!
19//! ## Two Inference Backends
20//!
21//! **CLI mode (default)** — pipes through `claude -p --output-format json`, using
22//! your Claude Max subscription. Zero per-token cost.
23//!
24//! **API mode** — direct POST to `api.anthropic.com/v1/messages`. Set
25//! `ANTHROPIC_API_KEY` and `AGENT_BACKEND=api`.
26//!
27//! ## Features
28//!
29//! - **Zero API cost** — calls `claude -p` using your Max subscription
30//! - **8 built-in tools** — bash, read, write, edit, glob, grep, web_fetch, agent
31//! - **SSE streaming** — real-time token output for both API and CLI backends
32//! - **MCP integration** — full JSON-RPC 2.0 client, connects to all your MCP servers
33//! - **Session persistence** — SQLite-backed save/restore with `--resume`
34//! - **Multi-model routing** — auto-route Opus (complex) vs Haiku (simple) with `--auto-route`
35//! - **Sub-agents** — spawn parallel `claude -p` processes with optional git worktree isolation
36//! - **Hooks & skills** — lifecycle hooks and /command dispatch
37//! - **4.5MB binary** — single static binary with SQLite bundled
38//!
39//! ## Architecture
40//!
41//! The agent is organized into 11 layers:
42//!
43//! | Layer | Name | Purpose |
44//! |-------|------|---------|
45//! | 1 | Crown | Skill registry + /command dispatch |
46//! | 2 | Skull | Lifecycle hooks (pre/post tool use) |
47//! | 3 | Security Cage | Permission-gated tool access |
48//! | 4 | Brain Core | Dual-backend inference (CLI + API) |
49//! | 5 | Frontal Lobe | Context engine + CLAUDE.md injection |
50//! | 6 | Temporal Lobe | Sub-agent spawner with worktree isolation |
51//! | 7 | Auth Gate | Authentication routing |
52//! | 8 | Brainstem | 8 built-in tools |
53//! | 9 | Root System | MCP server registry (JSON-RPC 2.0) |
54//! | 10 | Pedestal | HTTP client (reqwest + rustls) |
55//! | 11 | Workbench | Feature flags + session metrics |
56//!
57//! ## Using as a Library
58//!
59//! ```rust,no_run
60//! use claude_agent::{InferenceEngine, InferenceBackend, Message, Role, MessageContent};
61//!
62//! #[tokio::main]
63//! async fn main() -> anyhow::Result<()> {
64//! let engine = InferenceEngine::new(Some("sk-ant-..."), InferenceBackend::Api);
65//! let messages = vec![Message {
66//! role: Role::User,
67//! content: MessageContent::Text("Hello!".into()),
68//! }];
69//! let request = engine.build_request(&messages, None, &[], None);
70//!
71//! // Tokens stream to the callback as they arrive
72//! let response = engine.chat_stream(&request, &mut |text| {
73//! print!("{}", text);
74//! }).await?;
75//!
76//! println!("\nTokens used: {}", response.usage.output_tokens);
77//! Ok(())
78//! }
79//! ```
80
81pub mod types;
82pub mod auth;
83pub mod inference;
84pub mod context;
85pub mod permissions;
86pub mod hooks;
87pub mod skills;
88pub mod flags;
89pub mod memory;
90pub mod telemetry;
91pub mod tools;
92pub mod mcp;
93pub mod sessions;
94pub mod agents;
95pub mod task_queue;
96pub mod plugins;
97
98// Re-export core types for ergonomic use
99pub use types::{
100 Message, Role, MessageContent, ContentBlock,
101 InferenceRequest, InferenceResponse, Usage,
102 ToolDefinition, ToolCall, ToolResult,
103 HookEvent, HookResult,
104 FeatureFlags,
105 AgentStats, AgentConfig,
106};
107pub use auth::AuthGate;
108pub use inference::{InferenceEngine, InferenceBackend};
109pub use context::ContextEngine;
110pub use permissions::PermissionEngine;
111pub use hooks::HookSystem;
112pub use skills::SkillRegistry;
113pub use tools::{Tool, ToolRegistry};
114pub use mcp::McpRegistry;
115
116/// High-level agent interface for library consumers.
117///
118/// Wraps all 11 layers into a single entry point. Create with [`Agent::new`],
119/// then call [`Agent::chat`] to send messages.
120pub struct Agent {
121 pub engine: InferenceEngine,
122 pub context: ContextEngine,
123 pub tools: ToolRegistry,
124 pub hooks: HookSystem,
125 pub skills: SkillRegistry,
126 pub permissions: PermissionEngine,
127 pub mcp: McpRegistry,
128 pub stats: AgentStats,
129 pub cwd: String,
130}
131
132/// Response from a single agent turn.
133pub struct AgentResponse {
134 /// Content blocks returned by the model.
135 pub content: Vec<ContentBlock>,
136 /// Token usage for this turn.
137 pub usage: Usage,
138 /// Model that generated the response.
139 pub model: String,
140}
141
142impl AgentResponse {
143 /// Extract the text content from the response.
144 pub fn text(&self) -> String {
145 self.content.iter().filter_map(|b| {
146 if let ContentBlock::Text { text } = b { Some(text.as_str()) } else { None }
147 }).collect::<Vec<_>>().join("\n")
148 }
149}
150
151impl Agent {
152 /// Create a new agent with the given configuration.
153 ///
154 /// This authenticates, loads MCP servers, builds the system prompt,
155 /// and initializes all 11 layers.
156 pub async fn new(config: AgentConfig) -> anyhow::Result<Self> {
157 let cwd = std::env::current_dir()?.to_string_lossy().into_owned();
158
159 let mut auth = AuthGate::new();
160 auth.authenticate()?;
161
162 let mut hooks = HookSystem::new();
163 hooks.register_defaults();
164
165 let mut mcp = McpRegistry::new();
166 mcp.load_config(&config.mcp_config_path).await;
167
168 let mut context = ContextEngine::new();
169 context.build_system_prompt(&cwd, &config.memory_path).await;
170
171 let engine = InferenceEngine::new(auth.api_key(), auth.backend());
172
173 Ok(Self {
174 engine,
175 context,
176 tools: ToolRegistry::new(),
177 hooks,
178 skills: SkillRegistry::new(),
179 permissions: PermissionEngine::new(),
180 mcp,
181 stats: telemetry::create_stats(),
182 cwd,
183 })
184 }
185
186 /// Send a message and get a response (single turn, no tool execution).
187 pub async fn chat(&self, prompt: &str) -> anyhow::Result<AgentResponse> {
188 let messages = vec![Message {
189 role: Role::User,
190 content: MessageContent::Text(prompt.to_string()),
191 }];
192
193 let tool_defs = self.tools.definitions();
194 let request = self.engine.build_request(
195 &messages,
196 Some(self.context.system_prompt()),
197 &tool_defs,
198 None,
199 );
200
201 let response = self.engine.chat_stream(&request, &mut |_| {}).await?;
202
203 Ok(AgentResponse {
204 content: response.content,
205 usage: response.usage,
206 model: response.model,
207 })
208 }
209}