velesdb_memory/model.rs
1//! Domain data model: the request/response value types of the memory layer.
2//!
3//! These are pure data — the shapes a caller links, recalls, filters on, and
4//! gets back — with no dependency on [`MemoryService`](crate::service::MemoryService)
5//! itself. Keeping them here separates *what the memory layer exchanges* from
6//! *how the service computes it*, and gives every adapter (MCP, bindings) one
7//! canonical place to import the contract from.
8
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12
13/// A typed link from a freshly remembered fact to an existing memory.
14#[derive(Debug, Clone, Deserialize, JsonSchema)]
15pub struct Link {
16 /// Id of the memory being linked to.
17 pub target: u64,
18 /// Relationship label (e.g. `"decided_in"`, `"references"`, `"depends_on"`).
19 pub relation: String,
20}
21
22/// One semantically recalled memory.
23#[derive(Debug, Clone, Serialize, JsonSchema)]
24pub struct Recollection {
25 /// Stable id of the memory.
26 pub id: u64,
27 /// Similarity score (higher is closer).
28 pub score: f32,
29 /// Stored fact content.
30 pub content: String,
31}
32
33/// Comparison operator for a [`ColumnFilter`] in
34/// [`MemoryService::recall_where`](crate::service::MemoryService::recall_where).
35#[derive(Debug, Clone, Copy, Deserialize, JsonSchema)]
36#[serde(rename_all = "lowercase")]
37pub enum ColumnOp {
38 /// `=`
39 Eq,
40 /// `!=`
41 Ne,
42 /// `<`
43 Lt,
44 /// `<=`
45 Le,
46 /// `>`
47 Gt,
48 /// `>=`
49 Ge,
50}
51
52impl ColumnOp {
53 /// The `VelesQL` operator token.
54 pub(crate) fn as_sql(self) -> &'static str {
55 match self {
56 Self::Eq => "=",
57 Self::Ne => "!=",
58 Self::Lt => "<",
59 Self::Le => "<=",
60 Self::Gt => ">",
61 Self::Ge => ">=",
62 }
63 }
64}
65
66/// A structured predicate over a memory's metadata column, for the fused
67/// vector+`ColumnStore` recall
68/// [`MemoryService::recall_where`](crate::service::MemoryService::recall_where).
69/// Unlike the exact-match filter on
70/// [`MemoryService::recall`](crate::service::MemoryService::recall), this supports
71/// ranges and comparisons (e.g. `timestamp >= …`), so temporal and numeric facets
72/// become queryable, not just equal-matchable.
73#[derive(Debug, Clone, Deserialize, JsonSchema)]
74pub struct ColumnFilter {
75 /// Metadata field name (alphanumeric/underscore).
76 pub field: String,
77 /// Comparison operator.
78 pub op: ColumnOp,
79 /// Value to compare against (numbers, strings, booleans).
80 pub value: Value,
81}
82
83/// A node in an [`Explanation`] subgraph.
84#[derive(Debug, Clone, Serialize, JsonSchema)]
85pub struct MemoryNode {
86 /// Stable id of the memory.
87 pub id: u64,
88 /// Stored fact content.
89 pub content: String,
90 /// Distance in hops from the seed memory (the seed is hop `0`).
91 pub hop: usize,
92}
93
94/// A typed edge in an [`Explanation`] subgraph.
95#[derive(Debug, Clone, Serialize, JsonSchema)]
96pub struct MemoryEdge {
97 /// Source memory id.
98 pub from: u64,
99 /// Target memory id.
100 pub to: u64,
101 /// Relationship label.
102 pub relation: String,
103}
104
105/// The connected answer to a `why` question: the best-matching seed memory plus
106/// everything reachable from it within a hop budget. This connected subgraph is
107/// the differentiator — it surfaces related memories a purely vector recall is
108/// blind to (no textual similarity required).
109#[derive(Debug, Clone, Default, Serialize, JsonSchema)]
110pub struct Explanation {
111 /// Memories in the subgraph, seed first.
112 pub nodes: Vec<MemoryNode>,
113 /// Typed edges connecting the nodes.
114 pub edges: Vec<MemoryEdge>,
115}