Skip to main content

traitclaw_core/traits/
strategy.rs

1//! Agent Strategy — pluggable reasoning loop.
2//!
3//! The `AgentStrategy` trait allows you to replace the default agent execution
4//! loop with a custom reasoning architecture (e.g., MCTS, ReAct, Chain-of-Thought).
5//!
6//! # Architecture Decision
7//!
8//! Dispatch is dynamic (`Box<dyn AgentStrategy>`) because LLM latency
9//! (200-2000ms) dwarfs vtable overhead (nanoseconds). This simplifies
10//! the API: strategies can be swapped at runtime without recompilation.
11//!
12//! # Example
13//!
14//! ```rust,no_run
15//! use traitclaw_core::traits::strategy::AgentStrategy;
16//! use traitclaw_core::agent::AgentOutput;
17//!
18//! struct MctsStrategy { /* config */ }
19//!
20//! #[async_trait::async_trait]
21//! impl AgentStrategy for MctsStrategy {
22//!     async fn execute(
23//!         &self,
24//!         runtime: &traitclaw_core::traits::strategy::AgentRuntime,
25//!         input: &str,
26//!         session_id: &str,
27//!     ) -> traitclaw_core::Result<AgentOutput> {
28//!         // Your custom reasoning loop here
29//!         todo!()
30//!     }
31//! }
32//! ```
33
34use std::sync::Arc;
35
36use async_trait::async_trait;
37
38use crate::agent::AgentOutput;
39use crate::config::AgentConfig;
40use crate::traits::context_manager::ContextManager;
41use crate::traits::execution_strategy::ExecutionStrategy;
42use crate::traits::guard::Guard;
43use crate::traits::hint::Hint;
44use crate::traits::memory::Memory;
45use crate::traits::output_transformer::OutputTransformer;
46use crate::traits::provider::Provider;
47use crate::traits::tool::ErasedTool;
48use crate::traits::tool_registry::ToolRegistry;
49use crate::traits::tracker::Tracker;
50use crate::Result;
51
52/// Runtime context provided to strategies.
53///
54/// Exposes all agent components needed to execute a reasoning loop,
55/// without exposing the strategy itself (avoiding recursion).
56#[derive(Clone)]
57pub struct AgentRuntime {
58    /// The LLM provider.
59    pub provider: Arc<dyn Provider>,
60    /// Registered tools (v0.2.0 compat — prefer `tool_registry`).
61    pub tools: Vec<Arc<dyn ErasedTool>>,
62    /// Memory backend.
63    pub memory: Arc<dyn Memory>,
64    /// Active guards.
65    pub guards: Vec<Arc<dyn Guard>>,
66    /// Active hints.
67    pub hints: Vec<Arc<dyn Hint>>,
68    /// Runtime tracker.
69    pub tracker: Arc<dyn Tracker>,
70    /// Context window manager.
71    pub context_manager: Arc<dyn ContextManager>,
72    /// Tool execution strategy (sequential/parallel).
73    pub execution_strategy: Arc<dyn ExecutionStrategy>,
74    /// Tool output transformer.
75    pub output_transformer: Arc<dyn OutputTransformer>,
76    /// Tool registry.
77    pub tool_registry: Arc<dyn ToolRegistry>,
78    /// Agent configuration.
79    pub config: AgentConfig,
80    /// Lifecycle hooks.
81    pub hooks: Vec<Arc<dyn super::hook::AgentHook>>,
82}
83
84/// Pluggable agent execution strategy.
85///
86/// Implement this trait to define a custom reasoning loop. The default
87/// implementation (`DefaultStrategy`) preserves the v0.1.0 behavior.
88///
89/// # Object Safety
90///
91/// This trait is object-safe and used as `Box<dyn AgentStrategy>`.
92#[async_trait]
93pub trait AgentStrategy: Send + Sync + 'static {
94    /// Execute the agent reasoning loop.
95    ///
96    /// Receives the full `AgentRuntime` with all agent components and
97    /// the user's input. Returns the final `AgentOutput`.
98    async fn execute(
99        &self,
100        runtime: &AgentRuntime,
101        input: &str,
102        session_id: &str,
103    ) -> Result<AgentOutput>;
104
105    /// Execute the agent reasoning loop and return a streaming response.
106    ///
107    /// Not all strategies support streaming natively. The default implementation
108    /// returns an error indicating streaming is not supported. Custom strategies
109    /// must opt-in by implementing this method.
110    fn stream(
111        &self,
112        _runtime: &AgentRuntime,
113        _input: &str,
114        _session_id: &str,
115    ) -> std::pin::Pin<
116        Box<dyn tokio_stream::Stream<Item = Result<crate::types::stream::StreamEvent>> + Send>,
117    > {
118        let (tx, rx) = tokio::sync::mpsc::channel(1);
119        let _ = tx.try_send(Err(crate::Error::Runtime(
120            "Streaming is not supported by this AgentStrategy".into(),
121        )));
122        Box::pin(tokio_stream::wrappers::ReceiverStream::new(rx))
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    // Verify trait is object-safe
131    fn _assert_object_safe(_: &dyn AgentStrategy) {}
132
133    // Verify AgentRuntime is Send + Sync
134    fn _assert_send_sync(_: &(dyn AgentStrategy + Send + Sync)) {}
135}