@amemhq/core 1.0.1 → 2.0.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/README.md +2 -2
- package/dist/chunk-K6WZTDM7.js +2353 -0
- package/dist/chunk-K6WZTDM7.js.map +1 -0
- package/dist/cli-migrate.cjs +1086 -0
- package/dist/cli-migrate.cjs.map +1 -0
- package/dist/cli-migrate.d.cts +30 -0
- package/dist/cli-migrate.d.ts +30 -0
- package/dist/cli-migrate.js +147 -0
- package/dist/cli-migrate.js.map +1 -0
- package/dist/index.cjs +289 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +181 -28
- package/dist/index.d.ts +181 -28
- package/dist/index.js +82 -2120
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
package/dist/index.d.cts
CHANGED
|
@@ -6,15 +6,37 @@ declare function configure(opts: {
|
|
|
6
6
|
* embedding.ts — Local ONNX embedding via @huggingface/transformers
|
|
7
7
|
* Matches Python: SentenceTransformer.encode(text, normalize_embeddings=True)
|
|
8
8
|
*
|
|
9
|
-
* The model is selectable
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*/
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
* The model is selectable, and changing it is a breaking change whenever the
|
|
10
|
+
* dimension differs — Qdrant fixes a collection's vector size at creation. So
|
|
11
|
+
* nothing here decides on its own: a collection that already exists keeps the
|
|
12
|
+
* model it was built with (see `pinEmbeddingModel`), and moving is a deliberate
|
|
13
|
+
* `amem-migrate` run. See docs/reference/embedding-models.md.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* What a fresh install embeds with.
|
|
17
|
+
*
|
|
18
|
+
* Chosen for having no architectural question mark rather than for topping a
|
|
19
|
+
* leaderboard: XLM-RoBERTa, natively supported by Transformers.js, its ONNX
|
|
20
|
+
* maintained by that library's own author. 8192 tokens against the 128 of the
|
|
21
|
+
* model this replaced, which is the whole reason for the change — anything longer
|
|
22
|
+
* than a couple of sentences was being truncated before it reached the vector.
|
|
23
|
+
*/
|
|
24
|
+
declare const DEFAULT_EMBEDDING_MODEL = "Xenova/bge-m3";
|
|
25
|
+
/**
|
|
26
|
+
* The default before 2.0.0. Every store built by an earlier version holds its
|
|
27
|
+
* vectors, and nothing recorded that at the time — `LEGACY_DEFAULT_DIM` is how a
|
|
28
|
+
* collection from back then is recognised.
|
|
29
|
+
*/
|
|
30
|
+
declare const LEGACY_DEFAULT_EMBEDDING_MODEL = "Xenova/paraphrase-multilingual-MiniLM-L12-v2";
|
|
31
|
+
declare const LEGACY_DEFAULT_DIM = 384;
|
|
32
|
+
/**
|
|
33
|
+
* Which model this process embeds with: the env var, else whatever the open
|
|
34
|
+
* collection was built with, else the shipped default.
|
|
35
|
+
*
|
|
36
|
+
* An explicit `AMEM_EMBED_MODEL` outranks the pin on purpose — someone who set it
|
|
37
|
+
* is migrating deliberately, and silently overriding them with the collection's
|
|
38
|
+
* old model would make the setting look broken.
|
|
39
|
+
*/
|
|
18
40
|
declare function getEmbeddingModel(): string;
|
|
19
41
|
/** How token embeddings are collapsed into one sentence vector. */
|
|
20
42
|
type PoolingMode = 'mean' | 'cls';
|
|
@@ -29,6 +51,30 @@ type PoolingMode = 'mean' | 'cls';
|
|
|
29
51
|
* assumed.
|
|
30
52
|
*/
|
|
31
53
|
declare function getEmbeddingPooling(): PoolingMode;
|
|
54
|
+
/**
|
|
55
|
+
* Where inference runs. Unset means Transformers.js picks, which on Node is `cpu`.
|
|
56
|
+
*
|
|
57
|
+
* It is not CPU-only for lack of anything else: `onnxruntime-node`'s macOS arm64
|
|
58
|
+
* binary links CoreML.framework and exports the CoreML provider, and
|
|
59
|
+
* Transformers.js lists `coreml` (macOS), `dml` (Windows), `cuda` (Linux x64) and
|
|
60
|
+
* `webgpu` alongside `cpu`. It simply defaults to `cpu` and amem never asked for
|
|
61
|
+
* anything else.
|
|
62
|
+
*
|
|
63
|
+
* Whether asking helps is **unmeasured**. CoreML partitions a graph operator by
|
|
64
|
+
* operator and falls back to CPU for the ones it cannot take, so it can lose to
|
|
65
|
+
* plain CPU on some models and pay a compile cost on first load. Hence: opt-in,
|
|
66
|
+
* default unchanged, and no recommendation until someone benchmarks it.
|
|
67
|
+
*/
|
|
68
|
+
declare function getEmbeddingDevice(): string | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Weight precision. Unset means Transformers.js picks, which on Node is `fp32` —
|
|
71
|
+
* the largest download of every variant a model publishes.
|
|
72
|
+
*
|
|
73
|
+
* Passed through rather than validated against a list: Transformers.js already
|
|
74
|
+
* rejects an unknown value and names the valid ones, and a list here would go
|
|
75
|
+
* stale the moment it gains a quantization.
|
|
76
|
+
*/
|
|
77
|
+
declare function getEmbeddingDtype(): string | undefined;
|
|
32
78
|
/**
|
|
33
79
|
* The vector width this model produces, measured rather than looked up.
|
|
34
80
|
*
|
|
@@ -39,7 +85,9 @@ declare function getEmbeddingPooling(): PoolingMode;
|
|
|
39
85
|
*/
|
|
40
86
|
declare function getEmbeddingDim(): Promise<number>;
|
|
41
87
|
/**
|
|
42
|
-
* Encode text to
|
|
88
|
+
* Encode text to a normalized embedding vector. The width is the model's — 1024
|
|
89
|
+
* for the default, 384 for the one before it — so nothing here should assume a
|
|
90
|
+
* number.
|
|
43
91
|
* Singleton model, loaded once and reused.
|
|
44
92
|
*/
|
|
45
93
|
declare function encode(text: string): Promise<number[]>;
|
|
@@ -58,7 +106,8 @@ declare function isModelLoaded(): boolean;
|
|
|
58
106
|
/**
|
|
59
107
|
* storage.ts — Qdrant vector storage for A-MEM
|
|
60
108
|
* Uses native fetch (Node 18+) to avoid undici compatibility issues with Node v26
|
|
61
|
-
* Collection: amem_notes,
|
|
109
|
+
* Collection: amem_notes, cosine, width set by the embedding model, with
|
|
110
|
+
* agent_id isolation
|
|
62
111
|
*/
|
|
63
112
|
/** Per-agent override config. If collection is set, mode B (isolated collection) is used. */
|
|
64
113
|
interface AgentAmemConfig {
|
|
@@ -153,6 +202,33 @@ declare class EmbeddingDimensionMismatchError extends Error {
|
|
|
153
202
|
readonly model: string;
|
|
154
203
|
constructor(collection: string, collectionDim: number, modelDim: number, model: string);
|
|
155
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Which embedding model built a collection, and what the process wants to use.
|
|
207
|
+
*
|
|
208
|
+
* Vector width is the only thing Qdrant can check for us, and two models of the
|
|
209
|
+
* same width are indistinguishable to it. This is the case that check misses.
|
|
210
|
+
*/
|
|
211
|
+
declare class EmbeddingModelMismatchError extends Error {
|
|
212
|
+
readonly collection: string;
|
|
213
|
+
readonly collectionModel: string;
|
|
214
|
+
readonly configuredModel: string;
|
|
215
|
+
constructor(collection: string, collectionModel: string, configuredModel: string);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Two collections open in one process that need two different models.
|
|
219
|
+
*
|
|
220
|
+
* Only reachable in mode B, and normally only mid-migration: per-agent
|
|
221
|
+
* collections built before 2.0.0 all resolve to the same old model, until one of
|
|
222
|
+
* them is migrated and the others are not. One process embeds with one model, so
|
|
223
|
+
* this has to stop rather than pick a winner — picking would write vectors of the
|
|
224
|
+
* wrong width into whichever collection lost.
|
|
225
|
+
*/
|
|
226
|
+
declare class MixedEmbeddingModelsError extends Error {
|
|
227
|
+
readonly collection: string;
|
|
228
|
+
readonly wanted: string;
|
|
229
|
+
readonly inUse: string;
|
|
230
|
+
constructor(collection: string, wanted: string, inUse: string);
|
|
231
|
+
}
|
|
156
232
|
/**
|
|
157
233
|
* Ask Qdrant whether it can serve, right now.
|
|
158
234
|
*
|
|
@@ -178,28 +254,34 @@ declare function makeCrud(collectionName: string, modeBIsolated?: boolean): {
|
|
|
178
254
|
addNote(note: MemoryNote): Promise<void>;
|
|
179
255
|
/**
|
|
180
256
|
* Story 36: this is the one read that bypasses the agent filter — it fetches
|
|
181
|
-
* straight by UUID.
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
257
|
+
* straight by UUID. An unreadable note comes back as `null`, indistinguishable
|
|
258
|
+
* from missing, so nothing leaks and callers already handle it.
|
|
259
|
+
*
|
|
260
|
+
* `reader` is required. It used to be optional, and omitting it skipped the
|
|
261
|
+
* check — which meant the safe behaviour was the one you had to remember to
|
|
262
|
+
* ask for. Pass `SYSTEM_ACTOR` to read as the engine itself; that reads as a
|
|
263
|
+
* deliberate act at the call site, where an absent argument did not.
|
|
185
264
|
*/
|
|
186
|
-
getNote(id: string,
|
|
265
|
+
getNote(id: string, reader: string): Promise<MemoryNote | null>;
|
|
187
266
|
updateNote(note: MemoryNote): Promise<void>;
|
|
188
267
|
findByHash(hash: string, agentId: string): Promise<MemoryNote | null>;
|
|
189
268
|
/**
|
|
190
|
-
* Story 33:
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
269
|
+
* Story 33: enforces the writers policy. Returns false — without writing —
|
|
270
|
+
* when the caller may not write. This fetch-then-check path exists for callers
|
|
271
|
+
* that only have an id (the plugin's CRUD hook); callers already holding the
|
|
272
|
+
* note can check `canWrite` themselves and skip a round trip.
|
|
273
|
+
*
|
|
274
|
+
* `caller` is required. Pass `SYSTEM_ACTOR` for the engine's own maintenance
|
|
275
|
+
* writes. The self-read below is a genuine `SYSTEM_ACTOR` case: it fetches the
|
|
276
|
+
* note in order to decide whether the caller may write it, and gating that
|
|
277
|
+
* fetch on the same policy it exists to evaluate would be circular.
|
|
196
278
|
*/
|
|
197
|
-
updateNoteContent(id: string, content: string, embedding: number[], hash: string,
|
|
279
|
+
updateNoteContent(id: string, content: string, embedding: number[], hash: string, caller: string): Promise<boolean>;
|
|
198
280
|
queryByEmbedding(embedding: number[], topK: number, agentId: string, scoreThreshold?: number, subject?: string): Promise<QueryResult[]>;
|
|
199
281
|
listNotes(agentId?: string, subject?: string): Promise<MemoryNote[]>;
|
|
200
282
|
deleteNote(id: string): Promise<void>;
|
|
201
283
|
/** Story 33: see `updateNoteContent` — returns false, unwritten, when denied. */
|
|
202
|
-
invalidateNote(id: string,
|
|
284
|
+
invalidateNote(id: string, caller: string): Promise<boolean>;
|
|
203
285
|
getNotesByDatePrefix(datePrefix: string, agentId: string): Promise<MemoryNote[]>;
|
|
204
286
|
countNotes(agentId?: string): Promise<number>;
|
|
205
287
|
updateNoteLinks(id: string, links: string[]): Promise<void>;
|
|
@@ -213,11 +295,11 @@ type StorageContext = ReturnType<typeof makeCrud>;
|
|
|
213
295
|
* Mode B (isolated collection): pass collectionName = 'amem_notes_<agentId>' and modeBIsolated = true.
|
|
214
296
|
*/
|
|
215
297
|
declare function createStorageContext(collectionName?: string, modeBIsolated?: boolean): StorageContext;
|
|
216
|
-
declare function getNote(id: string,
|
|
298
|
+
declare function getNote(id: string, reader: string): Promise<MemoryNote | null>;
|
|
217
299
|
declare function updateNote(note: MemoryNote): Promise<void>;
|
|
218
300
|
declare function listNotes(agentId?: string, subject?: string): Promise<MemoryNote[]>;
|
|
219
301
|
declare function deleteNote(id: string): Promise<void>;
|
|
220
|
-
declare function invalidateNote(id: string,
|
|
302
|
+
declare function invalidateNote(id: string, caller: string): Promise<boolean>;
|
|
221
303
|
declare function patchNotePayload(id: string, fields: Record<string, unknown>): Promise<void>;
|
|
222
304
|
|
|
223
305
|
/**
|
|
@@ -267,8 +349,28 @@ interface SearchResult {
|
|
|
267
349
|
keywords: string[];
|
|
268
350
|
links: string[];
|
|
269
351
|
timestamp: string;
|
|
352
|
+
/**
|
|
353
|
+
* Cosine similarity to the query. **Not** what ordered this list — that is
|
|
354
|
+
* `rrf`, which fuses the dense and BM25 rankings and then applies a heat/recency
|
|
355
|
+
* boost. The two disagree often, and a consumer that reads `similarity` as the
|
|
356
|
+
* ranking score concludes the ranking is broken.
|
|
357
|
+
*/
|
|
270
358
|
similarity: number;
|
|
359
|
+
/**
|
|
360
|
+
* The fused score the matches are sorted by, and 0 for anything no retriever
|
|
361
|
+
* ranked. It is `via`, not this, that says why a row is here.
|
|
362
|
+
*/
|
|
271
363
|
rrf: number;
|
|
364
|
+
/**
|
|
365
|
+
* Why this note is in the results.
|
|
366
|
+
*
|
|
367
|
+
* `match` — it was retrieved for the query and ranked by `rrf`.
|
|
368
|
+
* `link` — it was **not** retrieved; it is here because it links to one that
|
|
369
|
+
* was, within two hops and above the relevance gate. These are appended in
|
|
370
|
+
* discovery order after the matches and have no `rrf` of their own, so reading
|
|
371
|
+
* the tail of the list as "lower-ranked matches" is wrong.
|
|
372
|
+
*/
|
|
373
|
+
via: 'match' | 'link';
|
|
272
374
|
topics: string[];
|
|
273
375
|
note_type: 'memory' | 'knowledge';
|
|
274
376
|
}
|
|
@@ -374,6 +476,28 @@ declare function generateReviewBatch(agentId: string, outputPath?: string): Prom
|
|
|
374
476
|
* policy is unit-testable on its own and identical everywhere it is applied.
|
|
375
477
|
*/
|
|
376
478
|
|
|
479
|
+
/**
|
|
480
|
+
* The engine acting as itself, rather than on behalf of any agent.
|
|
481
|
+
*
|
|
482
|
+
* `getNote`, `updateNoteContent` and `invalidateNote` all used to take an
|
|
483
|
+
* *optional* identity, and omitting it skipped the authorization check. That put
|
|
484
|
+
* the safe behaviour behind remembering to ask for it, and made "no check here"
|
|
485
|
+
* invisible — an absent argument looks the same as an oversight. The identity is
|
|
486
|
+
* now required, and this is what a call declares when it genuinely has no agent
|
|
487
|
+
* on whose behalf it acts.
|
|
488
|
+
*
|
|
489
|
+
* Two call sites use it, both inside storage.ts: the fetch that
|
|
490
|
+
* `updateNoteContent` and `invalidateNote` perform in order to *evaluate* the
|
|
491
|
+
* write policy. Gating that read on the policy it exists to check would be
|
|
492
|
+
* circular. Everything else passes a real agent id — the audit that prompted this
|
|
493
|
+
* found the identity was already in scope at every one of them.
|
|
494
|
+
*
|
|
495
|
+
* The prefix keeps it from colliding with any plausible agent id. It is not a
|
|
496
|
+
* secret and does not need to be: amem is self-hosted, the operator owns every
|
|
497
|
+
* memory in the store, and there is no privilege boundary here to defend. This
|
|
498
|
+
* guards against a call site forgetting to pass an identity, not against a user.
|
|
499
|
+
*/
|
|
500
|
+
declare const SYSTEM_ACTOR = "__amem_system__";
|
|
377
501
|
/**
|
|
378
502
|
* May `callerAgentId` mutate `note`?
|
|
379
503
|
*
|
|
@@ -403,8 +527,10 @@ interface MigrateResult {
|
|
|
403
527
|
missingDerived: number;
|
|
404
528
|
/** Notes whose fields were re-extracted. 0 unless refreshFields. */
|
|
405
529
|
refreshed: number;
|
|
406
|
-
/** Notes written into the target. 0 on a dry run. */
|
|
530
|
+
/** Notes written into the target by THIS run. 0 on a dry run. */
|
|
407
531
|
migrated: number;
|
|
532
|
+
/** Notes a previous interrupted run had already written. */
|
|
533
|
+
skipped: number;
|
|
408
534
|
sourceDim: number | null;
|
|
409
535
|
targetDim: number;
|
|
410
536
|
model: string;
|
|
@@ -424,6 +550,33 @@ declare function migrateCollection(opts: {
|
|
|
424
550
|
warn: (m: string) => void;
|
|
425
551
|
};
|
|
426
552
|
}): Promise<MigrateResult>;
|
|
553
|
+
/**
|
|
554
|
+
* Put the migrated collection behind the name the source used, and drop the
|
|
555
|
+
* source.
|
|
556
|
+
*
|
|
557
|
+
* This is the only irreversible step in the whole migration, which is why it is
|
|
558
|
+
* a separate call rather than the tail of `migrateCollection`. Everything before
|
|
559
|
+
* it leaves the original untouched and can simply be abandoned.
|
|
560
|
+
*
|
|
561
|
+
* Qdrant cannot rename a collection and cannot create an alias over a name a real
|
|
562
|
+
* collection holds (409), so freeing the name means deleting it — after checking
|
|
563
|
+
* the target holds at least as much as the source, because that check is the last
|
|
564
|
+
* thing standing between a half-finished migration and a deleted store.
|
|
565
|
+
*/
|
|
566
|
+
declare function switchToMigrated(opts: {
|
|
567
|
+
/** The name readers are configured with. Becomes an alias. */
|
|
568
|
+
name: string;
|
|
569
|
+
/** The collection built by `migrateCollection`. */
|
|
570
|
+
to: string;
|
|
571
|
+
logger?: {
|
|
572
|
+
info: (m: string) => void;
|
|
573
|
+
warn: (m: string) => void;
|
|
574
|
+
};
|
|
575
|
+
}): Promise<{
|
|
576
|
+
name: string;
|
|
577
|
+
to: string;
|
|
578
|
+
moved: number;
|
|
579
|
+
}>;
|
|
427
580
|
|
|
428
581
|
/**
|
|
429
582
|
* Similarity floor for accepting an UPDATE target.
|
|
@@ -512,4 +665,4 @@ declare function llmCrudDecision(userText: string, assistantText: string, existi
|
|
|
512
665
|
content: string;
|
|
513
666
|
}>): Promise<MemoryOperation[]>;
|
|
514
667
|
|
|
515
|
-
export { type AgentAmemConfig, type AmemPluginConfig, type ConflictMode, type ConflictSweepResult, DEFAULT_CRUD_UPDATE_MIN_SIM, DEFAULT_EMBEDDING_MODEL, EmbeddingDimensionMismatchError, type EvolutionEntry, type LlmConfig, type LowQualityItem, type LowQualityReason, type MemoryNote, type MemoryOperation, type MigrateResult, type PoolingMode, type QueryResult, type SearchResult, type StorageContext, addEpisodic, addMemory, canRead, canWrite, checkQuality, configure, configureLlm, conflictSweep, consolidateMemories, createStorageContext, deleteNote, encode, ensureCollection, generateReviewBatch, getEmbeddingDim, getEmbeddingModel, getEmbeddingPooling, getNote, invalidateNote, isModelLoaded, isPlausibleUpdateTarget, listMemories, listNotes, llmCrudDecision, loadModel, mergeSimilarNotes, migrateCollection, patchNotePayload, pingQdrant, resolveCrudUpdateMinSim, scanLowQuality, searchMemory, updateNote };
|
|
668
|
+
export { type AgentAmemConfig, type AmemPluginConfig, type ConflictMode, type ConflictSweepResult, DEFAULT_CRUD_UPDATE_MIN_SIM, DEFAULT_EMBEDDING_MODEL, EmbeddingDimensionMismatchError, EmbeddingModelMismatchError, type EvolutionEntry, LEGACY_DEFAULT_DIM, LEGACY_DEFAULT_EMBEDDING_MODEL, type LlmConfig, type LowQualityItem, type LowQualityReason, type MemoryNote, type MemoryOperation, type MigrateResult, MixedEmbeddingModelsError, type PoolingMode, type QueryResult, SYSTEM_ACTOR, type SearchResult, type StorageContext, addEpisodic, addMemory, canRead, canWrite, checkQuality, configure, configureLlm, conflictSweep, consolidateMemories, createStorageContext, deleteNote, encode, ensureCollection, generateReviewBatch, getEmbeddingDevice, getEmbeddingDim, getEmbeddingDtype, getEmbeddingModel, getEmbeddingPooling, getNote, invalidateNote, isModelLoaded, isPlausibleUpdateTarget, listMemories, listNotes, llmCrudDecision, loadModel, mergeSimilarNotes, migrateCollection, patchNotePayload, pingQdrant, resolveCrudUpdateMinSim, scanLowQuality, searchMemory, switchToMigrated, updateNote };
|
package/dist/index.d.ts
CHANGED
|
@@ -6,15 +6,37 @@ declare function configure(opts: {
|
|
|
6
6
|
* embedding.ts — Local ONNX embedding via @huggingface/transformers
|
|
7
7
|
* Matches Python: SentenceTransformer.encode(text, normalize_embeddings=True)
|
|
8
8
|
*
|
|
9
|
-
* The model is selectable
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*/
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
* The model is selectable, and changing it is a breaking change whenever the
|
|
10
|
+
* dimension differs — Qdrant fixes a collection's vector size at creation. So
|
|
11
|
+
* nothing here decides on its own: a collection that already exists keeps the
|
|
12
|
+
* model it was built with (see `pinEmbeddingModel`), and moving is a deliberate
|
|
13
|
+
* `amem-migrate` run. See docs/reference/embedding-models.md.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* What a fresh install embeds with.
|
|
17
|
+
*
|
|
18
|
+
* Chosen for having no architectural question mark rather than for topping a
|
|
19
|
+
* leaderboard: XLM-RoBERTa, natively supported by Transformers.js, its ONNX
|
|
20
|
+
* maintained by that library's own author. 8192 tokens against the 128 of the
|
|
21
|
+
* model this replaced, which is the whole reason for the change — anything longer
|
|
22
|
+
* than a couple of sentences was being truncated before it reached the vector.
|
|
23
|
+
*/
|
|
24
|
+
declare const DEFAULT_EMBEDDING_MODEL = "Xenova/bge-m3";
|
|
25
|
+
/**
|
|
26
|
+
* The default before 2.0.0. Every store built by an earlier version holds its
|
|
27
|
+
* vectors, and nothing recorded that at the time — `LEGACY_DEFAULT_DIM` is how a
|
|
28
|
+
* collection from back then is recognised.
|
|
29
|
+
*/
|
|
30
|
+
declare const LEGACY_DEFAULT_EMBEDDING_MODEL = "Xenova/paraphrase-multilingual-MiniLM-L12-v2";
|
|
31
|
+
declare const LEGACY_DEFAULT_DIM = 384;
|
|
32
|
+
/**
|
|
33
|
+
* Which model this process embeds with: the env var, else whatever the open
|
|
34
|
+
* collection was built with, else the shipped default.
|
|
35
|
+
*
|
|
36
|
+
* An explicit `AMEM_EMBED_MODEL` outranks the pin on purpose — someone who set it
|
|
37
|
+
* is migrating deliberately, and silently overriding them with the collection's
|
|
38
|
+
* old model would make the setting look broken.
|
|
39
|
+
*/
|
|
18
40
|
declare function getEmbeddingModel(): string;
|
|
19
41
|
/** How token embeddings are collapsed into one sentence vector. */
|
|
20
42
|
type PoolingMode = 'mean' | 'cls';
|
|
@@ -29,6 +51,30 @@ type PoolingMode = 'mean' | 'cls';
|
|
|
29
51
|
* assumed.
|
|
30
52
|
*/
|
|
31
53
|
declare function getEmbeddingPooling(): PoolingMode;
|
|
54
|
+
/**
|
|
55
|
+
* Where inference runs. Unset means Transformers.js picks, which on Node is `cpu`.
|
|
56
|
+
*
|
|
57
|
+
* It is not CPU-only for lack of anything else: `onnxruntime-node`'s macOS arm64
|
|
58
|
+
* binary links CoreML.framework and exports the CoreML provider, and
|
|
59
|
+
* Transformers.js lists `coreml` (macOS), `dml` (Windows), `cuda` (Linux x64) and
|
|
60
|
+
* `webgpu` alongside `cpu`. It simply defaults to `cpu` and amem never asked for
|
|
61
|
+
* anything else.
|
|
62
|
+
*
|
|
63
|
+
* Whether asking helps is **unmeasured**. CoreML partitions a graph operator by
|
|
64
|
+
* operator and falls back to CPU for the ones it cannot take, so it can lose to
|
|
65
|
+
* plain CPU on some models and pay a compile cost on first load. Hence: opt-in,
|
|
66
|
+
* default unchanged, and no recommendation until someone benchmarks it.
|
|
67
|
+
*/
|
|
68
|
+
declare function getEmbeddingDevice(): string | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Weight precision. Unset means Transformers.js picks, which on Node is `fp32` —
|
|
71
|
+
* the largest download of every variant a model publishes.
|
|
72
|
+
*
|
|
73
|
+
* Passed through rather than validated against a list: Transformers.js already
|
|
74
|
+
* rejects an unknown value and names the valid ones, and a list here would go
|
|
75
|
+
* stale the moment it gains a quantization.
|
|
76
|
+
*/
|
|
77
|
+
declare function getEmbeddingDtype(): string | undefined;
|
|
32
78
|
/**
|
|
33
79
|
* The vector width this model produces, measured rather than looked up.
|
|
34
80
|
*
|
|
@@ -39,7 +85,9 @@ declare function getEmbeddingPooling(): PoolingMode;
|
|
|
39
85
|
*/
|
|
40
86
|
declare function getEmbeddingDim(): Promise<number>;
|
|
41
87
|
/**
|
|
42
|
-
* Encode text to
|
|
88
|
+
* Encode text to a normalized embedding vector. The width is the model's — 1024
|
|
89
|
+
* for the default, 384 for the one before it — so nothing here should assume a
|
|
90
|
+
* number.
|
|
43
91
|
* Singleton model, loaded once and reused.
|
|
44
92
|
*/
|
|
45
93
|
declare function encode(text: string): Promise<number[]>;
|
|
@@ -58,7 +106,8 @@ declare function isModelLoaded(): boolean;
|
|
|
58
106
|
/**
|
|
59
107
|
* storage.ts — Qdrant vector storage for A-MEM
|
|
60
108
|
* Uses native fetch (Node 18+) to avoid undici compatibility issues with Node v26
|
|
61
|
-
* Collection: amem_notes,
|
|
109
|
+
* Collection: amem_notes, cosine, width set by the embedding model, with
|
|
110
|
+
* agent_id isolation
|
|
62
111
|
*/
|
|
63
112
|
/** Per-agent override config. If collection is set, mode B (isolated collection) is used. */
|
|
64
113
|
interface AgentAmemConfig {
|
|
@@ -153,6 +202,33 @@ declare class EmbeddingDimensionMismatchError extends Error {
|
|
|
153
202
|
readonly model: string;
|
|
154
203
|
constructor(collection: string, collectionDim: number, modelDim: number, model: string);
|
|
155
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Which embedding model built a collection, and what the process wants to use.
|
|
207
|
+
*
|
|
208
|
+
* Vector width is the only thing Qdrant can check for us, and two models of the
|
|
209
|
+
* same width are indistinguishable to it. This is the case that check misses.
|
|
210
|
+
*/
|
|
211
|
+
declare class EmbeddingModelMismatchError extends Error {
|
|
212
|
+
readonly collection: string;
|
|
213
|
+
readonly collectionModel: string;
|
|
214
|
+
readonly configuredModel: string;
|
|
215
|
+
constructor(collection: string, collectionModel: string, configuredModel: string);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Two collections open in one process that need two different models.
|
|
219
|
+
*
|
|
220
|
+
* Only reachable in mode B, and normally only mid-migration: per-agent
|
|
221
|
+
* collections built before 2.0.0 all resolve to the same old model, until one of
|
|
222
|
+
* them is migrated and the others are not. One process embeds with one model, so
|
|
223
|
+
* this has to stop rather than pick a winner — picking would write vectors of the
|
|
224
|
+
* wrong width into whichever collection lost.
|
|
225
|
+
*/
|
|
226
|
+
declare class MixedEmbeddingModelsError extends Error {
|
|
227
|
+
readonly collection: string;
|
|
228
|
+
readonly wanted: string;
|
|
229
|
+
readonly inUse: string;
|
|
230
|
+
constructor(collection: string, wanted: string, inUse: string);
|
|
231
|
+
}
|
|
156
232
|
/**
|
|
157
233
|
* Ask Qdrant whether it can serve, right now.
|
|
158
234
|
*
|
|
@@ -178,28 +254,34 @@ declare function makeCrud(collectionName: string, modeBIsolated?: boolean): {
|
|
|
178
254
|
addNote(note: MemoryNote): Promise<void>;
|
|
179
255
|
/**
|
|
180
256
|
* Story 36: this is the one read that bypasses the agent filter — it fetches
|
|
181
|
-
* straight by UUID.
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
257
|
+
* straight by UUID. An unreadable note comes back as `null`, indistinguishable
|
|
258
|
+
* from missing, so nothing leaks and callers already handle it.
|
|
259
|
+
*
|
|
260
|
+
* `reader` is required. It used to be optional, and omitting it skipped the
|
|
261
|
+
* check — which meant the safe behaviour was the one you had to remember to
|
|
262
|
+
* ask for. Pass `SYSTEM_ACTOR` to read as the engine itself; that reads as a
|
|
263
|
+
* deliberate act at the call site, where an absent argument did not.
|
|
185
264
|
*/
|
|
186
|
-
getNote(id: string,
|
|
265
|
+
getNote(id: string, reader: string): Promise<MemoryNote | null>;
|
|
187
266
|
updateNote(note: MemoryNote): Promise<void>;
|
|
188
267
|
findByHash(hash: string, agentId: string): Promise<MemoryNote | null>;
|
|
189
268
|
/**
|
|
190
|
-
* Story 33:
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
269
|
+
* Story 33: enforces the writers policy. Returns false — without writing —
|
|
270
|
+
* when the caller may not write. This fetch-then-check path exists for callers
|
|
271
|
+
* that only have an id (the plugin's CRUD hook); callers already holding the
|
|
272
|
+
* note can check `canWrite` themselves and skip a round trip.
|
|
273
|
+
*
|
|
274
|
+
* `caller` is required. Pass `SYSTEM_ACTOR` for the engine's own maintenance
|
|
275
|
+
* writes. The self-read below is a genuine `SYSTEM_ACTOR` case: it fetches the
|
|
276
|
+
* note in order to decide whether the caller may write it, and gating that
|
|
277
|
+
* fetch on the same policy it exists to evaluate would be circular.
|
|
196
278
|
*/
|
|
197
|
-
updateNoteContent(id: string, content: string, embedding: number[], hash: string,
|
|
279
|
+
updateNoteContent(id: string, content: string, embedding: number[], hash: string, caller: string): Promise<boolean>;
|
|
198
280
|
queryByEmbedding(embedding: number[], topK: number, agentId: string, scoreThreshold?: number, subject?: string): Promise<QueryResult[]>;
|
|
199
281
|
listNotes(agentId?: string, subject?: string): Promise<MemoryNote[]>;
|
|
200
282
|
deleteNote(id: string): Promise<void>;
|
|
201
283
|
/** Story 33: see `updateNoteContent` — returns false, unwritten, when denied. */
|
|
202
|
-
invalidateNote(id: string,
|
|
284
|
+
invalidateNote(id: string, caller: string): Promise<boolean>;
|
|
203
285
|
getNotesByDatePrefix(datePrefix: string, agentId: string): Promise<MemoryNote[]>;
|
|
204
286
|
countNotes(agentId?: string): Promise<number>;
|
|
205
287
|
updateNoteLinks(id: string, links: string[]): Promise<void>;
|
|
@@ -213,11 +295,11 @@ type StorageContext = ReturnType<typeof makeCrud>;
|
|
|
213
295
|
* Mode B (isolated collection): pass collectionName = 'amem_notes_<agentId>' and modeBIsolated = true.
|
|
214
296
|
*/
|
|
215
297
|
declare function createStorageContext(collectionName?: string, modeBIsolated?: boolean): StorageContext;
|
|
216
|
-
declare function getNote(id: string,
|
|
298
|
+
declare function getNote(id: string, reader: string): Promise<MemoryNote | null>;
|
|
217
299
|
declare function updateNote(note: MemoryNote): Promise<void>;
|
|
218
300
|
declare function listNotes(agentId?: string, subject?: string): Promise<MemoryNote[]>;
|
|
219
301
|
declare function deleteNote(id: string): Promise<void>;
|
|
220
|
-
declare function invalidateNote(id: string,
|
|
302
|
+
declare function invalidateNote(id: string, caller: string): Promise<boolean>;
|
|
221
303
|
declare function patchNotePayload(id: string, fields: Record<string, unknown>): Promise<void>;
|
|
222
304
|
|
|
223
305
|
/**
|
|
@@ -267,8 +349,28 @@ interface SearchResult {
|
|
|
267
349
|
keywords: string[];
|
|
268
350
|
links: string[];
|
|
269
351
|
timestamp: string;
|
|
352
|
+
/**
|
|
353
|
+
* Cosine similarity to the query. **Not** what ordered this list — that is
|
|
354
|
+
* `rrf`, which fuses the dense and BM25 rankings and then applies a heat/recency
|
|
355
|
+
* boost. The two disagree often, and a consumer that reads `similarity` as the
|
|
356
|
+
* ranking score concludes the ranking is broken.
|
|
357
|
+
*/
|
|
270
358
|
similarity: number;
|
|
359
|
+
/**
|
|
360
|
+
* The fused score the matches are sorted by, and 0 for anything no retriever
|
|
361
|
+
* ranked. It is `via`, not this, that says why a row is here.
|
|
362
|
+
*/
|
|
271
363
|
rrf: number;
|
|
364
|
+
/**
|
|
365
|
+
* Why this note is in the results.
|
|
366
|
+
*
|
|
367
|
+
* `match` — it was retrieved for the query and ranked by `rrf`.
|
|
368
|
+
* `link` — it was **not** retrieved; it is here because it links to one that
|
|
369
|
+
* was, within two hops and above the relevance gate. These are appended in
|
|
370
|
+
* discovery order after the matches and have no `rrf` of their own, so reading
|
|
371
|
+
* the tail of the list as "lower-ranked matches" is wrong.
|
|
372
|
+
*/
|
|
373
|
+
via: 'match' | 'link';
|
|
272
374
|
topics: string[];
|
|
273
375
|
note_type: 'memory' | 'knowledge';
|
|
274
376
|
}
|
|
@@ -374,6 +476,28 @@ declare function generateReviewBatch(agentId: string, outputPath?: string): Prom
|
|
|
374
476
|
* policy is unit-testable on its own and identical everywhere it is applied.
|
|
375
477
|
*/
|
|
376
478
|
|
|
479
|
+
/**
|
|
480
|
+
* The engine acting as itself, rather than on behalf of any agent.
|
|
481
|
+
*
|
|
482
|
+
* `getNote`, `updateNoteContent` and `invalidateNote` all used to take an
|
|
483
|
+
* *optional* identity, and omitting it skipped the authorization check. That put
|
|
484
|
+
* the safe behaviour behind remembering to ask for it, and made "no check here"
|
|
485
|
+
* invisible — an absent argument looks the same as an oversight. The identity is
|
|
486
|
+
* now required, and this is what a call declares when it genuinely has no agent
|
|
487
|
+
* on whose behalf it acts.
|
|
488
|
+
*
|
|
489
|
+
* Two call sites use it, both inside storage.ts: the fetch that
|
|
490
|
+
* `updateNoteContent` and `invalidateNote` perform in order to *evaluate* the
|
|
491
|
+
* write policy. Gating that read on the policy it exists to check would be
|
|
492
|
+
* circular. Everything else passes a real agent id — the audit that prompted this
|
|
493
|
+
* found the identity was already in scope at every one of them.
|
|
494
|
+
*
|
|
495
|
+
* The prefix keeps it from colliding with any plausible agent id. It is not a
|
|
496
|
+
* secret and does not need to be: amem is self-hosted, the operator owns every
|
|
497
|
+
* memory in the store, and there is no privilege boundary here to defend. This
|
|
498
|
+
* guards against a call site forgetting to pass an identity, not against a user.
|
|
499
|
+
*/
|
|
500
|
+
declare const SYSTEM_ACTOR = "__amem_system__";
|
|
377
501
|
/**
|
|
378
502
|
* May `callerAgentId` mutate `note`?
|
|
379
503
|
*
|
|
@@ -403,8 +527,10 @@ interface MigrateResult {
|
|
|
403
527
|
missingDerived: number;
|
|
404
528
|
/** Notes whose fields were re-extracted. 0 unless refreshFields. */
|
|
405
529
|
refreshed: number;
|
|
406
|
-
/** Notes written into the target. 0 on a dry run. */
|
|
530
|
+
/** Notes written into the target by THIS run. 0 on a dry run. */
|
|
407
531
|
migrated: number;
|
|
532
|
+
/** Notes a previous interrupted run had already written. */
|
|
533
|
+
skipped: number;
|
|
408
534
|
sourceDim: number | null;
|
|
409
535
|
targetDim: number;
|
|
410
536
|
model: string;
|
|
@@ -424,6 +550,33 @@ declare function migrateCollection(opts: {
|
|
|
424
550
|
warn: (m: string) => void;
|
|
425
551
|
};
|
|
426
552
|
}): Promise<MigrateResult>;
|
|
553
|
+
/**
|
|
554
|
+
* Put the migrated collection behind the name the source used, and drop the
|
|
555
|
+
* source.
|
|
556
|
+
*
|
|
557
|
+
* This is the only irreversible step in the whole migration, which is why it is
|
|
558
|
+
* a separate call rather than the tail of `migrateCollection`. Everything before
|
|
559
|
+
* it leaves the original untouched and can simply be abandoned.
|
|
560
|
+
*
|
|
561
|
+
* Qdrant cannot rename a collection and cannot create an alias over a name a real
|
|
562
|
+
* collection holds (409), so freeing the name means deleting it — after checking
|
|
563
|
+
* the target holds at least as much as the source, because that check is the last
|
|
564
|
+
* thing standing between a half-finished migration and a deleted store.
|
|
565
|
+
*/
|
|
566
|
+
declare function switchToMigrated(opts: {
|
|
567
|
+
/** The name readers are configured with. Becomes an alias. */
|
|
568
|
+
name: string;
|
|
569
|
+
/** The collection built by `migrateCollection`. */
|
|
570
|
+
to: string;
|
|
571
|
+
logger?: {
|
|
572
|
+
info: (m: string) => void;
|
|
573
|
+
warn: (m: string) => void;
|
|
574
|
+
};
|
|
575
|
+
}): Promise<{
|
|
576
|
+
name: string;
|
|
577
|
+
to: string;
|
|
578
|
+
moved: number;
|
|
579
|
+
}>;
|
|
427
580
|
|
|
428
581
|
/**
|
|
429
582
|
* Similarity floor for accepting an UPDATE target.
|
|
@@ -512,4 +665,4 @@ declare function llmCrudDecision(userText: string, assistantText: string, existi
|
|
|
512
665
|
content: string;
|
|
513
666
|
}>): Promise<MemoryOperation[]>;
|
|
514
667
|
|
|
515
|
-
export { type AgentAmemConfig, type AmemPluginConfig, type ConflictMode, type ConflictSweepResult, DEFAULT_CRUD_UPDATE_MIN_SIM, DEFAULT_EMBEDDING_MODEL, EmbeddingDimensionMismatchError, type EvolutionEntry, type LlmConfig, type LowQualityItem, type LowQualityReason, type MemoryNote, type MemoryOperation, type MigrateResult, type PoolingMode, type QueryResult, type SearchResult, type StorageContext, addEpisodic, addMemory, canRead, canWrite, checkQuality, configure, configureLlm, conflictSweep, consolidateMemories, createStorageContext, deleteNote, encode, ensureCollection, generateReviewBatch, getEmbeddingDim, getEmbeddingModel, getEmbeddingPooling, getNote, invalidateNote, isModelLoaded, isPlausibleUpdateTarget, listMemories, listNotes, llmCrudDecision, loadModel, mergeSimilarNotes, migrateCollection, patchNotePayload, pingQdrant, resolveCrudUpdateMinSim, scanLowQuality, searchMemory, updateNote };
|
|
668
|
+
export { type AgentAmemConfig, type AmemPluginConfig, type ConflictMode, type ConflictSweepResult, DEFAULT_CRUD_UPDATE_MIN_SIM, DEFAULT_EMBEDDING_MODEL, EmbeddingDimensionMismatchError, EmbeddingModelMismatchError, type EvolutionEntry, LEGACY_DEFAULT_DIM, LEGACY_DEFAULT_EMBEDDING_MODEL, type LlmConfig, type LowQualityItem, type LowQualityReason, type MemoryNote, type MemoryOperation, type MigrateResult, MixedEmbeddingModelsError, type PoolingMode, type QueryResult, SYSTEM_ACTOR, type SearchResult, type StorageContext, addEpisodic, addMemory, canRead, canWrite, checkQuality, configure, configureLlm, conflictSweep, consolidateMemories, createStorageContext, deleteNote, encode, ensureCollection, generateReviewBatch, getEmbeddingDevice, getEmbeddingDim, getEmbeddingDtype, getEmbeddingModel, getEmbeddingPooling, getNote, invalidateNote, isModelLoaded, isPlausibleUpdateTarget, listMemories, listNotes, llmCrudDecision, loadModel, mergeSimilarNotes, migrateCollection, patchNotePayload, pingQdrant, resolveCrudUpdateMinSim, scanLowQuality, searchMemory, switchToMigrated, updateNote };
|