Skip to main content

kmp_application/memory/
types.rs

1use kmp_domain::{
2    DimensionSelection, ResolutionTier, TemporalAxis, TemporalCoordinate, TemporalCursor,
3    TemporalDirection, TemporalInterval, TemporalSelection, TemporalWindow,
4};
5
6use crate::queries::{GetNodeDetailResult, GraphRelationshipView};
7
8pub const DEFAULT_TRACE_PAGE_ENTRIES: usize = 64;
9/// A relate page counts facts, declared relations, coordinate relations and
10/// tensions alike; facts carry their text, so the page is kept smaller than
11/// a trace page.
12pub const DEFAULT_RELATE_PAGE_ENTRIES: usize = 32;
13pub const MAX_RELATE_PAGE_ENTRIES: usize = 256;
14pub const MAX_TRACE_PAGE_ENTRIES: usize = 256;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct MemoryIngestCommand {
18    pub about: String,
19    pub memory: MemoryData,
20    pub provenance: Option<MemoryProvenanceData>,
21    pub idempotency_key: String,
22    pub dry_run: bool,
23    pub label_policy: LabelPolicy,
24    /// Writer diagnostics attached to the accepted command, never semantic memory.
25    pub receipt_context: Option<serde_json::Value>,
26    /// Resolve missing observation only for this semantic write, at ingestion.
27    pub default_observation_to_ingestion: bool,
28    /// None: canonical ingest. Some(""): request semantic context review.
29    /// Some(token): acknowledge the exact previously served neighborhood.
30    pub neighborhood_review: Option<String>,
31}
32
33/// What an ingest does with a dimension that resembles a label the about
34/// already holds: the same identifier up to case and separators, or the
35/// same value under another key.
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
37pub enum LabelPolicy {
38    /// Write it and say so: `warnings` and `resembling_labels` name the
39    /// match, so vocabulary drift is seen when it happens.
40    #[default]
41    Warn,
42    /// Refuse the ingest naming the match, unless the dimension carries the
43    /// metadata that says the writer read the catalogue and insists.
44    Refuse,
45}
46
47/// A label a write named beside the existing label it resembles.
48#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
49pub struct ResemblingLabelData {
50    pub key: String,
51    pub value: String,
52    pub existing_key: String,
53    pub existing_value: String,
54    pub kind: String,
55    pub why: String,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
59pub struct MemoryData {
60    pub dimensions: Vec<MemoryDimensionData>,
61    pub entries: Vec<MemoryEntryData>,
62    pub relations: Vec<MemoryRelationData>,
63    pub evidence: Vec<MemoryEvidenceData>,
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
67pub struct MemoryDimensionData {
68    pub id: String,
69    pub kind: String,
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub title: Option<String>,
72    #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
73    pub metadata: std::collections::BTreeMap<String, String>,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
77pub struct MemoryEntryData {
78    pub id: String,
79    pub kind: String,
80    pub text: String,
81    pub coordinates: Vec<MemoryCoordinateData>,
82    #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
83    pub metadata: std::collections::BTreeMap<String, String>,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
87pub struct MemoryCoordinateData {
88    pub dimension: String,
89    pub scope_id: String,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub occurred_at: Option<String>,
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub observed_at: Option<String>,
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub ingested_at: Option<String>,
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub valid_from: Option<String>,
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub valid_until: Option<String>,
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub sequence: Option<u32>,
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub rank: Option<u32>,
104    #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
105    pub metadata: std::collections::BTreeMap<String, String>,
106}
107
108#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
109pub struct MemoryRelationData {
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub clocks: Option<super::MemoryRelationClocks>,
112    #[serde(rename = "from")]
113    pub source_ref: String,
114    #[serde(rename = "to")]
115    pub target_ref: String,
116    pub rel: String,
117    #[serde(rename = "class")]
118    pub semantic_class: String,
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub why: Option<String>,
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub evidence: Option<String>,
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub confidence: Option<String>,
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub sequence: Option<u32>,
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub motivation: Option<String>,
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub method: Option<String>,
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub decision_id: Option<String>,
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub caused_by_node_id: Option<String>,
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub coordinate: Option<MemoryCoordinateData>,
137}
138
139#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
140pub struct MemoryEvidenceData {
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub support_clocks: Option<super::EvidenceSupportClocks>,
143    pub id: String,
144    pub supports: Vec<String>,
145    pub text: String,
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub source: Option<String>,
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub time: Option<String>,
150    #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
151    pub metadata: std::collections::BTreeMap<String, String>,
152}
153
154#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
155pub struct MemoryProvenanceData {
156    pub source_kind: String,
157    pub source_agent: String,
158    pub observed_at: Option<String>,
159    pub correlation_id: Option<String>,
160    pub causation_id: Option<String>,
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
164pub struct MemoryAcceptedCounts {
165    pub entries: usize,
166    pub relations: usize,
167    pub evidence: usize,
168}
169
170#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct MemoryIngestOutcome {
172    pub neighborhood: Option<super::WriteNeighborhood>,
173    pub replayed: bool,
174    pub clocks: Option<super::WriteClocks>,
175    pub receipt_ref: Option<String>,
176    pub about: String,
177    pub memory_id: String,
178    pub accepted: MemoryAcceptedCounts,
179    pub read_after_write_ready: bool,
180    pub warnings: Vec<String>,
181    /// The dimension nodes this ingest declares for the first time, as
182    /// namespaced ids: the labels the write created rather than reused.
183    pub created_dimensions: Vec<String>,
184    /// Labels this ingest declared that resemble one the about already
185    /// holds, written under `LabelPolicy::Warn`.
186    pub resembling_labels: Vec<ResemblingLabelData>,
187}
188
189#[derive(Debug, Clone, PartialEq, Eq)]
190pub struct WakeMemoryQuery {
191    pub about: String,
192    pub role: String,
193    pub intent: String,
194    pub dimensions: DimensionSelection,
195    pub token_budget: u32,
196    pub depth: u32,
197    pub max_tier: Option<ResolutionTier>,
198    /// Cap on surfaced proof.evidence entries (None = unbounded). When set and
199    /// the about has more, Wake returns the first `max_entries` and reports the
200    /// withheld count via proof.frontier_size so the client near-expands.
201    pub max_entries: Option<usize>,
202    /// Which instants the packet stands on: the memory's frontier, one
203    /// instant, or a half-open span on one clock.
204    pub temporal: TemporalSelection,
205}
206
207#[derive(Debug, Clone, PartialEq, Eq)]
208pub struct AskMemoryQuery {
209    pub about: String,
210    pub question: String,
211    /// The user's own words when `question` is the agent's rendering of them
212    /// in the kernel's search language. Searched never; echoed and read
213    /// against the question so a rendering that lost something says so.
214    pub asked_as: Option<String>,
215    pub answer_policy: MemoryAnswerPolicy,
216    pub dimensions: DimensionSelection,
217    pub token_budget: u32,
218    pub depth: u32,
219    pub max_tier: Option<ResolutionTier>,
220    /// Cap on answer evidence entries after relevance filtering.
221    pub max_entries: Option<usize>,
222    /// Which instants the answer stands on: the memory's frontier, one
223    /// instant, or a half-open span on one clock. Only what the selection
224    /// admits competes, and the lifecycles are read as they stood then.
225    pub temporal: TemporalSelection,
226}
227
228#[derive(Debug, Clone, PartialEq, Eq)]
229pub struct TemporalMemoryQuery {
230    pub about: String,
231    pub direction: TemporalDirection,
232    pub entry_selection: Option<kmp_domain::TemporalEntrySelection>,
233    pub axis: TemporalAxis,
234    pub cursor: Option<TemporalCursor>,
235    pub interval: Option<TemporalInterval>,
236    pub dimensions: DimensionSelection,
237    pub window: TemporalWindow,
238    pub limit_entries: Option<usize>,
239    pub include: TemporalIncludeOptions,
240    pub token_budget: u32,
241    pub depth: u32,
242    pub max_tier: Option<ResolutionTier>,
243}
244
245/// What memories of several abouts have to do with each other: the abouts
246/// the selection names or resolves, read within one span on one clock.
247#[derive(Debug, Clone, PartialEq, Eq)]
248pub struct RelateMemoryQuery {
249    pub about: String,
250    pub dimensions: DimensionSelection,
251    /// The span and clock the facts fall within; the memory's frontier when
252    /// the caller named none.
253    pub temporal: TemporalSelection,
254    pub token_budget: u32,
255    pub depth: u32,
256    pub max_tier: Option<ResolutionTier>,
257    pub page: RelatePageRequest,
258}
259
260#[derive(Debug, Clone, PartialEq, Eq, Default)]
261pub struct RelatePageRequest {
262    pub entries: Option<usize>,
263    pub cursor: Option<usize>,
264}
265
266impl RelatePageRequest {
267    pub fn offset(&self) -> usize {
268        self.cursor.unwrap_or_default()
269    }
270
271    pub fn entries_or_default(&self) -> usize {
272        self.entries.unwrap_or(DEFAULT_RELATE_PAGE_ENTRIES)
273    }
274}
275
276#[derive(Debug, Clone, PartialEq, Eq)]
277pub struct TraceMemoryQuery {
278    pub about: String,
279    pub from: String,
280    pub to: String,
281    pub role: String,
282    pub token_budget: u32,
283    pub page: TracePageRequest,
284}
285
286#[derive(Debug, Clone, PartialEq, Eq, Default)]
287pub struct TracePageRequest {
288    pub entries: Option<usize>,
289    pub cursor: Option<usize>,
290}
291
292impl TracePageRequest {
293    pub fn offset(&self) -> usize {
294        self.cursor.unwrap_or_default()
295    }
296
297    pub fn entries_or_default(&self) -> usize {
298        self.entries.unwrap_or(DEFAULT_TRACE_PAGE_ENTRIES)
299    }
300}
301
302#[derive(Debug, Clone, PartialEq, Eq)]
303pub struct InspectMemoryQuery {
304    pub about: String,
305    pub ref_id: String,
306    pub include_details: bool,
307    pub include_incoming: bool,
308    pub include_outgoing: bool,
309    pub include_raw: bool,
310    /// The body revision the caller declares it wants. `None` inspects
311    /// whatever is current. `Some` turns this read into an exact expansion:
312    /// the store holds one body version per node, so it either is that
313    /// revision or the read is a conflict naming the one that is there.
314    pub expect_revision: Option<u64>,
315}
316
317#[derive(Clone, PartialEq, Eq)]
318pub struct InspectMemoryResult {
319    pub detail: GetNodeDetailResult,
320    pub incoming: Vec<GraphRelationshipView>,
321    pub outgoing: Vec<GraphRelationshipView>,
322    pub evidence: Vec<InspectedEvidence>,
323    pub raw_coordinates: Vec<TemporalCoordinate>,
324    pub include_details: bool,
325    pub include_raw: bool,
326}
327
328#[derive(Clone, PartialEq, Eq)]
329pub struct InspectedEvidence {
330    pub detail: GetNodeDetailResult,
331    pub supports: Vec<String>,
332}
333
334#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
335pub enum MemoryAnswerPolicy {
336    #[default]
337    EvidenceOrUnknown,
338    ShowConflicts,
339    BestEffort,
340}
341
342#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
343pub struct TemporalIncludeOptions {
344    pub evidence: bool,
345    pub relations: bool,
346    pub raw_refs: bool,
347    /// Include bounded, evidenced memory dependencies with their stored bodies.
348    /// Implies evidence and relations; does not change temporal entry selection.
349    pub dependencies: bool,
350}
351
352#[derive(Debug, Clone, PartialEq)]
353pub struct TemporalMemoryResult {
354    pub traversal: kmp_domain::TemporalTraversalResult,
355    /// Scoped admission catalogue with canonical nodes, support relations and
356    /// bodies materialized for this page's proof. Unselected source payloads
357    /// are absent; this bundle is not a fully materialized graph export.
358    pub source_bundle: kmp_domain::KmpBundle,
359    pub include: TemporalIncludeOptions,
360}
361
362/// One label an entry stands in, as the pair a reader names it by: `key`
363/// is the dimension kind, `value` the bare scope id.
364#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)]
365pub struct EntryLabelData {
366    pub key: String,
367    pub value: String,
368}
369
370/// Change the labels an entry stands in without rewriting its text: labels
371/// to add, labels to take off, and why. Translated against what the about
372/// holds, like an ingest, into one change the log keeps.
373#[derive(Debug, Clone, PartialEq, Eq)]
374pub struct MemoryRelabelCommand {
375    pub about: String,
376    pub ref_id: String,
377    pub add: Vec<EntryLabelData>,
378    pub remove: Vec<EntryLabelData>,
379    pub why: String,
380    pub provenance: Option<MemoryProvenanceData>,
381    pub idempotency_key: String,
382    pub dry_run: bool,
383    pub label_policy: LabelPolicy,
384    /// Label keys the writer insists are new even where the catalogue holds
385    /// one that resembles them: it read the catalogue and means something
386    /// else. Those labels are left out of the resemblance check.
387    pub intended_new: std::collections::BTreeSet<String>,
388}
389
390#[derive(Debug, Clone, PartialEq, Eq)]
391pub struct MemoryRelabelOutcome {
392    pub about: String,
393    pub ref_id: String,
394    pub added: Vec<EntryLabelData>,
395    pub removed: Vec<EntryLabelData>,
396    /// Every label the entry stands in after this relabel, by key then value.
397    pub labels: Vec<EntryLabelData>,
398    /// The dimension nodes this relabel declared for the first time, as
399    /// namespaced ids: the labels it created rather than reused.
400    pub created_dimensions: Vec<String>,
401    /// Labels this relabel added that resemble one the about already holds,
402    /// written under `LabelPolicy::Warn`.
403    pub resembling_labels: Vec<ResemblingLabelData>,
404    pub read_after_write_ready: bool,
405    pub warnings: Vec<String>,
406}