@amemhq/core 1.1.0 → 2.1.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-B2NS7WAM.js → chunk-XEMQZNLD.js} +257 -57
- package/dist/chunk-XEMQZNLD.js.map +1 -0
- package/dist/cli-migrate.cjs +259 -52
- package/dist/cli-migrate.cjs.map +1 -1
- package/dist/cli-migrate.d.cts +21 -7
- package/dist/cli-migrate.d.ts +21 -7
- package/dist/cli-migrate.js +128 -43
- package/dist/cli-migrate.js.map +1 -1
- package/dist/index.cjs +256 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +167 -29
- package/dist/index.d.ts +167 -29
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-B2NS7WAM.js.map +0 -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';
|
|
@@ -48,6 +70,14 @@ declare function getEmbeddingDevice(): string | undefined;
|
|
|
48
70
|
* Weight precision. Unset means Transformers.js picks, which on Node is `fp32` —
|
|
49
71
|
* the largest download of every variant a model publishes.
|
|
50
72
|
*
|
|
73
|
+
* 2.0.0 defaulted this to `fp16` for bge-m3, to halve a 2.27 GB download. That
|
|
74
|
+
* shipped broken: `onnxruntime-node` 1.24.3 aborts loading those weights in
|
|
75
|
+
* `SimplifiedLayerNormFusion`, on a node the fp16 conversion inserts, so the
|
|
76
|
+
* default install could not embed at all. Reproduced on bge-m3 and on
|
|
77
|
+
* gte-multilingual-base, on two machines — it is the runtime and fp16, not one
|
|
78
|
+
* model. Nothing here picks a dtype now; saving the download is not worth
|
|
79
|
+
* choosing a build we have not loaded.
|
|
80
|
+
*
|
|
51
81
|
* Passed through rather than validated against a list: Transformers.js already
|
|
52
82
|
* rejects an unknown value and names the valid ones, and a list here would go
|
|
53
83
|
* stale the moment it gains a quantization.
|
|
@@ -63,8 +93,9 @@ declare function getEmbeddingDtype(): string | undefined;
|
|
|
63
93
|
*/
|
|
64
94
|
declare function getEmbeddingDim(): Promise<number>;
|
|
65
95
|
/**
|
|
66
|
-
* Encode text to a normalized embedding vector. The width is the model's —
|
|
67
|
-
* for the default,
|
|
96
|
+
* Encode text to a normalized embedding vector. The width is the model's — 1024
|
|
97
|
+
* for the default, 384 for the one before it — so nothing here should assume a
|
|
98
|
+
* number.
|
|
68
99
|
* Singleton model, loaded once and reused.
|
|
69
100
|
*/
|
|
70
101
|
declare function encode(text: string): Promise<number[]>;
|
|
@@ -83,7 +114,8 @@ declare function isModelLoaded(): boolean;
|
|
|
83
114
|
/**
|
|
84
115
|
* storage.ts — Qdrant vector storage for A-MEM
|
|
85
116
|
* Uses native fetch (Node 18+) to avoid undici compatibility issues with Node v26
|
|
86
|
-
* Collection: amem_notes,
|
|
117
|
+
* Collection: amem_notes, cosine, width set by the embedding model, with
|
|
118
|
+
* agent_id isolation
|
|
87
119
|
*/
|
|
88
120
|
/** Per-agent override config. If collection is set, mode B (isolated collection) is used. */
|
|
89
121
|
interface AgentAmemConfig {
|
|
@@ -190,6 +222,21 @@ declare class EmbeddingModelMismatchError extends Error {
|
|
|
190
222
|
readonly configuredModel: string;
|
|
191
223
|
constructor(collection: string, collectionModel: string, configuredModel: string);
|
|
192
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Two collections open in one process that need two different models.
|
|
227
|
+
*
|
|
228
|
+
* Only reachable in mode B, and normally only mid-migration: per-agent
|
|
229
|
+
* collections built before 2.0.0 all resolve to the same old model, until one of
|
|
230
|
+
* them is migrated and the others are not. One process embeds with one model, so
|
|
231
|
+
* this has to stop rather than pick a winner — picking would write vectors of the
|
|
232
|
+
* wrong width into whichever collection lost.
|
|
233
|
+
*/
|
|
234
|
+
declare class MixedEmbeddingModelsError extends Error {
|
|
235
|
+
readonly collection: string;
|
|
236
|
+
readonly wanted: string;
|
|
237
|
+
readonly inUse: string;
|
|
238
|
+
constructor(collection: string, wanted: string, inUse: string);
|
|
239
|
+
}
|
|
193
240
|
/**
|
|
194
241
|
* Ask Qdrant whether it can serve, right now.
|
|
195
242
|
*
|
|
@@ -215,28 +262,34 @@ declare function makeCrud(collectionName: string, modeBIsolated?: boolean): {
|
|
|
215
262
|
addNote(note: MemoryNote): Promise<void>;
|
|
216
263
|
/**
|
|
217
264
|
* Story 36: this is the one read that bypasses the agent filter — it fetches
|
|
218
|
-
* straight by UUID.
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
265
|
+
* straight by UUID. An unreadable note comes back as `null`, indistinguishable
|
|
266
|
+
* from missing, so nothing leaks and callers already handle it.
|
|
267
|
+
*
|
|
268
|
+
* `reader` is required. It used to be optional, and omitting it skipped the
|
|
269
|
+
* check — which meant the safe behaviour was the one you had to remember to
|
|
270
|
+
* ask for. Pass `SYSTEM_ACTOR` to read as the engine itself; that reads as a
|
|
271
|
+
* deliberate act at the call site, where an absent argument did not.
|
|
222
272
|
*/
|
|
223
|
-
getNote(id: string,
|
|
273
|
+
getNote(id: string, reader: string): Promise<MemoryNote | null>;
|
|
224
274
|
updateNote(note: MemoryNote): Promise<void>;
|
|
225
275
|
findByHash(hash: string, agentId: string): Promise<MemoryNote | null>;
|
|
226
276
|
/**
|
|
227
|
-
* Story 33:
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
277
|
+
* Story 33: enforces the writers policy. Returns false — without writing —
|
|
278
|
+
* when the caller may not write. This fetch-then-check path exists for callers
|
|
279
|
+
* that only have an id (the plugin's CRUD hook); callers already holding the
|
|
280
|
+
* note can check `canWrite` themselves and skip a round trip.
|
|
281
|
+
*
|
|
282
|
+
* `caller` is required. Pass `SYSTEM_ACTOR` for the engine's own maintenance
|
|
283
|
+
* writes. The self-read below is a genuine `SYSTEM_ACTOR` case: it fetches the
|
|
284
|
+
* note in order to decide whether the caller may write it, and gating that
|
|
285
|
+
* fetch on the same policy it exists to evaluate would be circular.
|
|
233
286
|
*/
|
|
234
|
-
updateNoteContent(id: string, content: string, embedding: number[], hash: string,
|
|
287
|
+
updateNoteContent(id: string, content: string, embedding: number[], hash: string, caller: string): Promise<boolean>;
|
|
235
288
|
queryByEmbedding(embedding: number[], topK: number, agentId: string, scoreThreshold?: number, subject?: string): Promise<QueryResult[]>;
|
|
236
289
|
listNotes(agentId?: string, subject?: string): Promise<MemoryNote[]>;
|
|
237
290
|
deleteNote(id: string): Promise<void>;
|
|
238
291
|
/** Story 33: see `updateNoteContent` — returns false, unwritten, when denied. */
|
|
239
|
-
invalidateNote(id: string,
|
|
292
|
+
invalidateNote(id: string, caller: string): Promise<boolean>;
|
|
240
293
|
getNotesByDatePrefix(datePrefix: string, agentId: string): Promise<MemoryNote[]>;
|
|
241
294
|
countNotes(agentId?: string): Promise<number>;
|
|
242
295
|
updateNoteLinks(id: string, links: string[]): Promise<void>;
|
|
@@ -250,11 +303,11 @@ type StorageContext = ReturnType<typeof makeCrud>;
|
|
|
250
303
|
* Mode B (isolated collection): pass collectionName = 'amem_notes_<agentId>' and modeBIsolated = true.
|
|
251
304
|
*/
|
|
252
305
|
declare function createStorageContext(collectionName?: string, modeBIsolated?: boolean): StorageContext;
|
|
253
|
-
declare function getNote(id: string,
|
|
306
|
+
declare function getNote(id: string, reader: string): Promise<MemoryNote | null>;
|
|
254
307
|
declare function updateNote(note: MemoryNote): Promise<void>;
|
|
255
308
|
declare function listNotes(agentId?: string, subject?: string): Promise<MemoryNote[]>;
|
|
256
309
|
declare function deleteNote(id: string): Promise<void>;
|
|
257
|
-
declare function invalidateNote(id: string,
|
|
310
|
+
declare function invalidateNote(id: string, caller: string): Promise<boolean>;
|
|
258
311
|
declare function patchNotePayload(id: string, fields: Record<string, unknown>): Promise<void>;
|
|
259
312
|
|
|
260
313
|
/**
|
|
@@ -304,8 +357,28 @@ interface SearchResult {
|
|
|
304
357
|
keywords: string[];
|
|
305
358
|
links: string[];
|
|
306
359
|
timestamp: string;
|
|
360
|
+
/**
|
|
361
|
+
* Cosine similarity to the query. **Not** what ordered this list — that is
|
|
362
|
+
* `rrf`, which fuses the dense and BM25 rankings and then applies a heat/recency
|
|
363
|
+
* boost. The two disagree often, and a consumer that reads `similarity` as the
|
|
364
|
+
* ranking score concludes the ranking is broken.
|
|
365
|
+
*/
|
|
307
366
|
similarity: number;
|
|
367
|
+
/**
|
|
368
|
+
* The fused score the matches are sorted by, and 0 for anything no retriever
|
|
369
|
+
* ranked. It is `via`, not this, that says why a row is here.
|
|
370
|
+
*/
|
|
308
371
|
rrf: number;
|
|
372
|
+
/**
|
|
373
|
+
* Why this note is in the results.
|
|
374
|
+
*
|
|
375
|
+
* `match` — it was retrieved for the query and ranked by `rrf`.
|
|
376
|
+
* `link` — it was **not** retrieved; it is here because it links to one that
|
|
377
|
+
* was, within two hops and above the relevance gate. These are appended in
|
|
378
|
+
* discovery order after the matches and have no `rrf` of their own, so reading
|
|
379
|
+
* the tail of the list as "lower-ranked matches" is wrong.
|
|
380
|
+
*/
|
|
381
|
+
via: 'match' | 'link';
|
|
309
382
|
topics: string[];
|
|
310
383
|
note_type: 'memory' | 'knowledge';
|
|
311
384
|
}
|
|
@@ -411,6 +484,28 @@ declare function generateReviewBatch(agentId: string, outputPath?: string): Prom
|
|
|
411
484
|
* policy is unit-testable on its own and identical everywhere it is applied.
|
|
412
485
|
*/
|
|
413
486
|
|
|
487
|
+
/**
|
|
488
|
+
* The engine acting as itself, rather than on behalf of any agent.
|
|
489
|
+
*
|
|
490
|
+
* `getNote`, `updateNoteContent` and `invalidateNote` all used to take an
|
|
491
|
+
* *optional* identity, and omitting it skipped the authorization check. That put
|
|
492
|
+
* the safe behaviour behind remembering to ask for it, and made "no check here"
|
|
493
|
+
* invisible — an absent argument looks the same as an oversight. The identity is
|
|
494
|
+
* now required, and this is what a call declares when it genuinely has no agent
|
|
495
|
+
* on whose behalf it acts.
|
|
496
|
+
*
|
|
497
|
+
* Two call sites use it, both inside storage.ts: the fetch that
|
|
498
|
+
* `updateNoteContent` and `invalidateNote` perform in order to *evaluate* the
|
|
499
|
+
* write policy. Gating that read on the policy it exists to check would be
|
|
500
|
+
* circular. Everything else passes a real agent id — the audit that prompted this
|
|
501
|
+
* found the identity was already in scope at every one of them.
|
|
502
|
+
*
|
|
503
|
+
* The prefix keeps it from colliding with any plausible agent id. It is not a
|
|
504
|
+
* secret and does not need to be: amem is self-hosted, the operator owns every
|
|
505
|
+
* memory in the store, and there is no privilege boundary here to defend. This
|
|
506
|
+
* guards against a call site forgetting to pass an identity, not against a user.
|
|
507
|
+
*/
|
|
508
|
+
declare const SYSTEM_ACTOR = "__amem_system__";
|
|
414
509
|
/**
|
|
415
510
|
* May `callerAgentId` mutate `note`?
|
|
416
511
|
*
|
|
@@ -440,8 +535,10 @@ interface MigrateResult {
|
|
|
440
535
|
missingDerived: number;
|
|
441
536
|
/** Notes whose fields were re-extracted. 0 unless refreshFields. */
|
|
442
537
|
refreshed: number;
|
|
443
|
-
/** Notes written into the target. 0 on a dry run. */
|
|
538
|
+
/** Notes written into the target by THIS run. 0 on a dry run. */
|
|
444
539
|
migrated: number;
|
|
540
|
+
/** Notes a previous interrupted run had already written. */
|
|
541
|
+
skipped: number;
|
|
445
542
|
sourceDim: number | null;
|
|
446
543
|
targetDim: number;
|
|
447
544
|
model: string;
|
|
@@ -461,6 +558,47 @@ declare function migrateCollection(opts: {
|
|
|
461
558
|
warn: (m: string) => void;
|
|
462
559
|
};
|
|
463
560
|
}): Promise<MigrateResult>;
|
|
561
|
+
/**
|
|
562
|
+
* Put the migrated collection behind the name the source used, and drop the
|
|
563
|
+
* source.
|
|
564
|
+
*
|
|
565
|
+
* This is the only irreversible step in the whole migration, which is why it is
|
|
566
|
+
* a separate call rather than the tail of `migrateCollection`. Everything before
|
|
567
|
+
* it leaves the original untouched and can simply be abandoned.
|
|
568
|
+
*
|
|
569
|
+
* Qdrant cannot rename a collection and cannot create an alias over a name a real
|
|
570
|
+
* collection holds (409), so freeing the name means deleting it — after checking
|
|
571
|
+
* the target holds at least as much as the source, because that check is the last
|
|
572
|
+
* thing standing between a half-finished migration and a deleted store.
|
|
573
|
+
*/
|
|
574
|
+
declare function switchToMigrated(opts: {
|
|
575
|
+
/** The name readers are configured with. Becomes an alias. */
|
|
576
|
+
name: string;
|
|
577
|
+
/** The collection built by `migrateCollection`. */
|
|
578
|
+
to: string;
|
|
579
|
+
/**
|
|
580
|
+
* Snapshot the source before dropping it. Default true.
|
|
581
|
+
*
|
|
582
|
+
* Freeing the name means deleting the collection, but it does not have to mean
|
|
583
|
+
* losing the data, and those should not be the same decision. The snapshot is
|
|
584
|
+
* what makes "switch over" reversible and leaves "throw the old vectors away"
|
|
585
|
+
* as a separate thing to do later, by hand, once the new store has proven
|
|
586
|
+
* itself in use.
|
|
587
|
+
*/
|
|
588
|
+
snapshot?: boolean;
|
|
589
|
+
logger?: {
|
|
590
|
+
info: (m: string) => void;
|
|
591
|
+
warn: (m: string) => void;
|
|
592
|
+
};
|
|
593
|
+
}): Promise<{
|
|
594
|
+
name: string;
|
|
595
|
+
to: string;
|
|
596
|
+
moved: number;
|
|
597
|
+
snapshot?: {
|
|
598
|
+
name: string;
|
|
599
|
+
size: number;
|
|
600
|
+
};
|
|
601
|
+
}>;
|
|
464
602
|
|
|
465
603
|
/**
|
|
466
604
|
* Similarity floor for accepting an UPDATE target.
|
|
@@ -549,4 +687,4 @@ declare function llmCrudDecision(userText: string, assistantText: string, existi
|
|
|
549
687
|
content: string;
|
|
550
688
|
}>): Promise<MemoryOperation[]>;
|
|
551
689
|
|
|
552
|
-
export { type AgentAmemConfig, type AmemPluginConfig, type ConflictMode, type ConflictSweepResult, DEFAULT_CRUD_UPDATE_MIN_SIM, DEFAULT_EMBEDDING_MODEL, EmbeddingDimensionMismatchError, EmbeddingModelMismatchError, 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, getEmbeddingDevice, getEmbeddingDim, getEmbeddingDtype, getEmbeddingModel, getEmbeddingPooling, getNote, invalidateNote, isModelLoaded, isPlausibleUpdateTarget, listMemories, listNotes, llmCrudDecision, loadModel, mergeSimilarNotes, migrateCollection, patchNotePayload, pingQdrant, resolveCrudUpdateMinSim, scanLowQuality, searchMemory, updateNote };
|
|
690
|
+
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';
|
|
@@ -48,6 +70,14 @@ declare function getEmbeddingDevice(): string | undefined;
|
|
|
48
70
|
* Weight precision. Unset means Transformers.js picks, which on Node is `fp32` —
|
|
49
71
|
* the largest download of every variant a model publishes.
|
|
50
72
|
*
|
|
73
|
+
* 2.0.0 defaulted this to `fp16` for bge-m3, to halve a 2.27 GB download. That
|
|
74
|
+
* shipped broken: `onnxruntime-node` 1.24.3 aborts loading those weights in
|
|
75
|
+
* `SimplifiedLayerNormFusion`, on a node the fp16 conversion inserts, so the
|
|
76
|
+
* default install could not embed at all. Reproduced on bge-m3 and on
|
|
77
|
+
* gte-multilingual-base, on two machines — it is the runtime and fp16, not one
|
|
78
|
+
* model. Nothing here picks a dtype now; saving the download is not worth
|
|
79
|
+
* choosing a build we have not loaded.
|
|
80
|
+
*
|
|
51
81
|
* Passed through rather than validated against a list: Transformers.js already
|
|
52
82
|
* rejects an unknown value and names the valid ones, and a list here would go
|
|
53
83
|
* stale the moment it gains a quantization.
|
|
@@ -63,8 +93,9 @@ declare function getEmbeddingDtype(): string | undefined;
|
|
|
63
93
|
*/
|
|
64
94
|
declare function getEmbeddingDim(): Promise<number>;
|
|
65
95
|
/**
|
|
66
|
-
* Encode text to a normalized embedding vector. The width is the model's —
|
|
67
|
-
* for the default,
|
|
96
|
+
* Encode text to a normalized embedding vector. The width is the model's — 1024
|
|
97
|
+
* for the default, 384 for the one before it — so nothing here should assume a
|
|
98
|
+
* number.
|
|
68
99
|
* Singleton model, loaded once and reused.
|
|
69
100
|
*/
|
|
70
101
|
declare function encode(text: string): Promise<number[]>;
|
|
@@ -83,7 +114,8 @@ declare function isModelLoaded(): boolean;
|
|
|
83
114
|
/**
|
|
84
115
|
* storage.ts — Qdrant vector storage for A-MEM
|
|
85
116
|
* Uses native fetch (Node 18+) to avoid undici compatibility issues with Node v26
|
|
86
|
-
* Collection: amem_notes,
|
|
117
|
+
* Collection: amem_notes, cosine, width set by the embedding model, with
|
|
118
|
+
* agent_id isolation
|
|
87
119
|
*/
|
|
88
120
|
/** Per-agent override config. If collection is set, mode B (isolated collection) is used. */
|
|
89
121
|
interface AgentAmemConfig {
|
|
@@ -190,6 +222,21 @@ declare class EmbeddingModelMismatchError extends Error {
|
|
|
190
222
|
readonly configuredModel: string;
|
|
191
223
|
constructor(collection: string, collectionModel: string, configuredModel: string);
|
|
192
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Two collections open in one process that need two different models.
|
|
227
|
+
*
|
|
228
|
+
* Only reachable in mode B, and normally only mid-migration: per-agent
|
|
229
|
+
* collections built before 2.0.0 all resolve to the same old model, until one of
|
|
230
|
+
* them is migrated and the others are not. One process embeds with one model, so
|
|
231
|
+
* this has to stop rather than pick a winner — picking would write vectors of the
|
|
232
|
+
* wrong width into whichever collection lost.
|
|
233
|
+
*/
|
|
234
|
+
declare class MixedEmbeddingModelsError extends Error {
|
|
235
|
+
readonly collection: string;
|
|
236
|
+
readonly wanted: string;
|
|
237
|
+
readonly inUse: string;
|
|
238
|
+
constructor(collection: string, wanted: string, inUse: string);
|
|
239
|
+
}
|
|
193
240
|
/**
|
|
194
241
|
* Ask Qdrant whether it can serve, right now.
|
|
195
242
|
*
|
|
@@ -215,28 +262,34 @@ declare function makeCrud(collectionName: string, modeBIsolated?: boolean): {
|
|
|
215
262
|
addNote(note: MemoryNote): Promise<void>;
|
|
216
263
|
/**
|
|
217
264
|
* Story 36: this is the one read that bypasses the agent filter — it fetches
|
|
218
|
-
* straight by UUID.
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
265
|
+
* straight by UUID. An unreadable note comes back as `null`, indistinguishable
|
|
266
|
+
* from missing, so nothing leaks and callers already handle it.
|
|
267
|
+
*
|
|
268
|
+
* `reader` is required. It used to be optional, and omitting it skipped the
|
|
269
|
+
* check — which meant the safe behaviour was the one you had to remember to
|
|
270
|
+
* ask for. Pass `SYSTEM_ACTOR` to read as the engine itself; that reads as a
|
|
271
|
+
* deliberate act at the call site, where an absent argument did not.
|
|
222
272
|
*/
|
|
223
|
-
getNote(id: string,
|
|
273
|
+
getNote(id: string, reader: string): Promise<MemoryNote | null>;
|
|
224
274
|
updateNote(note: MemoryNote): Promise<void>;
|
|
225
275
|
findByHash(hash: string, agentId: string): Promise<MemoryNote | null>;
|
|
226
276
|
/**
|
|
227
|
-
* Story 33:
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
277
|
+
* Story 33: enforces the writers policy. Returns false — without writing —
|
|
278
|
+
* when the caller may not write. This fetch-then-check path exists for callers
|
|
279
|
+
* that only have an id (the plugin's CRUD hook); callers already holding the
|
|
280
|
+
* note can check `canWrite` themselves and skip a round trip.
|
|
281
|
+
*
|
|
282
|
+
* `caller` is required. Pass `SYSTEM_ACTOR` for the engine's own maintenance
|
|
283
|
+
* writes. The self-read below is a genuine `SYSTEM_ACTOR` case: it fetches the
|
|
284
|
+
* note in order to decide whether the caller may write it, and gating that
|
|
285
|
+
* fetch on the same policy it exists to evaluate would be circular.
|
|
233
286
|
*/
|
|
234
|
-
updateNoteContent(id: string, content: string, embedding: number[], hash: string,
|
|
287
|
+
updateNoteContent(id: string, content: string, embedding: number[], hash: string, caller: string): Promise<boolean>;
|
|
235
288
|
queryByEmbedding(embedding: number[], topK: number, agentId: string, scoreThreshold?: number, subject?: string): Promise<QueryResult[]>;
|
|
236
289
|
listNotes(agentId?: string, subject?: string): Promise<MemoryNote[]>;
|
|
237
290
|
deleteNote(id: string): Promise<void>;
|
|
238
291
|
/** Story 33: see `updateNoteContent` — returns false, unwritten, when denied. */
|
|
239
|
-
invalidateNote(id: string,
|
|
292
|
+
invalidateNote(id: string, caller: string): Promise<boolean>;
|
|
240
293
|
getNotesByDatePrefix(datePrefix: string, agentId: string): Promise<MemoryNote[]>;
|
|
241
294
|
countNotes(agentId?: string): Promise<number>;
|
|
242
295
|
updateNoteLinks(id: string, links: string[]): Promise<void>;
|
|
@@ -250,11 +303,11 @@ type StorageContext = ReturnType<typeof makeCrud>;
|
|
|
250
303
|
* Mode B (isolated collection): pass collectionName = 'amem_notes_<agentId>' and modeBIsolated = true.
|
|
251
304
|
*/
|
|
252
305
|
declare function createStorageContext(collectionName?: string, modeBIsolated?: boolean): StorageContext;
|
|
253
|
-
declare function getNote(id: string,
|
|
306
|
+
declare function getNote(id: string, reader: string): Promise<MemoryNote | null>;
|
|
254
307
|
declare function updateNote(note: MemoryNote): Promise<void>;
|
|
255
308
|
declare function listNotes(agentId?: string, subject?: string): Promise<MemoryNote[]>;
|
|
256
309
|
declare function deleteNote(id: string): Promise<void>;
|
|
257
|
-
declare function invalidateNote(id: string,
|
|
310
|
+
declare function invalidateNote(id: string, caller: string): Promise<boolean>;
|
|
258
311
|
declare function patchNotePayload(id: string, fields: Record<string, unknown>): Promise<void>;
|
|
259
312
|
|
|
260
313
|
/**
|
|
@@ -304,8 +357,28 @@ interface SearchResult {
|
|
|
304
357
|
keywords: string[];
|
|
305
358
|
links: string[];
|
|
306
359
|
timestamp: string;
|
|
360
|
+
/**
|
|
361
|
+
* Cosine similarity to the query. **Not** what ordered this list — that is
|
|
362
|
+
* `rrf`, which fuses the dense and BM25 rankings and then applies a heat/recency
|
|
363
|
+
* boost. The two disagree often, and a consumer that reads `similarity` as the
|
|
364
|
+
* ranking score concludes the ranking is broken.
|
|
365
|
+
*/
|
|
307
366
|
similarity: number;
|
|
367
|
+
/**
|
|
368
|
+
* The fused score the matches are sorted by, and 0 for anything no retriever
|
|
369
|
+
* ranked. It is `via`, not this, that says why a row is here.
|
|
370
|
+
*/
|
|
308
371
|
rrf: number;
|
|
372
|
+
/**
|
|
373
|
+
* Why this note is in the results.
|
|
374
|
+
*
|
|
375
|
+
* `match` — it was retrieved for the query and ranked by `rrf`.
|
|
376
|
+
* `link` — it was **not** retrieved; it is here because it links to one that
|
|
377
|
+
* was, within two hops and above the relevance gate. These are appended in
|
|
378
|
+
* discovery order after the matches and have no `rrf` of their own, so reading
|
|
379
|
+
* the tail of the list as "lower-ranked matches" is wrong.
|
|
380
|
+
*/
|
|
381
|
+
via: 'match' | 'link';
|
|
309
382
|
topics: string[];
|
|
310
383
|
note_type: 'memory' | 'knowledge';
|
|
311
384
|
}
|
|
@@ -411,6 +484,28 @@ declare function generateReviewBatch(agentId: string, outputPath?: string): Prom
|
|
|
411
484
|
* policy is unit-testable on its own and identical everywhere it is applied.
|
|
412
485
|
*/
|
|
413
486
|
|
|
487
|
+
/**
|
|
488
|
+
* The engine acting as itself, rather than on behalf of any agent.
|
|
489
|
+
*
|
|
490
|
+
* `getNote`, `updateNoteContent` and `invalidateNote` all used to take an
|
|
491
|
+
* *optional* identity, and omitting it skipped the authorization check. That put
|
|
492
|
+
* the safe behaviour behind remembering to ask for it, and made "no check here"
|
|
493
|
+
* invisible — an absent argument looks the same as an oversight. The identity is
|
|
494
|
+
* now required, and this is what a call declares when it genuinely has no agent
|
|
495
|
+
* on whose behalf it acts.
|
|
496
|
+
*
|
|
497
|
+
* Two call sites use it, both inside storage.ts: the fetch that
|
|
498
|
+
* `updateNoteContent` and `invalidateNote` perform in order to *evaluate* the
|
|
499
|
+
* write policy. Gating that read on the policy it exists to check would be
|
|
500
|
+
* circular. Everything else passes a real agent id — the audit that prompted this
|
|
501
|
+
* found the identity was already in scope at every one of them.
|
|
502
|
+
*
|
|
503
|
+
* The prefix keeps it from colliding with any plausible agent id. It is not a
|
|
504
|
+
* secret and does not need to be: amem is self-hosted, the operator owns every
|
|
505
|
+
* memory in the store, and there is no privilege boundary here to defend. This
|
|
506
|
+
* guards against a call site forgetting to pass an identity, not against a user.
|
|
507
|
+
*/
|
|
508
|
+
declare const SYSTEM_ACTOR = "__amem_system__";
|
|
414
509
|
/**
|
|
415
510
|
* May `callerAgentId` mutate `note`?
|
|
416
511
|
*
|
|
@@ -440,8 +535,10 @@ interface MigrateResult {
|
|
|
440
535
|
missingDerived: number;
|
|
441
536
|
/** Notes whose fields were re-extracted. 0 unless refreshFields. */
|
|
442
537
|
refreshed: number;
|
|
443
|
-
/** Notes written into the target. 0 on a dry run. */
|
|
538
|
+
/** Notes written into the target by THIS run. 0 on a dry run. */
|
|
444
539
|
migrated: number;
|
|
540
|
+
/** Notes a previous interrupted run had already written. */
|
|
541
|
+
skipped: number;
|
|
445
542
|
sourceDim: number | null;
|
|
446
543
|
targetDim: number;
|
|
447
544
|
model: string;
|
|
@@ -461,6 +558,47 @@ declare function migrateCollection(opts: {
|
|
|
461
558
|
warn: (m: string) => void;
|
|
462
559
|
};
|
|
463
560
|
}): Promise<MigrateResult>;
|
|
561
|
+
/**
|
|
562
|
+
* Put the migrated collection behind the name the source used, and drop the
|
|
563
|
+
* source.
|
|
564
|
+
*
|
|
565
|
+
* This is the only irreversible step in the whole migration, which is why it is
|
|
566
|
+
* a separate call rather than the tail of `migrateCollection`. Everything before
|
|
567
|
+
* it leaves the original untouched and can simply be abandoned.
|
|
568
|
+
*
|
|
569
|
+
* Qdrant cannot rename a collection and cannot create an alias over a name a real
|
|
570
|
+
* collection holds (409), so freeing the name means deleting it — after checking
|
|
571
|
+
* the target holds at least as much as the source, because that check is the last
|
|
572
|
+
* thing standing between a half-finished migration and a deleted store.
|
|
573
|
+
*/
|
|
574
|
+
declare function switchToMigrated(opts: {
|
|
575
|
+
/** The name readers are configured with. Becomes an alias. */
|
|
576
|
+
name: string;
|
|
577
|
+
/** The collection built by `migrateCollection`. */
|
|
578
|
+
to: string;
|
|
579
|
+
/**
|
|
580
|
+
* Snapshot the source before dropping it. Default true.
|
|
581
|
+
*
|
|
582
|
+
* Freeing the name means deleting the collection, but it does not have to mean
|
|
583
|
+
* losing the data, and those should not be the same decision. The snapshot is
|
|
584
|
+
* what makes "switch over" reversible and leaves "throw the old vectors away"
|
|
585
|
+
* as a separate thing to do later, by hand, once the new store has proven
|
|
586
|
+
* itself in use.
|
|
587
|
+
*/
|
|
588
|
+
snapshot?: boolean;
|
|
589
|
+
logger?: {
|
|
590
|
+
info: (m: string) => void;
|
|
591
|
+
warn: (m: string) => void;
|
|
592
|
+
};
|
|
593
|
+
}): Promise<{
|
|
594
|
+
name: string;
|
|
595
|
+
to: string;
|
|
596
|
+
moved: number;
|
|
597
|
+
snapshot?: {
|
|
598
|
+
name: string;
|
|
599
|
+
size: number;
|
|
600
|
+
};
|
|
601
|
+
}>;
|
|
464
602
|
|
|
465
603
|
/**
|
|
466
604
|
* Similarity floor for accepting an UPDATE target.
|
|
@@ -549,4 +687,4 @@ declare function llmCrudDecision(userText: string, assistantText: string, existi
|
|
|
549
687
|
content: string;
|
|
550
688
|
}>): Promise<MemoryOperation[]>;
|
|
551
689
|
|
|
552
|
-
export { type AgentAmemConfig, type AmemPluginConfig, type ConflictMode, type ConflictSweepResult, DEFAULT_CRUD_UPDATE_MIN_SIM, DEFAULT_EMBEDDING_MODEL, EmbeddingDimensionMismatchError, EmbeddingModelMismatchError, 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, getEmbeddingDevice, getEmbeddingDim, getEmbeddingDtype, getEmbeddingModel, getEmbeddingPooling, getNote, invalidateNote, isModelLoaded, isPlausibleUpdateTarget, listMemories, listNotes, llmCrudDecision, loadModel, mergeSimilarNotes, migrateCollection, patchNotePayload, pingQdrant, resolveCrudUpdateMinSim, scanLowQuality, searchMemory, updateNote };
|
|
690
|
+
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.js
CHANGED
|
@@ -2,6 +2,10 @@ import {
|
|
|
2
2
|
DEFAULT_EMBEDDING_MODEL,
|
|
3
3
|
EmbeddingDimensionMismatchError,
|
|
4
4
|
EmbeddingModelMismatchError,
|
|
5
|
+
LEGACY_DEFAULT_DIM,
|
|
6
|
+
LEGACY_DEFAULT_EMBEDDING_MODEL,
|
|
7
|
+
MixedEmbeddingModelsError,
|
|
8
|
+
SYSTEM_ACTOR,
|
|
5
9
|
addEpisodic,
|
|
6
10
|
addMemory,
|
|
7
11
|
canRead,
|
|
@@ -33,8 +37,9 @@ import {
|
|
|
33
37
|
patchNotePayload,
|
|
34
38
|
pingQdrant,
|
|
35
39
|
searchMemory,
|
|
40
|
+
switchToMigrated,
|
|
36
41
|
updateNote
|
|
37
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-XEMQZNLD.js";
|
|
38
43
|
|
|
39
44
|
// src/quality.ts
|
|
40
45
|
import * as fs from "fs";
|
|
@@ -236,6 +241,10 @@ export {
|
|
|
236
241
|
DEFAULT_EMBEDDING_MODEL,
|
|
237
242
|
EmbeddingDimensionMismatchError,
|
|
238
243
|
EmbeddingModelMismatchError,
|
|
244
|
+
LEGACY_DEFAULT_DIM,
|
|
245
|
+
LEGACY_DEFAULT_EMBEDDING_MODEL,
|
|
246
|
+
MixedEmbeddingModelsError,
|
|
247
|
+
SYSTEM_ACTOR,
|
|
239
248
|
addEpisodic,
|
|
240
249
|
addMemory,
|
|
241
250
|
canRead,
|
|
@@ -270,6 +279,7 @@ export {
|
|
|
270
279
|
resolveCrudUpdateMinSim,
|
|
271
280
|
scanLowQuality,
|
|
272
281
|
searchMemory,
|
|
282
|
+
switchToMigrated,
|
|
273
283
|
updateNote
|
|
274
284
|
};
|
|
275
285
|
//# sourceMappingURL=index.js.map
|