Skip to main content

shodh_memory/mif/
schema.rs

1//! MIF v2 Schema — vendor-neutral types for memory interchange.
2//!
3//! Design principles:
4//! 1. Core layer is universal: content, timestamps, types, tags, metadata.
5//! 2. Knowledge graph uses property graph model (nodes + edges with properties).
6//! 3. UUIDs preserved across round-trips for lossless import/export.
7//! 4. Vendor extensions section for system-specific metadata (Hebbian state, decay, LTP).
8//! 5. All string enums use lowercase snake_case for cross-system compatibility.
9
10use chrono::{DateTime, Utc};
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use uuid::Uuid;
14
15// =============================================================================
16// TOP-LEVEL DOCUMENT
17// =============================================================================
18
19/// MIF v2 document — the top-level interchange container.
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct MifDocument {
22    pub mif_version: String,
23    pub generator: MifGenerator,
24    pub export_meta: MifExportMeta,
25    #[serde(default)]
26    pub memories: Vec<MifMemory>,
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub knowledge_graph: Option<MifKnowledgeGraph>,
29    #[serde(default)]
30    pub todos: Vec<MifTodo>,
31    #[serde(default)]
32    pub projects: Vec<MifProject>,
33    #[serde(default)]
34    pub reminders: Vec<MifReminder>,
35    /// Vendor-specific metadata. Key = vendor name (e.g., "shodh-memory").
36    /// Allows lossless round-trip of system-specific data without polluting the core schema.
37    #[serde(default)]
38    pub vendor_extensions: HashMap<String, serde_json::Value>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct MifGenerator {
43    pub name: String,
44    pub version: String,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct MifExportMeta {
49    pub id: String,
50    pub created_at: DateTime<Utc>,
51    pub user_id: String,
52    pub checksum: String,
53    #[serde(default, skip_serializing_if = "Option::is_none")]
54    pub privacy: Option<MifPrivacy>,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct MifPrivacy {
59    pub pii_detected: bool,
60    pub secrets_detected: bool,
61    #[serde(default)]
62    pub redacted_fields: Vec<String>,
63}
64
65// =============================================================================
66// MEMORIES
67// =============================================================================
68
69/// A single memory in vendor-neutral form.
70///
71/// All IDs are raw UUIDs — no `mem_` or `todo_` prefixes.
72/// Entity types are preserved (not "UNKNOWN").
73/// Embeddings are model-tagged for cross-system portability.
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct MifMemory {
76    pub id: Uuid,
77    pub content: String,
78    /// Lowercase type string: "observation", "decision", "learning", "error",
79    /// "discovery", "pattern", "context", "task", "code_edit", "file_access",
80    /// "search", "command", "conversation", "intention".
81    pub memory_type: String,
82    pub created_at: DateTime<Utc>,
83    #[serde(default)]
84    pub tags: Vec<String>,
85    #[serde(default)]
86    pub entities: Vec<MifEntityRef>,
87    #[serde(default)]
88    pub metadata: HashMap<String, String>,
89    #[serde(default, skip_serializing_if = "Option::is_none")]
90    pub embeddings: Option<MifEmbedding>,
91    #[serde(default, skip_serializing_if = "Option::is_none")]
92    pub source: Option<MifSource>,
93    #[serde(default, skip_serializing_if = "Option::is_none")]
94    pub parent_id: Option<Uuid>,
95    #[serde(default)]
96    pub related_memory_ids: Vec<Uuid>,
97    #[serde(default)]
98    pub related_todo_ids: Vec<Uuid>,
99    #[serde(default, skip_serializing_if = "Option::is_none")]
100    pub agent_id: Option<String>,
101    #[serde(default, skip_serializing_if = "Option::is_none")]
102    pub external_id: Option<String>,
103    #[serde(default = "default_version")]
104    pub version: u32,
105}
106
107fn default_version() -> u32 {
108    1
109}
110
111/// An entity mentioned in a memory, with its type preserved.
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct MifEntityRef {
114    pub name: String,
115    /// Lowercase entity type: "person", "organization", "location", "technology",
116    /// "concept", "event", "date", "product", "skill", "keyword", "unknown".
117    #[serde(default = "default_entity_type")]
118    pub entity_type: String,
119    #[serde(default = "default_confidence")]
120    pub confidence: f32,
121}
122
123fn default_entity_type() -> String {
124    "unknown".to_string()
125}
126
127fn default_confidence() -> f32 {
128    1.0
129}
130
131/// Model-tagged embedding vector for cross-system portability.
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct MifEmbedding {
134    pub model: String,
135    pub dimensions: usize,
136    pub vector: Vec<f32>,
137    #[serde(default = "default_true")]
138    pub normalized: bool,
139}
140
141fn default_true() -> bool {
142    true
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize, Default)]
146pub struct MifSource {
147    #[serde(default)]
148    pub source_type: String,
149    #[serde(default, skip_serializing_if = "Option::is_none")]
150    pub session_id: Option<String>,
151    #[serde(default, skip_serializing_if = "Option::is_none")]
152    pub agent: Option<String>,
153}
154
155// =============================================================================
156// KNOWLEDGE GRAPH
157// =============================================================================
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct MifKnowledgeGraph {
161    #[serde(default)]
162    pub entities: Vec<MifGraphEntity>,
163    #[serde(default)]
164    pub relationships: Vec<MifGraphRelationship>,
165    #[serde(default)]
166    pub episodes: Vec<MifGraphEpisode>,
167}
168
169/// A node in the knowledge graph.
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct MifGraphEntity {
172    pub id: Uuid,
173    pub name: String,
174    /// Lowercase type strings: ["person"], ["technology", "concept"], etc.
175    #[serde(default)]
176    pub types: Vec<String>,
177    #[serde(default)]
178    pub attributes: HashMap<String, String>,
179    #[serde(default)]
180    pub summary: String,
181    pub created_at: DateTime<Utc>,
182    pub last_seen_at: DateTime<Utc>,
183}
184
185/// An edge in the knowledge graph.
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct MifGraphRelationship {
188    pub id: Uuid,
189    pub source_entity_id: Uuid,
190    pub target_entity_id: Uuid,
191    /// Lowercase snake_case: "works_with", "part_of", "located_in", "causes", etc.
192    pub relation_type: String,
193    #[serde(default)]
194    pub context: String,
195    /// Normalized confidence/strength (0.0–1.0). Vendor-neutral.
196    #[serde(default, skip_serializing_if = "Option::is_none")]
197    pub confidence: Option<f32>,
198    pub created_at: DateTime<Utc>,
199    pub valid_at: DateTime<Utc>,
200    #[serde(default, skip_serializing_if = "Option::is_none")]
201    pub invalidated_at: Option<DateTime<Utc>>,
202}
203
204/// An episodic node — a temporal grouping of entities.
205#[derive(Debug, Clone, Serialize, Deserialize)]
206pub struct MifGraphEpisode {
207    pub id: Uuid,
208    pub name: String,
209    #[serde(default)]
210    pub content: String,
211    #[serde(default)]
212    pub entity_ids: Vec<Uuid>,
213    #[serde(default)]
214    pub source: String,
215    #[serde(default)]
216    pub metadata: HashMap<String, String>,
217    pub created_at: DateTime<Utc>,
218}
219
220// =============================================================================
221// TODOS
222// =============================================================================
223
224#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct MifTodo {
226    pub id: Uuid,
227    pub content: String,
228    /// "backlog", "todo", "in_progress", "blocked", "done", "cancelled"
229    pub status: String,
230    /// "urgent", "high", "medium", "low", "none"
231    pub priority: String,
232    pub created_at: DateTime<Utc>,
233    pub updated_at: DateTime<Utc>,
234    #[serde(default, skip_serializing_if = "Option::is_none")]
235    pub due_date: Option<DateTime<Utc>>,
236    #[serde(default, skip_serializing_if = "Option::is_none")]
237    pub completed_at: Option<DateTime<Utc>>,
238    #[serde(default, skip_serializing_if = "Option::is_none")]
239    pub project_id: Option<Uuid>,
240    #[serde(default, skip_serializing_if = "Option::is_none")]
241    pub parent_id: Option<Uuid>,
242    #[serde(default)]
243    pub tags: Vec<String>,
244    #[serde(default)]
245    pub contexts: Vec<String>,
246    #[serde(default, skip_serializing_if = "Option::is_none")]
247    pub notes: Option<String>,
248    #[serde(default, skip_serializing_if = "Option::is_none")]
249    pub blocked_on: Option<String>,
250    #[serde(default, skip_serializing_if = "Option::is_none")]
251    pub recurrence: Option<String>,
252    #[serde(default)]
253    pub comments: Vec<MifTodoComment>,
254    #[serde(default)]
255    pub related_memory_ids: Vec<Uuid>,
256    #[serde(default, skip_serializing_if = "Option::is_none")]
257    pub external_id: Option<String>,
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct MifTodoComment {
262    pub id: Uuid,
263    pub content: String,
264    /// "comment", "progress", "resolution", "activity"
265    #[serde(default = "default_comment_type")]
266    pub comment_type: String,
267    pub created_at: DateTime<Utc>,
268    #[serde(default, skip_serializing_if = "Option::is_none")]
269    pub author: Option<String>,
270}
271
272fn default_comment_type() -> String {
273    "comment".to_string()
274}
275
276// =============================================================================
277// PROJECTS
278// =============================================================================
279
280#[derive(Debug, Clone, Serialize, Deserialize)]
281pub struct MifProject {
282    pub id: Uuid,
283    pub name: String,
284    #[serde(default)]
285    pub prefix: String,
286    #[serde(default, skip_serializing_if = "Option::is_none")]
287    pub description: Option<String>,
288    /// "active", "archived", "completed"
289    #[serde(default = "default_project_status")]
290    pub status: String,
291    pub created_at: DateTime<Utc>,
292    #[serde(default, skip_serializing_if = "Option::is_none")]
293    pub color: Option<String>,
294    #[serde(default, skip_serializing_if = "Option::is_none")]
295    pub icon: Option<String>,
296}
297
298fn default_project_status() -> String {
299    "active".to_string()
300}
301
302// =============================================================================
303// REMINDERS
304// =============================================================================
305
306#[derive(Debug, Clone, Serialize, Deserialize)]
307pub struct MifReminder {
308    pub id: Uuid,
309    pub content: String,
310    pub trigger: MifTrigger,
311    /// "pending", "triggered", "dismissed", "expired"
312    pub status: String,
313    #[serde(default = "default_priority")]
314    pub priority: u8,
315    #[serde(default)]
316    pub tags: Vec<String>,
317    pub created_at: DateTime<Utc>,
318    #[serde(default, skip_serializing_if = "Option::is_none")]
319    pub triggered_at: Option<DateTime<Utc>>,
320    #[serde(default, skip_serializing_if = "Option::is_none")]
321    pub dismissed_at: Option<DateTime<Utc>>,
322}
323
324fn default_priority() -> u8 {
325    3
326}
327
328#[derive(Debug, Clone, Serialize, Deserialize)]
329#[serde(tag = "type", rename_all = "snake_case")]
330pub enum MifTrigger {
331    Time {
332        at: DateTime<Utc>,
333    },
334    Duration {
335        seconds: u64,
336        from: DateTime<Utc>,
337    },
338    Context {
339        keywords: Vec<String>,
340        #[serde(default = "default_threshold")]
341        threshold: f32,
342    },
343}
344
345fn default_threshold() -> f32 {
346    0.65
347}
348
349// =============================================================================
350// PII REDACTION RECORD
351// =============================================================================
352
353#[derive(Debug, Clone, Serialize, Deserialize)]
354pub struct MifRedaction {
355    pub redaction_type: String,
356    pub original_length: usize,
357    pub position: (usize, usize),
358}