1use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use uuid::Uuid;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum AinlNodeKind {
14 Episode,
15 Semantic,
16 Procedural,
17 Persona,
18}
19
20impl AinlNodeKind {
21 pub fn as_str(&self) -> &'static str {
22 match self {
23 Self::Episode => "episode",
24 Self::Semantic => "semantic",
25 Self::Procedural => "procedural",
26 Self::Persona => "persona",
27 }
28 }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
33#[serde(rename_all = "snake_case")]
34pub enum MemoryCategory {
35 Persona,
36 Semantic,
37 Episodic,
38 Procedural,
39 RuntimeState,
41}
42
43impl MemoryCategory {
44 pub fn from_node_type(node_type: &AinlNodeType) -> Self {
45 match node_type {
46 AinlNodeType::Episode { .. } => MemoryCategory::Episodic,
47 AinlNodeType::Semantic { .. } => MemoryCategory::Semantic,
48 AinlNodeType::Procedural { .. } => MemoryCategory::Procedural,
49 AinlNodeType::Persona { .. } => MemoryCategory::Persona,
50 AinlNodeType::RuntimeState { .. } => MemoryCategory::RuntimeState,
51 }
52 }
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
56#[serde(rename_all = "snake_case")]
57pub enum PersonaLayer {
58 #[default]
59 Base,
60 Delta,
61 Injection,
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
65#[serde(rename_all = "snake_case")]
66pub enum PersonaSource {
67 SystemDefault,
68 #[default]
69 UserConfigured,
70 Evolved,
71 Feedback,
72 Injection,
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
76#[serde(rename_all = "snake_case")]
77pub enum Sentiment {
78 Positive,
79 Neutral,
80 Negative,
81 Mixed,
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
85#[serde(rename_all = "snake_case")]
86pub enum ProcedureType {
87 #[default]
88 ToolSequence,
89 ResponsePattern,
90 WorkflowStep,
91 BehavioralRule,
92}
93
94#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
96pub struct StrengthEvent {
97 pub delta: f32,
98 pub reason: String,
99 pub episode_id: String,
100 pub timestamp: u64,
101}
102
103fn default_importance_score() -> f32 {
104 0.5
105}
106
107fn default_semantic_confidence() -> f32 {
108 0.7
109}
110
111fn default_decay_eligible() -> bool {
112 true
113}
114
115fn default_success_rate() -> f32 {
116 0.5
117}
118
119fn default_strength_floor() -> f32 {
120 0.0
121}
122
123#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
125pub struct PersonaNode {
126 pub trait_name: String,
127 pub strength: f32,
128 #[serde(default)]
129 pub learned_from: Vec<Uuid>,
130 #[serde(default)]
131 pub layer: PersonaLayer,
132 #[serde(default)]
133 pub source: PersonaSource,
134 #[serde(default = "default_strength_floor")]
135 pub strength_floor: f32,
136 #[serde(default)]
137 pub locked: bool,
138 #[serde(default)]
139 pub relevance_score: f32,
140 #[serde(default)]
141 pub provenance_episode_ids: Vec<String>,
142 #[serde(default)]
143 pub evolution_log: Vec<StrengthEvent>,
144 #[serde(default)]
146 pub axis_scores: HashMap<String, f32>,
147 #[serde(default)]
148 pub evolution_cycle: u32,
149 #[serde(default)]
151 pub last_evolved: String,
152 #[serde(default)]
154 pub agent_id: String,
155 #[serde(default)]
157 pub dominant_axes: Vec<String>,
158}
159
160#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
162pub struct SemanticNode {
163 pub fact: String,
164 #[serde(default = "default_semantic_confidence")]
165 pub confidence: f32,
166 pub source_turn_id: Uuid,
167 #[serde(default)]
168 pub topic_cluster: Option<String>,
169 #[serde(default)]
170 pub source_episode_id: String,
171 #[serde(default)]
172 pub contradiction_ids: Vec<String>,
173 #[serde(default)]
174 pub last_referenced_at: u64,
175 #[serde(default)]
178 pub reference_count: u32,
179 #[serde(default = "default_decay_eligible")]
180 pub decay_eligible: bool,
181 #[serde(default)]
183 pub tags: Vec<String>,
184 #[serde(default)]
191 pub recurrence_count: u32,
192 #[serde(rename = "_last_ref_snapshot", default)]
194 pub last_ref_snapshot: u32,
195}
196
197#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
199pub struct EpisodicNode {
200 pub turn_id: Uuid,
201 pub timestamp: i64,
202 #[serde(default)]
203 pub tool_calls: Vec<String>,
204 #[serde(default)]
205 pub delegation_to: Option<String>,
206 #[serde(skip_serializing_if = "Option::is_none")]
207 pub trace_event: Option<serde_json::Value>,
208 #[serde(default)]
209 pub turn_index: u32,
210 #[serde(default)]
211 pub user_message_tokens: u32,
212 #[serde(default)]
213 pub assistant_response_tokens: u32,
214 #[serde(default)]
216 pub tools_invoked: Vec<String>,
217 #[serde(default)]
220 pub persona_signals_emitted: Vec<String>,
221 #[serde(default)]
222 pub sentiment: Option<Sentiment>,
223 #[serde(default)]
224 pub flagged: bool,
225 #[serde(default)]
226 pub conversation_id: String,
227 #[serde(default)]
228 pub follows_episode_id: Option<String>,
229 #[serde(default, skip_serializing_if = "Option::is_none")]
231 pub user_message: Option<String>,
232 #[serde(default, skip_serializing_if = "Option::is_none")]
234 pub assistant_response: Option<String>,
235}
236
237impl EpisodicNode {
238 pub fn effective_tools(&self) -> &[String] {
240 if !self.tools_invoked.is_empty() {
241 &self.tools_invoked
242 } else {
243 &self.tool_calls
244 }
245 }
246}
247
248#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
250pub struct ProceduralNode {
251 pub pattern_name: String,
252 #[serde(default)]
253 pub compiled_graph: Vec<u8>,
254 #[serde(default, skip_serializing_if = "Vec::is_empty")]
255 pub tool_sequence: Vec<String>,
256 #[serde(default, skip_serializing_if = "Option::is_none")]
257 pub confidence: Option<f32>,
258 #[serde(default)]
259 pub procedure_type: ProcedureType,
260 #[serde(default)]
261 pub trigger_conditions: Vec<String>,
262 #[serde(default)]
263 pub success_count: u32,
264 #[serde(default)]
265 pub failure_count: u32,
266 #[serde(default = "default_success_rate")]
267 pub success_rate: f32,
268 #[serde(default)]
269 pub last_invoked_at: u64,
270 #[serde(default)]
271 pub reinforcement_episode_ids: Vec<String>,
272 #[serde(default)]
273 pub suppression_episode_ids: Vec<String>,
274 #[serde(default)]
276 pub patch_version: u32,
277 #[serde(default)]
279 pub fitness: Option<f32>,
280 #[serde(default)]
282 pub declared_reads: Vec<String>,
283 #[serde(default)]
285 pub retired: bool,
286 #[serde(default)]
288 pub label: String,
289}
290
291impl ProceduralNode {
292 pub fn recompute_success_rate(&mut self) {
293 let total = self.success_count.saturating_add(self.failure_count);
294 self.success_rate = if total == 0 {
295 0.5
296 } else {
297 self.success_count as f32 / total as f32
298 };
299 }
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
304pub struct RuntimeStateNode {
305 pub agent_id: String,
306 pub turn_count: u32,
307 pub last_extraction_turn: u32,
308 pub last_persona_prompt: Option<String>,
309 pub updated_at: String,
310}
311
312#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
314#[serde(tag = "type", rename_all = "snake_case")]
315pub enum AinlNodeType {
316 Episode {
318 #[serde(flatten)]
319 episodic: EpisodicNode,
320 },
321
322 Semantic {
324 #[serde(flatten)]
325 semantic: SemanticNode,
326 },
327
328 Procedural {
330 #[serde(flatten)]
331 procedural: ProceduralNode,
332 },
333
334 Persona {
336 #[serde(flatten)]
337 persona: PersonaNode,
338 },
339
340 RuntimeState {
342 runtime_state: RuntimeStateNode,
343 },
344}
345
346#[derive(Serialize, Debug, Clone, PartialEq)]
348pub struct AinlMemoryNode {
349 pub id: Uuid,
350 pub memory_category: MemoryCategory,
351 pub importance_score: f32,
352 pub agent_id: String,
353 pub node_type: AinlNodeType,
354 pub edges: Vec<AinlEdge>,
355}
356
357#[derive(Deserialize)]
358struct AinlMemoryNodeWire {
359 id: Uuid,
360 #[serde(default)]
361 memory_category: Option<MemoryCategory>,
362 #[serde(default)]
363 importance_score: Option<f32>,
364 #[serde(default)]
365 agent_id: Option<String>,
366 node_type: AinlNodeType,
367 #[serde(default)]
368 edges: Vec<AinlEdge>,
369}
370
371impl From<AinlMemoryNodeWire> for AinlMemoryNode {
372 fn from(w: AinlMemoryNodeWire) -> Self {
373 let memory_category = w
374 .memory_category
375 .unwrap_or_else(|| MemoryCategory::from_node_type(&w.node_type));
376 let importance_score = w.importance_score.unwrap_or_else(default_importance_score);
377 Self {
378 id: w.id,
379 memory_category,
380 importance_score,
381 agent_id: w.agent_id.unwrap_or_default(),
382 node_type: w.node_type,
383 edges: w.edges,
384 }
385 }
386}
387
388impl<'de> Deserialize<'de> for AinlMemoryNode {
389 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
390 where
391 D: serde::Deserializer<'de>,
392 {
393 let w = AinlMemoryNodeWire::deserialize(deserializer)?;
394 Ok(Self::from(w))
395 }
396}
397
398#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
400pub struct AinlEdge {
401 pub target_id: Uuid,
403
404 pub label: String,
406}
407
408impl AinlMemoryNode {
409 fn base(
410 memory_category: MemoryCategory,
411 importance_score: f32,
412 agent_id: String,
413 node_type: AinlNodeType,
414 ) -> Self {
415 Self {
416 id: Uuid::new_v4(),
417 memory_category,
418 importance_score,
419 agent_id,
420 node_type,
421 edges: Vec::new(),
422 }
423 }
424
425 pub fn new_episode(
427 turn_id: Uuid,
428 timestamp: i64,
429 tool_calls: Vec<String>,
430 delegation_to: Option<String>,
431 trace_event: Option<serde_json::Value>,
432 ) -> Self {
433 let tools_invoked = tool_calls.clone();
434 let episodic = EpisodicNode {
435 turn_id,
436 timestamp,
437 tool_calls,
438 delegation_to,
439 trace_event,
440 turn_index: 0,
441 user_message_tokens: 0,
442 assistant_response_tokens: 0,
443 tools_invoked,
444 persona_signals_emitted: Vec::new(),
445 sentiment: None,
446 flagged: false,
447 conversation_id: String::new(),
448 follows_episode_id: None,
449 user_message: None,
450 assistant_response: None,
451 };
452 Self::base(
453 MemoryCategory::Episodic,
454 default_importance_score(),
455 String::new(),
456 AinlNodeType::Episode { episodic },
457 )
458 }
459
460 pub fn new_fact(fact: String, confidence: f32, source_turn_id: Uuid) -> Self {
462 let semantic = SemanticNode {
463 fact,
464 confidence,
465 source_turn_id,
466 topic_cluster: None,
467 source_episode_id: String::new(),
468 contradiction_ids: Vec::new(),
469 last_referenced_at: 0,
470 reference_count: 0,
471 decay_eligible: true,
472 tags: Vec::new(),
473 recurrence_count: 0,
474 last_ref_snapshot: 0,
475 };
476 Self::base(
477 MemoryCategory::Semantic,
478 default_importance_score(),
479 String::new(),
480 AinlNodeType::Semantic { semantic },
481 )
482 }
483
484 pub fn new_pattern(pattern_name: String, compiled_graph: Vec<u8>) -> Self {
486 let mut procedural = ProceduralNode {
487 pattern_name,
488 compiled_graph,
489 tool_sequence: Vec::new(),
490 confidence: None,
491 procedure_type: ProcedureType::default(),
492 trigger_conditions: Vec::new(),
493 success_count: 0,
494 failure_count: 0,
495 success_rate: default_success_rate(),
496 last_invoked_at: 0,
497 reinforcement_episode_ids: Vec::new(),
498 suppression_episode_ids: Vec::new(),
499 patch_version: 1,
500 fitness: None,
501 declared_reads: Vec::new(),
502 retired: false,
503 label: String::new(),
504 };
505 procedural.recompute_success_rate();
506 Self::base(
507 MemoryCategory::Procedural,
508 default_importance_score(),
509 String::new(),
510 AinlNodeType::Procedural { procedural },
511 )
512 }
513
514 pub fn new_procedural_tools(
516 pattern_name: String,
517 tool_sequence: Vec<String>,
518 confidence: f32,
519 ) -> Self {
520 let mut procedural = ProceduralNode {
521 pattern_name,
522 compiled_graph: Vec::new(),
523 tool_sequence,
524 confidence: Some(confidence),
525 procedure_type: ProcedureType::ToolSequence,
526 trigger_conditions: Vec::new(),
527 success_count: 0,
528 failure_count: 0,
529 success_rate: default_success_rate(),
530 last_invoked_at: 0,
531 reinforcement_episode_ids: Vec::new(),
532 suppression_episode_ids: Vec::new(),
533 patch_version: 1,
534 fitness: None,
535 declared_reads: Vec::new(),
536 retired: false,
537 label: String::new(),
538 };
539 procedural.recompute_success_rate();
540 Self::base(
541 MemoryCategory::Procedural,
542 default_importance_score(),
543 String::new(),
544 AinlNodeType::Procedural { procedural },
545 )
546 }
547
548 pub fn new_persona(trait_name: String, strength: f32, learned_from: Vec<Uuid>) -> Self {
550 let persona = PersonaNode {
551 trait_name,
552 strength,
553 learned_from,
554 layer: PersonaLayer::default(),
555 source: PersonaSource::default(),
556 strength_floor: default_strength_floor(),
557 locked: false,
558 relevance_score: 0.0,
559 provenance_episode_ids: Vec::new(),
560 evolution_log: Vec::new(),
561 axis_scores: HashMap::new(),
562 evolution_cycle: 0,
563 last_evolved: String::new(),
564 agent_id: String::new(),
565 dominant_axes: Vec::new(),
566 };
567 Self::base(
568 MemoryCategory::Persona,
569 default_importance_score(),
570 String::new(),
571 AinlNodeType::Persona { persona },
572 )
573 }
574
575 pub fn episodic(&self) -> Option<&EpisodicNode> {
576 match &self.node_type {
577 AinlNodeType::Episode { episodic } => Some(episodic),
578 _ => None,
579 }
580 }
581
582 pub fn semantic(&self) -> Option<&SemanticNode> {
583 match &self.node_type {
584 AinlNodeType::Semantic { semantic } => Some(semantic),
585 _ => None,
586 }
587 }
588
589 pub fn procedural(&self) -> Option<&ProceduralNode> {
590 match &self.node_type {
591 AinlNodeType::Procedural { procedural } => Some(procedural),
592 _ => None,
593 }
594 }
595
596 pub fn persona(&self) -> Option<&PersonaNode> {
597 match &self.node_type {
598 AinlNodeType::Persona { persona } => Some(persona),
599 _ => None,
600 }
601 }
602
603 pub fn add_edge(&mut self, target_id: Uuid, label: impl Into<String>) {
605 self.edges.push(AinlEdge {
606 target_id,
607 label: label.into(),
608 });
609 }
610}