kmp-application 0.18.2

Application services behind KMP's ten memory moves and evidence-backed recall
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
use kmp_domain::{
    DimensionSelection, ResolutionTier, TemporalAxis, TemporalCoordinate, TemporalCursor,
    TemporalDirection, TemporalInterval, TemporalSelection, TemporalWindow,
};

use crate::queries::{GetNodeDetailResult, GraphRelationshipView};

pub const DEFAULT_TRACE_PAGE_ENTRIES: usize = 64;
/// A relate page counts facts, declared relations, coordinate relations and
/// tensions alike; facts carry their text, so the page is kept smaller than
/// a trace page.
pub const DEFAULT_RELATE_PAGE_ENTRIES: usize = 32;
pub const MAX_RELATE_PAGE_ENTRIES: usize = 256;
pub const MAX_TRACE_PAGE_ENTRIES: usize = 256;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MemoryIngestCommand {
    pub about: String,
    pub memory: MemoryData,
    pub provenance: Option<MemoryProvenanceData>,
    pub idempotency_key: String,
    pub dry_run: bool,
    pub label_policy: LabelPolicy,
    /// Writer diagnostics attached to the accepted command, never semantic memory.
    pub receipt_context: Option<serde_json::Value>,
    /// Resolve missing observation only for this semantic write, at ingestion.
    pub default_observation_to_ingestion: bool,
    /// None: canonical ingest. Some(""): request semantic context review.
    /// Some(token): acknowledge the exact previously served neighborhood.
    pub neighborhood_review: Option<String>,
}

/// What an ingest does with a dimension that resembles a label the about
/// already holds: the same identifier up to case and separators, or the
/// same value under another key.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LabelPolicy {
    /// Write it and say so: `warnings` and `resembling_labels` name the
    /// match, so vocabulary drift is seen when it happens.
    #[default]
    Warn,
    /// Refuse the ingest naming the match, unless the dimension carries the
    /// metadata that says the writer read the catalogue and insists.
    Refuse,
}

