oxios_memory/memory/storage.rs
1//! Storage abstraction for the memory subsystem.
2//!
3//! The memory crate does not depend on `oxios-kernel`. Instead, it
4//! operates against abstract traits. `oxios-kernel` implements
5//! these traits for its `StateStore` and `GitLayer`.
6
7use anyhow::Result;
8use async_trait::async_trait;
9use serde::de::DeserializeOwned;
10use serde::Serialize;
11use serde_json::Value;
12
13/// Persistent storage backend for memory entries.
14///
15/// Implemented by `oxios-kernel::StateStore` (file-based) and
16/// can be implemented by any other storage backend.
17///
18/// The core trait methods operate on `serde_json::Value` to remain
19/// dyn-compatible. Typed convenience helpers are provided as default
20/// methods that serialize/deserialize through `Value`.
21#[async_trait]
22pub trait MemoryStorage: Send + Sync {
23 /// Save a JSON value to a category/key.
24 async fn save_json_value(&self, category: &str, key: &str, value: &Value) -> Result<()>;
25
26 /// Load a JSON value from a category/key.
27 async fn load_json_value(&self, category: &str, key: &str) -> Result<Option<Value>>;
28
29 /// List all keys in a category.
30 async fn list_category(&self, category: &str) -> Result<Vec<String>>;
31
32 /// Delete a file by category/key. Returns true if the file existed.
33 async fn delete_file(&self, category: &str, key: &str) -> Result<bool>;
34}
35
36/// Typed helpers for `MemoryStorage`.
37///
38/// These are extension methods that serialize through `Value`, so the
39/// trait remains dyn-compatible.
40#[async_trait]
41pub trait MemoryStorageExt: MemoryStorage {
42 /// Save a typed serializable value as JSON.
43 async fn save_json<T: Serialize + Send + Sync>(
44 &self,
45 category: &str,
46 key: &str,
47 value: &T,
48 ) -> Result<()> {
49 let v = serde_json::to_value(value)?;
50 self.save_json_value(category, key, &v).await
51 }
52
53 /// Load a typed deserializable value from JSON.
54 async fn load_json<T: DeserializeOwned + Send>(
55 &self,
56 category: &str,
57 key: &str,
58 ) -> Result<Option<T>> {
59 match self.load_json_value(category, key).await? {
60 Some(v) => Ok(Some(serde_json::from_value(v)?)),
61 None => Ok(None),
62 }
63 }
64}
65
66impl<S: MemoryStorage + ?Sized> MemoryStorageExt for S {}
67
68/// Optional git-backed durability for memory entries.
69#[async_trait]
70pub trait MemoryGit: Send + Sync {
71 /// Commit a file to git.
72 async fn commit_file(&self, path: &str, message: &str) -> Result<()>;
73 /// Whether git integration is enabled.
74 fn is_enabled(&self) -> bool;
75}
76
77/// A single file/directory entry from a markdown knowledge base.
78#[derive(Debug, Clone)]
79pub struct NoteEntry {
80 /// Filename with extension (e.g., `"Rust.md"`).
81 pub name: String,
82 /// Parent directory (e.g., `"brain"` or `"/"`).
83 pub parent_dir: String,
84 /// Whether this entry is a directory.
85 pub is_dir: bool,
86}
87
88/// Abstract markdown knowledge source.
89///
90/// Implemented by `oxios_markdown::KnowledgeBase` in the kernel.
91/// Allows `auto_bridge` to read `.md` files without depending on
92/// `oxios-markdown` directly.
93pub trait MarkdownSource: Send + Sync {
94 /// Re-index all markdown files. Returns file count.
95 fn index_all(&self) -> Result<usize>;
96 /// List files/directories under `dir`.
97 fn note_tree(&self, dir: &str) -> Result<Vec<NoteEntry>>;
98 /// Read a note's content. Returns `None` if not found.
99 fn note_read(&self, path: &str) -> Result<Option<String>>;
100 /// Extract markdown headings from content.
101 fn extract_headings(&self, content: &str) -> Vec<String>;
102}