Skip to main content

synwire_core/agents/
execution_strategy.rs

1//! Execution strategies for agents.
2
3use crate::BoxFuture;
4use crate::agents::signal::SignalRoute;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use thiserror::Error;
8
9/// Execution strategy error.
10#[derive(Error, Debug, Clone)]
11#[non_exhaustive]
12pub enum StrategyError {
13    /// Invalid state transition.
14    #[error(
15        "Invalid transition from {current_state} via {attempted_action}. Valid actions: {valid_actions:?}"
16    )]
17    InvalidTransition {
18        /// Current state.
19        current_state: String,
20        /// Attempted action.
21        attempted_action: String,
22        /// Valid actions from current state.
23        valid_actions: Vec<String>,
24    },
25
26    /// Guard condition rejected transition.
27    #[error("Guard rejected transition: {0}")]
28    GuardRejected(String),
29
30    /// No initial state defined.
31    #[error("No initial state defined")]
32    NoInitialState,
33
34    /// Execution failed.
35    #[error("Execution failed: {0}")]
36    Execution(String),
37}
38
39/// FSM state identifier (newtype for type safety).
40#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
41pub struct FsmStateId(pub String);
42
43impl From<&str> for FsmStateId {
44    fn from(s: &str) -> Self {
45        Self(s.to_string())
46    }
47}
48
49impl From<String> for FsmStateId {
50    fn from(s: String) -> Self {
51        Self(s)
52    }
53}
54
55/// Action identifier (newtype for type safety).
56#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
57pub struct ActionId(pub String);
58
59impl From<&str> for ActionId {
60    fn from(s: &str) -> Self {
61        Self(s.to_string())
62    }
63}
64
65impl From<String> for ActionId {
66    fn from(s: String) -> Self {
67        Self(s)
68    }
69}
70
71/// Strategy snapshot for serialization.
72pub trait StrategySnapshot: Send + Sync {
73    /// Serialize snapshot to JSON.
74    fn to_value(&self) -> Result<Value, StrategyError>;
75}
76
77/// Execution strategy trait.
78///
79/// Controls how agent orchestrates actions (immediate vs state-constrained).
80pub trait ExecutionStrategy: Send + Sync {
81    /// Execute an action with input.
82    fn execute<'a>(
83        &'a self,
84        action: &'a str,
85        input: Value,
86    ) -> BoxFuture<'a, Result<Value, StrategyError>>;
87
88    /// Process pending work (for stateful strategies).
89    fn tick(&self) -> BoxFuture<'_, Result<Option<Value>, StrategyError>>;
90
91    /// Capture current strategy state.
92    fn snapshot(&self) -> Result<Box<dyn StrategySnapshot>, StrategyError>;
93
94    /// Get signal routes contributed by this strategy.
95    fn signal_routes(&self) -> Vec<SignalRoute> {
96        Vec::new()
97    }
98}
99
100/// Guard condition for FSM transitions.
101pub trait GuardCondition: Send + Sync {
102    /// Evaluate guard condition.
103    fn evaluate(&self, input: &Value) -> bool;
104
105    /// Guard name for error messages.
106    fn name(&self) -> &str;
107}
108
109/// Closure-based adapter for [`GuardCondition`].
110///
111/// Wraps an `Fn(&Value) -> bool` closure so it can be used as a guard.
112pub struct ClosureGuard {
113    name: String,
114    f: Box<dyn Fn(&Value) -> bool + Send + Sync>,
115}
116
117impl ClosureGuard {
118    /// Create a new closure guard with the given name and predicate.
119    pub fn new(
120        name: impl Into<String>,
121        f: impl Fn(&Value) -> bool + Send + Sync + 'static,
122    ) -> Self {
123        Self {
124            name: name.into(),
125            f: Box::new(f),
126        }
127    }
128}
129
130impl std::fmt::Debug for ClosureGuard {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        f.debug_struct("ClosureGuard")
133            .field("name", &self.name)
134            .finish_non_exhaustive()
135    }
136}
137
138impl GuardCondition for ClosureGuard {
139    fn evaluate(&self, input: &Value) -> bool {
140        (self.f)(input)
141    }
142
143    fn name(&self) -> &str {
144        &self.name
145    }
146}