use crate::axes::{default_axis_map, AxisState, PersonaAxis};
use crate::extractor::GraphExtractor;
use crate::fitness::PersonaSnapshot;
use crate::persona_node;
use crate::signals::RawSignal;
use ainl_memory::SqliteGraphStore;
use chrono::Utc;
use std::collections::HashMap;
pub const INGEST_SCORE_EPSILON: f32 = 0.001;
pub struct EvolutionEngine {
pub agent_id: String,
pub axes: HashMap<PersonaAxis, AxisState>,
}
impl EvolutionEngine {
pub fn new(agent_id: impl Into<String>) -> Self {
let agent_id = agent_id.into();
Self {
axes: default_axis_map(0.5),
agent_id,
}
}
pub fn extract_signals(&self, store: &SqliteGraphStore) -> Result<Vec<RawSignal>, String> {
GraphExtractor::extract(store, &self.agent_id)
}
pub fn ingest_signals(&mut self, signals: Vec<RawSignal>) -> usize {
let mut applied = 0usize;
for sig in signals {
if let Some(state) = self.axes.get_mut(&sig.axis) {
let prior = state.score;
state.update_weighted(sig.reward, sig.weight);
if (state.score - prior).abs() > INGEST_SCORE_EPSILON {
applied += 1;
}
}
}
applied
}
pub fn snapshot(&self) -> PersonaSnapshot {
PersonaSnapshot {
agent_id: self.agent_id.clone(),
axes: self.axes.clone(),
captured_at: Utc::now(),
}
}
pub fn write_persona_node(
&self,
store: &SqliteGraphStore,
snapshot: &PersonaSnapshot,
) -> Result<(), String> {
if snapshot.agent_id != self.agent_id {
return Err(format!(
"PersonaSnapshot agent_id {:?} does not match engine agent_id {:?}",
snapshot.agent_id, self.agent_id
));
}
persona_node::write_evolved_persona_snapshot(store, &self.agent_id, &snapshot.axes)
}
pub fn evolve(&mut self, store: &SqliteGraphStore) -> Result<PersonaSnapshot, String> {
let signals = self.extract_signals(store)?;
self.ingest_signals(signals);
let snapshot = self.snapshot();
self.write_persona_node(store, &snapshot)?;
Ok(snapshot)
}
pub fn correction_tick(&mut self, axis: PersonaAxis, correction: f32) {
if let Some(state) = self.axes.get_mut(&axis) {
state.update_weighted(correction.clamp(0.0, 1.0), 1.0);
}
}
}