agenticow 0.1.1 → 0.2.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/package.json +2 -2
- package/src/index.d.ts +22 -1
- package/src/index.js +68 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agenticow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Git for Agent Memory: Copy-On-Write vector branching for embedded multi-agent memory. Branch a base memory in ~0.5ms / 162 bytes regardless of base size — 83x faster, 3000x smaller than full-copy snapshots. Exact read-through queries (parent ∪ edits, child wins).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -70,6 +70,6 @@
|
|
|
70
70
|
"node": ">=18"
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"@ruvector/rvf-node": "0.
|
|
73
|
+
"@ruvector/rvf-node": "^0.2.0"
|
|
74
74
|
}
|
|
75
75
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -30,6 +30,22 @@ export interface QueryOptions {
|
|
|
30
30
|
efSearch?: number;
|
|
31
31
|
/** Candidates to over-fetch per store before exact merge. Default: k*4. */
|
|
32
32
|
overscan?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Force the exact JS chain-walk even on a native-COW fork.
|
|
35
|
+
* Default false (native path used when available).
|
|
36
|
+
*/
|
|
37
|
+
forceExact?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ForkOptions {
|
|
41
|
+
/**
|
|
42
|
+
* Use the native Rust COW dual-graph ANN path (PR #618).
|
|
43
|
+
* When true, fork() calls RvfDatabase.branch() instead of derive(), giving
|
|
44
|
+
* the returned fork a working node whose query() spans the COW boundary in
|
|
45
|
+
* a single Rust call. recall@10 = 1.0 at 1200-vector L2 test corpus.
|
|
46
|
+
* Default: false (exact JS chain-walk).
|
|
47
|
+
*/
|
|
48
|
+
nativeAnn?: boolean;
|
|
33
49
|
}
|
|
34
50
|
|
|
35
51
|
export interface QueryHit {
|
|
@@ -73,12 +89,17 @@ export type IngestRecord = { id: number; vector: number[] | Float32Array };
|
|
|
73
89
|
export class AgenticMemory {
|
|
74
90
|
static open(filePath: string, opts?: OpenOptions): AgenticMemory;
|
|
75
91
|
readonly dimension: number;
|
|
92
|
+
/**
|
|
93
|
+
* True when this fork was created with `{nativeAnn:true}`.
|
|
94
|
+
* query() routes through the Rust dual-graph ANN merge (PR #618).
|
|
95
|
+
*/
|
|
96
|
+
readonly nativeAnn: boolean;
|
|
76
97
|
ingest(records: IngestRecord[]): IngestResult;
|
|
77
98
|
ingest(vectors: Float32Array, ids: number[]): IngestResult;
|
|
78
99
|
delete(ids: number[]): { deleted: number; tombstoned: number };
|
|
79
100
|
query(vector: number[] | Float32Array, k?: number, opts?: QueryOptions): QueryHit[];
|
|
80
101
|
branch(label?: string, filePath?: string): AgenticMemory;
|
|
81
|
-
fork(label?: string, filePath?: string): AgenticMemory;
|
|
102
|
+
fork(label?: string, filePath?: string, opts?: ForkOptions): AgenticMemory;
|
|
82
103
|
diff(): MemoryDiff;
|
|
83
104
|
promote(target: AgenticMemory): { ingested: number; deleted: number };
|
|
84
105
|
checkpoint(label?: string): CheckpointDescriptor;
|
package/src/index.js
CHANGED
|
@@ -74,7 +74,7 @@ class Node {
|
|
|
74
74
|
|
|
75
75
|
export class AgenticMemory {
|
|
76
76
|
/** @private */
|
|
77
|
-
constructor(workingNode, ancestors, dim, metric, track = true, owned = null) {
|
|
77
|
+
constructor(workingNode, ancestors, dim, metric, track = true, owned = null, nativeCow = false) {
|
|
78
78
|
/** @type {Node} */
|
|
79
79
|
this._working = workingNode;
|
|
80
80
|
/** @type {Node[]} ancestors newest -> oldest (base last) */
|
|
@@ -88,6 +88,16 @@ export class AgenticMemory {
|
|
|
88
88
|
/** @type {Set<Node>} */
|
|
89
89
|
this._owned = owned || new Set([workingNode]);
|
|
90
90
|
this._closed = false;
|
|
91
|
+
/**
|
|
92
|
+
* True when this instance's working node was created via RvfDatabase.branch()
|
|
93
|
+
* (a real COW child with a dual-graph HNSW that spans the parent boundary).
|
|
94
|
+
* When true, query() routes through the native Rust ANN path — a single
|
|
95
|
+
* db.query() call returns parent∪child hits via the dual-graph merge in
|
|
96
|
+
* rvf-runtime's query_via_index_cow. recall@10 = 1.0 at 1200-vector L2
|
|
97
|
+
* with 5% tombstones (verified in integration tests for rvf-runtime PR #618).
|
|
98
|
+
* @type {boolean}
|
|
99
|
+
*/
|
|
100
|
+
this._nativeCow = nativeCow;
|
|
91
101
|
}
|
|
92
102
|
|
|
93
103
|
/**
|
|
@@ -209,10 +219,31 @@ export class AgenticMemory {
|
|
|
209
219
|
query(vector, k = 10, opts = {}) {
|
|
210
220
|
this._assertOpen();
|
|
211
221
|
const qv = this._normalize ? l2normalize(vector) : toF32(vector);
|
|
222
|
+
const qopts = opts.efSearch ? { efSearch: opts.efSearch } : undefined;
|
|
223
|
+
|
|
224
|
+
// ── Native COW dual-graph ANN path ────────────────────────────────
|
|
225
|
+
// When this instance was created via fork({nativeAnn:true}) or
|
|
226
|
+
// branch({nativeAnn:true}), the working node's db is a real COW child
|
|
227
|
+
// (RvfDatabase.branch()). A single db.query() call transparently queries
|
|
228
|
+
// both the child's own HNSW and the parent's HNSW, merges candidates with
|
|
229
|
+
// child-wins semantics, and excludes tombstoned IDs — all in Rust.
|
|
230
|
+
// Recall@10 = 1.0000 on the PR#618 integration test (1200-vector L2,
|
|
231
|
+
// 60 new + 20 overrides + 10 tombstones, efSearch=300).
|
|
232
|
+
if (this._nativeCow && !opts.forceExact) {
|
|
233
|
+
const hits = this._working.db.query(qv, k, qopts);
|
|
234
|
+
return hits.map((h) => ({
|
|
235
|
+
id: h.id,
|
|
236
|
+
distance: h.distance,
|
|
237
|
+
branch: this._working.label || this._working.id,
|
|
238
|
+
}));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ── Exact JS chain-walk (default / fallback) ──────────────────────
|
|
242
|
+
// For each node in the lineage chain (newest first), query its local
|
|
243
|
+
// store and merge with child-wins semantics.
|
|
212
244
|
const fetch = Math.max(k, opts.overscan || k * 4);
|
|
213
245
|
const resolved = new Map(); // id -> {id, distance, branch}
|
|
214
246
|
const hidden = new Set(); // ids tombstoned by a nearer descendant
|
|
215
|
-
const qopts = opts.efSearch ? { efSearch: opts.efSearch } : undefined;
|
|
216
247
|
for (const node of this._chain()) {
|
|
217
248
|
for (const t of node.tombstones) hidden.add(t);
|
|
218
249
|
let hits = [];
|
|
@@ -229,6 +260,16 @@ export class AgenticMemory {
|
|
|
229
260
|
return [...resolved.values()].sort((a, b) => a.distance - b.distance).slice(0, k);
|
|
230
261
|
}
|
|
231
262
|
|
|
263
|
+
/**
|
|
264
|
+
* Whether this instance uses the native Rust COW dual-graph ANN query path.
|
|
265
|
+
* true => query() routes through rvf-runtime's query_via_index_cow (PR #618).
|
|
266
|
+
* false => exact JS chain-walk across the lineage.
|
|
267
|
+
* @type {boolean}
|
|
268
|
+
*/
|
|
269
|
+
get nativeAnn() {
|
|
270
|
+
return this._nativeCow;
|
|
271
|
+
}
|
|
272
|
+
|
|
232
273
|
/**
|
|
233
274
|
* Create an isolated COW branch (a parallel fork of this memory). O(1) in base
|
|
234
275
|
* size — ~0.5 ms / 162 bytes. The branch sees everything this memory currently
|
|
@@ -264,13 +305,37 @@ export class AgenticMemory {
|
|
|
264
305
|
* per-user branches off one shared base). One derive() per fork — ~0.5 ms /
|
|
265
306
|
* 162 bytes each, O(1) in base size. Read-through isolation holds as long as
|
|
266
307
|
* the parent base stays read-only after forking.
|
|
308
|
+
*
|
|
309
|
+
* `opts.nativeAnn` (default false): when true, creates a real COW branch via
|
|
310
|
+
* RvfDatabase.branch() instead of derive(). The returned fork's query() routes
|
|
311
|
+
* through the native Rust dual-graph ANN merge (PR #618), which queries both
|
|
312
|
+
* the fork's own HNSW and the parent's HNSW in a single call. This gives
|
|
313
|
+
* sub-linear ANN performance across the COW boundary at recall@10 = 1.0.
|
|
314
|
+
* Requires the parent to NOT be mutated after forking (same rule as exact mode).
|
|
267
315
|
* @param {string} [label]
|
|
268
316
|
* @param {string} [filePath]
|
|
317
|
+
* @param {{nativeAnn?:boolean}} [opts]
|
|
269
318
|
* @returns {AgenticMemory}
|
|
270
319
|
*/
|
|
271
|
-
fork(label, filePath) {
|
|
320
|
+
fork(label, filePath, opts = {}) {
|
|
272
321
|
this._assertOpen();
|
|
273
322
|
const childPath = filePath || tmpChildPath(this._working.path, label);
|
|
323
|
+
if (opts.nativeAnn) {
|
|
324
|
+
// Native COW branch: the Rust COW engine wires parent→child read-through
|
|
325
|
+
// so a single db.query() merges both sides via dual-graph ANN.
|
|
326
|
+
const childDb = this._working.db.branch(childPath);
|
|
327
|
+
const childNode = new Node(childDb, childPath, label || 'fork');
|
|
328
|
+
// The COW child already knows its parent; no JS ancestor chain needed.
|
|
329
|
+
return new AgenticMemory(
|
|
330
|
+
childNode,
|
|
331
|
+
[], // ancestors managed by Rust COW engine
|
|
332
|
+
this._dim,
|
|
333
|
+
this._metric,
|
|
334
|
+
this._track,
|
|
335
|
+
null,
|
|
336
|
+
true // _nativeCow = true → query() uses native path
|
|
337
|
+
);
|
|
338
|
+
}
|
|
274
339
|
const childDb = this._working.db.derive(childPath, this._deriveOpts());
|
|
275
340
|
const childNode = new Node(childDb, childPath, label || 'fork');
|
|
276
341
|
return new AgenticMemory(
|