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}