/// A label a write named beside the existing label it resembles.
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct ResemblingLabelData {
    pub key: String,
    pub value: String,
    pub existing_key: String,
    pub existing_value: String,
    pub kind: String,
    pub why: String,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct MemoryData {
    pub dimensions: Vec<MemoryDimensionData>,
    pub entries: Vec<MemoryEntryData>,
    pub relations: Vec<MemoryRelationData>,
    pub evidence: Vec<MemoryEvidenceData>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct MemoryDimensionData {
    pub id: String,
    pub kind: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
    pub metadata: std::collections::BTreeMap<String, String>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct MemoryEntryData {
    pub id: String,
    pub kind: String,
    pub text: String,
    pub coordinates: Vec<MemoryCoordinateData>,
    #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
    pub metadata: std::collections::BTreeMap<String, String>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct MemoryCoordinateData {
    pub dimension: String,
    pub scope_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub occurred_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub observed_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ingested_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub valid_from: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub valid_until: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sequence: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub rank: Option<u32>,
    #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
    pub metadata: std::collections::BTreeMap<String, String>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct MemoryRelationData {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub clocks: Option<super::MemoryRelationClocks>,
    #[serde(rename = "from")]
    pub source_ref: String,
    #[serde(rename = "to")]
    pub target_ref: String,
    pub rel: String,
    #[serde(rename = "class")]
    pub semantic_class: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub why: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub evidence: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub confidence: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sequence: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub motivation: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub method: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub decision_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub caused_by_node_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub coordinate: Option<MemoryCoordinateData>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct MemoryEvidenceData {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub support_clocks: Option<super::EvidenceSupportClocks>,
    pub id: String,
    pub supports: Vec<String>,
    pub text: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub source: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub time: Option<String>,
    #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
    pub metadata: std::collections::BTreeMap<String, String>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct MemoryProvenanceData {
    pub source_kind: String,
    pub source_agent: String,
    pub observed_at: Option<String>,
    pub correlation_id: Option<String>,
    pub causation_id: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct MemoryAcceptedCounts {
    pub entries: usize,
    pub relations: usize,
    pub evidence: usize,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MemoryIngestOutcome {
    pub neighborhood: Option<super::WriteNeighborhood>,
    pub replayed: bool,
    pub clocks: Option<super::WriteClocks>,
    pub receipt_ref: Option<String>,
    pub about: String,
    pub memory_id: String,
    pub accepted: MemoryAcceptedCounts,
    pub read_after_write_ready: bool,
    pub warnings: Vec<String>,
    /// The dimension nodes this ingest declares for the first time, as
    /// namespaced ids: the labels the write created rather than reused.
    pub created_dimensions: Vec<String>,
    /// Labels this ingest declared that resemble one the about already
    /// holds, written under `LabelPolicy::Warn`.
    pub resembling_labels: Vec<ResemblingLabelData>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WakeMemoryQuery {
    pub about: String,
    pub role: String,
    pub intent: String,
    pub dimensions: DimensionSelection,
    pub token_budget: u32,
    pub depth: u32,
    pub max_tier: Option<ResolutionTier>,
    /// Cap on surfaced proof.evidence entries (None = unbounded). When set and
    /// the about has more, Wake returns the first `max_entries` and reports the
    /// withheld count via proof.frontier_size so the client near-expands.
    pub max_entries: Option<usize>,
    /// Which instants the packet stands on: the memory's frontier, one
    /// instant, or a half-open span on one clock.
    pub temporal: TemporalSelection,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AskMemoryQuery {
    pub about: String,
    pub question: String,
    /// The user's own words when `question` is the agent's rendering of them
    /// in the kernel's search language. Searched never; echoed and read
    /// against the question so a rendering that lost something says so.
    pub asked_as: Option<String>,
    pub answer_policy: MemoryAnswerPolicy,
    pub dimensions: DimensionSelection,
    pub token_budget: u32,
    pub depth: u32,
    pub max_tier: Option<ResolutionTier>,
    /// Cap on answer evidence entries after relevance filtering.
    pub max_entries: Option<usize>,
    /// Which instants the answer stands on: the memory's frontier, one
    /// instant, or a half-open span on one clock. Only what the selection
    /// admits competes, and the lifecycles are read as they stood then.
    pub temporal: TemporalSelection,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TemporalMemoryQuery {
    pub about: String,
    pub direction: TemporalDirection,
    pub entry_selection: Option<kmp_domain::TemporalEntrySelection>,
    pub axis: TemporalAxis,
    pub cursor: Option<TemporalCursor>,
    pub interval: Option<TemporalInterval>,
    pub dimensions: DimensionSelection,
    pub window: TemporalWindow,
    pub limit_entries: Option<usize>,
    pub include: TemporalIncludeOptions,
    pub token_budget: u32,
    pub depth: u32,
    pub max_tier: Option<ResolutionTier>,
}

/// What memories of several abouts have to do with each other: the abouts
/// the selection names or resolves, read within one span on one clock.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RelateMemoryQuery {
    pub about: String,
    pub dimensions: DimensionSelection,
    /// The span and clock the facts fall within; the memory's frontier when
    /// the caller named none.
    pub temporal: TemporalSelection,
    pub token_budget: u32,
    pub depth: u32,
    pub max_tier: Option<ResolutionTier>,
    pub page: RelatePageRequest,
}

#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct RelatePageRequest {
    pub entries: Option<usize>,
    pub cursor: Option<usize>,
}

impl RelatePageRequest {
    pub fn offset(&self) -> usize {
        self.cursor.unwrap_or_default()
    }

    pub fn entries_or_default(&self) -> usize {
        self.entries.unwrap_or(DEFAULT_RELATE_PAGE_ENTRIES)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TraceMemoryQuery {
    pub about: String,
    pub from: String,
    pub to: String,
    pub role: String,
    pub token_budget: u32,
    pub page: TracePageRequest,
}

#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct TracePageRequest {
    pub entries: Option<usize>,
    pub cursor: Option<usize>,
}

impl TracePageRequest {
    pub fn offset(&self) -> usize {
        self.cursor.unwrap_or_default()
    }

    pub fn entries_or_default(&self) -> usize {
        self.entries.unwrap_or(DEFAULT_TRACE_PAGE_ENTRIES)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InspectMemoryQuery {
    pub about: String,
    pub ref_id: String,
    pub include_details: bool,
    pub include_incoming: bool,
    pub include_outgoing: bool,
    pub include_raw: bool,
    /// The body revision the caller declares it wants. `None` inspects
    /// whatever is current. `Some` turns this read into an exact expansion:
    /// the store holds one body version per node, so it either is that
    /// revision or the read is a conflict naming the one that is there.
    pub expect_revision: Option<u64>,
}

#[derive(Clone, PartialEq, Eq)]
pub struct InspectMemoryResult {
    pub detail: GetNodeDetailResult,
    pub incoming: Vec<GraphRelationshipView>,
    pub outgoing: Vec<GraphRelationshipView>,
    pub evidence: Vec<InspectedEvidence>,
    pub raw_coordinates: Vec<TemporalCoordinate>,
    pub include_details: bool,
    pub include_raw: bool,
}

#[derive(Clone, PartialEq, Eq)]
pub struct InspectedEvidence {
    pub detail: GetNodeDetailResult,
    pub supports: Vec<String>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MemoryAnswerPolicy {
    #[default]
    EvidenceOrUnknown,
    ShowConflicts,
    BestEffort,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct TemporalIncludeOptions {
    pub evidence: bool,
    pub relations: bool,
    pub raw_refs: bool,
    /// Include bounded, evidenced memory dependencies with their stored bodies.
    /// Implies evidence and relations; does not change temporal entry selection.
    pub dependencies: bool,
}

#[derive(Debug, Clone, PartialEq)]
pub struct TemporalMemoryResult {
    pub traversal: kmp_domain::TemporalTraversalResult,
    /// Scoped admission catalogue with canonical nodes, support relations and
    /// bodies materialized for this page's proof. Unselected source payloads
    /// are absent; this bundle is not a fully materialized graph export.
    pub source_bundle: kmp_domain::KmpBundle,
    pub include: TemporalIncludeOptions,
}

/// One label an entry stands in, as the pair a reader names it by: `key`
/// is the dimension kind, `value` the bare scope id.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)]
pub struct EntryLabelData {
    pub key: String,
    pub value: String,
}

/// Change the labels an entry stands in without rewriting its text: labels
/// to add, labels to take off, and why. Translated against what the about
/// holds, like an ingest, into one change the log keeps.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MemoryRelabelCommand {
    pub about: String,
    pub ref_id: String,
    pub add: Vec<EntryLabelData>,
    pub remove: Vec<EntryLabelData>,
    pub why: String,
    pub provenance: Option<MemoryProvenanceData>,
    pub idempotency_key: String,
    pub dry_run: bool,
    pub label_policy: LabelPolicy,
    /// Label keys the writer insists are new even where the catalogue holds
    /// one that resembles them: it read the catalogue and means something
    /// else. Those labels are left out of the resemblance check.
    pub intended_new: std::collections::BTreeSet<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MemoryRelabelOutcome {
    pub about: String,
    pub ref_id: String,
    pub added: Vec<EntryLabelData>,
    pub removed: Vec<EntryLabelData>,
    /// Every label the entry stands in after this relabel, by key then value.
    pub labels: Vec<EntryLabelData>,
    /// The dimension nodes this relabel declared for the first time, as
    /// namespaced ids: the labels it created rather than reused.
    pub created_dimensions: Vec<String>,
    /// Labels this relabel added that resemble one the about already holds,
    /// written under `LabelPolicy::Warn`.
    pub resembling_labels: Vec<ResemblingLabelData>,
    pub read_after_write_ready: bool,
    pub warnings: Vec<String>,
}