Skip to main content

velesdb_memory/
lib.rs

1#![deny(unsafe_code)]
2//! # VelesDB-memory
3//!
4//! Local-first **memory** layer for AI agents, exposed through a single MCP
5//! server. This crate is the domain core: it maps nine memory operations onto
6//! `VelesDB`'s in-core Agent Memory SDK.
7//!
8//! | Operation           | Meaning                                            |
9//! |--------------------|-----------------------------------------------------|
10//! | `remember`          | store a fact (+ optional links to other memories)  |
11//! | `recall`            | semantic retrieval of similar facts                |
12//! | `recall_where`      | semantic retrieval filtered by metadata            |
13//! | `recall_fused`      | vector + graph fused retrieval                     |
14//! | `relate`            | create a typed edge between two memories           |
15//! | `forget`            | delete a memory                                    |
16//! | `why`               | recall + multi-hop graph traversal                 |
17//! | `feedback`          | reinforce or penalize a memory after use           |
18//! | `remember_extracted`| extract facts from raw text and auto-wire the graph|
19//!
20//! ## License boundary (non-negotiable)
21//!
22//! This crate exposes **memory semantics only** (results), never raw database
23//! capabilities (`query(velesql)`, `create_collection`, `upsert(vectors)`,
24//! `traverse(graph)`). Exposing the raw engine would constitute a "Substantial
25//! Set" of the Software's features and breach the `VelesDB` Core License 1.0
26//! (§1, No Hosted or Managed Service). See `VISION.md` §5 and `PLAN.md` Phase 4A.
27//!
28//! ## Generics at the core, `dyn` at the edges (doctrine)
29//!
30//! Two dispatch styles coexist in this crate, and the split is a rule, not an
31//! accident of history:
32//!
33//! - **Compile-time seams are generic parameters.**
34//!   [`service::MemoryService`]`<E: Embedder, S: FactStore>` is monomorphized
35//!   over its embedder and its storage backend: each consumer (native daemon,
36//!   WASM binding) compiles exactly the backend it uses, recall paths carry
37//!   no vtable, and a backend that cannot support an operation fails at
38//!   compile time instead of at a customer. Since #1959 the storage surface
39//!   is four facets ([`FactStore`], [`RecallStore`], [`GraphStore`],
40//!   [`ColumnStore`]; [`MemoryStore`] is their sum, kept as an alias), and
41//!   each service method carries the bound of the facet it consumes — so
42//!   "cannot support" is now judged per capability, not per backend.
43//! - **Runtime choices are type-erased once, at the edge.** A backend picked
44//!   by configuration — `VELESDB_MEMORY_EMBEDDER`, an `extractor` argument, a
45//!   bring-your-own reranker — crosses into the crate as [`DynEmbedder`],
46//!   [`DynExtractor`] or [`DynReranker`], built once at startup. The erasure
47//!   happens at construction, never inside an operation.
48//!
49//! A new abstraction follows the same test: chosen at compile time → generic
50//! parameter; chosen by configuration → a `Dyn*` alias resolved at startup.
51//! And an adapter over one of these traits forwards the **whole** trait —
52//! partial forwarding is how a binding silently loses a capability the server
53//! already publishes (the #1690–#1692 gap family), and is rejected in review.
54//! The storage facets refine the unit that rule applies to: an adapter picks
55//! which *facets* it serves, but each facet it implements is forwarded whole.
56
57/// Wall-clock "today" as a `YYYYMMDD` integer, read only by `remember`'s
58/// auto-date stamping (see [`storage::AUTO_DATE_FIELD`]) — never by the
59/// context compiler, which stays clock-free and deterministic. Internal:
60/// nothing outside the crate needs to read the clock directly.
61mod clock;
62/// The ONE `ColumnFilter` conformance table both `MemoryStore` backends run,
63/// so the native (`VelesQL`-translating) and WASM (payload-testing) paths
64/// cannot drift apart again (#1759). Deliberately NOT target-gated: the WASM
65/// backend is one of the two that must run it.
66pub mod column_filter_conformance;
67/// The optional TOML configuration file: one place to set every knob, with
68/// `command line > environment > file > default` precedence. Native-only —
69/// it reads the filesystem.
70#[cfg(not(target_arch = "wasm32"))]
71pub mod config;
72/// The deterministic context compiler (EPIC-P-070): classify, dedup, and pack
73/// caller-supplied context fragments under a token budget — no LLM, no cloud,
74/// every decision auditable. Gated behind the default `context` feature.
75#[cfg(feature = "context")]
76pub mod context;
77/// Format recalled facts as a chronological, date-prefixed timeline with a
78/// "now" anchor — the dated-context representation measured to lift temporal
79/// question answering, shipped as product behavior rather than a harness prompt.
80pub mod dated_context;
81pub mod embedder;
82/// Which embedding model filled a store, and whether the configured one can
83/// still read it. Gated on `persistence` because an unrecorded store is a
84/// directory on disk — see the module docs for why the *backend* is
85/// deliberately not part of the record.
86#[cfg(feature = "persistence")]
87pub mod embedding_provenance;
88pub mod error;
89#[cfg(feature = "persistence")]
90pub mod export;
91pub mod extract;
92/// Vector+graph score fusion — the ranking layer behind
93/// [`service::MemoryService::recall_fused`]. Internal: callers reach it only
94/// through that method.
95mod fusion;
96/// The streamable-HTTP transport (multi-client mode): lets several MCP
97/// clients share ONE `velesdb-memory` process instead of each spawning its
98/// own stdio process and fighting over the store's single-writer `flock`.
99/// Gated behind the (non-default) `http` feature — see the module docs and
100/// the crate README's "HTTP transport (multi-client)" section.
101#[cfg(feature = "http")]
102pub mod http;
103/// Synchronous retry + actionable failure reporting shared by the two blocking
104/// Ollama call sites ([`embedder`] and [`extract`]). Internal: it exists to make
105/// those two backends resilient, not to be a general-purpose retry API.
106#[cfg(any(feature = "embedder-http", feature = "extractor-http"))]
107mod http_retry;
108/// Content-addressed memory ids — internal; ids surface through the service API.
109pub(crate) mod id;
110/// Resource caps (DoS limits) shared by every adapter — the single source of
111/// truth for fact size, recall limit, and `why` hop depth.
112pub mod limits;
113/// Per-request observability, gated by `VELESDB_MEMORY_LOG` (#1780): silent
114/// by default, stderr only, never a payload. Rides the `mcp` feature with
115/// the server it observes.
116#[cfg(feature = "mcp")]
117pub mod logging;
118/// The MCP server transport. Gated behind the default `mcp` feature so library
119/// consumers (e.g. the language bindings) can depend on the memory core without
120/// pulling the `rmcp`/`tokio` server stack.
121#[cfg(feature = "mcp")]
122pub mod mcp;
123/// Read-only diagnosis of a store an embedding-model change made unopenable,
124/// and the feasibility proof the rebuild depends on (#1762). Never writes to
125/// the store it inspects.
126#[cfg(feature = "persistence")]
127pub mod migration;
128/// The domain data model — the value types the memory layer exchanges
129/// (`Link`, `Recollection`, `ColumnFilter`, `Explanation`, …), separate from the
130/// service that computes them.
131pub mod model;
132#[cfg(feature = "persistence")]
133mod mutation;
134
135/// Authenticated JSON over HTTP: the transport under every remote inference
136/// backend, with no knowledge of role or vendor.
137#[cfg(any(feature = "embedder-http", feature = "extractor-http"))]
138pub mod http_client;
139
140/// The OpenAI-compatible protocol — paths, bodies, responses — over
141/// [`http_client`].
142#[cfg(any(feature = "embedder-http", feature = "extractor-http"))]
143mod openai;
144/// Is a configured remote inference backend actually reachable? (#1751 D2)
145///
146/// Gated exactly like [`openai`], which it builds its URL with, and like the
147/// `ureq` agent it probes through: without either role's feature there is no
148/// remote backend to be unreachable, and no transport to ask with. Declaring
149/// it unconditionally compiled here and nowhere else — the default build has
150/// neither dependency.
151#[cfg(any(feature = "embedder-http", feature = "extractor-http"))]
152pub mod reachability;
153/// Where a remote embedding/extraction backend's URL, model and credential
154/// come from, resolved from the environment once so the daemon and the
155/// language bindings read the same variables the same way (#1886).
156#[cfg(any(feature = "embedder-http", feature = "extractor-http"))]
157pub mod remote_endpoint;
158/// Optional second-stage re-scoring of a fused recall pool (bring your own
159/// cross-encoder/LLM). Never wired in by default — see [`rerank::Reranker`].
160pub mod rerank;
161/// Shared JSON Schema post-processing (strips `schemars`' non-standard integer
162/// `format` keywords so strict MCP clients don't warn on every id field).
163mod schema;
164pub mod service;
165/// The storage backend abstraction — [`storage::MemoryStore`] and the
166/// default, file-backed [`storage::NativeStore`]. Implement `MemoryStore` to
167/// run the wedge over a different backend (e.g. an in-memory one for WASM).
168pub mod storage;
169/// Locally-generated TLS material (a cached self-signed CA + short-lived
170/// leaf certs) for the streamable-HTTP transport's HTTPS-by-default
171/// listener — see the module docs for the full design rationale. Gated
172/// behind `http` since it exists only to serve that transport.
173#[cfg(feature = "http")]
174pub mod tls;
175/// Shared defensive deserialization for non-string scalar and structured
176/// inputs whose client-side schema can degrade to untyped JSON.
177#[cfg(any(feature = "mcp", feature = "context"))]
178mod wire;
179
180/// Default embedding dimension — the single source of truth, taken from the
181/// SDK's own default so the server, library, and tests never restate the
182/// value. `velesdb_core::agent` (where the canonical constant lives) is
183/// itself `persistence`-gated, so a `persistence`-free build (e.g.
184/// `velesdb-wasm`) falls back to `FALLBACK_DIMENSION`.
185#[cfg(feature = "persistence")]
186pub const DEFAULT_DIMENSION: usize = velesdb_core::agent::DEFAULT_DIMENSION;
187#[cfg(not(feature = "persistence"))]
188pub const DEFAULT_DIMENSION: usize = FALLBACK_DIMENSION;
189
190/// The hand-written value the `persistence`-free arm of
191/// [`DEFAULT_DIMENSION`] falls back to (the canonical constant's module is
192/// feature-gated away there). The `persistence` build — CI's default —
193/// statically asserts it still equals the canonical value, so drift fails
194/// to compile instead of silently splitting the wasm default dimension
195/// from the native one.
196const FALLBACK_DIMENSION: usize = 384;
197#[cfg(feature = "persistence")]
198const _: () = assert!(
199    FALLBACK_DIMENSION == velesdb_core::agent::DEFAULT_DIMENSION,
200    "update FALLBACK_DIMENSION to match velesdb_core::agent::DEFAULT_DIMENSION"
201);
202
203#[cfg(feature = "context")]
204pub use context::ContextCompiler;
205pub use dated_context::{format_dated_context, DatedContext};
206pub use embedder::{
207    select_embedder, DynEmbedder, EmbedError, Embedder, EmbedderSelection, HashEmbedder,
208    HASH_EMBEDDER_NOTICE,
209};
210#[cfg(feature = "embedder-http")]
211pub use embedder::{OllamaEmbedder, OpenAiEmbedder, DEFAULT_OLLAMA_MODEL, DEFAULT_OLLAMA_URL};
212pub use error::{ErrorCategory, MemoryError};
213pub use extract::{
214    select_extractor, DynExtractor, ExtractError, ExtractedAttribute, ExtractedFact,
215    ExtractedRelation, Extraction, Extractor, ExtractorSelection, OutlineExtractor,
216};
217#[cfg(feature = "extractor-http")]
218pub use extract::{OllamaExtractor, OpenAiExtractor};
219#[cfg(any(feature = "embedder-http", feature = "extractor-http"))]
220pub use http_client::{Auth, HttpJsonClient};
221#[cfg(feature = "mcp")]
222pub use mcp::McpServer;
223pub use model::{
224    column_value_matches, BoundedMemoryEdges, ColumnFilter, ColumnOp, EntityProfile,
225    EntityRelation, Explanation, FusionOptions, Link, MemoryEdge, MemoryNode, Recollection,
226    RememberedExtraction, UnrelateOutcome,
227};
228#[cfg(feature = "embedder-http")]
229pub use remote_endpoint::embedder_env_endpoint;
230#[cfg(any(feature = "embedder-http", feature = "extractor-http"))]
231pub use remote_endpoint::{role_auth, RemoteEndpoint};
232pub use rerank::{DynReranker, RerankError, Reranker};
233pub use service::{AutographWorkerHandle, MemoryService, Metadata};
234#[cfg(feature = "persistence")]
235pub use storage::NativeStore;
236pub use storage::{ColumnStore, FactStore, GraphStore, MemoryStore, RecallStore, AUTO_DATE_FIELD};