Skip to main content

synaptic_deep/
lib.rs

1//! Deep agent harness for Synaptic.
2//!
3//! Provides an opinionated agent harness that bundles filesystem tools,
4//! subagent spawning, skills, memory, and auto-summarization — all
5//! implemented as [`AgentMiddleware`](synaptic_middleware::AgentMiddleware).
6//!
7//! # Quick Start
8//!
9//! ```rust,ignore
10//! use synaptic_deep::{create_deep_agent, DeepAgentOptions, backend::StateBackend};
11//!
12//! let backend = Arc::new(StateBackend::new());
13//! let options = DeepAgentOptions::new(backend);
14//! let agent = create_deep_agent(model, options)?;
15//! let result = agent.invoke(MessageState::with_messages(vec![
16//!     Message::human("Write hello.txt"),
17//! ])).await?;
18//! ```
19
20pub mod backend;
21#[cfg(feature = "config-builder")]
22pub mod builder;
23pub mod middleware;
24pub mod skill;
25pub mod tools;
26
27use std::sync::Arc;
28
29use synaptic_core::{ChatModel, Store, SynapticError, Tool};
30use synaptic_graph::{create_agent, AgentOptions, Checkpointer, CompiledGraph, MessageState};
31use synaptic_macros::traceable;
32use synaptic_middleware::AgentMiddleware;
33
34use backend::Backend;
35pub use middleware::subagent::SubAgentDef;
36
37#[cfg(feature = "config-builder")]
38pub use builder::build_agent_from_config;
39
40/// Configuration for [`create_deep_agent`].
41pub struct DeepAgentOptions {
42    /// Backend for filesystem operations.
43    pub backend: Arc<dyn Backend>,
44    /// Optional system prompt prepended to all model calls.
45    pub system_prompt: Option<String>,
46    /// Additional tools beyond the built-in filesystem tools.
47    pub tools: Vec<Arc<dyn Tool>>,
48    /// Additional middleware beyond the built-in stack.
49    pub middleware: Vec<Arc<dyn AgentMiddleware>>,
50    /// Optional checkpointer for graph state persistence.
51    pub checkpointer: Option<Arc<dyn Checkpointer>>,
52    /// Optional store for runtime tool injection.
53    pub store: Option<Arc<dyn Store>>,
54    /// Maximum input tokens before summarization (default 128,000).
55    pub max_input_tokens: usize,
56    /// Fraction of max_input_tokens that triggers summarization (default 0.85).
57    pub summarization_threshold: f64,
58    /// Token count above which tool results are evicted to files (default 20,000).
59    pub eviction_threshold: usize,
60    /// Maximum nested subagent depth (default 3).
61    pub max_subagent_depth: usize,
62    /// Skills directory path in the backend (default ".skills").
63    pub skills_dir: Option<String>,
64    /// Memory file path in the backend (default "AGENTS.md").
65    pub memory_file: Option<String>,
66    /// Custom subagent definitions for the task tool.
67    pub subagents: Vec<SubAgentDef>,
68    /// Enable subagent spawning via task tool (default true).
69    pub enable_subagents: bool,
70    /// Enable filesystem tools (default true).
71    pub enable_filesystem: bool,
72    /// Enable skills middleware (default true).
73    pub enable_skills: bool,
74    /// Enable memory middleware (default true).
75    pub enable_memory: bool,
76    /// Enable parallel tool execution in ToolNode (default false).
77    pub parallel_tools: bool,
78}
79
80impl DeepAgentOptions {
81    /// Create options with the given backend and sensible defaults.
82    pub fn new(backend: Arc<dyn Backend>) -> Self {
83        Self {
84            backend,
85            system_prompt: None,
86            tools: Vec::new(),
87            middleware: Vec::new(),
88            checkpointer: None,
89            store: None,
90            max_input_tokens: 128_000,
91            summarization_threshold: 0.85,
92            eviction_threshold: 20_000,
93            max_subagent_depth: 3,
94            skills_dir: Some(".skills".to_string()),
95            memory_file: Some("AGENTS.md".to_string()),
96            subagents: Vec::new(),
97            enable_subagents: true,
98            enable_filesystem: true,
99            enable_skills: true,
100            enable_memory: true,
101            parallel_tools: false,
102        }
103    }
104}
105
106/// Create a deep agent with the given model and options.
107///
108/// Assembles a middleware stack and tool set:
109/// 1. **DeepMemoryMiddleware** — loads memory file into system prompt
110/// 2. **SkillsMiddleware** — progressive disclosure of skills
111/// 3. **FilesystemMiddleware** — 6–7 filesystem tools + large result eviction
112/// 4. **SubAgentMiddleware** — `task` tool for child agent spawning
113/// 5. **DeepSummarizationMiddleware** — auto-summarize context on overflow
114/// 6. **PatchToolCallsMiddleware** — fix malformed tool calls
115/// 7. User-provided middleware
116#[traceable(skip = "model,options")]
117pub fn create_deep_agent(
118    model: Arc<dyn ChatModel>,
119    options: DeepAgentOptions,
120) -> Result<CompiledGraph<MessageState>, SynapticError> {
121    let mut all_middleware: Vec<Arc<dyn AgentMiddleware>> = Vec::new();
122    let mut all_tools: Vec<Arc<dyn Tool>> = Vec::new();
123
124    // 1. Memory middleware
125    if options.enable_memory {
126        let memory_file = options
127            .memory_file
128            .clone()
129            .unwrap_or_else(|| "AGENTS.md".to_string());
130        all_middleware.push(Arc::new(middleware::memory::DeepMemoryMiddleware::new(
131            options.backend.clone(),
132            memory_file,
133        )));
134    }
135
136    // 2. Skills middleware
137    if options.enable_skills {
138        let skills_dir = options
139            .skills_dir
140            .clone()
141            .unwrap_or_else(|| ".skills".to_string());
142        all_middleware.push(Arc::new(middleware::skills::SkillsMiddleware::new(
143            options.backend.clone(),
144            skills_dir,
145        )));
146    }
147
148    // 3. Filesystem middleware + tools
149    if options.enable_filesystem {
150        let fs_tools = tools::create_filesystem_tools(options.backend.clone());
151        all_tools.extend(fs_tools);
152        all_middleware.push(Arc::new(middleware::filesystem::FilesystemMiddleware::new(
153            options.backend.clone(),
154            options.eviction_threshold,
155        )));
156    }
157
158    // 4. Subagent middleware + task tool
159    if options.enable_subagents {
160        let subagent_mw = middleware::subagent::SubAgentMiddleware::new(
161            options.backend.clone(),
162            model.clone(),
163            options.max_subagent_depth,
164            options.subagents.clone(),
165        );
166        all_tools.push(subagent_mw.create_task_tool());
167    }
168
169    // 5. Summarization middleware
170    all_middleware.push(Arc::new(
171        middleware::summarization::DeepSummarizationMiddleware::new(
172            options.backend.clone(),
173            model.clone(),
174            options.max_input_tokens,
175            options.summarization_threshold,
176        ),
177    ));
178
179    // 6. Patch tool calls middleware
180    all_middleware.push(Arc::new(
181        middleware::patch_tool_calls::PatchToolCallsMiddleware,
182    ));
183
184    // 7. User-provided middleware
185    all_middleware.extend(options.middleware);
186
187    // Add user-provided tools
188    all_tools.extend(options.tools);
189
190    // Build agent options
191    let agent_options = AgentOptions {
192        checkpointer: options.checkpointer,
193        interrupt_before: Vec::new(),
194        interrupt_after: Vec::new(),
195        system_prompt: options.system_prompt,
196        middleware: all_middleware,
197        store: options.store,
198        name: Some("deep_agent".to_string()),
199        pre_model_hook: None,
200        post_model_hook: None,
201        response_format: None,
202        parallel_tools: options.parallel_tools,
203    };
204
205    create_agent(model, all_tools, agent_options)
206}