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)]
15#[schemars(transform = crate::schema::strip_int_formats)]
16pub struct Link {
17 /// Id of the memory being linked to.
18 pub target: u64,
19 /// Relationship label (e.g. `"decided_in"`, `"references"`, `"depends_on"`).
20 pub relation: String,
21}
22
23/// One semantically recalled memory.
24#[derive(Debug, Clone, Serialize, JsonSchema)]
25#[schemars(transform = crate::schema::strip_int_formats)]
26pub struct Recollection {
27 /// Stable id of the memory.
28 pub id: u64,
29 /// Similarity score (higher is closer).
30 pub score: f32,
31 /// Stored fact content.
32 pub content: String,
33}
34
35/// Comparison operator for a [`ColumnFilter`] in
36/// [`MemoryService::recall_where`](crate::service::MemoryService::recall_where).
37#[derive(Debug, Clone, Copy, Deserialize, JsonSchema)]
38#[serde(rename_all = "lowercase")]
39pub enum ColumnOp {
40 /// `=`
41 Eq,
42 /// `!=`
43 Ne,
44 /// `<`
45 Lt,
46 /// `<=`
47 Le,
48 /// `>`
49 Gt,
50 /// `>=`
51 Ge,
52}
53
54impl ColumnOp {
55 /// The `VelesQL` operator token.
56 #[must_use]
57 pub(crate) fn as_sql(self) -> &'static str {
58 match self {
59 Self::Eq => "=",
60 Self::Ne => "!=",
61 Self::Lt => "<",
62 Self::Le => "<=",
63 Self::Gt => ">",
64 Self::Ge => ">=",
65 }
66 }
67}
68
69/// A structured predicate over a memory's metadata column, for the fused
70/// vector+`ColumnStore` recall
71/// [`MemoryService::recall_where`](crate::service::MemoryService::recall_where).
72/// Unlike the exact-match filter on
73/// [`MemoryService::recall`](crate::service::MemoryService::recall), this supports
74/// ranges and comparisons (e.g. `timestamp >= …`), so temporal and numeric facets
75/// become queryable, not just equal-matchable.
76#[derive(Debug, Clone, Deserialize, JsonSchema)]
77pub struct ColumnFilter {
78 /// Metadata field name (alphanumeric/underscore).
79 pub field: String,
80 /// Comparison operator.
81 pub op: ColumnOp,
82 /// Value to compare against (numbers, strings, booleans).
83 pub value: Value,
84}
85
86/// A node in an [`Explanation`] subgraph.
87#[derive(Debug, Clone, Serialize, JsonSchema)]
88#[schemars(transform = crate::schema::strip_int_formats)]
89pub struct MemoryNode {
90 /// Stable id of the memory.
91 pub id: u64,
92 /// Stored fact content.
93 pub content: String,
94 /// Distance in hops from the seed memory (the seed is hop `0`).
95 pub hop: usize,
96}
97
98/// A typed edge in an [`Explanation`] subgraph.
99#[derive(Debug, Clone, Serialize, JsonSchema)]
100#[schemars(transform = crate::schema::strip_int_formats)]
101pub struct MemoryEdge {
102 /// Source memory id.
103 pub from: u64,
104 /// Target memory id.
105 pub to: u64,
106 /// Relationship label.
107 pub relation: String,
108}
109
110/// The connected answer to a `why` question: the best-matching seed memory plus
111/// everything reachable from it within a hop budget. This connected subgraph is
112/// the differentiator — it surfaces related memories a purely vector recall is
113/// blind to (no textual similarity required).
114#[derive(Debug, Clone, Default, Serialize, JsonSchema)]
115pub struct Explanation {
116 /// Memories in the subgraph, seed first.
117 pub nodes: Vec<MemoryNode>,
118 /// Typed edges connecting the nodes.
119 pub edges: Vec<MemoryEdge>,
120}