@anuma/sdk 6.21.0 → 6.22.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.
- package/dist/expo/{chunk-CIMZZ7GQ.mjs → chunk-6BULYLRY.mjs} +92 -8
- package/dist/expo/index.cjs +776 -168
- package/dist/expo/index.d.mts +152 -11
- package/dist/expo/index.d.ts +152 -11
- package/dist/expo/index.mjs +531 -20
- package/dist/{react/recall-7Q3V337H.mjs → expo/recall-CUTJWQDQ.mjs} +1 -1
- package/dist/memory/context.cjs +183 -0
- package/dist/memory/context.d.mts +936 -0
- package/dist/memory/context.d.ts +936 -0
- package/dist/memory/context.mjs +157 -0
- package/dist/react/{chunk-AVYB5IXC.mjs → chunk-GPLMMMZN.mjs} +1 -1
- package/dist/react/{chunk-JRVAUZHM.mjs → chunk-K4N52PSK.mjs} +93 -9
- package/dist/react/{consolidate-URHGJT7I.mjs → consolidate-XKS6IZFC.mjs} +2 -2
- package/dist/react/index.cjs +891 -276
- package/dist/react/index.d.mts +210 -11
- package/dist/react/index.d.ts +210 -11
- package/dist/react/index.mjs +584 -71
- package/dist/{expo/recall-FQQGE6A2.mjs → react/recall-LFHIWONV.mjs} +2 -2
- package/dist/server/{chunk-4DSDATYG.mjs → chunk-FEDRFVHD.mjs} +182 -98
- package/dist/server/index.cjs +830 -222
- package/dist/server/index.d.mts +151 -7
- package/dist/server/index.d.ts +151 -7
- package/dist/server/index.mjs +524 -13
- package/dist/server/{recall-HA4UX4BB.mjs → recall-Z2WJGXOL.mjs} +1 -1
- package/dist/telemetry/index.d.mts +7 -1
- package/dist/telemetry/index.d.ts +7 -1
- package/package.json +8 -1
- 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 };
|