1use phago_core::types::*;
7use serde::{Deserialize, Serialize};
8use std::collections::HashSet;
9
10#[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#[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#[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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
78pub enum SerializedAgent {
79 Digester(DigesterState),
80 Synthesizer(SynthesizerState),
81 Sentinel(SentinelState),
82}
83
84impl SerializedAgent {
85 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 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 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
113pub trait SerializableAgent {
115 fn export_state(&self) -> SerializedAgent;
117
118 fn from_state(state: &SerializedAgent) -> Option<Self>
120 where
121 Self: Sized;
122}
123
124pub(crate) fn hashset_to_vec(set: &HashSet<String>) -> Vec<String> {
126 set.iter().cloned().collect()
127}
128
129pub(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}