Skip to main content

phago_agents/
serialize.rs

1//! Agent state serialization for session persistence.
2//!
3//! Enables saving and restoring agent state across sessions.
4//! Each agent type has a corresponding serializable state struct.
5
6use phago_core::types::*;
7use serde::{Deserialize, Serialize};
8use std::collections::HashSet;
9
10/// Enumeration of all agent types for deserialization dispatch.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12pub enum AgentType {
13    Digester,
14    Synthesizer,
15    Sentinel,
16    #[cfg(feature = "semantic")]
17    SemanticDigester,
18}
19
20impl std::fmt::Display for AgentType {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        match self {
23            AgentType::Digester => write!(f, "digester"),
24            AgentType::Synthesizer => write!(f, "synthesizer"),
25            AgentType::Sentinel => write!(f, "sentinel"),
26            #[cfg(feature = "semantic")]
27            AgentType::SemanticDigester => write!(f, "semantic_digester"),
28        }
29    }
30}
31
32/// Serializable state for a Digester agent.
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct DigesterState {
35    pub id: AgentId,
36    pub position: Position,
37    pub age_ticks: u64,
38    pub idle_ticks: u64,
39    pub useful_outputs: u64,
40    pub all_presentations: Vec<String>,
41    pub known_vocabulary: Vec<String>,
42    pub has_exported: bool,
43    pub boundary_permeability: f64,
44    pub max_idle_ticks: u64,
45    pub sense_radius: f64,
46}
47
48/// Serializable state for a Synthesizer agent.
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct SynthesizerState {
51    pub id: AgentId,
52    pub position: Position,
53    pub age_ticks: u64,
54    pub idle_ticks: u64,
55    pub insights_produced: u64,
56    pub sense_radius: f64,
57    pub cooldown_ticks: u64,
58    pub max_idle_ticks: u64,
59}
60
61/// Serializable state for a Sentinel agent.
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct SentinelState {
64    pub id: AgentId,
65    pub position: Position,
66    pub age_ticks: u64,
67    pub idle_ticks: u64,
68    pub anomalies_detected: u64,
69    pub last_scan_tick: u64,
70    pub self_model_concepts: Vec<String>,
71    pub sense_radius: f64,
72    pub max_idle_ticks: u64,
73    pub scan_interval: u64,
74}
75
76/// Union of all serializable agent states.
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub enum SerializedAgent {
79    Digester(DigesterState),
80    Synthesizer(SynthesizerState),
81    Sentinel(SentinelState),
82}
83
84impl SerializedAgent {
85    /// Get the agent type.
86    pub fn agent_type(&self) -> AgentType {
87        match self {
88            SerializedAgent::Digester(_) => AgentType::Digester,
89            SerializedAgent::Synthesizer(_) => AgentType::Synthesizer,
90            SerializedAgent::Sentinel(_) => AgentType::Sentinel,
91        }
92    }
93
94    /// Get the agent ID.
95    pub fn id(&self) -> AgentId {
96        match self {
97            SerializedAgent::Digester(s) => s.id,
98            SerializedAgent::Synthesizer(s) => s.id,
99            SerializedAgent::Sentinel(s) => s.id,
100        }
101    }
102
103    /// Get the agent position.
104    pub fn position(&self) -> Position {
105        match self {
106            SerializedAgent::Digester(s) => s.position,
107            SerializedAgent::Synthesizer(s) => s.position,
108            SerializedAgent::Sentinel(s) => s.position,
109        }
110    }
111}
112
113/// Trait for agents that can be serialized.
114pub trait SerializableAgent {
115    /// Export the agent's state for serialization.
116    fn export_state(&self) -> SerializedAgent;
117
118    /// Create an agent from serialized state.
119    fn from_state(state: &SerializedAgent) -> Option<Self>
120    where
121        Self: Sized;
122}
123
124// Helper function to convert HashSet to Vec for serialization
125pub(crate) fn hashset_to_vec(set: &HashSet<String>) -> Vec<String> {
126    set.iter().cloned().collect()
127}
128
129// Helper function to convert Vec to HashSet for deserialization
130pub(crate) fn vec_to_hashset(vec: &[String]) -> HashSet<String> {
131    vec.iter().cloned().collect()
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn agent_type_display() {
140        assert_eq!(AgentType::Digester.to_string(), "digester");
141        assert_eq!(AgentType::Synthesizer.to_string(), "synthesizer");
142        assert_eq!(AgentType::Sentinel.to_string(), "sentinel");
143    }
144
145    #[test]
146    fn digester_state_serializes() {
147        let state = DigesterState {
148            id: AgentId::from_seed(42),
149            position: Position::new(1.0, 2.0),
150            age_ticks: 100,
151            idle_ticks: 5,
152            useful_outputs: 10,
153            all_presentations: vec!["cell".to_string(), "membrane".to_string()],
154            known_vocabulary: vec!["protein".to_string()],
155            has_exported: true,
156            boundary_permeability: 0.5,
157            max_idle_ticks: 30,
158            sense_radius: 10.0,
159        };
160
161        let json = serde_json::to_string(&state).unwrap();
162        let restored: DigesterState = serde_json::from_str(&json).unwrap();
163
164        assert_eq!(restored.id, state.id);
165        assert_eq!(restored.age_ticks, 100);
166        assert_eq!(restored.all_presentations.len(), 2);
167    }
168
169    #[test]
170    fn serialized_agent_enum_works() {
171        let state = DigesterState {
172            id: AgentId::from_seed(1),
173            position: Position::new(0.0, 0.0),
174            age_ticks: 50,
175            idle_ticks: 0,
176            useful_outputs: 5,
177            all_presentations: vec![],
178            known_vocabulary: vec![],
179            has_exported: false,
180            boundary_permeability: 0.0,
181            max_idle_ticks: 30,
182            sense_radius: 10.0,
183        };
184
185        let agent = SerializedAgent::Digester(state);
186        assert_eq!(agent.agent_type(), AgentType::Digester);
187
188        let json = serde_json::to_string(&agent).unwrap();
189        let restored: SerializedAgent = serde_json::from_str(&json).unwrap();
190        assert_eq!(restored.agent_type(), AgentType::Digester);
191    }
192}