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}