mnemo/lib.rs
1//! # Mnemo
2//!
3//! Mnemo is an **encrypted, single-file, portable agent-memory engine**.
4//!
5//! A whole memory store — vectors, content, metadata, and the multi-signal
6//! recall machinery an agent needs — lives in one file you can copy, back up,
7//! or hand to another process. The file is encrypted at rest with a two-tier
8//! key hierarchy: an Argon2id key-encryption key (KEK) derived from a
9//! passphrase wraps a random data-encryption key (DEK), and the DEK encrypts
10//! every page with AES-256-GCM.
11//!
12//! ## Quick start
13//!
14//! ```no_run
15//! use mnemo::{Mnemo, MnemoConfig, Memory, MemoryType, RecallRequest};
16//!
17//! # fn main() -> mnemo::Result<()> {
18//! let cfg = MnemoConfig { dimensions: 3, ..Default::default() };
19//! let mut db = Mnemo::create("agent.mnemo", "correct horse battery", cfg)?;
20//!
21//! db.remember(
22//! Memory::new("the user prefers dark mode", MemoryType::Semantic, vec![0.1, 0.2, 0.9])
23//! .with_agent("assistant-1")
24//! .with_importance(0.8),
25//! )?;
26//! db.flush()?;
27//!
28//! let hits = db.recall(&RecallRequest::new(vec![0.1, 0.2, 0.9]).top_k(5))?;
29//! for h in hits {
30//! println!("{:.3} {}", h.score, h.memory.content);
31//! }
32//! # Ok(())
33//! # }
34//! ```
35//!
36//! ## What is and is not built
37//!
38//! This crate implements a real, tested core: the encrypted single-file
39//! storage engine, the crypto layer, the agent-memory model, multi-signal
40//! recall, an IVF+PQ approximate-nearest-neighbour index, a write-ahead log,
41//! snapshot-based point-in-time recovery, a bounded LRU page cache, and the
42//! [`Session`] conversation wrapper. Exact brute-force search remains
43//! available as the ground-truth baseline; a built index makes
44//! [`Mnemo::recall`] sub-linear. Each [`Mnemo::flush`] is one atomic,
45//! WAL-committed transaction and a restorable snapshot — [`Mnemo::restore_to`]
46//! rewinds the database to any past transaction. Python and TypeScript
47//! language bindings are the one documented roadmap item — see the README.
48
49#![forbid(unsafe_code)]
50#![warn(missing_docs)]
51
52mod cache;
53mod crypto;
54mod embed;
55mod embed_cmd;
56mod error;
57mod format;
58mod index;
59pub mod mcp;
60mod memory;
61mod pager;
62mod result_cache;
63mod session;
64mod store;
65mod wal;
66
67pub use crypto::KdfParams;
68pub use embed::{Embedder, EmbedderIdentity};
69pub use embed_cmd::{EmbedCmd, DEFAULT_EMBED_TIMEOUT};
70pub use error::{MnemoError, Result};
71pub use index::{IndexConfig, IndexInfo};
72pub use memory::{Memory, MemoryType, Metric, Scope, ScoreWeights};
73pub use result_cache::{
74 CacheBudget, CacheFlushPolicy, CachePutOpts, CacheStats, CachedValue, SemanticCachePutOpts,
75 DEFAULT_BATCH_MAX_AGE, DEFAULT_BATCH_MAX_DIRTY, DEFAULT_MAX_BYTES, DEFAULT_MAX_ENTRIES,
76 DEFAULT_SEMANTIC_THRESHOLD,
77};
78pub use session::{Role, Session, Turn};
79pub use store::{
80 CompactReport, Mnemo, MnemoConfig, RecallRequest, RecallResult, SnapshotInfo, Stats,
81};
82
83/// Re-export of [`ulid::Ulid`], the identifier type used for memories.
84pub use ulid::Ulid;
85
86/// Test-only crash-injection surface used by the Phase 1.3 proptest
87/// (`mnemo/tests/proptest_store.rs`). Compiled in under `cfg(test)`
88/// and the `failpoints` feature only; absent from release builds.
89///
90/// **Not part of the stable public API.** The double-underscore
91/// convention makes the "internal" contract obvious at the call site.
92#[cfg(any(test, feature = "failpoints"))]
93#[doc(hidden)]
94pub mod __failpoints {
95 /// Arm the pager and WAL commit path to fail on the Nth
96 /// subsequent write. `n = 0` fails the very next write; `n = -1`
97 /// (or any negative) disables. One-shot — disarms on trigger.
98 ///
99 /// Every write is checked before the byte hits the file, so a
100 /// triggered failure is guaranteed torn-BEFORE, never
101 /// torn-AFTER — critical for the "either pre-flush or post-flush"
102 /// invariant the property test asserts.
103 pub fn set_writes_until_fail(n: i64) {
104 crate::pager::failpoints::set_writes_until_fail(n)
105 }
106}
107
108/// Internal parse surfaces exposed for cargo-fuzz targets (Phase 1.2).
109///
110/// **Not part of the stable public API.** Each function feeds an
111/// arbitrary byte slice into a parse/decode path that a hostile
112/// `.mnemo` file (or a hostile MCP client) can send us, and returns
113/// `Result<()>` so a fuzz target only asserts "no panic." Marked
114/// `#[doc(hidden)]` so `cargo doc` doesn't advertise it; do not import
115/// from application code.
116#[doc(hidden)]
117pub mod __fuzz {
118 /// Parse a `.mnemo` header page (page 0, plaintext) from arbitrary
119 /// bytes. This is the pre-passphrase attack surface — a hostile
120 /// file hits it before any DEK exists.
121 pub fn parse_header(bytes: &[u8]) -> crate::Result<()> {
122 crate::format::Header::from_page(bytes).map(|_| ())
123 }
124
125 /// Replay the WAL scan over an arbitrary byte region. Delegates
126 /// to [`crate::wal::recover_bytes`]; a corrupt / truncated /
127 /// adversarial region must return `Err` or `Ok(None/Some(_))`,
128 /// never panic.
129 pub fn wal_recover_bytes(region: &[u8], wal_seq: u64) -> crate::Result<()> {
130 crate::wal::recover_bytes(region, wal_seq).map(|_| ())
131 }
132
133 /// Decode a `Memory` record body from arbitrary bytes.
134 pub fn decode_memory(bytes: &[u8]) -> crate::Result<()> {
135 rmp_serde::from_slice::<crate::Memory>(bytes)
136 .map(|_| ())
137 .map_err(|e| crate::MnemoError::Serialize(e.to_string()))
138 }
139
140 /// Decode a `CacheEntry` record body from arbitrary bytes. Type is
141 /// `pub(crate)`, so this wrapper hides it behind a unit return.
142 pub fn decode_cache_entry(bytes: &[u8]) -> crate::Result<()> {
143 rmp_serde::from_slice::<crate::result_cache::CacheEntry>(bytes)
144 .map(|_| ())
145 .map_err(|e| crate::MnemoError::Serialize(e.to_string()))
146 }
147
148 /// Decode a cache-directory page-run body (a `Vec<CacheDirectoryEntry>`)
149 /// from arbitrary bytes.
150 pub fn decode_cache_directory(bytes: &[u8]) -> crate::Result<()> {
151 rmp_serde::from_slice::<Vec<crate::result_cache::CacheDirectoryEntry>>(bytes)
152 .map(|_| ())
153 .map_err(|e| crate::MnemoError::Serialize(e.to_string()))
154 }
155
156 /// Parse a single MCP stdio JSON-RPC request line. Malformed
157 /// input returns `Err(String)`; the fuzz target only asserts
158 /// no panic and that the server would not die on this line.
159 pub fn parse_mcp_request_line(line: &str) -> Result<(), String> {
160 crate::mcp::__fuzz_parse_request_line(line)
161 }
162}