Skip to main content

ainl_graph_extractor/
extractor.rs

1//! One-shot extraction pass wired to [`ainl_persona::EvolutionEngine`].
2//!
3//! Instrumentality from episode tools is emitted by [`ainl_persona::GraphExtractor`] when the
4//! episode is processable; `extract_pass` skips its redundant `tool_affinity` leg in that case
5//! (see [`crate::persona_signals::extract_pass`]).
6
7use crate::persona_signals::{extract_pass, PersonaSignalExtractorState};
8use crate::recurrence::update_semantic_recurrence;
9use ainl_memory::SqliteGraphStore;
10use ainl_persona::{EvolutionEngine, PersonaSnapshot, RawSignal};
11use chrono::{DateTime, Utc};
12
13pub struct GraphExtractorTask {
14    pub agent_id: String,
15    pub evolution_engine: EvolutionEngine,
16    pub signal_state: PersonaSignalExtractorState,
17}
18
19#[derive(Debug, Clone)]
20pub struct ExtractionReport {
21    pub agent_id: String,
22    pub semantic_nodes_updated: usize,
23    /// Raw signals returned from the store this pass (diagnostics / "what the agent saw").
24    pub signals_extracted: usize,
25    /// Signals that moved an axis EMA by more than the persona ingest epsilon (sparkline input).
26    pub signals_applied: usize,
27    /// Merged graph + pattern signals ingested this pass (diagnostics, tests).
28    pub merged_signals: Vec<RawSignal>,
29    pub persona_snapshot: PersonaSnapshot,
30    pub timestamp: DateTime<Utc>,
31}
32
33impl GraphExtractorTask {
34    pub fn new(agent_id: &str) -> Self {
35        Self {
36            agent_id: agent_id.to_string(),
37            evolution_engine: EvolutionEngine::new(agent_id),
38            signal_state: PersonaSignalExtractorState::new(),
39        }
40    }
41
42    /// Semantic recurrence updates, then extract → ingest → snapshot → explicit persona write.
43    pub fn run_pass(&mut self, store: &SqliteGraphStore) -> Result<ExtractionReport, String> {
44        let semantic_nodes_updated = update_semantic_recurrence(store, &self.agent_id)?;
45        let mut signals = self.evolution_engine.extract_signals(store)?;
46        signals.extend(extract_pass(store, &self.agent_id, &mut self.signal_state)?);
47        let signals_extracted = signals.len();
48        let merged_signals = signals.clone();
49        let signals_applied = self.evolution_engine.ingest_signals(signals);
50        let persona_snapshot = self.evolution_engine.snapshot();
51        self.evolution_engine
52            .write_persona_node(store, &persona_snapshot)?;
53        Ok(ExtractionReport {
54            agent_id: self.agent_id.clone(),
55            semantic_nodes_updated,
56            signals_extracted,
57            signals_applied,
58            merged_signals,
59            persona_snapshot,
60            timestamp: Utc::now(),
61        })
62    }
63}