1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11pub struct MemoryNodeView {
12 pub node_id: String,
13 pub node_kind: String,
14 pub title: String,
15 pub summary: String,
16 pub status: String,
17 pub labels: Vec<String>,
18 pub properties: BTreeMap<String, String>,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23pub struct MemoryRelationshipView {
24 pub source_node_id: String,
25 pub target_node_id: String,
26 pub relationship_type: String,
27 pub why: Option<String>,
30 pub evidence: Option<String>,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36pub struct MemoryDetailView {
37 pub node_id: String,
38 pub detail: String,
39 pub content_hash: String,
40 pub revision: u64,
41}
42
43#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
50pub struct MemoryQualityView {
51 pub raw_equivalent_tokens: u32,
53 pub compression_ratio: f64,
54 pub causal_density: f64,
55 pub noise_ratio: f64,
56 pub detail_coverage: f64,
57}
58
59#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
65pub struct RenderedMemoryView {
66 pub content: String,
67 pub content_hash: String,
68 pub token_count: u32,
69 pub quality: MemoryQualityView,
70}
71
72#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
74pub struct MemoryRecallView {
75 pub about: String,
77 pub revision: u64,
81 pub content_hash: String,
84 pub root: MemoryNodeView,
85 pub neighbors: Vec<MemoryNodeView>,
86 pub relationships: Vec<MemoryRelationshipView>,
87 pub details: Vec<MemoryDetailView>,
88 pub rendered: RenderedMemoryView,
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 fn node(id: &str) -> MemoryNodeView {
96 MemoryNodeView {
97 node_id: id.to_string(),
98 node_kind: "decision".to_string(),
99 title: "First decision".to_string(),
100 summary: "What was decided".to_string(),
101 status: "active".to_string(),
102 labels: vec!["timeline".to_string()],
103 properties: BTreeMap::new(),
104 }
105 }
106
107 #[test]
108 fn a_recall_survives_the_wire() {
109 let view = MemoryRecallView {
110 about: "project:checkout".to_string(),
111 revision: 7,
112 content_hash: "snapshot-hash".to_string(),
113 root: node("about:project:checkout"),
114 neighbors: vec![node("decision:first")],
115 relationships: vec![MemoryRelationshipView {
116 source_node_id: "about:project:checkout".to_string(),
117 target_node_id: "decision:first".to_string(),
118 relationship_type: "contains".to_string(),
119 why: Some("the about holds its decisions".to_string()),
120 evidence: None,
121 }],
122 details: vec![MemoryDetailView {
123 node_id: "decision:first".to_string(),
124 detail: "The full text.".to_string(),
125 content_hash: "abc".to_string(),
126 revision: 1,
127 }],
128 rendered: RenderedMemoryView {
129 content: "# Context".to_string(),
130 content_hash: "def".to_string(),
131 token_count: 3,
132 quality: MemoryQualityView {
133 raw_equivalent_tokens: 12,
134 compression_ratio: 4.0,
135 causal_density: 0.5,
136 noise_ratio: 0.1,
137 detail_coverage: 0.9,
138 },
139 },
140 };
141 let bytes = serde_json::to_vec(&view).expect("serializes");
142 assert_eq!(
143 serde_json::from_slice::<MemoryRecallView>(&bytes).expect("deserializes"),
144 view
145 );
146 }
147}