@anuma/sdk 6.21.1 → 6.23.0

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.
Files changed (32) hide show
  1. package/dist/expo/{chunk-CIMZZ7GQ.mjs → chunk-6BULYLRY.mjs} +92 -8
  2. package/dist/expo/index.cjs +761 -162
  3. package/dist/expo/index.d.mts +151 -9
  4. package/dist/expo/index.d.ts +151 -9
  5. package/dist/expo/index.mjs +516 -14
  6. package/dist/{react/recall-7Q3V337H.mjs → expo/recall-CUTJWQDQ.mjs} +1 -1
  7. package/dist/memory/context.cjs +183 -0
  8. package/dist/memory/context.d.mts +936 -0
  9. package/dist/memory/context.d.ts +936 -0
  10. package/dist/memory/context.mjs +157 -0
  11. package/dist/react/{chunk-AVYB5IXC.mjs → chunk-GPLMMMZN.mjs} +1 -1
  12. package/dist/react/{chunk-JRVAUZHM.mjs → chunk-K4N52PSK.mjs} +93 -9
  13. package/dist/react/{consolidate-URHGJT7I.mjs → consolidate-XKS6IZFC.mjs} +2 -2
  14. package/dist/react/index.cjs +876 -270
  15. package/dist/react/index.d.mts +209 -9
  16. package/dist/react/index.d.ts +209 -9
  17. package/dist/react/index.mjs +569 -65
  18. package/dist/{expo/recall-FQQGE6A2.mjs → react/recall-LFHIWONV.mjs} +2 -2
  19. package/dist/server/{chunk-4DSDATYG.mjs → chunk-FEDRFVHD.mjs} +182 -98
  20. package/dist/server/index.cjs +815 -216
  21. package/dist/server/index.d.mts +150 -5
  22. package/dist/server/index.d.ts +150 -5
  23. package/dist/server/index.mjs +509 -7
  24. package/dist/server/{recall-HA4UX4BB.mjs → recall-Z2WJGXOL.mjs} +1 -1
  25. package/dist/telemetry/index.d.mts +7 -1
  26. package/dist/telemetry/index.d.ts +7 -1
  27. package/dist/tools/index.cjs +347 -1
  28. package/dist/tools/index.d.mts +167 -1
  29. package/dist/tools/index.d.ts +167 -1
  30. package/dist/tools/index.mjs +343 -1
  31. package/package.json +8 -1
  32. package/dist/react/{chunk-WUG6GTHO.mjs → chunk-MHZB6KIX.mjs} +8 -8
