Skip to main content

kmp_application/memory/
types.rs

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