Skip to main content

ainl_memory/
node.rs

1//! AINL graph node types - the vocabulary of agent memory.
2//!
3//! Four core memory types: Episode, Semantic, Procedural, Persona.
4//! Designed to be standalone (zero ArmaraOS deps) yet compatible with
5//! OrchestrationTraceEvent serialization.
6
7use serde::{Deserialize, Serialize};
8use uuid::Uuid;
9
10/// Core AINL node types - the vocabulary of agent memory.
11#[derive(Serialize, Deserialize, Debug, Clone)]
12#[serde(tag = "type", rename_all = "snake_case")]
13pub enum AinlNodeType {
14    /// Episodic memory: what happened during an agent turn
15    Episode {
16        /// Unique turn identifier
17        turn_id: Uuid,
18
19        /// When this episode occurred (Unix timestamp)
20        timestamp: i64,
21
22        /// Tool calls executed during this turn
23        tool_calls: Vec<String>,
24
25        /// Agent this turn delegated to (if any)
26        delegation_to: Option<String>,
27
28        /// Orchestration trace event (serialized, compatible with OrchestrationTraceEvent)
29        /// This allows ArmaraOS to embed full trace context without creating a dependency
30        #[serde(skip_serializing_if = "Option::is_none")]
31        trace_event: Option<serde_json::Value>,
32    },
33
34    /// Semantic memory: facts learned, with confidence
35    Semantic {
36        /// The fact itself (natural language)
37        fact: String,
38
39        /// Confidence score (0.0-1.0)
40        confidence: f32,
41
42        /// Which turn generated this fact
43        source_turn_id: Uuid,
44    },
45
46    /// Procedural memory: reusable compiled workflow patterns
47    Procedural {
48        /// Name/identifier for this pattern
49        pattern_name: String,
50
51        /// Compiled graph representation (binary format)
52        compiled_graph: Vec<u8>,
53    },
54
55    /// Persona memory: traits learned over time
56    Persona {
57        /// Name of the trait (e.g., "prefers_concise_responses")
58        trait_name: String,
59
60        /// Strength of this trait (0.0-1.0)
61        strength: f32,
62
63        /// Turn IDs where this trait was observed
64        learned_from: Vec<Uuid>,
65    },
66}
67
68/// A node in the AINL memory graph
69#[derive(Serialize, Deserialize, Debug, Clone)]
70pub struct AinlMemoryNode {
71    /// Unique node identifier
72    pub id: Uuid,
73
74    /// The node's type and payload
75    pub node_type: AinlNodeType,
76
77    /// Edges to other nodes
78    pub edges: Vec<AinlEdge>,
79}
80
81/// Typed edge connecting memory nodes
82#[derive(Serialize, Deserialize, Debug, Clone)]
83pub struct AinlEdge {
84    /// Target node ID
85    pub target_id: Uuid,
86
87    /// Edge label (e.g., "delegated_to", "learned_from", "caused_by")
88    pub label: String,
89}
90
91impl AinlMemoryNode {
92    /// Create a new episode node
93    pub fn new_episode(
94        turn_id: Uuid,
95        timestamp: i64,
96        tool_calls: Vec<String>,
97        delegation_to: Option<String>,
98        trace_event: Option<serde_json::Value>,
99    ) -> Self {
100        Self {
101            id: Uuid::new_v4(),
102            node_type: AinlNodeType::Episode {
103                turn_id,
104                timestamp,
105                tool_calls,
106                delegation_to,
107                trace_event,
108            },
109            edges: Vec::new(),
110        }
111    }
112
113    /// Create a new semantic fact node
114    pub fn new_fact(fact: String, confidence: f32, source_turn_id: Uuid) -> Self {
115        Self {
116            id: Uuid::new_v4(),
117            node_type: AinlNodeType::Semantic {
118                fact,
119                confidence,
120                source_turn_id,
121            },
122            edges: Vec::new(),
123        }
124    }
125
126    /// Create a new procedural pattern node
127    pub fn new_pattern(pattern_name: String, compiled_graph: Vec<u8>) -> Self {
128        Self {
129            id: Uuid::new_v4(),
130            node_type: AinlNodeType::Procedural {
131                pattern_name,
132                compiled_graph,
133            },
134            edges: Vec::new(),
135        }
136    }
137
138    /// Create a new persona trait node
139    pub fn new_persona(trait_name: String, strength: f32, learned_from: Vec<Uuid>) -> Self {
140        Self {
141            id: Uuid::new_v4(),
142            node_type: AinlNodeType::Persona {
143                trait_name,
144                strength,
145                learned_from,
146            },
147            edges: Vec::new(),
148        }
149    }
150
151    /// Add an edge to another node
152    pub fn add_edge(&mut self, target_id: Uuid, label: impl Into<String>) {
153        self.edges.push(AinlEdge {
154            target_id,
155            label: label.into(),
156        });
157    }
158}