@@ -0,0 +1,936 @@
1
+ import { Model, Database, Collection } from '@nozbe/watermelondb';
2
+
3
+ /**
4
+ * Options for signing messages.
5
+ */
6
+ interface SignMessageOptions {
7
+ /** Whether to show wallet UI during signing. Default: true */
8
+ showWalletUIs?: boolean;
9
+ }
10
+ /**
11
+ * Type for the signMessage function that client must provide.
12
+ * This is typically from Privy's useSignMessage hook.
13
+ */
14
+ type SignMessageFn = (message: string, options?: SignMessageOptions) => Promise<string>;
15
+ /**
16
+ * Type for embedded wallet signer function that enables silent signing.
17
+ * For Privy embedded wallets, this can sign programmatically without user interaction
18
+ * when configured correctly in the Privy dashboard.
19
+ */
20
+ type EmbeddedWalletSignerFn = (message: string, options?: SignMessageOptions) => Promise<string>;
21
+
22
+ declare class VaultMemory extends Model {
23
+ static table: string;
24
+ content: string;
25
+ scope: string;
26
+ folderId: string | null;
27
+ userId: string | null;
28
+ embedding: string | null;
29
+ /** Model that produced `embedding`. Null on legacy rows (grandfathered). */
30
+ embeddingModel: string | null;
31
+ sourceChunkIds: string | null;
32
+ proofCount: number | null;
33
+ source: string | null;
34
+ /** W6 temporal lane — Unix ms timestamp of when the event occurred. */
35
+ eventTimeStart: number | null;
36
+ /** W6 temporal lane — Unix ms timestamp of event end (range/ongoing). */
37
+ eventTimeEnd: number | null;
38
+ /** W6 temporal lane — `point | range | ongoing | null`. */
39
+ eventTimeKind: string | null;
40
+ /** When true, the user has manually set this memory's topics; auto-extraction
41
+ * leaves its entity links alone. Null on legacy rows (treated as false). */
42
+ topicsUserManaged: boolean | null;
43
+ /** The durable, synced record of this memory's topics — JSON `StoredTopic[]`.
44
+ * `entity` / `memory_entity` are a device-local index over it. Null = pre-v42
45
+ * (backfilled from the row's current links by the sweep). */
46
+ topics: string | null;
47
+ /** Unix ms of the last `topics` write. Separate from `updated_at`, which every
48
+ * topic writer pins on purpose (recall recency) — see the schema note. */
49
+ topicsUpdatedAt: number | null;
50
+ /** The photo(s) a SERVER-EXTRACTED memory came from — JSON
51
+ * `[{feed_item_id, object_key}]`, exactly the shape
52
+ * GET /api/memories/published returns. Null on anything not from a photo. */
53
+ media: string | null;
54
+ /** Unix ms of the last LLM topic-extraction pass. Null = never extracted
55
+ * standalone (linked legacy rows are grandfathered as extracted).
56
+ * DEPRECATED (v42) — subsumed by `topics_updated_at`; see the schema note. */
57
+ topicsExtractedAt: number | null;
58
+ /** A2 supersession: newer memory id that replaced this one, or null if live. */
59
+ supersededBy: string | null;
60
+ supersededAt: number | null;
61
+ /** Extraction-logic version this memory was last stamped under. Null (pre-v38)
62
+ * reads as 0, so a TOPICS_EXTRACTION_VERSION bump re-extracts stale rows.
63
+ * DEPRECATED (v42) — subsumed by `topics_updated_at`; see the schema note. */
64
+ topicsExtractedVersion: number | null;
65
+ /** C3 re-observation watermark: Unix ms of the last retain() merge, or null. */
66
+ lastObservedAt: number | null;
67
+ /** Typed memory (PR1) — the extractor's FactType. Null on legacy/manual rows. */
68
+ factType: string | null;
69
+ /** Decay archive state (PR2) — Unix ms when archived, null when active. */
70
+ archivedAt: number | null;
71
+ /** Tier-0 security (PR3) — "quarantined" | "trusted" | null. */
72
+ trustTier: string | null;
73
+ /** People Nearby visibility: 'private' | 'public'. Null on legacy rows, and
74
+ * any unrecognised value, read as 'private' — never published without opt-in. */
75
+ visibility: string | null;
76
+ /** Owner opted this memory into their own digital twin (twin-scoped only). */
77
+ twinOptIn: boolean | null;
78
+ /** Unix ms when visibility last became non-private; null while private. */
79
+ publishedAt: number | null;
80
+ /** Reserved coarse-geohash slot for landmark/Trail memories. */
81
+ geohash: string | null;
82
+ createdAt: Date;
83
+ updatedAt: Date;
84
+ isDeleted: boolean;
85
+ }
86
+
87
+ /**
88
+ * A canonical entity (person, place, thing) extracted from the user's
89
+ * conversations. Many-to-many with vault memories via {@link MemoryEntity}.
90
+ *
91
+ * Created on the write path by the auto-extraction worker (W2) when a
92
+ * candidate fact arrives with non-empty `entities[]`. Used at retrieval
93
+ * time as a 3rd RRF lane (W5 graph traversal): query entity overlap
94
+ * with stored memories surfaces topically-related facts that pure
95
+ * semantic search misses.
96
+ */
97
+ declare class Entity extends Model {
98
+ static table: string;
99
+ /** Lowercased, trimmed name used for dedup and lookup. */
100
+ canonicalName: string;
101
+ /** Optional category — "person" | "place" | "thing" | "concept" | etc. */
102
+ kind: string | null;
103
+ createdAt: Date;
104
+ updatedAt: Date;
105
+ }
106
+ /**
107
+ * Join row: a memory references an entity (and vice versa). Multiple
108
+ * rows per memory (one per entity) and per entity (one per memory).
109
+ */
110
+ declare class MemoryEntity extends Model {
111
+ static table: string;
112
+ memoryId: string;
113
+ entityId: string;
114
+ /**
115
+ * User-scope for the W5 graph lane. Optional because client-side
116
+ * (single-user) deployments may leave it null; server-side multi-user
117
+ * deployments must set it so retrieval can filter cross-user IDs.
118
+ */
119
+ userId: string | null;
120
+ createdAt: Date;
121
+ }
122
+
123
+ /**
124
+ * Who put a topic on a memory. Written to `memory_vault.topics` but not yet
125
+ * READ anywhere — it exists so a later improvement can refresh the `auto`
126
+ * entries of a curated memory while leaving `user` entries alone (today
127
+ * `topics_user_managed` is all-or-nothing per memory) without needing a second
128
+ * migration or backfill. Don't build that behavior off it yet.
129
+ *
130
+ * When you do: a value written by the v42 backfill is NOT ground truth. Pre-v42
131
+ * rows carry no per-topic provenance, so `backfillMemoryTopicsOp` derives it
132
+ * from the per-memory `topics_user_managed` flag and stamps a curated legacy
133
+ * row's every topic `user` — see the note at that derivation.
134
+ * @public
135
+ */
136
+ type TopicSource = "user" | "auto";
137
+ /**
138
+ * One entry in `memory_vault.topics` — the durable, synced record of a memory's
139
+ * topics. `name` carries the CALLER's display casing, unlike
140
+ * {@link StoredEntity.canonicalName}, which {@link normalizeEntityName}
141
+ * lowercases and which has no display column: preserving casing here is the
142
+ * point of storing names on the memory row.
143
+ * @public
144
+ */
145
+ interface StoredTopic {
146
+ name: string;
147
+ kind?: string;
148
+ source: TopicSource;
149
+ }
150
+
151
+ interface EntityOperationsContext {
152
+ database: Database;
153
+ entityCollection: Collection<Entity>;
154
+ memoryEntityCollection: Collection<MemoryEntity>;
155
+ /**
156
+ * Optional user-scope. When provided, `linkMemoryEntitiesOp` stamps
157
+ * `user_id` on new memory_entity rows and `getMemoriesByEntityNamesOp`
158
+ * filters lookups by it. Leave undefined for single-user clients.
159
+ */
160
+ userId?: string;
161
+ /**
162
+ * When `true`, `getMemoriesByEntityNamesOp` admits rows with
163
+ * `user_id = null` alongside the strict `userId` match. Set this on
164
+ * LokiJS (web) adapters where the v31 `unsafeExecuteSql` backfill
165
+ * is a no-op — pre-v31 rows otherwise become invisible to the W5
166
+ * lane until `backfillMemoryEntityUserIdsOp` runs. Default `false`
167
+ * (server / SQLite, where the migration backfill is authoritative).
168
+ */
169
+ allowUnscopedRows?: boolean;
170
+ }
171
+
172
+ /**
173
+ * Shared portal LLM call for the memory pipeline.
174
+ *
175
+ * The three LLM-driven steps (`extractFacts`, `consolidateMemory`,
176
+ * `decomposeQuery`) share the same shape: POST to `/api/v1/chat/completions`
177
+ * with a system+user prompt and `response_format: json_object`, parse
178
+ * `choices[0].message.content` as JSON, validate, return null on any failure.
179
+ * This helper owns the fetch+timeout+parse+log boilerplate so each caller
180
+ * stays focused on its prompt and validator.
181
+ *
182
+ * PROMPT CACHING. The portal inserts explicit prompt-cache breakpoints only for
183
+ * `anthropic/` models. Its request schema does carry OpenAI's
184
+ * `prompt_cache_key`, but that is a routing hint for OpenAI's own implicit
185
+ * cache rather than a switch that enables caching, and we deliberately don't
186
+ * send it: the memory pipeline runs open-weights models by default (gpt-oss for
187
+ * extraction/topics, ling for consolidation), and this module already has one
188
+ * hard lesson about OpenAI-standard fields a non-OpenAI provider hard-400s on
189
+ * (`response_format`, below). Revisit only with a verified per-provider gate.
190
+ *
191
+ * So on the models this pipeline actually uses, the only cache that can hit is
192
+ * the provider's own implicit prefix cache — which makes prefix stability the
193
+ * one lever a caller controls. Messages are assembled system-first (below), so
194
+ * every caller must pass a CONSTANT `systemPrompt` and keep all per-call
195
+ * material in `userMessage`: a single interpolated timestamp, id, or count in a
196
+ * system prompt shifts every byte after it and misses the cache on every
197
+ * request thereafter. The memory callers are regression-gated on that property
198
+ * in `promptPrefixStability.test.ts`.
199
+ *
200
+ * Whether those implicit caches hit is observable: each successful response
201
+ * logs the portal's reported token counts at debug level, including
202
+ * `portal.cached_tokens` (the prompt tokens the provider served from cache —
203
+ * the portal omits the field when there was no hit). See {@link logPortalUsage}.
204
+ *
205
+ * There is no batch/async-bulk route on the portal, so a backfill or reindex
206
+ * has no discounted-throughput path either; its only levers are the same stable
207
+ * prefix and caller-side batching (e.g. topicExtract's 10-memories-per-call).
208
+ */
209
+
210
+ /**
211
+ * Auth for portal LLM calls (extraction, consolidation, decomposition,
212
+ * reflection). Mirrors `memoryEngine`'s `EmbeddingOptions` dual-auth:
213
+ *
214
+ * - `apiKey`: For direct API keys (uses x-api-key header)
215
+ * - `getToken`: For Privy identity tokens (uses Authorization: Bearer header)
216
+ *
217
+ * At least one of `apiKey` or `getToken` must be provided (enforced at
218
+ * runtime); `apiKey` takes precedence when both are set.
219
+ *
220
+ * @public
221
+ */
222
+ interface PortalLlmAuth {
223
+ /** Direct API key — sent as `x-api-key` (server-side / CLI usage). Wins when both are provided. */
224
+ apiKey?: string;
225
+ /** Function to get an auth token (e.g., Privy's getIdentityToken). Token is sent as `Authorization: Bearer`. */
226
+ getToken?: () => Promise<string | null>;
227
+ }
228
+
229
+ /**
230
+ * Linear recency decay multiplier — applied post-fusion to nudge fresh
231
+ * memories above stale ones with otherwise comparable scores.
232
+ *
233
+ * Formula:
234
+ * multiplier(ageDays) = max(floor, 1 - perYearDecay * (ageDays / 365))
235
+ *
236
+ * Memories without an `updatedAt` get a neutral 0.5 (Hindsight pattern —
237
+ * neither boost nor penalty when freshness is unknown).
238
+ */
239
+ interface RecencyOptions {
240
+ /** Override "now" — useful for deterministic tests and back-dated benchmarks. */
241
+ now?: Date;
242
+ /** Linear decay slope per year. Default 0.2 (1y → 0.8x, 4.5y → floor). */
243
+ perYearDecay?: number;
244
+ /** Lower bound on the multiplier so very old memories don't vanish. Default 0.1. */
245
+ floor?: number;
246
+ /** Multiplier returned when `updatedAt` is missing. Default 0.5 (neutral). */
247
+ noDateMultiplier?: number;
248
+ }
249
+
250
+ /**
251
+ * How a fact's evidence has been evolving.
252
+ *
253
+ * - `new` — first seen inside the recent window, few proofs yet.
254
+ * - `strengthening` — established fact re-observed recently (rising density).
255
+ * - `stable` — known for a while, still observed inside the stale window.
256
+ * - `weakening` — quiet for ≥30 and <90 days after having been observed.
257
+ * - `stale` — no observation in ≥90 days.
258
+ */
259
+ type ObservationTrend = "new" | "strengthening" | "stable" | "weakening" | "stale";
260
+
261
+ /**
262
+ * Unified memory layer types — the Recall / Retain API.
263
+ *
264
+ * This module defines the public surface for the new unified memory layer
265
+ * that sits above `memoryVault` (curated facts) and `memoryEngine` (raw
266
+ * conversation chunks). Callers should prefer these types over the legacy
267
+ * `VaultSearchResult` / `ChunkSearchResult` shapes; the underlying stores
268
+ * may converge later, the API will not.
269
+ */
270
+
271
+ type MemoryKind = "fact" | "chunk";
272
+ /**
273
+ * Budget controls retrieval depth/cost. Higher budgets enable more
274
+ * candidate sources and the cross-encoder reranker.
275
+ *
276
+ * - `low`: cosine + BM25 + recency. No reranker. Mobile default.
277
+ * - `mid`: + cross-encoder rerank.
278
+ * - `high`: + multi-hop graph traversal. LLM-free — query decomposition
279
+ * (when wanted) lives in the tool/agent layer ({@link createRecallTool}),
280
+ * not inside `recall()` itself (719/B4).
281
+ */
282
+ type Budget = "low" | "mid" | "high";
283
+ interface ScoreBreakdown {
284
+ /** Raw cosine similarity. Set only when callers compute it explicitly
285
+ * (e.g. cosine-only `useFusion: false` search); the fusion path sets
286
+ * {@link fused} instead so the label stays honest. */
287
+ cosine?: number;
288
+ /** Composite score from the fused ranker (cosine + BM25 + RRF + recency
289
+ * + proof boost). What `RankedMemory.score` carries when the fusion
290
+ * pipeline ran. */
291
+ fused?: number;
292
+ bm25?: number;
293
+ recency?: number;
294
+ rerank?: number;
295
+ /** Final RRF rank (1-indexed) before any boost. */
296
+ rrfRank?: number;
297
+ }
298
+ /**
299
+ * A ranked memory returned by `recall()`. Shape is uniform across kinds —
300
+ * `kind` discriminates, kind-specific metadata is optional.
301
+ */
302
+ interface RankedMemory {
303
+ id: string;
304
+ kind: MemoryKind;
305
+ content: string;
306
+ score: number;
307
+ scoreBreakdown?: ScoreBreakdown;
308
+ sourceChunkIds?: string[];
309
+ proofCount?: number;
310
+ source?: string;
311
+ folderId?: string | null;
312
+ /**
313
+ * C3 re-observation watermark (Unix ms). Null/undefined when the fact
314
+ * has never been merge-reinforced since the column landed.
315
+ */
316
+ lastObservedAt?: number | null;
317
+ /**
318
+ * C2 observation-trend label derived from createdAt / lastObservedAt /
319
+ * proofCount over 30/90-day windows. Fact-only; omitted for chunks.
320
+ */
321
+ observationTrend?: ObservationTrend;
322
+ /**
323
+ * Anchored event-time for the fact (the date the underlying event
324
+ * occurred, not the write time). When present, the recall executor
325
+ * surfaces it to the LLM as `(event: YYYY-MM-DD)` so the answer model
326
+ * can do date arithmetic for temporal-reasoning questions. Null /
327
+ * undefined means the fact has no anchored date.
328
+ */
329
+ eventTimeStart?: number | null;
330
+ eventTimeEnd?: number | null;
331
+ eventTimeKind?: "point" | "range" | "ongoing" | null;
332
+ /**
333
+ * Typed memory (PR1) — the extractor's FactType classification for this
334
+ * fact, threaded through the same channel as the event-time anchors so
335
+ * recall results (and UI) can show the type without a second DB read.
336
+ * Null/undefined on legacy/untyped/manual facts. Kept as a loose string
337
+ * (not narrowed to FactType) since it originates from a stored column.
338
+ */
339
+ factType?: string | null;
340
+ conversationId?: string;
341
+ messageId?: string;
342
+ role?: "user" | "assistant";
343
+ createdAt: Date;
344
+ updatedAt: Date;
345
+ }
346
+ interface RecallOptions {
347
+ /** Which kinds to search. Default: ['fact']. */
348
+ types?: MemoryKind[];
349
+ /** Max items returned. Default: 8. */
350
+ limit?: number;
351
+ /** Token budget for the result; greedy-truncates after ranking. Not yet enforced; reserved for W1. */
352
+ maxTokens?: number;
353
+ /** Search depth. Default: 'low'. */
354
+ budget?: Budget;
355
+ /** Include source chunks for fact memories that have provenance. Default: false. */
356
+ includeChunks?: boolean;
357
+ /** Vault scope filter. Vault-only. */
358
+ scopes?: string[];
359
+ /** Vault folder filter. Vault-only. */
360
+ folderId?: string | null;
361
+ /**
362
+ * Typed memory (PR1) — restrict fact recall to these FactTypes. Optional
363
+ * and no-op when unset (all types are eligible). Vault-only.
364
+ */
365
+ factTypes?: FactType[];
366
+ /** Restrict fact candidates BEFORE ranking. Empty means no facts. When set,
367
+ * unrestricted chunk search is disabled; it must not escape a topic scope.
368
+ * Asking for `"chunk"` alongside this yields no excerpts and reports
369
+ * `"chunks-scope-restricted"` on {@link RecallDiagnostics.degraded}. */
370
+ memoryIds?: string[];
371
+ /**
372
+ * PR5 — optional per-FactType score multiplier applied in the fusion boost
373
+ * stage (e.g. boost `identity`/`constraint`, down-weight `ongoing_context`).
374
+ * A type absent from the map (and untyped rows) uses 1.0, so an empty/omitted
375
+ * map is a no-op (uniform weighting). Vault-only.
376
+ */
377
+ factTypeWeights?: Partial<Record<FactType, number>>;
378
+ /** Restrict chunk search to one conversation. Chunk-only. */
379
+ conversationId?: string;
380
+ /** Exclude one conversation from chunk search. Chunk-only. */
381
+ excludeConversationId?: string;
382
+ /** Drop results below this score. Default: 0.1 for facts, 0.5 for chunks (mirrors today's defaults). */
383
+ minScore?: number;
384
+ /**
385
+ * Pre-decomposed facet queries for the composite ranker. When ≥2 are
386
+ * supplied, the vault lane runs `rankComposite` over them (no LLM call
387
+ * inside `recall()` — 719/B4). Callers that still want LLM rewrite
388
+ * (e.g. {@link createRecallTool} at `budget: 'high'`) call
389
+ * `decomposeQuery` themselves and pass the result here. A single entry
390
+ * (or omitting this) keeps the single-query path.
391
+ */
392
+ subQueries?: string[];
393
+ /**
394
+ * Auth + endpoint for optional LLM helpers that reuse portal auth —
395
+ * currently {@link RecallOptions.graphRefine} neighbor selection.
396
+ * Query decomposition is **not** driven by this field inside `recall()`
397
+ * (719/B4); {@link createRecallTool} reads the same shape from
398
+ * `RecallToolOptions.decomposeOptions` for tool-layer rewrite.
399
+ *
400
+ * Callers that still pass `{ budget: 'high', decomposeOptions }` without
401
+ * {@link RecallOptions.subQueries} keep compiling but no longer rewrite —
402
+ * `recall()` emits `decompose-moved` on {@link RecallDiagnostics.degraded}
403
+ * so upgrades without a changelog read still leave a telemetry breadcrumb.
404
+ *
405
+ * Auth is the dual pattern — one of `apiKey` / `getToken` is required;
406
+ * see {@link PortalLlmAuth}.
407
+ */
408
+ decomposeOptions?: PortalLlmAuth & {
409
+ baseUrl?: string;
410
+ model?: string;
411
+ };
412
+ /**
413
+ * Reference "now" for resolving relative temporal phrases in the
414
+ * query ("last week", "yesterday", "N days ago"). Default: `Date.now()`.
415
+ * Override for back-dated evaluation harnesses (bench corpora dated
416
+ * 2021–2023) and for deterministic tests — otherwise the W6 lane
417
+ * resolves windows in 2026 and never overlaps stored event_time.
418
+ */
419
+ now?: number;
420
+ /**
421
+ * Best-effort observability hook. Called once per `recall()` with per-lane
422
+ * timings, lane counts, and soft-degradation signals — the raw material for
423
+ * tuning latency/quality and for wiring recall telemetry to PostHog. Invoked
424
+ * synchronously just before `recall()` returns; a throwing callback is
425
+ * swallowed (diagnostics must never break retrieval). Off unless provided.
426
+ */
427
+ onDiagnostics?: (diagnostics: RecallDiagnostics) => void;
428
+ /** Number of candidates fed to the cross-encoder rerank stage. Default: 5;
429
+ * was 30 until 2026-08-13 — see anuma-ai/sdk#845. */
430
+ rerankTopN?: number;
431
+ /** Multiplicative cross-encoder blend weight. Default: 0.1. */
432
+ ceWeight?: number;
433
+ /** Recency boost slope in the fused ranker. Default: 1.0. */
434
+ recencyAlpha?: number;
435
+ /** Recency decay curve overrides (per-year decay slope, floor, no-date multiplier). */
436
+ recency?: RecencyOptions;
437
+ /** Apply MMR diversification after ranking (rerank pipeline only). Default: false. */
438
+ mmr?: boolean;
439
+ /** Supersession score-gap transfer factor. Default: 0.8. */
440
+ supersessionBoost?: number;
441
+ /** Hard cap on the supersession candidate window. Default: 50. */
442
+ supersessionWindow?: number;
443
+ /** Proof-count log-boost scale. Default: 0.1. */
444
+ proofCountAlpha?: number;
445
+ /** Divisor mapping BM25 scores to the admission floor. Default: 50. */
446
+ bm25AdmissionDivisor?: number;
447
+ /** RRF smoothing constant for lane fusion (facts × chunks and side lanes). Default: 60. */
448
+ rrfK?: number;
449
+ /**
450
+ * Decrypt vault memory content only for the top-N ranked candidates
451
+ * instead of the whole vault. Forwarded verbatim to the vault search
452
+ * pipeline's `MemoryVaultSearchOptions`. Default: off (legacy
453
+ * whole-vault decrypt path).
454
+ */
455
+ decryptLast?: boolean;
456
+ /** Total graph hops incl. the seed lookup (hop 1). Default: 1 (seed only). */
457
+ maxHops?: number;
458
+ /** Max neighbor entities expanded per hop. Default: 8. */
459
+ entityFanout?: number;
460
+ /** Hard cap on accumulated memory IDs across all hops. Default: 64. */
461
+ nodeBudget?: number;
462
+ /**
463
+ * PR5 — enable LLM graph path-refinement: at each traversal hop a model picks
464
+ * which neighbor entities to expand instead of pure co-occurrence ranking.
465
+ * Opt-in (default false); only active on the `high` budget (needs the
466
+ * `traverse` flag) AND when `decomposeOptions` is set (reuses that auth).
467
+ * Falls back to deterministic co-occurrence order on any error. Adds ≤1 LLM
468
+ * call per expansion hop.
469
+ */
470
+ graphRefine?: boolean;
471
+ }
472
+ interface RecallResult {
473
+ memories: RankedMemory[];
474
+ /** Diagnostic: budget actually used (may downgrade if reranker fails). */
475
+ usedBudget: Budget;
476
+ /** Diagnostic: was the reranker invoked? */
477
+ reranked: boolean;
478
+ /** Diagnostic: total candidates considered before truncation. */
479
+ candidateCount: number;
480
+ /** Diagnostic: total memories in the vault when fact lane was queried. */
481
+ vaultSize?: number;
482
+ }
483
+ /** Soft-degradation signals surfaced via {@link RecallDiagnostics.degraded}. */
484
+ type RecallDegradation =
485
+ /** Rerank was requested (budget mid/high) but the cross-encoder didn't run
486
+ * this call — unavailable (e.g. React Native) or a transient failure. */
487
+ "rerank-unavailable"
488
+ /**
489
+ * @deprecated 719/B4 moved query decomposition out of `recall()`. Budget
490
+ * `high` no longer requires `decomposeOptions` and this signal is never
491
+ * emitted. Prefer `"decompose-moved"` for the upgrade breadcrumb. Kept in
492
+ * the union so existing telemetry consumers stay typed.
493
+ */
494
+ | "decompose-unavailable"
495
+ /**
496
+ * `budget: 'high'` with `decomposeOptions` set but no usable
497
+ * {@link RecallOptions.subQueries}. Pre-B4 this call shape triggered LLM
498
+ * rewrite inside `recall()`; post-B4 it stays on the single-query high
499
+ * path (rerank + graph) and callers must pass facets (or use
500
+ * {@link createRecallTool}). Surfaced so silent quality regressions show
501
+ * up in diagnostics instead of looking healthier than before.
502
+ */
503
+ | "decompose-moved"
504
+ /** There was no usable cosine lane, so results were ranked on BM25 (lexical)
505
+ * alone: either the query embedding failed (or came back empty), or no
506
+ * candidate had a vector to score against it because the row (re)embed failed.
507
+ * Also set when the chunk lane — cosine-only, with no lexical equivalent — was
508
+ * skipped for the same reason. The embeddings provider is a single upstream
509
+ * with no fallback, so this is a whole-provider outage rather than a per-item
510
+ * miss — expect a quality drop, not a total loss.
511
+ *
512
+ * NOT set for a partial failure that leaves cosine running (a sub-query embed
513
+ * failing back to the single-query path, or a row batch failing while other
514
+ * rows keep usable vectors) — those are logged, not reported, so this stays a
515
+ * reliable outage signal rather than a general embedding-error counter. */
516
+ | "embeddings-unavailable"
517
+ /** The W5 graph (entity) side lane threw and was dropped. Auxiliary, so recall
518
+ * still returned — but its RRF signal is missing from the ranking, and until
519
+ * this existed the drop was a log line and nothing else. */
520
+ | "graph-lane-failed"
521
+ /** The W6 temporal side lane threw and was dropped. Same posture as the graph
522
+ * lane: recall still returned, ranked without the temporal signal. */
523
+ | "temporal-lane-failed"
524
+ /** {@link RecallOptions.memoryIds} was set and `types` asked for `"chunk"`.
525
+ * Conversation excerpts have no equivalent membership filter, so running the
526
+ * chunk lane would escape the topic/folder scope — it is dropped instead.
527
+ * Without this signal a scoped caller asking for excerpts got an empty
528
+ * result indistinguishable from "the vault had no match". */
529
+ | "chunks-scope-restricted";
530
+ /**
531
+ * Why a recall returned nothing. `""` when it returned something, so the field
532
+ * is always present and groupable rather than being absent on the healthy path.
533
+ *
534
+ * The four are different problems: an empty query is a caller bug, no-lanes is a
535
+ * context wiring bug (the requested kinds have no store), vault-empty is a new
536
+ * user, and no-candidates is the only one that is about retrieval quality. They
537
+ * were previously indistinguishable from outside — every one reported
538
+ * `candidateCount: 0`.
539
+ */
540
+ type RecallEmptyReason = "" | "empty-query" | "no-lanes" | "vault-empty" | "no-candidates";
541
+ /**
542
+ * Per-call recall observability payload (see {@link RecallOptions.onDiagnostics}).
543
+ * All timings are wall-clock milliseconds. Lane counts are post-dedupe,
544
+ * pre-fusion. Intended to be forwarded to a metrics sink (e.g. PostHog).
545
+ */
546
+ interface RecallDiagnostics {
547
+ /** Budget actually executed (may have downgraded from the requested one). */
548
+ usedBudget: Budget;
549
+ /** Whether the cross-encoder actually reranked the fact lane this call. */
550
+ reranked: boolean;
551
+ /** Total candidates considered before truncation. */
552
+ candidateCount: number;
553
+ /** Total vault size when the fact lane ran (absent if it didn't). */
554
+ vaultSize?: number;
555
+ /**
556
+ * Which vault read path the fact lane actually executed: `true` for the
557
+ * projected key scan that decrypts only the admission window, `false` for the
558
+ * legacy whole-vault load. Absent when the fact lane didn't run.
559
+ *
560
+ * Reported because "the option was passed" and "the branch ran" are different
561
+ * facts, and #845 needed the second one: the projected path was enabled in
562
+ * production and the p50 did not move, with no way to tell a flag that never
563
+ * reached the bundle from a projection that isn't cheaper at that vault size.
564
+ */
565
+ decryptLast?: boolean;
566
+ /**
567
+ * Rows the fact lane paid to decrypt. Absent when it didn't run.
568
+ *
569
+ * Read against {@link RecallDiagnostics.vaultSize} — that ratio is the whole
570
+ * point. `decryptLast` true with `vaultRowsDecrypted` ≈ `vaultSize` means the
571
+ * admission window is admitting the entire vault and the projection is buying
572
+ * nothing. Far below `vaultSize` with latency unchanged means the decrypt was
573
+ * never the cost.
574
+ */
575
+ vaultRowsDecrypted?: number;
576
+ /**
577
+ * Rows the fact lane had to re-embed through the portal because their stored
578
+ * vector was unusable — stale `embedding_model`, wrong dimension, or
579
+ * unparseable. Absent when the fact lane didn't run.
580
+ *
581
+ * Expected to be 0 on a healthy vault: rows are embedded at write time and the
582
+ * re-embed writes the current model back, so a persistently non-zero value
583
+ * means the writeback is not sticking and every turn is paying for it. On the
584
+ * LEGACY read path this batch is uncapped, so it can be the whole vault.
585
+ */
586
+ vaultRowsEmbedded?: number;
587
+ /** Facts the fact lane returned (post-dedupe, pre-fusion). */
588
+ factCount: number;
589
+ /** Chunks the chunk lane returned (post-dedupe, pre-fusion). */
590
+ chunkCount: number;
591
+ /**
592
+ * Memories actually RETURNED — `memories.length` after fusion, cross-lane
593
+ * dedup and the `limit` slice.
594
+ *
595
+ * `candidateCount` is what was considered; this is what the caller got, and
596
+ * the two are routinely far apart (the fact lane pulls `limit * 2` when fusing).
597
+ * Every consumer that wanted "how many memories did this turn actually get"
598
+ * was reading `candidateCount` and overcounting.
599
+ */
600
+ admittedCount: number;
601
+ /** Highest score among the returned memories; -1 when none were returned. */
602
+ topScore: number;
603
+ /** Lowest score among the returned memories; -1 when none were returned. */
604
+ lowestAdmittedScore: number;
605
+ /**
606
+ * The similarity floor the lane that produced these scores actually applied:
607
+ * the fact lane's when it RETURNED results, otherwise the chunk lane's, and
608
+ * **-1 when neither ran** (empty query, unwired context). Gated on results
609
+ * rather than on the lane running, because a fact lane that ran and came
610
+ * back empty filtered none of the scores in the payload — reporting its
611
+ * 0.1 default against chunks that cleared 0.5 corrupted the telemetry.
612
+ * When NOTHING was admitted, the floor a lane did apply is still reported:
613
+ * "searched at this floor, found nothing" is the useful reading.
614
+ *
615
+ * Per-lane rather than one constant because the two defaults differ (0.1 fact
616
+ * / 0.5 chunk), so a single seeded value reported a floor that a chunk-only
617
+ * recall never applied. Reported next to the scores because the scores alone
618
+ * cannot say what they cleared.
619
+ */
620
+ minScoreApplied: number;
621
+ /**
622
+ * Whether the `limit` cut an ELIGIBLE result — recorded at the cut, not
623
+ * derived from `candidateCount > limit`. In the fused path `candidateCount`
624
+ * counts before provenance suppression, so a recall whose suppressed chunks
625
+ * brought it under the limit would otherwise report a truncation that never
626
+ * happened.
627
+ */
628
+ truncated: boolean;
629
+ /** Memory ids the W5 graph (entity) side lane contributed to the fusion. */
630
+ graphLaneCount: number;
631
+ /** Memory ids the W6 temporal side lane contributed to the fusion. */
632
+ temporalLaneCount: number;
633
+ /** Why nothing came back — see {@link RecallEmptyReason}. `""` when something did. */
634
+ emptyReason: RecallEmptyReason;
635
+ /** Wall-clock phase timings (ms). */
636
+ timings: {
637
+ /** Whole `recall()` call. */
638
+ total: number;
639
+ /** Parallel query-embed + graph/temporal side-lane build. */
640
+ prep: number;
641
+ /** Vault fact-lane search (`searchVaultMemoriesWithSize`). */
642
+ factLane: number;
643
+ /**
644
+ * The cross-encoder's share of {@link factLane} — wall-clock spent inside
645
+ * `rerankPairs`. Billed even when the CE threw partway through, because a
646
+ * rerank that burned three seconds and then failed still cost them.
647
+ *
648
+ * A SUBSET of `factLane`, not a sibling: read `factLane - rerank` for
649
+ * everything else the lane did (query embed, vault read, fused ranking).
650
+ * 0 when the CE did not run; `reranked` is what distinguishes that from a
651
+ * rerank that cost nothing.
652
+ *
653
+ * Exists because #845 spent three rounds arguing about which stage inside
654
+ * the fact lane dominated — whole-vault read, then admission window, then
655
+ * the CE — with one aggregate number for all of them. Every hypothesis was
656
+ * an inference; this makes the question a query.
657
+ */
658
+ rerank: number;
659
+ /**
660
+ * The query embed's share of {@link factLane} — one portal round trip.
661
+ *
662
+ * Also a SUBSET of `factLane`. Together with `rerank` this makes the lane
663
+ * decomposable: `factLane - queryEmbed - rerank` is the local work (vault
664
+ * read, decrypt, BM25/cosine fusion), and each of the three implies a
665
+ * completely different fix.
666
+ *
667
+ * 0 when the lane returned before embedding, which includes every empty
668
+ * vault — `prepareVaultCandidates` short-circuits first. That is why the
669
+ * fast `vault_size = 0` population never established a baseline for this
670
+ * cost, and why the ~850ms floor on the smallest NON-empty vaults had no
671
+ * attributable owner.
672
+ */
673
+ queryEmbed: number;
674
+ /** Chunk-lane search (`searchChunksOp`). */
675
+ chunkLane: number;
676
+ /** Cross-lane RRF fusion + provenance dedup after both lanes. */
677
+ fuse: number;
678
+ };
679
+ /** Soft-degradation signals that fired this call (empty when clean). */
680
+ degraded: RecallDegradation[];
681
+ }
682
+
683
+ /**
684
+ * Auto-extraction worker — W2.
685
+ *
686
+ * After each assistant turn, this pipeline reads the recent conversation,
687
+ * asks an LLM to identify durable user facts, and writes them to the
688
+ * vault via retain(). The user never needs to ask Anuma to remember
689
+ * something — durable facts are captured automatically.
690
+ *
691
+ * Two-stage pipeline (extract → retain). The retain step uses
692
+ * write-time auto-merge (W4) so the same fact re-observed in a later
693
+ * conversation increments proof_count instead of duplicating.
694
+ *
695
+ * Spec: see `tasks/hackathon/auto-extraction-prompt.md`.
696
+ */
697
+
698
+ declare const FACT_TYPES: readonly ["identity", "preference", "relationship", "plan", "ongoing_context", "constraint", "other"];
699
+ type FactType = (typeof FACT_TYPES)[number];
700
+
701
+ /**
702
+ * LEGACY. This column no longer decides anything.
703
+ *
704
+ * `scope === 'shared'` is now the single publication axis: it means the memory
705
+ * is on the People Nearby profile, its text goes to the nearby server, and it
706
+ * may inform what strangers see. `scope: 'private'` means it is not. There is
707
+ * no second control, and scope no longer gates model access either — every
708
+ * model tier reads every memory. Do NOT reason about publication from this
709
+ * type; read `scope`.
710
+ *
711
+ * The column survives in the schema and is still stamped on writes so that
712
+ * stored rows stay readable by builds that predate the change. Nothing on a
713
+ * current build reads it to make a decision.
714
+ *
715
+ * Values, kept for tolerance of already-stored data:
716
+ * - `private`: local-only (a null column reads as private)
717
+ * - `public`: the row was published
718
+ *
719
+ * TWO TIERS ONLY (decided 2026-07-27). An earlier design had a middle
720
+ * `matchable` tier that published a memory's embedding while keeping its text
721
+ * on-device; it was dropped. Any unrecognised stored value — including a
722
+ * `matchable` row written by a pre-release build — reads as `private`, which is
723
+ * the fail-safe direction: it un-publishes rather than exposes.
724
+ */
725
+ type VaultMemoryVisibility = "private" | "public";
726
+ interface StoredVaultMemory {
727
+ /** WatermelonDB internal ID */
728
+ uniqueId: string;
729
+ /** Plain text memory content */
730
+ content: string;
731
+ /** Scope for partitioning memories (e.g., "private", "shared") */
732
+ scope: string;
733
+ /** Folder ID for organization, null if unfiled */
734
+ folderId: string | null;
735
+ /** User ID for multi-user server-side scoping, null on client */
736
+ userId: string | null;
737
+ /** JSON-stringified embedding vector, null if not yet computed */
738
+ embedding: string | null;
739
+ /** Model that produced `embedding`. Null on legacy rows (grandfathered as
740
+ * compatible with the current model). */
741
+ embeddingModel: string | null;
742
+ /** JSON-stringified array of source message IDs this fact was extracted from. */
743
+ sourceChunkIds: string[] | null;
744
+ /** Times this fact has been re-observed (for ranking + UX badges). */
745
+ proofCount: number | null;
746
+ /** How the memory was created: manual | auto-extracted | capsule | photo. */
747
+ source: string | null;
748
+ /** The photo(s) a SERVER-EXTRACTED memory was read out of. Null on every
749
+ * memory that did not come from a photo. See {@link PhotoMediaRef}.
750
+ *
751
+ * OPTIONAL rather than required: every read path sets it (the row mappers
752
+ * always call parseMedia), but making it required would be a compile-break
753
+ * for any caller that CONSTRUCTS a StoredVaultMemory — fixtures, mocks, and
754
+ * anything downstream — for a field that is null on all but photo rows. */
755
+ media?: PhotoMediaRef[] | null;
756
+ /** W6 temporal lane — Unix ms when the event occurred (point/start of range). */
757
+ eventTimeStart: number | null;
758
+ /** W6 temporal lane — Unix ms when the event ended (range only). */
759
+ eventTimeEnd: number | null;
760
+ /** W6 temporal lane — `point | range | ongoing | null`. */
761
+ eventTimeKind: string | null;
762
+ /** When true, the user has manually set this memory's topics (entity links);
763
+ * auto-extraction leaves them alone. False on legacy/auto rows. */
764
+ topicsUserManaged: boolean;
765
+ /** The memory's topics as the DURABLE, synced record — `entity` /
766
+ * `memory_entity` are a device-local index over it. Null = pre-v42, no record
767
+ * yet; `[]` = a record of "no topics". */
768
+ topics: StoredTopic[] | null;
769
+ /** Unix ms of the last `topics` write, or null if never written. Separate from
770
+ * `updatedAt`, which topic writes deliberately pin (recall recency). */
771
+ topicsUpdatedAt: number | null;
772
+ /** Unix ms of the last LLM topic-extraction pass over this memory's content.
773
+ * Null = never extracted standalone; rows that already carry entity links
774
+ * are grandfathered as extracted (see getMemoriesNeedingTopicExtractionOp).
775
+ * DEPRECATED (v42) — subsumed by `topicsUpdatedAt`; see the schema note. */
776
+ topicsExtractedAt: number | null;
777
+ /** Write-time supersession (A2): id of the newer memory that replaced this
778
+ * one (incompatible-value update, e.g. "Lives in Portland" → "Lives in SF").
779
+ * Null = live. Superseded rows are excluded from recall/dedup by default but
780
+ * kept for history + the read-time fallback. */
781
+ supersededBy: string | null;
782
+ /** Unix ms when this memory was superseded. Null when live. */
783
+ supersededAt: number | null;
784
+ /** Extraction-logic version this memory was last stamped under. Null (pre-v38)
785
+ * reads as 0, so a TOPICS_EXTRACTION_VERSION bump re-extracts stale rows.
786
+ * DEPRECATED (v42) — subsumed by `topicsUpdatedAt`; see the schema note. */
787
+ topicsExtractedVersion: number | null;
788
+ /** C3 re-observation watermark: Unix ms of the last retain() merge into this
789
+ * fact. Distinct from `updatedAt` (which merges preserve). Null = never
790
+ * re-observed since the column was added; synthesis falls back to
791
+ * `updatedAt` in that case. */
792
+ lastObservedAt: number | null;
793
+ /** Typed memory (PR1) — the extractor's FactType for this fact, or null on
794
+ * legacy/manual/untyped rows. Plaintext string (not narrowed to FactType
795
+ * here since the DB can hold any stored value). */
796
+ factType: string | null;
797
+ /** Decay archive state (PR2) — Unix ms when archived, or null when active. */
798
+ archivedAt: number | null;
799
+ /** Tier-0 security (PR3) — "quarantined" | "trusted" | null. */
800
+ trustTier: string | null;
801
+ /** People Nearby cross-user visibility. Null column reads as "private". */
802
+ visibility: VaultMemoryVisibility;
803
+ /** Owner opted this memory into their own digital twin even when otherwise
804
+ * private (twin-scoped only — never indexed for matching, never displayed). */
805
+ twinOptIn: boolean;
806
+ /** Unix ms when visibility last became non-private; null while private. */
807
+ publishedAt: number | null;
808
+ /** Reserved coarse-geohash slot for landmark/Trail memories. */
809
+ geohash: string | null;
810
+ createdAt: Date;
811
+ updatedAt: Date;
812
+ isDeleted: boolean;
813
+ }
814
+ /**
815
+ * A photo a server-extracted memory came from, as
816
+ * GET /api/memories/published returns it in `media[]`.
817
+ *
818
+ * Both identifiers travel because both are needed: `feedItemId` is the server's
819
+ * handle for the photo, and `objectKey` is what a client turns into a CDN URL
820
+ * without another round-trip per memory.
821
+ */
822
+ interface PhotoMediaRef {
823
+ feedItemId: number;
824
+ objectKey: string | null;
825
+ }
826
+
827
+ interface VaultMemoryOperationsContext {
828
+ database: Database;
829
+ vaultMemoryCollection: Collection<VaultMemory>;
830
+ walletAddress?: string;
831
+ signMessage?: SignMessageFn;
832
+ embeddedWalletSigner?: EmbeddedWalletSignerFn;
833
+ /** When set, operations scope to this user (server-side multi-user). */
834
+ userId?: string;
835
+ /** Optional extraction source eligibility check, executed inside the writer.
836
+ * Must only read the database (must not start another writer). */
837
+ canWrite?: () => Promise<boolean>;
838
+ /**
839
+ * Asserts this context runs against a physically single-tenant database — one
840
+ * where every row belongs to the same owner (the per-wallet client DBs, which
841
+ * hold exactly one wallet's rows written with `user_id = null`). This is the
842
+ * ONLY thing that makes the decay sweep's unscoped scan/archive/delete safe
843
+ * without a `userId`: see {@link assertVaultScopeForSweep}. A shared /
844
+ * multi-tenant DB must NOT set this — it must scope by `userId` instead.
845
+ * `walletAddress` presence alone is NOT a substitute (the sweep query filters
846
+ * by `user_id` only, so a bare `walletAddress` on a shared DB would sweep
847
+ * every tenant).
848
+ */
849
+ singleTenant?: boolean;
850
+ /**
851
+ * When set, vault delete ops cascade to memory_entity rows pointing at
852
+ * the deleted memories. Without this the W5 graph lane keeps returning
853
+ * IDs of soft-deleted memories and the join table grows unbounded.
854
+ */
855
+ entityCtx?: EntityOperationsContext;
856
+ }
857
+ declare function getAllVaultMemoriesOp(ctx: VaultMemoryOperationsContext, options?: {
858
+ scopes?: string[];
859
+ since?: Date;
860
+ limit?: number;
861
+ folderId?: string | null;
862
+ /**
863
+ * Include soft-deleted memories in the result (each carries
864
+ * `isDeleted: true`). Default `false` — deleted rows are excluded, as
865
+ * they are from every other read path. Used by the Memory Graph to
866
+ * render "forgotten" nodes; ordinary consumers should leave this off.
867
+ */
868
+ includeDeleted?: boolean;
869
+ /** Include archived (decayed) memories. Default `false` (PR1 choke point). */
870
+ includeArchived?: boolean;
871
+ /** Include quarantined memories. Default `false` (PR1 choke point). */
872
+ includeQuarantined?: boolean;
873
+ /** Typed memory (PR1) — restrict to these fact types. Omit for no filter. */
874
+ factTypes?: string[];
875
+ memoryIds?: string[];
876
+ /**
877
+ * Include A2-superseded memories (each carries `supersededBy`). Default
878
+ * `false` — superseded rows are excluded, as they are from recall/dedup.
879
+ * Used by a "memory history" view to render retired facts.
880
+ */
881
+ includeSuperseded?: boolean;
882
+ /**
883
+ * Filter by People Nearby visibility. Legacy rows with a NULL column
884
+ * count as "private". Used by the publish reconciler to fetch the
885
+ * published set to diff against the server index.
886
+ */
887
+ visibility?: VaultMemoryVisibility[];
888
+ }): Promise<StoredVaultMemory[]>;
889
+
890
+ type MemoryContextLane = "profile" | "fact" | "episode" | "session";
891
+ interface MemoryContextItem {
892
+ id: string;
893
+ content: string;
894
+ score: number;
895
+ lane: MemoryContextLane;
896
+ sourceIds: string[];
897
+ /** Ranked fact evidence, retained when profile deduplication wins placement. */
898
+ recalled?: boolean;
899
+ }
900
+ interface MemoryContextOptions {
901
+ query: string;
902
+ recall: (query: string, options: RecallOptions) => Promise<RecallResult>;
903
+ loadFacts?: ((options: Parameters<typeof getAllVaultMemoriesOp>[1]) => Promise<StoredVaultMemory[]>) | null;
904
+ loadSessionRefs?: (() => Promise<Array<{
905
+ memoryId: string;
906
+ score: number;
907
+ }>>) | null;
908
+ recallOptions?: RecallOptions;
909
+ /** Topic membership is applied before ranking and to every context lane. */
910
+ memoryIds?: string[];
911
+ /** Character budget for contents (formatting overhead is excluded). Default 12000. */
912
+ maxChars?: number;
913
+ includeEpisodes?: boolean;
914
+ onDegraded?: (lane: MemoryContextLane, error: unknown) => void;
915
+ }
916
+ interface MemoryContextResult {
917
+ items: MemoryContextItem[];
918
+ ranked: boolean;
919
+ rankedCount: number;
920
+ degraded: MemoryContextLane[];
921
+ truncated: boolean;
922
+ }
923
+ /** Only skip confidently trivial utterances. Word counts reject useful short
924
+ * queries and languages whose writing does not separate words with spaces.
925
+ *
926
+ * The stoplist is an exact-match allowance for greetings and acknowledgments,
927
+ * not a language model: anything not listed still recalls, so a missing
928
+ * language costs a wasted lookup, never a missed memory. Kept to the handful of
929
+ * forms that cannot carry a fact in the languages the app ships in. */
930
+ declare function shouldRecallMemory(query: string): boolean;
931
+ /** Assemble bounded stable/dynamic profile, relevant facts, source excerpts and
932
+ * prior-turn facts. Lane failures are isolated: an outage cannot replace a
933
+ * legitimate empty search with unrelated vault contents. */
934
+ declare function assembleMemoryContext(options: MemoryContextOptions): Promise<MemoryContextResult>;
935
+
936
+ export { type MemoryContextItem, type MemoryContextLane, type MemoryContextOptions, type MemoryContextResult, assembleMemoryContext, shouldRecallMemory };