Skip to main content

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}