@noir-ai/memory 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +21 -0
- package/dist/index.d.ts +803 -0
- package/dist/index.js +762 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,803 @@
|
|
|
1
|
+
import { ProjectId } from '@noir-ai/core';
|
|
2
|
+
export { ProjectId } from '@noir-ai/core';
|
|
3
|
+
import { Store } from '@noir-ai/store';
|
|
4
|
+
import { EmbedFn } from '@noir-ai/context';
|
|
5
|
+
export { EmbedFn, EmbedderConfig } from '@noir-ai/context';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Known observation types (dev-flavored, DS-3). `lesson` is reserved for
|
|
9
|
+
* consolidation output (task t5); the others are user-supplied on save. This
|
|
10
|
+
* list is intentionally NOT a closed set: {@link MemoryType} also accepts any
|
|
11
|
+
* unknown string so forward-compatible / user-defined types round-trip through
|
|
12
|
+
* the store without a migration.
|
|
13
|
+
*/
|
|
14
|
+
declare const MEMORY_TYPES: readonly ["pattern", "preference", "architecture", "bug", "workflow", "fact", "decision", "lesson"];
|
|
15
|
+
/**
|
|
16
|
+
* Open enum: one of {@link MEMORY_TYPES} OR any string. The `(string & {})`
|
|
17
|
+
* intersection preserves literal autocompletion for the known values while
|
|
18
|
+
* permitting arbitrary user-defined types (DS-3: unknown values accepted +
|
|
19
|
+
* stored). `lesson` is reserved for consolidation output.
|
|
20
|
+
*/
|
|
21
|
+
type MemoryType = (typeof MEMORY_TYPES)[number] | (string & {});
|
|
22
|
+
/**
|
|
23
|
+
* Provenance of a capture. `'explicit'` = a deliberate `memory_save` (the v1
|
|
24
|
+
* default, DS-4). `'auto:<hook>'` = an opt-in Claude Code hooks template
|
|
25
|
+
* capture (e.g. `'auto:stop'`, `'auto:posttooluse'`); the template ships as
|
|
26
|
+
* files the user installs deliberately — never auto-wired by `noir init`/`sync`.
|
|
27
|
+
*/
|
|
28
|
+
type MemorySource = 'explicit' | `auto:${string}`;
|
|
29
|
+
/**
|
|
30
|
+
* Default observation salience (spec §4.1). Applied by `MemoryEngine.save` when
|
|
31
|
+
* {@link SaveInput.importance} is omitted, and by consolidation when appending a
|
|
32
|
+
* derived `type:'lesson'` row. Shared via types.ts so save + consolidation agree
|
|
33
|
+
* on the baseline without a cross-module value import.
|
|
34
|
+
*/
|
|
35
|
+
declare const DEFAULT_IMPORTANCE = 0.5;
|
|
36
|
+
/**
|
|
37
|
+
* The canonical memory row. Realized ON TOP of the store (DS-2): the full row
|
|
38
|
+
* lives in KV `memory:obs:<id>` (the authoritative source of truth — the line
|
|
39
|
+
* between it and the indexes is R4's mitigation), with
|
|
40
|
+
* `indexDoc({source:'memory'})` (FTS5) + `upsertVec({source:'memory'})`
|
|
41
|
+
* (sqlite-vec) as search indexes. `content` is NEVER truncated (DS-9) — the
|
|
42
|
+
* FTS snippet is only a preview window hydrated-around on recall.
|
|
43
|
+
*/
|
|
44
|
+
interface Observation {
|
|
45
|
+
/** Unique id (crypto.randomUUID()). Key into KV `memory:obs:<id>`. */
|
|
46
|
+
id: string;
|
|
47
|
+
/** Open enum (DS-3). `lesson` is reserved for consolidation output. */
|
|
48
|
+
type: MemoryType;
|
|
49
|
+
/** Full text — never truncated (DS-9). Indexed into FTS5 + embedded into vec0. */
|
|
50
|
+
content: string;
|
|
51
|
+
/** Canonical project id (NEVER a filesystem path — blueprint D6). */
|
|
52
|
+
project: ProjectId;
|
|
53
|
+
/** Host session id if known, else null. */
|
|
54
|
+
sessionId: string | null;
|
|
55
|
+
/** Created-at epoch millis. */
|
|
56
|
+
ts: number;
|
|
57
|
+
/** Bumped on recall hit (best-effort, single writer). */
|
|
58
|
+
lastAccessTs: number;
|
|
59
|
+
/** Salience 0..1 (default 0.5, applied at save time). */
|
|
60
|
+
importance: number;
|
|
61
|
+
/** User tags (no auto-LLM tagging in v1 — explicit only). */
|
|
62
|
+
concepts: string[];
|
|
63
|
+
/** Repo-relative paths mentioned. */
|
|
64
|
+
files: string[];
|
|
65
|
+
/** Capture provenance (DS-4). */
|
|
66
|
+
source: MemorySource;
|
|
67
|
+
/**
|
|
68
|
+
* Source observation ids, set ONLY on derived `type:'lesson'` rows produced
|
|
69
|
+
* by consolidation (task t5). Absent on user-saved observations. Originals
|
|
70
|
+
* are never mutated or deleted (append-only — reversible + auditable, DS-6).
|
|
71
|
+
*/
|
|
72
|
+
provenance?: string[];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Input to {@link MemoryEngine.save} / the `memory_save` MCP tool. Only
|
|
76
|
+
* `content` is required; the engine applies defaults (`type`, `importance`,
|
|
77
|
+
* `source:'explicit'`, `ts`, `id`) at save time. No field here triggers a
|
|
78
|
+
* network or LLM call — capture is always local + free (DS-10).
|
|
79
|
+
*/
|
|
80
|
+
interface SaveInput {
|
|
81
|
+
/** Full text to remember. Required. */
|
|
82
|
+
content: string;
|
|
83
|
+
/** Open enum (DS-3); a default is applied at save time when omitted. */
|
|
84
|
+
type?: MemoryType;
|
|
85
|
+
/** User tags. */
|
|
86
|
+
concepts?: string[];
|
|
87
|
+
/** Repo-relative paths mentioned. */
|
|
88
|
+
files?: string[];
|
|
89
|
+
/** Salience 0..1 (defaults to 0.5 at save time). */
|
|
90
|
+
importance?: number;
|
|
91
|
+
/** Host session id (recorded when known). */
|
|
92
|
+
sessionId?: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Options for {@link MemoryEngine.recall} (the hybrid path). Mirrors the S6
|
|
96
|
+
* retriever's source-scoped search plus memory-side filters.
|
|
97
|
+
*/
|
|
98
|
+
interface RecallOptions {
|
|
99
|
+
/** Max results (default 10). */
|
|
100
|
+
limit?: number;
|
|
101
|
+
/** Filter to a single {@link MemoryType}. */
|
|
102
|
+
type?: MemoryType;
|
|
103
|
+
/** Filter to a single host session. */
|
|
104
|
+
sessionId?: string;
|
|
105
|
+
}
|
|
106
|
+
/** Options for {@link MemoryEngine.search} (the BM25-only instant path). */
|
|
107
|
+
interface SearchOptions {
|
|
108
|
+
/** Max results (default 10). */
|
|
109
|
+
limit?: number;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* A ranked recall/search hit. `content` is the FULL observation text hydrated
|
|
113
|
+
* from the authoritative KV row — never the truncated FTS snippet (DS-9).
|
|
114
|
+
* `score` is the RRF-fused rank score (recall, k=60) or the BM25 score
|
|
115
|
+
* (search); it is rank-based, not a normalized similarity.
|
|
116
|
+
*/
|
|
117
|
+
interface MemoryHit {
|
|
118
|
+
id: string;
|
|
119
|
+
type: MemoryType;
|
|
120
|
+
/** Full text — never truncated (DS-9). */
|
|
121
|
+
content: string;
|
|
122
|
+
/** RRF-fused rank score (recall) or BM25 score (search). */
|
|
123
|
+
score: number;
|
|
124
|
+
concepts: string[];
|
|
125
|
+
files: string[];
|
|
126
|
+
/** Created-at epoch millis (from the observation). */
|
|
127
|
+
ts: number;
|
|
128
|
+
importance: number;
|
|
129
|
+
source: MemorySource;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Per-session rollup, listed by {@link MemoryEngine.sessions}. Stored in KV
|
|
133
|
+
* `memory:sessions` as a per-project list (solo user v1 — A3).
|
|
134
|
+
*/
|
|
135
|
+
interface SessionInfo {
|
|
136
|
+
/** Host session id. */
|
|
137
|
+
id: string;
|
|
138
|
+
/** Canonical project id (NEVER a filesystem path — D6). */
|
|
139
|
+
project: ProjectId;
|
|
140
|
+
/** Number of observations in this session. */
|
|
141
|
+
count: number;
|
|
142
|
+
/** Epoch millis of the most recent observation in this session. */
|
|
143
|
+
lastTs: number;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Consolidation config block. Provider-EXPLICIT (blueprint D5/D6, DS-6): the
|
|
147
|
+
* provider is NEVER inferred from env-var presence — no explicit `provider`
|
|
148
|
+
* ⇒ {@link MemoryEngine.consolidate} refuses + logs
|
|
149
|
+
* (`{ok:false, reason:'no-provider'}`) and writes a `memory:consolidation:miss`
|
|
150
|
+
* KV audit entry. NEVER a silent paid call.
|
|
151
|
+
*/
|
|
152
|
+
interface ConsolidationConfig {
|
|
153
|
+
/** Master switch (default false). When false, `consolidate` is unregistered. */
|
|
154
|
+
enabled?: boolean;
|
|
155
|
+
/** Provider key, e.g. 'anthropic' | 'openai' | 'ollama'. Required to run. */
|
|
156
|
+
provider?: string;
|
|
157
|
+
/** Provider-specific model id. */
|
|
158
|
+
model?: string;
|
|
159
|
+
/** Restrict candidates to these types (default: every non-`lesson` type). */
|
|
160
|
+
types?: string[];
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Runtime memory config consumed by the engine (the subset of the core
|
|
164
|
+
* `memory:` block that affects engine behavior). The full user-facing schema
|
|
165
|
+
* (capture / hooksTemplate / recall / consolidation) lives in
|
|
166
|
+
* `NoirConfigSchema.memory` (@noir-ai/core, task t6); this is its runtime
|
|
167
|
+
* projection. Defaults to `{}` ⇒ consolidation disabled (offline, free).
|
|
168
|
+
*/
|
|
169
|
+
interface MemoryConfig {
|
|
170
|
+
consolidation?: ConsolidationConfig;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Result of {@link MemoryEngine.forget}: the KV row removed + best-effort
|
|
174
|
+
* doc/vec purge (deleteDoc + deleteVec — A2's acceptable v1 behavior).
|
|
175
|
+
*/
|
|
176
|
+
interface ForgetResult {
|
|
177
|
+
/** Number of observations actually removed. */
|
|
178
|
+
deleted: number;
|
|
179
|
+
/** Ids passed to forget (echoed for the MCP envelope). */
|
|
180
|
+
ids: string[];
|
|
181
|
+
}
|
|
182
|
+
/** Options for {@link MemoryEngine.consolidate}. */
|
|
183
|
+
interface ConsolidateOptions {
|
|
184
|
+
/** Restrict candidates to these types (overrides config). */
|
|
185
|
+
types?: string[];
|
|
186
|
+
/** Cap on candidate observations. */
|
|
187
|
+
limit?: number;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Result of {@link MemoryEngine.consolidate} (DS-6). Either a success appending
|
|
191
|
+
* one or more derived `type:'lesson'` rows (originals never mutated), or a
|
|
192
|
+
* documented refusal. A refusal is NEVER a crash and NEVER a silent paid call:
|
|
193
|
+
* `logged:true` records the miss so the user can see why nothing happened.
|
|
194
|
+
*/
|
|
195
|
+
type ConsolidationResult = {
|
|
196
|
+
ok: true;
|
|
197
|
+
lessons: Observation[];
|
|
198
|
+
from: string[];
|
|
199
|
+
} | {
|
|
200
|
+
ok: false;
|
|
201
|
+
/** Why consolidation did not run. */
|
|
202
|
+
reason: 'no-provider' | 'model-unavailable' | 'no-candidates';
|
|
203
|
+
/** True once the miss has been written to the KV audit log. */
|
|
204
|
+
logged: boolean;
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Snapshot returned by {@link MemoryEngine.status} (mirrors `ContextStatus` /
|
|
208
|
+
* `StoreStatus`). `observations` is a live read off the single writer handle.
|
|
209
|
+
*/
|
|
210
|
+
interface MemoryStatus {
|
|
211
|
+
ok: boolean;
|
|
212
|
+
/** Canonical project id (never a filesystem path — D6). */
|
|
213
|
+
projectId: string;
|
|
214
|
+
/** Number of observations (rows indexed with `source:'memory'`). */
|
|
215
|
+
observations: number;
|
|
216
|
+
/** True when the store handle is read-only (saves/forgets will refuse). */
|
|
217
|
+
degraded: boolean;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* The memory engine — the `ctx.memory` service (the contract the daemon seam
|
|
221
|
+
* in task t4 types against, and tests mock). Constructed once per serve
|
|
222
|
+
* lifecycle from the daemon's store handle + the shared S6 `EmbedFn` (mirrors
|
|
223
|
+
* `ContextEngine` / `WorkflowEngine`). The daemon owns the single embedder and
|
|
224
|
+
* passes the SAME `EmbedFn` to both the context and memory engines — no
|
|
225
|
+
* embedder duplication (plan §Architecture).
|
|
226
|
+
*
|
|
227
|
+
* `consolidate` is OPTIONAL: registered only when consolidation is enabled AND
|
|
228
|
+
* a provider is configured (task t5, OQ-5). Its absence is the static signal
|
|
229
|
+
* that no LLM surface is wired (DS-6/D5).
|
|
230
|
+
*
|
|
231
|
+
* Single-writer discipline: the engine — like the context indexer — is the
|
|
232
|
+
* ONLY thing that writes `source:'memory'` rows through the injected handle; it
|
|
233
|
+
* never opens a second store connection (blueprint D6: in-process only, no
|
|
234
|
+
* sidecar).
|
|
235
|
+
*/
|
|
236
|
+
interface MemoryEngine {
|
|
237
|
+
/** Persist an observation (FTS5 + vec0 + KV `memory:obs:<id>`); returns the row. */
|
|
238
|
+
save(input: SaveInput): Promise<Observation>;
|
|
239
|
+
/**
|
|
240
|
+
* Hybrid recall: BM25 ∪ kNN fused by RRF (k=60), scoped to `source:'memory'`,
|
|
241
|
+
* + cheap regex entity-boost, hydrated from KV (DS-5/DS-9). Degrades to
|
|
242
|
+
* BM25-only when the embedder is unavailable.
|
|
243
|
+
*/
|
|
244
|
+
recall(query: string, opts?: RecallOptions): Promise<MemoryHit[]>;
|
|
245
|
+
/** Instant BM25-only lookup scoped to `source:'memory'` (no embed cost). */
|
|
246
|
+
search(query: string, opts?: SearchOptions): Promise<MemoryHit[]>;
|
|
247
|
+
/** Per-session rollups from KV `memory:sessions`. */
|
|
248
|
+
sessions(): SessionInfo[];
|
|
249
|
+
/** Remove observations: KV row + best-effort doc/vec purge. */
|
|
250
|
+
forget(ids: string[]): ForgetResult;
|
|
251
|
+
/**
|
|
252
|
+
* Explicit consolidation job (DS-6). Provider-gated: refuses + logs if no
|
|
253
|
+
* provider is configured — NEVER a silent paid call. Appends derived
|
|
254
|
+
* `type:'lesson'` rows; originals are never mutated.
|
|
255
|
+
*/
|
|
256
|
+
consolidate?(opts?: ConsolidateOptions): Promise<ConsolidationResult>;
|
|
257
|
+
/** State snapshot (mirrors `ContextStatus` / `StoreStatus`). */
|
|
258
|
+
status(): MemoryStatus;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Known host hook events Noir can capture (the four Claude Code hooks the v1
|
|
263
|
+
* template targets, DS-4 / spec §5). The list is intentionally NOT closed:
|
|
264
|
+
* {@link CaptureEventType} also accepts any unknown string so a future host's
|
|
265
|
+
* hook names round-trip through the mapper without a code change (mirrors the
|
|
266
|
+
* open-enum taxonomy in types.ts).
|
|
267
|
+
*/
|
|
268
|
+
declare const CAPTURE_HOOKS: readonly ["PreToolUse", "PostToolUse", "UserPromptSubmit", "Stop"];
|
|
269
|
+
/** One of {@link CAPTURE_HOOKS} OR any host-defined hook name (open enum). */
|
|
270
|
+
type CaptureEventType = (typeof CAPTURE_HOOKS)[number] | (string & {});
|
|
271
|
+
/**
|
|
272
|
+
* Hook events captured by DEFAULT (DS-4). Persist session-end summaries + the
|
|
273
|
+
* prompts a user submits; SKIP the noisy per-tool events (`PreToolUse` /
|
|
274
|
+
* `PostToolUse` fire on every tool call and would flood memory + leak tool
|
|
275
|
+
* inputs the user did not ask to remember). A user opts INTO tool-event capture
|
|
276
|
+
* by passing a custom {@link CapturePolicy.hooks}.
|
|
277
|
+
*/
|
|
278
|
+
declare const DEFAULT_CAPTURE_HOOKS: readonly CaptureEventType[];
|
|
279
|
+
/**
|
|
280
|
+
* The default capture policy: capture {@link DEFAULT_CAPTURE_HOOKS} only. Used
|
|
281
|
+
* when {@link toSaveInput} is called with no `policy` argument. Host-neutral +
|
|
282
|
+
* conservative — the user owns the policy.
|
|
283
|
+
*/
|
|
284
|
+
declare const DEFAULT_CAPTURE_POLICY: CapturePolicy;
|
|
285
|
+
/**
|
|
286
|
+
* The structured payload a host hook forwards. All fields optional — a hook
|
|
287
|
+
* populates whichever it has (a `Stop` hook carries a `summary`; a
|
|
288
|
+
* `UserPromptSubmit` hook carries the `prompt`; a tool hook carries `toolName`
|
|
289
|
+
* + `toolInput`). Unknown extra fields are tolerated (the mapper reads only the
|
|
290
|
+
* fields below), so a host can forward its raw stdin without shape-massaging.
|
|
291
|
+
*/
|
|
292
|
+
interface CapturePayload {
|
|
293
|
+
/** Tool name for tool hooks (e.g. `'Bash'`, `'Edit'`, `'Write'`). */
|
|
294
|
+
toolName?: string;
|
|
295
|
+
/** The tool's input object (e.g. `{ command: 'npm test' }`, `{ file_path }`). */
|
|
296
|
+
toolInput?: unknown;
|
|
297
|
+
/** The submitted prompt text (`UserPromptSubmit`). */
|
|
298
|
+
prompt?: string;
|
|
299
|
+
/** A session-end summary (`Stop`) — free text the host derived or was given. */
|
|
300
|
+
summary?: string;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* A host-neutral capture event. Built by a host hook (or a `noir memory capture`
|
|
304
|
+
* CLI) from whatever the host emitted on stdin, then handed to
|
|
305
|
+
* {@link toSaveInput}. The shape is deliberately minimal + portable so it is not
|
|
306
|
+
* tied to Claude Code's stdin schema (a future host adapter translates its own
|
|
307
|
+
* payload into this).
|
|
308
|
+
*
|
|
309
|
+
* `project` is the CANONICAL project id (NEVER a filesystem path — blueprint
|
|
310
|
+
* D6); the capture command resolves it from `cwd` before constructing the event.
|
|
311
|
+
*/
|
|
312
|
+
interface CaptureEvent {
|
|
313
|
+
/** Hook event name (open enum — {@link CaptureEventType}). */
|
|
314
|
+
event_type: CaptureEventType;
|
|
315
|
+
/** Epoch millis of the event (host-supplied or `Date.now()` at capture). */
|
|
316
|
+
ts: number;
|
|
317
|
+
/** Host session id if known, else null (recorded on the observation). */
|
|
318
|
+
sessionId: string | null;
|
|
319
|
+
/** Canonical project identifier (NEVER a filesystem path — D6). */
|
|
320
|
+
project: ProjectId;
|
|
321
|
+
/** The event-specific fields (see {@link CapturePayload}). */
|
|
322
|
+
payload: CapturePayload;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* An opinionated capture policy. Controls WHICH hook events are persisted when
|
|
326
|
+
* {@link toSaveInput} is called. Defaults to {@link DEFAULT_CAPTURE_POLICY}
|
|
327
|
+
* (session summaries + submitted prompts only). The user owns this — it is the
|
|
328
|
+
* one knob that keeps auto-capture from flooding memory (R3).
|
|
329
|
+
*/
|
|
330
|
+
interface CapturePolicy {
|
|
331
|
+
/**
|
|
332
|
+
* Hook events to capture. An event whose `event_type` is NOT in this list is
|
|
333
|
+
* skipped (`toSaveInput` returns `null`). Defaults to
|
|
334
|
+
* {@link DEFAULT_CAPTURE_HOOKS}.
|
|
335
|
+
*/
|
|
336
|
+
hooks?: ReadonlyArray<CaptureEventType>;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Project a host-neutral {@link CaptureEvent} into a {@link SaveInput} ready for
|
|
340
|
+
* `MemoryEngine.save` / the `memory_save` MCP tool, OR return `null` when the
|
|
341
|
+
* policy says to skip this event (a noisy hook the user did not opt into, or an
|
|
342
|
+
* event with no usable content).
|
|
343
|
+
*
|
|
344
|
+
* The mapper is PURE: it reads no environment, holds no secrets, performs NO I/O
|
|
345
|
+
* and NO LLM call (capture is always local + free, DS-10). It applies the
|
|
346
|
+
* opinionated defaults from spec §5:
|
|
347
|
+
* • only the policy's `hooks` are captured (default: Stop + UserPromptSubmit);
|
|
348
|
+
* • content is built from the event's payload fields (summary / prompt / a
|
|
349
|
+
* compact tool-call description);
|
|
350
|
+
* • `type` is inferred lightly (a decision-shaped prompt → `'decision'`; a
|
|
351
|
+
* tool hook → `'workflow'`; otherwise `'fact'`) — a heuristic the user can
|
|
352
|
+
* override by editing the saved row;
|
|
353
|
+
* • `files` is pulled best-effort from a tool input's `file_path`;
|
|
354
|
+
* • `sessionId` is forwarded when present.
|
|
355
|
+
*
|
|
356
|
+
* Returning `null` is the documented "skip" signal — the caller (a capture
|
|
357
|
+
* command) MUST NOT treat it as an error; it is the policy working as intended.
|
|
358
|
+
*
|
|
359
|
+
* @param event The host-neutral capture event.
|
|
360
|
+
* @param policy Capture policy (defaults to {@link DEFAULT_CAPTURE_POLICY}).
|
|
361
|
+
* @returns The {@link SaveInput}, or `null` to skip.
|
|
362
|
+
*/
|
|
363
|
+
declare function toSaveInput(event: CaptureEvent, policy?: CapturePolicy): SaveInput | null;
|
|
364
|
+
/**
|
|
365
|
+
* Build the observation `content` for an event from its payload, or `null` when
|
|
366
|
+
* the payload carries nothing persistable. Per hook:
|
|
367
|
+
* • `Stop` → `payload.summary` (a session-end summary); null when absent.
|
|
368
|
+
* • `UserPromptSubmit` → `payload.prompt`; null when absent.
|
|
369
|
+
* • tool hooks (`PreToolUse` / `PostToolUse`, or any `toolName`-bearing
|
|
370
|
+
* event) → a compact {@link describeToolCall} line.
|
|
371
|
+
* Pure.
|
|
372
|
+
*/
|
|
373
|
+
declare function buildContent(event: CaptureEvent): string | null;
|
|
374
|
+
/**
|
|
375
|
+
* Render a compact one-line description of a tool call for memory content, e.g.
|
|
376
|
+
* `"Ran Bash: npm test"` or `"Edited src/foo.ts"`. Returns `null` when the
|
|
377
|
+
* payload carries neither a toolName nor a toolInput. Pure + best-effort — it
|
|
378
|
+
* never throws on an unexpected toolInput shape (it renders what it can).
|
|
379
|
+
*/
|
|
380
|
+
declare function describeToolCall(payload: CapturePayload): string | null;
|
|
381
|
+
/**
|
|
382
|
+
* Light type inference for a captured event (a heuristic — the user can override
|
|
383
|
+
* by editing the saved row; no LLM is involved, DS-10). Decision-shaped prompts
|
|
384
|
+
* → `'decision'`; tool hooks → `'workflow'`; otherwise `'fact'` (the engine's
|
|
385
|
+
* default type). Pure.
|
|
386
|
+
*/
|
|
387
|
+
declare function inferType(event: CaptureEvent): MemoryType;
|
|
388
|
+
/**
|
|
389
|
+
* Extract repo-relative file paths from a tool input best-effort: the
|
|
390
|
+
* `file_path` field (Edit / Write / Read) when present. Returns `[]` when none.
|
|
391
|
+
* Pure; never throws on a non-object toolInput.
|
|
392
|
+
*/
|
|
393
|
+
declare function extractFiles(payload: CapturePayload): string[];
|
|
394
|
+
/**
|
|
395
|
+
* The memory `source` provenance for a captured event: `'auto:<hook>'` (e.g.
|
|
396
|
+
* `'auto:stop'`, `'auto:posttooluse'`), lowercased so the bucket name is a
|
|
397
|
+
* stable identifier. Exported for a dedicated `noir memory capture` CLI (S9)
|
|
398
|
+
* that persists an event OUTSIDE the `memory_save` envelope and wants to tag
|
|
399
|
+
* provenance — the MCP `memory_save` path itself records `source:'explicit'`
|
|
400
|
+
* (SaveInput carries no source field by design).
|
|
401
|
+
*/
|
|
402
|
+
declare function captureSource(eventType: CaptureEventType): MemorySource;
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* User-facing memory config shape — mirrors `NoirConfig['memory']` (the zod block
|
|
406
|
+
* @noir-ai/core ships, slice S7 / task t6). Declared LOCALLY with every field
|
|
407
|
+
* optional so this module type-checks WITHOUT a forward dependency on a core
|
|
408
|
+
* type (core never imports memory — no cycle; @noir-ai/core is not even
|
|
409
|
+
* consulted here), AND so a config with no `memory:` block (or a partial one)
|
|
410
|
+
* maps cleanly to a fully-disabled runtime config. The fully-resolved zod output
|
|
411
|
+
* is structurally assignable to this permissive shape, so the mapper accepts a
|
|
412
|
+
* `NoirConfig['memory']` directly.
|
|
413
|
+
*/
|
|
414
|
+
interface MemoryUserConfig {
|
|
415
|
+
/** Consolidation gate (provider-explicit — never silent paid, DS-6). */
|
|
416
|
+
consolidation?: {
|
|
417
|
+
/** Master switch (default false). When false, `consolidate` refuses + logs. */
|
|
418
|
+
enabled?: boolean;
|
|
419
|
+
/** Provider key, e.g. 'anthropic' | 'openai' | 'ollama'. Required to run. */
|
|
420
|
+
provider?: string;
|
|
421
|
+
/** Provider-specific model id. */
|
|
422
|
+
model?: string;
|
|
423
|
+
/** Restrict candidates to these types (default: every non-`lesson` type). */
|
|
424
|
+
types?: string[];
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Resolve a user-facing {@link MemoryUserConfig} into the runtime
|
|
429
|
+
* {@link MemoryConfig} the memory engine (and `runConsolidation`) consume.
|
|
430
|
+
*
|
|
431
|
+
* - `undefined` / missing block ⇒ `{ consolidation: { enabled: false } }`
|
|
432
|
+
* (consolidation disabled — the safe default; capture/store/retrieve stay
|
|
433
|
+
* local + free. `runConsolidation` then refuses with `'no-provider'` + logs,
|
|
434
|
+
* making NO paid call, blueprint D6).
|
|
435
|
+
* - A block whose `consolidation.enabled` is absent or `false` ⇒ the same
|
|
436
|
+
* disabled default, regardless of any `provider`/`model` written alongside it
|
|
437
|
+
* (the master switch is the first gate `runConsolidation` checks).
|
|
438
|
+
* - A block with `consolidation.enabled === true` + a `provider` ⇒ the provider
|
|
439
|
+
* + optional `model`/`types` pass straight through; `runConsolidation` then
|
|
440
|
+
* proceeds to its model-availability + candidate gates.
|
|
441
|
+
*
|
|
442
|
+
* `consolidation` is ALWAYS populated (with `enabled` defaulted) so consumers
|
|
443
|
+
* read `config.consolidation.enabled` without a separate undefined check —
|
|
444
|
+
* mirrors how `resolveModelConfig` normalizes `tiers`/`providers` to
|
|
445
|
+
* always-present objects.
|
|
446
|
+
*
|
|
447
|
+
* This mapper is a PURE projection — it copies fields through unchanged, NEVER
|
|
448
|
+
* infers a provider from env-var presence (DS-6), reads NO environment, holds NO
|
|
449
|
+
* secrets, and never throws. Whether a configured provider is actually USABLE is
|
|
450
|
+
* decided at call time inside `complete()` (S8, which re-reads env idempotently),
|
|
451
|
+
* NOT here.
|
|
452
|
+
*/
|
|
453
|
+
declare function resolveMemoryConfig(raw?: MemoryUserConfig): MemoryConfig;
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Per-call request shape passed to {@link MemoryModel.complete}. A structural
|
|
457
|
+
* subset of S8's `CompleteRequest` (provider-EXPLICIT; no `tools`/`stream` —
|
|
458
|
+
* single-shot only, blueprint D5). Defined locally so the memory package has no
|
|
459
|
+
* value-level dependency on `@noir-ai/model`; the daemon seam (t6) binds
|
|
460
|
+
* `complete(req, cfg)` into this shape, and tests inject a fake.
|
|
461
|
+
*/
|
|
462
|
+
interface MemoryCompleteRequest {
|
|
463
|
+
system?: string;
|
|
464
|
+
prompt: string;
|
|
465
|
+
/** Provider block name — explicit, NEVER env-inferred (D5/DS-6). */
|
|
466
|
+
provider: string;
|
|
467
|
+
/** Model id for this call. */
|
|
468
|
+
model: string;
|
|
469
|
+
maxTokens?: number;
|
|
470
|
+
/** Bounded task tier (DS-9). Selects a per-tier output cap; never a provider. */
|
|
471
|
+
tier?: 'draft' | 'title' | 'summarize' | 'consolidate';
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* The subset of S8's `CompleteResult` that consolidation consumes. `null` is
|
|
475
|
+
* first-class degradation (no provider resolvable at call time — the always-
|
|
476
|
+
* available offline path, D5); `{ok:false}` is an attempted-call failure.
|
|
477
|
+
*/
|
|
478
|
+
type MemoryCompleteResult = {
|
|
479
|
+
ok: true;
|
|
480
|
+
text: string;
|
|
481
|
+
} | {
|
|
482
|
+
ok: false;
|
|
483
|
+
reason: string;
|
|
484
|
+
} | null;
|
|
485
|
+
/**
|
|
486
|
+
* The optional S8 model injection. Its absence is the runtime signal that the
|
|
487
|
+
* bounded model layer is not wired (consolidation then refuses
|
|
488
|
+
* `'model-unavailable'` — the documented S7 stub, OQ-3/OQ-8). When present,
|
|
489
|
+
* `complete` is the SOLE LLM entry point and is reached ONLY after the provider
|
|
490
|
+
* gate passes (DS-6: never a silent paid call).
|
|
491
|
+
*/
|
|
492
|
+
interface MemoryModel {
|
|
493
|
+
complete(req: MemoryCompleteRequest): Promise<MemoryCompleteResult>;
|
|
494
|
+
}
|
|
495
|
+
/** Construction options for {@link MemoryEngineImpl} / {@link createMemoryEngine}. */
|
|
496
|
+
interface MemoryEngineOptions {
|
|
497
|
+
/** The daemon's store handle — the ONLY storage surface used (single writer). */
|
|
498
|
+
store: Store;
|
|
499
|
+
/**
|
|
500
|
+
* Project root. Stored for API symmetry with {@link ContextEngine} and future
|
|
501
|
+
* path normalization; v1 stores {@link Observation.files} repo-relative as-is.
|
|
502
|
+
*/
|
|
503
|
+
root: string;
|
|
504
|
+
/** Canonical project identifier (NEVER a filesystem path — blueprint D6). */
|
|
505
|
+
projectId: ProjectId;
|
|
506
|
+
/**
|
|
507
|
+
* The shared S6 embedder (the daemon resolves it once and passes the SAME
|
|
508
|
+
* `EmbedFn` to context + memory). A throw on `embed()` ⇒ the vec index is
|
|
509
|
+
* skipped for that observation (F8-style degradation — the row is still
|
|
510
|
+
* BM25-searchable via FTS5 + the authoritative KV row).
|
|
511
|
+
*/
|
|
512
|
+
embed: EmbedFn;
|
|
513
|
+
/** Optional S8 model injection for consolidation (absent ⇒ consolidation refuses). */
|
|
514
|
+
model?: MemoryModel;
|
|
515
|
+
/** Runtime memory config (the consolidation gate). Defaults to `{}` (offline). */
|
|
516
|
+
config?: MemoryConfig;
|
|
517
|
+
/** True when `store` was opened read-only (the daemon-down fallback). */
|
|
518
|
+
storeDegraded?: boolean;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Noir's cross-session memory engine — the `ctx.memory` service. Constructed
|
|
522
|
+
* once per serve lifecycle (mirror `ContextEngine` / `WorkflowEngine`) from the
|
|
523
|
+
* daemon's store handle + the shared S6 `EmbedFn` + an optional S8 model.
|
|
524
|
+
* Implements the {@link MemoryEngine} contract; {@link get} is an extra public
|
|
525
|
+
* method (beyond the interface) for recall hydration + tests.
|
|
526
|
+
*
|
|
527
|
+
* `consolidate` is ALWAYS present on the class and self-gates on the configured
|
|
528
|
+
* provider; the daemon decides whether to register the `memory_consolidate` MCP
|
|
529
|
+
* tool from `config.memory.consolidation.enabled` (OQ-5). Its refusal paths are
|
|
530
|
+
* the documented stub behavior (spec §7) — never a crash, never a silent call.
|
|
531
|
+
*/
|
|
532
|
+
declare class MemoryEngineImpl implements MemoryEngine {
|
|
533
|
+
/** The daemon's single-writer store handle (possibly read-only). */
|
|
534
|
+
readonly store: Store;
|
|
535
|
+
/** Project root (stored for symmetry / future path normalization). */
|
|
536
|
+
readonly root: string;
|
|
537
|
+
/** Canonical project identifier (NEVER a filesystem path — D6). */
|
|
538
|
+
readonly projectId: ProjectId;
|
|
539
|
+
/**
|
|
540
|
+
* Persistent degradation flag (read-only store). Mutating ops throw a clear
|
|
541
|
+
* error upfront instead of letting the first write fail mid-run; reads
|
|
542
|
+
* (recall/search/sessions/status) keep working.
|
|
543
|
+
*/
|
|
544
|
+
readonly degraded: boolean;
|
|
545
|
+
private readonly embed;
|
|
546
|
+
private readonly model;
|
|
547
|
+
private readonly config;
|
|
548
|
+
private chain;
|
|
549
|
+
constructor(opts: MemoryEngineOptions);
|
|
550
|
+
/** @inheritDoc MemoryEngine.save */
|
|
551
|
+
save(input: SaveInput): Promise<Observation>;
|
|
552
|
+
private saveInternal;
|
|
553
|
+
/**
|
|
554
|
+
* Write one observation to ALL three indexes in a single synchronous block:
|
|
555
|
+
* FTS5 (`indexDoc`, content searchable) + sqlite-vec (`upsertVec`, best-effort
|
|
556
|
+
* — skipped if the embedder or vec0 is unavailable, F8-style) + the
|
|
557
|
+
* authoritative KV row (`memory:obs:<id>`) + the id index + the sessions
|
|
558
|
+
* rollup. The `docs.meta` payload is the denormalized search projection
|
|
559
|
+
* (Observation minus content); the KV row is the source of truth (R4).
|
|
560
|
+
*/
|
|
561
|
+
private indexObservation;
|
|
562
|
+
/**
|
|
563
|
+
* Hydrate the full {@link Observation} for `id` from the authoritative KV row.
|
|
564
|
+
* Returns `null` for an unknown / forgotten id. This is the only correct way
|
|
565
|
+
* to read an observation's complete `content` (DS-9).
|
|
566
|
+
*/
|
|
567
|
+
get(id: string): Observation | null;
|
|
568
|
+
/** @inheritDoc MemoryEngine.recall */
|
|
569
|
+
recall(query: string, opts?: RecallOptions): Promise<MemoryHit[]>;
|
|
570
|
+
/** @inheritDoc MemoryEngine.search */
|
|
571
|
+
search(query: string, opts?: SearchOptions): Promise<MemoryHit[]>;
|
|
572
|
+
/**
|
|
573
|
+
* Hydrate a ranked id list into {@link MemoryHit}s from the authoritative KV
|
|
574
|
+
* row, applying the optional `type` / `sessionId` filters. Each hit carries
|
|
575
|
+
* the FULL `content` (DS-9). A ranked id with no KV row (a stale vec-only hit
|
|
576
|
+
* whose row was forgotten) is dropped — never emitted with partial data.
|
|
577
|
+
*/
|
|
578
|
+
private hydrateHits;
|
|
579
|
+
/** @inheritDoc MemoryEngine.sessions */
|
|
580
|
+
sessions(): SessionInfo[];
|
|
581
|
+
/** @inheritDoc MemoryEngine.forget */
|
|
582
|
+
forget(ids: string[]): ForgetResult;
|
|
583
|
+
/** @inheritDoc MemoryEngine.consolidate */
|
|
584
|
+
consolidate(opts?: ConsolidateOptions): Promise<ConsolidationResult>;
|
|
585
|
+
private consolidateInternal;
|
|
586
|
+
/** @inheritDoc MemoryEngine.status */
|
|
587
|
+
status(): MemoryStatus;
|
|
588
|
+
/**
|
|
589
|
+
* Best-effort embedding: returns the vec, or `null` if the embedder is
|
|
590
|
+
* unavailable (`kind:'none'`, native load failure, provider error). A `null`
|
|
591
|
+
* vec skips `upsertVec` so the save still succeeds — the row is BM25-searchable
|
|
592
|
+
* + hydrated from KV (F8-style degradation).
|
|
593
|
+
*/
|
|
594
|
+
private embedBestEffort;
|
|
595
|
+
/** Throw a clear, typed error when the store handle is read-only. */
|
|
596
|
+
private assertNotDegraded;
|
|
597
|
+
/**
|
|
598
|
+
* Single-flight serialization of async mutating ops (mirrors the context
|
|
599
|
+
* indexer's `serialized`). Forces `work` to run strictly after the previous
|
|
600
|
+
* queued op completes. A failed op does NOT poison the queue — the chain
|
|
601
|
+
* advances regardless, and the caller observes the real outcome via `result`.
|
|
602
|
+
*/
|
|
603
|
+
private serialized;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Build a {@link MemoryEngineImpl} bound to a single store handle + the shared
|
|
607
|
+
* S6 embedder + an optional S8 model. Constructed once per serve lifecycle
|
|
608
|
+
* alongside the context + workflow engines. The daemon passes the SAME `embed`
|
|
609
|
+
* it resolved for S6 (no embedder duplication) and resolves the model via S8's
|
|
610
|
+
* `resolveModelConfig` only when `config.memory.consolidation.enabled` is set
|
|
611
|
+
* (provider-explicit — never a silent paid call, D6).
|
|
612
|
+
*/
|
|
613
|
+
declare function createMemoryEngine(opts: MemoryEngineOptions): MemoryEngineImpl;
|
|
614
|
+
|
|
615
|
+
/** Default cap on consolidation candidates (spec §7.4 — deterministic selection). */
|
|
616
|
+
declare const DEFAULT_CONSOLIDATE_LIMIT = 50;
|
|
617
|
+
/**
|
|
618
|
+
* Single-shot consolidation instruction (D5: no tools, no loop). The lesson is
|
|
619
|
+
* free-text PROSE (not JSON): a lesson is a synthesized insight, and free-text
|
|
620
|
+
* avoids a JSON-parse/repair round on what is naturally unstructured output.
|
|
621
|
+
*/
|
|
622
|
+
declare const CONSOLIDATION_SYSTEM_PROMPT: string;
|
|
623
|
+
/**
|
|
624
|
+
* Capabilities `runConsolidation` needs from the engine. The engine owns the
|
|
625
|
+
* store handle + the embedder; `indexDerived` is the callback that writes a
|
|
626
|
+
* derived observation to every index (embed → FTS5 + vec + KV + id index),
|
|
627
|
+
* reusing the engine's shared `indexObservation` path so a lesson lands exactly
|
|
628
|
+
* like a user-saved observation. Consolidation itself never touches the
|
|
629
|
+
* embedder or the write path directly — it builds the lesson row + hands it off.
|
|
630
|
+
*/
|
|
631
|
+
interface ConsolidationDeps {
|
|
632
|
+
/** The daemon's single-writer store handle (possibly read-only — D6). */
|
|
633
|
+
store: Store;
|
|
634
|
+
/** Optional S8 model injection; absent ⇒ `'model-unavailable'` refusal. */
|
|
635
|
+
model: MemoryModel | undefined;
|
|
636
|
+
/** Runtime memory config (the provider-explicit consolidation gate). */
|
|
637
|
+
config: MemoryConfig;
|
|
638
|
+
/** Canonical project identifier (NEVER a filesystem path — D6). */
|
|
639
|
+
projectId: ProjectId;
|
|
640
|
+
/**
|
|
641
|
+
* Write a derived lesson observation to all indexes. The engine implements
|
|
642
|
+
* this as `embedBestEffort(content)` then the shared `indexObservation(obs,
|
|
643
|
+
* vec)` — so the lesson is searchable (BM25 + vec) + hydrated from KV just
|
|
644
|
+
* like a user save.
|
|
645
|
+
*/
|
|
646
|
+
indexDerived: (observation: Observation) => Promise<void>;
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Run one explicit consolidation pass.
|
|
650
|
+
*
|
|
651
|
+
* Gate order (each refusal logs to `memory:consolidation:miss` and returns
|
|
652
|
+
* `logged:true`; NONE makes a paid call before its gate passes):
|
|
653
|
+
* 1. `no-provider` — consolidation not enabled OR no explicit `provider`
|
|
654
|
+
* configured. The provider is NEVER inferred from env-var presence (D5/D6).
|
|
655
|
+
* 2. `model-unavailable` — enabled + provider set, but the S8 model injection
|
|
656
|
+
* is absent OR no explicit `model` id is configured (the documented S7 stub,
|
|
657
|
+
* OQ-3/OQ-8). Also the refusal when `complete()` returns `null` (provider
|
|
658
|
+
* not resolvable at call time — e.g. key missing) OR `{ok:false}` (an
|
|
659
|
+
* attempted call failed): both are wrapped as `'model-unavailable'`.
|
|
660
|
+
* 3. `no-candidates` — nothing matches the (optional) type filter / lookback,
|
|
661
|
+
* OR the model returned empty text.
|
|
662
|
+
*
|
|
663
|
+
* On success: appends ONE derived `type:'lesson'` observation with
|
|
664
|
+
* `provenance:[candidate ids]` via `indexDerived`; originals are NEVER mutated
|
|
665
|
+
* (append-only — reversible + auditable, DS-6).
|
|
666
|
+
*/
|
|
667
|
+
declare function runConsolidation(deps: ConsolidationDeps, opts?: ConsolidateOptions): Promise<ConsolidationResult>;
|
|
668
|
+
/**
|
|
669
|
+
* Gather consolidation candidates: observations with `type != 'lesson'`,
|
|
670
|
+
* newest-first, optionally restricted to `types`, capped at `limit`. Pure read
|
|
671
|
+
* off the id index + KV — no LLM, no clustering (spec §7.4). Exported so the
|
|
672
|
+
* deterministic selection is testable without a model.
|
|
673
|
+
*/
|
|
674
|
+
declare function gatherCandidates(store: Store, types: ReadonlyArray<string> | undefined, limit: number): Observation[];
|
|
675
|
+
/**
|
|
676
|
+
* Serialize candidates into the consolidation prompt body (deterministic order,
|
|
677
|
+
* 1-indexed, with type + ts for the model's context). Pure.
|
|
678
|
+
*/
|
|
679
|
+
declare function serializeCandidates(candidates: ReadonlyArray<Observation>): string;
|
|
680
|
+
/**
|
|
681
|
+
* De-duplicate + collect concept tags across the candidate set (becomes the
|
|
682
|
+
* derived lesson's `concepts`). Order is first-seen; pure.
|
|
683
|
+
*/
|
|
684
|
+
declare function dedupeConcepts(candidates: ReadonlyArray<Observation>): string[];
|
|
685
|
+
|
|
686
|
+
/** The injected store handle + the shared S6 embedder (read-only pipeline). */
|
|
687
|
+
interface RecallDeps {
|
|
688
|
+
/** The daemon's single-writer store handle (may be read-only — reads keep working). */
|
|
689
|
+
store: Store;
|
|
690
|
+
/** Query embedder. A throw on `embed(query)` ⇒ BM25-only degradation. */
|
|
691
|
+
embed: EmbedFn;
|
|
692
|
+
}
|
|
693
|
+
/** Internal outcome of {@link recallMemory} (the engine projects this to `MemoryHit[]`). */
|
|
694
|
+
interface RecallMemoryResult {
|
|
695
|
+
/** Ranked, hydrated hits (FULL content — DS-9), truncated to `limit`. */
|
|
696
|
+
hits: MemoryHit[];
|
|
697
|
+
/** True when the kNN OR BM25 leg failed this call (honest per-query signal). */
|
|
698
|
+
degraded: boolean;
|
|
699
|
+
/** `'hybrid'` when the kNN leg ran, `'bm25-only'` when it was skipped. */
|
|
700
|
+
mode: 'hybrid' | 'bm25-only';
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Cheap regex extraction of identifiers + file/path tokens from a query (NO LLM
|
|
704
|
+
* — DS-5). Two kinds of entity are collected, de-duplicated:
|
|
705
|
+
*
|
|
706
|
+
* 1. **Qualified tokens** — whitespace-delimited tokens that contain a `/`,
|
|
707
|
+
* `.`, or `:` (e.g. `packages/memory/src/recall.ts`, `memory:obs`,
|
|
708
|
+
* `MemoryEngine.save`). Kept whole + lowercased so they match an
|
|
709
|
+
* observation's `files` / `concepts` exactly when it mentions the same path
|
|
710
|
+
* or qualified name.
|
|
711
|
+
* 2. **Identifier subwords** — every token is also split at camelCase /
|
|
712
|
+
* PascalCase / snake_case / kebab-case boundaries into lowercase subwords
|
|
713
|
+
* (mirrors the index-side `explodeIdentifiers` convention from
|
|
714
|
+
* `@noir-ai/context`), so a query for `ContextEngine` yields `context` +
|
|
715
|
+
* `engine` and a query for `recall_memory` yields `recall` + `memory`.
|
|
716
|
+
*
|
|
717
|
+
* Tokens shorter than {@link MIN_ENTITY_LEN} and {@link STOPWORDS} are dropped
|
|
718
|
+
* to keep matching precise. Pure + deterministic.
|
|
719
|
+
*/
|
|
720
|
+
declare function extractEntities(query: string): string[];
|
|
721
|
+
/**
|
|
722
|
+
* Run hybrid recall (BM25 ∪ kNN → RRF → entity-boost → KV hydration) scoped to
|
|
723
|
+
* `source:'memory'`. Read-only against the injected store; no second connection,
|
|
724
|
+
* no network, no LLM (blueprint D6).
|
|
725
|
+
*
|
|
726
|
+
* The returned hits carry the FULL `content` hydrated from the authoritative KV
|
|
727
|
+
* row (DS-9 — never the truncated FTS snippet). `degraded`/`mode` describe the
|
|
728
|
+
* actual outcome of THIS call: `mode:'bm25-only'` + `degraded:true` when the
|
|
729
|
+
* embedder was unavailable and recall fell back to BM25.
|
|
730
|
+
*/
|
|
731
|
+
declare function recallMemory(deps: RecallDeps, query: string, opts?: RecallOptions): Promise<RecallMemoryResult>;
|
|
732
|
+
|
|
733
|
+
/** Per-observation authoritative-row key prefix; value is the full {@link Observation}. */
|
|
734
|
+
declare const OBS_PREFIX = "memory:obs:";
|
|
735
|
+
/** KV key holding the per-project {@link SessionInfo} rollup list. */
|
|
736
|
+
declare const SESSIONS_KEY = "memory:sessions";
|
|
737
|
+
/** KV key holding the sorted list of all observation ids (status + candidates). */
|
|
738
|
+
declare const INDEX_KEY = "memory:index";
|
|
739
|
+
/** KV key holding the consolidation refusal audit log (DS-6: refuse + LOG). */
|
|
740
|
+
declare const CONSOLIDATION_MISS_KEY = "memory:consolidation:miss";
|
|
741
|
+
/** Build a `memory:obs:<id>` KV key. */
|
|
742
|
+
declare function obsKey(id: string): string;
|
|
743
|
+
/**
|
|
744
|
+
* Hydrate the FULL {@link Observation} for `id` from the authoritative KV row.
|
|
745
|
+
* Returns `null` when the id was never saved (or has been forgotten — the
|
|
746
|
+
* tombstone reads back as `null`). This is the ONLY correct way to read an
|
|
747
|
+
* observation's complete `content` (DS-9: never the truncated FTS snippet).
|
|
748
|
+
*/
|
|
749
|
+
declare function getObservation(store: Store, id: string): Observation | null;
|
|
750
|
+
/** Write the authoritative full row (the source of truth — DS-2/R4). */
|
|
751
|
+
declare function setObservation(store: Store, obs: Observation): void;
|
|
752
|
+
/**
|
|
753
|
+
* Clear the authoritative KV row for `id` (tombstone — mirrors the context
|
|
754
|
+
* indexer's `persist(..., tombstones)` which writes `null`). The id MUST also
|
|
755
|
+
* be dropped from {@link INDEX_KEY} and the session rollup by the caller so the
|
|
756
|
+
* index stays consistent (see engine.ts `forget`).
|
|
757
|
+
*/
|
|
758
|
+
declare function clearObservation(store: Store, id: string): void;
|
|
759
|
+
/** All observation ids currently tracked (insertion order). Empty array if none. */
|
|
760
|
+
declare function getObservationIds(store: Store): string[];
|
|
761
|
+
/** Overwrite the full id list (used by `save`/`forget` after an atomic RMW). */
|
|
762
|
+
declare function setObservationIds(store: Store, ids: string[]): void;
|
|
763
|
+
/** The per-project {@link SessionInfo} rollup list (empty array if none). */
|
|
764
|
+
declare function getSessions(store: Store): SessionInfo[];
|
|
765
|
+
/** Overwrite the sessions rollup (used by the RMW helpers below). */
|
|
766
|
+
declare function setSessions(store: Store, sessions: SessionInfo[]): void;
|
|
767
|
+
/**
|
|
768
|
+
* Increment the rollup for `sessionId` (inserting a new entry when first seen),
|
|
769
|
+
* bumping `lastTs` to the max. RMW — call within the engine's serialized/sync KV
|
|
770
|
+
* block so two concurrent saves cannot lose an increment.
|
|
771
|
+
*/
|
|
772
|
+
declare function bumpSession(store: Store, sessionId: string, project: SessionInfo['project'], ts: number): void;
|
|
773
|
+
/**
|
|
774
|
+
* Decrement the rollup for `sessionId`, dropping the entry when it reaches zero
|
|
775
|
+
* so a forgotten observation cleans up its own empty session. RMW — call within
|
|
776
|
+
* the engine's serialized/sync KV block. No-op for an unknown session.
|
|
777
|
+
*/
|
|
778
|
+
declare function decrementSession(store: Store, sessionId: string): void;
|
|
779
|
+
/**
|
|
780
|
+
* One recorded consolidation refusal. `reason` is the documented
|
|
781
|
+
* {@link ConsolidationResult} refusal cause; `provider` is recorded when a
|
|
782
|
+
* provider WAS configured but the run still refused (e.g. the S8 layer was
|
|
783
|
+
* unavailable), so the user can see exactly why nothing happened.
|
|
784
|
+
*/
|
|
785
|
+
interface ConsolidationMiss {
|
|
786
|
+
/** Epoch millis of the refusal. */
|
|
787
|
+
ts: number;
|
|
788
|
+
/** Documented refusal reason (`'no-provider' | 'model-unavailable' | 'no-candidates'`). */
|
|
789
|
+
reason: string;
|
|
790
|
+
/** Provider key, when one was configured (absent for the no-provider case). */
|
|
791
|
+
provider?: string;
|
|
792
|
+
}
|
|
793
|
+
/** The full refusal audit log (newest appended last). Empty array if none. */
|
|
794
|
+
declare function getConsolidationMisses(store: Store): ConsolidationMiss[];
|
|
795
|
+
/**
|
|
796
|
+
* Append a refusal record to the audit log. RMW — called ONLY from the engine's
|
|
797
|
+
* serialized `consolidate`, so the append cannot race itself. DS-6: a refusal is
|
|
798
|
+
* never silent — the miss is recorded so the user can see why no lesson was
|
|
799
|
+
* written (and that NO paid call was made).
|
|
800
|
+
*/
|
|
801
|
+
declare function appendConsolidationMiss(store: Store, miss: ConsolidationMiss): void;
|
|
802
|
+
|
|
803
|
+
export { CAPTURE_HOOKS, CONSOLIDATION_MISS_KEY, CONSOLIDATION_SYSTEM_PROMPT, type CaptureEvent, type CaptureEventType, type CapturePayload, type CapturePolicy, type ConsolidateOptions, type ConsolidationConfig, type ConsolidationDeps, type ConsolidationMiss, type ConsolidationResult, DEFAULT_CAPTURE_HOOKS, DEFAULT_CAPTURE_POLICY, DEFAULT_CONSOLIDATE_LIMIT, DEFAULT_IMPORTANCE, type ForgetResult, INDEX_KEY, MEMORY_TYPES, type MemoryCompleteRequest, type MemoryCompleteResult, type MemoryConfig, type MemoryEngine, MemoryEngineImpl, type MemoryEngineOptions, type MemoryHit, type MemoryModel, type MemorySource, type MemoryStatus, type MemoryType, type MemoryUserConfig, OBS_PREFIX, type Observation, type RecallDeps, type RecallMemoryResult, type RecallOptions, SESSIONS_KEY, type SaveInput, type SearchOptions, type SessionInfo, appendConsolidationMiss, buildContent, bumpSession, captureSource, clearObservation, createMemoryEngine, decrementSession, dedupeConcepts, describeToolCall, extractEntities, extractFiles, gatherCandidates, getConsolidationMisses, getObservation, getObservationIds, getSessions, inferType, obsKey, recallMemory, resolveMemoryConfig, runConsolidation, serializeCandidates, setObservation, setObservationIds, setSessions, toSaveInput };
|