1use crate::axes::{default_axis_map, AxisState, PersonaAxis};
8use crate::extractor::GraphExtractor;
9use crate::fitness::PersonaSnapshot;
10use crate::persona_node;
11use crate::signals::RawSignal;
12use ainl_memory::SqliteGraphStore;
13use chrono::Utc;
14use std::collections::HashMap;
15
16pub const INGEST_SCORE_EPSILON: f32 = 0.001;
18
19pub struct EvolutionEngine {
20 pub agent_id: String,
21 pub axes: HashMap<PersonaAxis, AxisState>,
22}
23
24impl EvolutionEngine {
25 pub fn new(agent_id: impl Into<String>) -> Self {
26 let agent_id = agent_id.into();
27 Self {
28 axes: default_axis_map(0.5),
29 agent_id,
30 }
31 }
32
33 pub fn extract_signals(&self, store: &SqliteGraphStore) -> Result<Vec<RawSignal>, String> {
35 GraphExtractor::extract(store, &self.agent_id)
36 }
37
38 pub fn ingest_signals(&mut self, signals: Vec<RawSignal>) -> usize {
41 let mut applied = 0usize;
42 for sig in signals {
43 if let Some(state) = self.axes.get_mut(&sig.axis) {
44 let prior = state.score;
45 state.update_weighted(sig.reward, sig.weight);
46 if (state.score - prior).abs() > INGEST_SCORE_EPSILON {
47 applied += 1;
48 }
49 }
50 }
51 applied
52 }
53
54 pub fn snapshot(&self) -> PersonaSnapshot {
56 PersonaSnapshot {
57 agent_id: self.agent_id.clone(),
58 axes: self.axes.clone(),
59 captured_at: Utc::now(),
60 }
61 }
62
63 pub fn write_persona_node(
67 &self,
68 store: &SqliteGraphStore,
69 snapshot: &PersonaSnapshot,
70 ) -> Result<(), String> {
71 if snapshot.agent_id != self.agent_id {
72 return Err(format!(
73 "PersonaSnapshot agent_id {:?} does not match engine agent_id {:?}",
74 snapshot.agent_id, self.agent_id
75 ));
76 }
77 persona_node::write_evolved_persona_snapshot(store, &self.agent_id, &snapshot.axes)
78 }
79
80 pub fn evolve(&mut self, store: &SqliteGraphStore) -> Result<PersonaSnapshot, String> {
82 let signals = self.extract_signals(store)?;
83 self.ingest_signals(signals);
84 let snapshot = self.snapshot();
85 self.write_persona_node(store, &snapshot)?;
86 Ok(snapshot)
87 }
88
89 pub fn correction_tick(&mut self, axis: PersonaAxis, correction: f32) {
90 if let Some(state) = self.axes.get_mut(&axis) {
91 state.update_weighted(correction.clamp(0.0, 1.0), 1.0);
92 }
93 }
94}