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