claude-flow 3.19.0 → 3.21.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/v3/@claude-flow/cli/dist/src/agenticow/speculative-exploration.d.ts +148 -0
- package/v3/@claude-flow/cli/dist/src/agenticow/speculative-exploration.js +218 -0
- package/v3/@claude-flow/cli/dist/src/commands/autopilot.js +45 -0
- package/v3/@claude-flow/cli/dist/src/commands/neural.js +366 -12
- package/v3/@claude-flow/cli/dist/src/mcp-client.js +6 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-execute-core.js +33 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-tools.js +47 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-loader.d.ts +59 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-loader.js +105 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-speculate-tools.d.ts +24 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-speculate-tools.js +202 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-tools.js +186 -79
- package/v3/@claude-flow/cli/dist/src/mcp-tools/index.d.ts +1 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/index.js +2 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/index.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/ruvector/index.js +1 -1
- package/v3/@claude-flow/cli/dist/src/ruvector/lora-adapter.d.ts +35 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/lora-adapter.js +108 -1
- package/v3/@claude-flow/cli/dist/src/ruvector/router-trajectory.d.ts +33 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/router-trajectory.js +31 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/run-transcript-recorder.d.ts +154 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/run-transcript-recorder.js +209 -0
- package/v3/@claude-flow/cli/dist/src/services/checkpoint-gate.d.ts +140 -0
- package/v3/@claude-flow/cli/dist/src/services/checkpoint-gate.js +223 -0
- package/v3/@claude-flow/cli/dist/src/services/distill-oracle.d.ts +190 -0
- package/v3/@claude-flow/cli/dist/src/services/distill-oracle.js +349 -0
- package/v3/@claude-flow/cli/dist/src/services/fable-harness.d.ts +168 -0
- package/v3/@claude-flow/cli/dist/src/services/fable-harness.js +347 -0
- package/v3/@claude-flow/cli/dist/src/services/native-training.d.ts +28 -0
- package/v3/@claude-flow/cli/dist/src/services/native-training.js +62 -5
- package/v3/@claude-flow/cli/dist/src/services/swarm-memory-branches.d.ts +135 -0
- package/v3/@claude-flow/cli/dist/src/services/swarm-memory-branches.js +213 -0
- package/v3/@claude-flow/cli/dist/src/services/weight-eft.d.ts +305 -0
- package/v3/@claude-flow/cli/dist/src/services/weight-eft.js +296 -0
- package/v3/@claude-flow/cli/package.json +5 -4
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agenticow shared loader + helpers.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `agenticow-tools.ts` so both the MCP tool surface AND the
|
|
5
|
+
* `SwarmMemoryBranches` service (src/services/swarm-memory-branches.ts) share
|
|
6
|
+
* one loader, one degradation contract, and one set of path/lineage helpers —
|
|
7
|
+
* rather than duplicating the optional-dep dance in two places.
|
|
8
|
+
*
|
|
9
|
+
* Architectural constraint (ADR-150, mirrors metaharness-tools.ts):
|
|
10
|
+
* - `agenticow` lives in `optionalDependencies` — never a hard runtime dep.
|
|
11
|
+
* - When the package is missing, callers get `null` from `loadAgenticow()`
|
|
12
|
+
* (or a `{degraded:true}` result via `degradedResult`) and MUST fall back
|
|
13
|
+
* gracefully — never throw a MODULE_NOT_FOUND.
|
|
14
|
+
*
|
|
15
|
+
* @module @claude-flow/cli/mcp-tools/agenticow-loader
|
|
16
|
+
*/
|
|
17
|
+
import { existsSync } from 'node:fs';
|
|
18
|
+
import { resolve, isAbsolute } from 'node:path';
|
|
19
|
+
import { getProjectCwd } from './types.js';
|
|
20
|
+
export const PACKAGE_NAME = 'agenticow';
|
|
21
|
+
// Cache: module load is expensive enough to amortize across handler calls.
|
|
22
|
+
// null = not yet attempted; false = attempted and unavailable; module = loaded.
|
|
23
|
+
let _agenticowMod = null;
|
|
24
|
+
let _loadAttempted = false;
|
|
25
|
+
/**
|
|
26
|
+
* Lazily import `agenticow`. Returns the module on success, or `null` when the
|
|
27
|
+
* package is not installed (the optional-dep degraded path). Any OTHER load
|
|
28
|
+
* error (e.g. a corrupt install) is re-thrown so it is not silently masked.
|
|
29
|
+
*/
|
|
30
|
+
export async function loadAgenticow() {
|
|
31
|
+
if (_loadAttempted)
|
|
32
|
+
return _agenticowMod || null;
|
|
33
|
+
_loadAttempted = true;
|
|
34
|
+
try {
|
|
35
|
+
_agenticowMod = await import(PACKAGE_NAME);
|
|
36
|
+
return _agenticowMod;
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
if (err && (err.code === 'ERR_MODULE_NOT_FOUND' || err.code === 'MODULE_NOT_FOUND' ||
|
|
40
|
+
/Cannot find (module|package)/i.test(String(err.message)))) {
|
|
41
|
+
_agenticowMod = false;
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Reset the module-level load cache. Test-only seam. */
|
|
48
|
+
export function __resetAgenticowCache() {
|
|
49
|
+
_agenticowMod = null;
|
|
50
|
+
_loadAttempted = false;
|
|
51
|
+
}
|
|
52
|
+
export function degradedResult(reason) {
|
|
53
|
+
return { success: true, degraded: true, reason };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Resolve a user-supplied memory path against the project cwd, rejecting path
|
|
57
|
+
* traversal and NUL bytes (D-2 style hardening — same rule the MCP verbs use).
|
|
58
|
+
*/
|
|
59
|
+
export function resolveMemoryPath(path) {
|
|
60
|
+
if (!path || typeof path !== 'string')
|
|
61
|
+
throw new Error('memory path is required');
|
|
62
|
+
if (/\.\.[\\/]|\0/.test(path))
|
|
63
|
+
throw new Error('memory path contains disallowed characters');
|
|
64
|
+
return isAbsolute(path) ? path : resolve(getProjectCwd(), path);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Lineage manifest companion path. agenticow persists the COW chain
|
|
68
|
+
* (working → checkpoints → base) into `<file>.agenticow.json` next to the
|
|
69
|
+
* `.rvf` data file. Without it, forks/checkpoints are in-memory only and
|
|
70
|
+
* disappear when the AgenticMemory handle closes.
|
|
71
|
+
*/
|
|
72
|
+
export function manifestFor(file) {
|
|
73
|
+
return `${file}.agenticow.json`;
|
|
74
|
+
}
|
|
75
|
+
/** Validate a COW branch/checkpoint label (alnum + a small safe symbol set). */
|
|
76
|
+
export function validateLabel(label) {
|
|
77
|
+
if (!label || typeof label !== 'string')
|
|
78
|
+
throw new Error('label is required');
|
|
79
|
+
if (label.length > 256)
|
|
80
|
+
throw new Error('label exceeds 256 chars');
|
|
81
|
+
if (!/^[A-Za-z0-9_.\-:/@]+$/.test(label)) {
|
|
82
|
+
throw new Error('label may only contain [A-Za-z0-9_.\\-:/@]');
|
|
83
|
+
}
|
|
84
|
+
return label;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Open (or create) a memory file, restoring its COW chain from the lineage
|
|
88
|
+
* manifest when one exists. When neither the `.rvf` nor the manifest exists,
|
|
89
|
+
* `dimension` is required to create a fresh base.
|
|
90
|
+
*/
|
|
91
|
+
export async function openWithLineage(api, file, dimension) {
|
|
92
|
+
const manifest = manifestFor(file);
|
|
93
|
+
if (existsSync(manifest)) {
|
|
94
|
+
return api.AgenticMemory.load(manifest);
|
|
95
|
+
}
|
|
96
|
+
const opts = {};
|
|
97
|
+
if (typeof dimension === 'number' && Number.isInteger(dimension) && dimension > 0) {
|
|
98
|
+
opts.dimension = dimension;
|
|
99
|
+
}
|
|
100
|
+
else if (!existsSync(file)) {
|
|
101
|
+
throw new Error('dimension is required when creating a new memory file');
|
|
102
|
+
}
|
|
103
|
+
return api.open(file, opts);
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=agenticow-loader.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agenticow speculate — MCP surface for speculative branch-and-promote
|
|
3
|
+
* (agenticow step 4).
|
|
4
|
+
*
|
|
5
|
+
* Exposes the `SpeculativeExploration` module as a single MCP tool
|
|
6
|
+
* `agenticow_speculate` so agents can drive parallel A/B memory exploration:
|
|
7
|
+
* fan out N candidate ingest-sets, each on its own 162-byte COW branch of a
|
|
8
|
+
* shared base `.rvf`, score them by a probe query, PROMOTE the winner into
|
|
9
|
+
* base, and DISCARD the losers (delete their branch files).
|
|
10
|
+
*
|
|
11
|
+
* Because MCP inputs are JSON (not JS functions), candidates are DECLARATIVE:
|
|
12
|
+
* each candidate is a label + a set of vectors to ingest, and scoring is a
|
|
13
|
+
* probe query (`nearest`) or an ingest count (`count`). The generic
|
|
14
|
+
* `explore(base, {label, fn}[], score)` core does the real work.
|
|
15
|
+
*
|
|
16
|
+
* Optional-dep contract (mirrors agenticow-tools.ts): when `agenticow` is not
|
|
17
|
+
* installed the tool returns `{success:true, degraded:true,
|
|
18
|
+
* reason:'agenticow-not-found'}` and never throws for that reason.
|
|
19
|
+
*
|
|
20
|
+
* @module @claude-flow/cli/mcp-tools/agenticow-speculate
|
|
21
|
+
*/
|
|
22
|
+
import type { MCPTool } from './types.js';
|
|
23
|
+
export declare const agenticowSpeculateTools: MCPTool[];
|
|
24
|
+
//# sourceMappingURL=agenticow-speculate-tools.d.ts.map
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agenticow speculate — MCP surface for speculative branch-and-promote
|
|
3
|
+
* (agenticow step 4).
|
|
4
|
+
*
|
|
5
|
+
* Exposes the `SpeculativeExploration` module as a single MCP tool
|
|
6
|
+
* `agenticow_speculate` so agents can drive parallel A/B memory exploration:
|
|
7
|
+
* fan out N candidate ingest-sets, each on its own 162-byte COW branch of a
|
|
8
|
+
* shared base `.rvf`, score them by a probe query, PROMOTE the winner into
|
|
9
|
+
* base, and DISCARD the losers (delete their branch files).
|
|
10
|
+
*
|
|
11
|
+
* Because MCP inputs are JSON (not JS functions), candidates are DECLARATIVE:
|
|
12
|
+
* each candidate is a label + a set of vectors to ingest, and scoring is a
|
|
13
|
+
* probe query (`nearest`) or an ingest count (`count`). The generic
|
|
14
|
+
* `explore(base, {label, fn}[], score)` core does the real work.
|
|
15
|
+
*
|
|
16
|
+
* Optional-dep contract (mirrors agenticow-tools.ts): when `agenticow` is not
|
|
17
|
+
* installed the tool returns `{success:true, degraded:true,
|
|
18
|
+
* reason:'agenticow-not-found'}` and never throws for that reason.
|
|
19
|
+
*
|
|
20
|
+
* @module @claude-flow/cli/mcp-tools/agenticow-speculate
|
|
21
|
+
*/
|
|
22
|
+
import { loadAgenticow, degradedResult, resolveMemoryPath, openWithLineage, manifestFor, validateLabel, } from './agenticow-loader.js';
|
|
23
|
+
import { explore, } from '../agenticow/speculative-exploration.js';
|
|
24
|
+
/** Turn a validated label into a filesystem-safe branch filename segment. */
|
|
25
|
+
function safeSegment(label) {
|
|
26
|
+
return label.replace(/[^A-Za-z0-9_-]+/g, '_').slice(0, 64) || 'branch';
|
|
27
|
+
}
|
|
28
|
+
export const agenticowSpeculateTools = [
|
|
29
|
+
{
|
|
30
|
+
name: 'agenticow_speculate',
|
|
31
|
+
description: "agenticow — speculative branch-and-promote for parallel A/B memory exploration. " +
|
|
32
|
+
"Forks a 162-byte COW branch per candidate off a shared base .rvf, ingests each candidate's " +
|
|
33
|
+
"vectors into its OWN isolated branch, scores every branch (probe-query 'nearest' distance or " +
|
|
34
|
+
"ingest 'count'), PROMOTES the winning branch's edits into base, and DISCARDS the losers by " +
|
|
35
|
+
"deleting their branch files. Use when you want to try N competing memory-write strategies and " +
|
|
36
|
+
"keep only the best — the memory-state analogue of worktree-per-agent code exploration. " +
|
|
37
|
+
"Copying the base per candidate is wrong (full-copy snapshots grow linearly, the 3.3 GB Darwin " +
|
|
38
|
+
"bloat); COW forks are constant-size and losers cost ~162 B to throw away. Optional dep — " +
|
|
39
|
+
"degrades to {degraded:true} when agenticow is missing.",
|
|
40
|
+
category: 'memory',
|
|
41
|
+
tags: ['agenticow', 'memory', 'cow', 'speculative', 'branch', 'promote', 'ab'],
|
|
42
|
+
inputSchema: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {
|
|
45
|
+
basePath: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
description: 'Path to base .rvf memory file (absolute or relative to cwd)',
|
|
48
|
+
},
|
|
49
|
+
dimension: {
|
|
50
|
+
type: 'integer',
|
|
51
|
+
description: 'Vector dimension (required only when basePath does not exist yet)',
|
|
52
|
+
},
|
|
53
|
+
candidates: {
|
|
54
|
+
type: 'array',
|
|
55
|
+
description: 'The A/B candidates. Each explores its own COW branch.',
|
|
56
|
+
items: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {
|
|
59
|
+
label: { type: 'string', description: 'Branch label (alnum + _.-:/@ only)' },
|
|
60
|
+
ingest: {
|
|
61
|
+
type: 'array',
|
|
62
|
+
description: 'Vectors to ingest into this candidate branch',
|
|
63
|
+
items: {
|
|
64
|
+
type: 'object',
|
|
65
|
+
properties: {
|
|
66
|
+
id: { type: 'integer', description: 'Explicit vector id (auto when omitted)' },
|
|
67
|
+
vector: { type: 'array', items: { type: 'number' }, description: 'Dense vector' },
|
|
68
|
+
text: { type: 'string', description: 'Optional payload text' },
|
|
69
|
+
},
|
|
70
|
+
required: ['vector'],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
branchPath: {
|
|
74
|
+
type: 'string',
|
|
75
|
+
description: 'Optional explicit path for this branch file (default: alongside base)',
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
required: ['label', 'ingest'],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
probe: {
|
|
82
|
+
type: 'array',
|
|
83
|
+
items: { type: 'number' },
|
|
84
|
+
description: "Probe vector for scoreBy='nearest' (scores each branch by best-hit similarity)",
|
|
85
|
+
},
|
|
86
|
+
k: { type: 'integer', description: 'Nearest-neighbours to fetch per probe (default 1)' },
|
|
87
|
+
scoreBy: {
|
|
88
|
+
type: 'string',
|
|
89
|
+
enum: ['nearest', 'count'],
|
|
90
|
+
description: "'nearest' = closest probe distance wins; 'count' = most accepted ingests wins. Default 'nearest'.",
|
|
91
|
+
},
|
|
92
|
+
requireClearance: {
|
|
93
|
+
type: 'boolean',
|
|
94
|
+
description: 'ADR-171 fail-closed gate. When true, the top-scored winner is NOT promoted (base stays unchanged) and a provenance-tagged receipt is emitted — score alone cannot graduate work. Use when speculating over TASK outcomes rather than pure memory A/B. Default false (score-only promotion, tagged `unverified`).',
|
|
95
|
+
default: false,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
required: ['basePath', 'candidates'],
|
|
99
|
+
},
|
|
100
|
+
handler: async (input) => {
|
|
101
|
+
const api = await loadAgenticow();
|
|
102
|
+
if (!api)
|
|
103
|
+
return degradedResult('agenticow-not-found');
|
|
104
|
+
const basePath = resolveMemoryPath(String(input.basePath));
|
|
105
|
+
const dimension = input.dimension;
|
|
106
|
+
const scoreBy = input.scoreBy === 'count' ? 'count' : 'nearest';
|
|
107
|
+
const k = Number.isInteger(input.k) && input.k > 0 ? input.k : 1;
|
|
108
|
+
const probe = Array.isArray(input.probe) ? input.probe : null;
|
|
109
|
+
const rawCandidates = input.candidates;
|
|
110
|
+
if (!Array.isArray(rawCandidates) || rawCandidates.length === 0) {
|
|
111
|
+
throw new Error('at least one candidate is required');
|
|
112
|
+
}
|
|
113
|
+
if (scoreBy === 'nearest' && !probe) {
|
|
114
|
+
throw new Error("scoreBy='nearest' requires a probe vector");
|
|
115
|
+
}
|
|
116
|
+
// Build the generic {label, fn} candidates. Each fn ingests into its own
|
|
117
|
+
// branch handle, then (for 'nearest') probes it so we can score.
|
|
118
|
+
const candidates = rawCandidates.map((c) => {
|
|
119
|
+
const label = validateLabel(String(c.label));
|
|
120
|
+
if (!Array.isArray(c.ingest) || c.ingest.length === 0) {
|
|
121
|
+
throw new Error(`candidate ${label} must ingest at least one vector`);
|
|
122
|
+
}
|
|
123
|
+
const records = c.ingest.map((r) => ({
|
|
124
|
+
...(Number.isInteger(r.id) ? { id: r.id } : {}),
|
|
125
|
+
vector: r.vector,
|
|
126
|
+
...(typeof r.text === 'string' ? { text: r.text } : {}),
|
|
127
|
+
}));
|
|
128
|
+
return {
|
|
129
|
+
label,
|
|
130
|
+
fn: (branch) => {
|
|
131
|
+
const res = branch.ingest(records);
|
|
132
|
+
const accepted = Number(res?.accepted ?? records.length);
|
|
133
|
+
let hits = [];
|
|
134
|
+
if (probe) {
|
|
135
|
+
hits = (branch.query(probe, k) || []).map((h) => ({
|
|
136
|
+
id: h.id,
|
|
137
|
+
distance: h.distance,
|
|
138
|
+
}));
|
|
139
|
+
}
|
|
140
|
+
const bestDistance = hits.length ? hits[0].distance : null;
|
|
141
|
+
return { accepted, bestDistance, hits };
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
const score = (r) => {
|
|
146
|
+
if (scoreBy === 'count')
|
|
147
|
+
return r.accepted;
|
|
148
|
+
// nearest: smaller distance = better. Map to a higher-is-better score.
|
|
149
|
+
if (r.bestDistance === null)
|
|
150
|
+
return -Infinity;
|
|
151
|
+
return 1 / (1 + Math.max(0, r.bestDistance));
|
|
152
|
+
};
|
|
153
|
+
const branchPath = (label) => {
|
|
154
|
+
const explicit = rawCandidates.find((c) => validateLabel(String(c.label)) === label)?.branchPath;
|
|
155
|
+
if (explicit)
|
|
156
|
+
return resolveMemoryPath(String(explicit));
|
|
157
|
+
return `${basePath}.spec-${safeSegment(label)}.rvf`;
|
|
158
|
+
};
|
|
159
|
+
// ADR-171 promotion gate. For pure memory A/B the `score` IS the chosen
|
|
160
|
+
// metric (not a proxy for task correctness), so score-only promotion is
|
|
161
|
+
// legitimate and stays the default (tagged `unverified`, never
|
|
162
|
+
// masquerading as ground truth). Callers grafting this onto TASK work
|
|
163
|
+
// pass requireClearance:true to fail-closed — the winner is then
|
|
164
|
+
// ineligible unless a real clearance mechanism graduates it.
|
|
165
|
+
const requireClearance = input.requireClearance === true;
|
|
166
|
+
const exploreOpts = { branchPath, persist: true };
|
|
167
|
+
if (requireClearance)
|
|
168
|
+
exploreOpts.requireClearance = true;
|
|
169
|
+
// Open base, run the speculative exploration, persist base with the winner promoted.
|
|
170
|
+
const base = await openWithLineage(api, basePath, dimension);
|
|
171
|
+
try {
|
|
172
|
+
const result = await explore(base, candidates, score, exploreOpts);
|
|
173
|
+
base.save?.(manifestFor(basePath));
|
|
174
|
+
return {
|
|
175
|
+
success: true,
|
|
176
|
+
basePath,
|
|
177
|
+
scoreBy,
|
|
178
|
+
winner: result.winner,
|
|
179
|
+
scores: result.scores,
|
|
180
|
+
promoted: result.promoted,
|
|
181
|
+
promotedBy: result.promotedBy,
|
|
182
|
+
promotionDecision: result.promotionDecision,
|
|
183
|
+
promoteStats: result.promoteStats,
|
|
184
|
+
discarded: result.discarded,
|
|
185
|
+
receipts: result.receipts,
|
|
186
|
+
branches: result.branches.map((b) => ({
|
|
187
|
+
label: b.label,
|
|
188
|
+
path: b.path,
|
|
189
|
+
score: b.score,
|
|
190
|
+
kept: b.kept,
|
|
191
|
+
accepted: b.result.accepted,
|
|
192
|
+
bestDistance: b.result.bestDistance,
|
|
193
|
+
})),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
finally {
|
|
197
|
+
await base.close?.();
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
];
|
|
202
|
+
//# sourceMappingURL=agenticow-speculate-tools.js.map
|
|
@@ -31,83 +31,10 @@
|
|
|
31
31
|
*
|
|
32
32
|
* @module @claude-flow/cli/mcp-tools/agenticow
|
|
33
33
|
*/
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
// Cache: module load is expensive enough to amortize across handler calls.
|
|
39
|
-
// null = not yet attempted; false = attempted and unavailable; module = loaded.
|
|
40
|
-
let _agenticowMod = null;
|
|
41
|
-
let _loadAttempted = false;
|
|
42
|
-
async function loadAgenticow() {
|
|
43
|
-
if (_loadAttempted)
|
|
44
|
-
return _agenticowMod;
|
|
45
|
-
_loadAttempted = true;
|
|
46
|
-
try {
|
|
47
|
-
_agenticowMod = await import(PACKAGE_NAME);
|
|
48
|
-
return _agenticowMod;
|
|
49
|
-
}
|
|
50
|
-
catch (err) {
|
|
51
|
-
if (err && (err.code === 'ERR_MODULE_NOT_FOUND' || err.code === 'MODULE_NOT_FOUND' ||
|
|
52
|
-
/Cannot find (module|package)/i.test(String(err.message)))) {
|
|
53
|
-
_agenticowMod = false;
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
throw err;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
function degradedResult(reason) {
|
|
60
|
-
return { success: true, degraded: true, reason };
|
|
61
|
-
}
|
|
62
|
-
function resolveMemoryPath(path) {
|
|
63
|
-
if (!path || typeof path !== 'string')
|
|
64
|
-
throw new Error('memory path is required');
|
|
65
|
-
// D-2 style: reject path traversal in user-supplied paths
|
|
66
|
-
if (/\.\.[\\/]|\0/.test(path))
|
|
67
|
-
throw new Error('memory path contains disallowed characters');
|
|
68
|
-
return isAbsolute(path) ? path : resolve(getProjectCwd(), path);
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Lineage manifest companion path. agenticow persists the COW chain
|
|
72
|
-
* (working → checkpoints → base) into `<file>.agenticow.json` next to the
|
|
73
|
-
* `.rvf` data file. Without this, checkpoints and forks are in-memory only
|
|
74
|
-
* and disappear when the AgenticMemory handle closes. Mirrors the bin
|
|
75
|
-
* CLI's `manifestFor(file)` helper.
|
|
76
|
-
*/
|
|
77
|
-
function manifestFor(file) {
|
|
78
|
-
return `${file}.agenticow.json`;
|
|
79
|
-
}
|
|
80
|
-
function validateLabel(label) {
|
|
81
|
-
if (!label || typeof label !== 'string')
|
|
82
|
-
throw new Error('label is required');
|
|
83
|
-
if (label.length > 256)
|
|
84
|
-
throw new Error('label exceeds 256 chars');
|
|
85
|
-
if (!/^[A-Za-z0-9_.\-:/@]+$/.test(label)) {
|
|
86
|
-
throw new Error('label may only contain [A-Za-z0-9_.\\-:/@]');
|
|
87
|
-
}
|
|
88
|
-
return label;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Open (or create) a base memory file. When a lineage manifest exists at
|
|
92
|
-
* `<file>.agenticow.json`, we load that to restore the COW chain (checkpoints,
|
|
93
|
-
* ancestors). When only the `.rvf` exists, fresh-open it. When neither exists,
|
|
94
|
-
* dimension is required to create. Mirrors the bin CLI's `loadMem()` helper.
|
|
95
|
-
*/
|
|
96
|
-
async function openWithLineage(api, file, dimension) {
|
|
97
|
-
const manifest = manifestFor(file);
|
|
98
|
-
if (existsSync(manifest)) {
|
|
99
|
-
// The class-level static method `load` reconstructs the full chain.
|
|
100
|
-
return api.AgenticMemory.load(manifest);
|
|
101
|
-
}
|
|
102
|
-
const opts = {};
|
|
103
|
-
if (typeof dimension === 'number' && Number.isInteger(dimension) && dimension > 0) {
|
|
104
|
-
opts.dimension = dimension;
|
|
105
|
-
}
|
|
106
|
-
else if (!existsSync(file)) {
|
|
107
|
-
throw new Error('dimension is required when creating a new memory file');
|
|
108
|
-
}
|
|
109
|
-
return api.open(file, opts);
|
|
110
|
-
}
|
|
34
|
+
// Loader + path/lineage helpers are shared with the SwarmMemoryBranches
|
|
35
|
+
// service (src/services/swarm-memory-branches.ts) via one module so the
|
|
36
|
+
// optional-dep dance and the COW open/fork semantics live in exactly one place.
|
|
37
|
+
import { loadAgenticow, degradedResult, resolveMemoryPath, manifestFor, validateLabel, openWithLineage, } from './agenticow-loader.js';
|
|
111
38
|
export const agenticowTools = [
|
|
112
39
|
{
|
|
113
40
|
name: 'agenticow_branch',
|
|
@@ -121,6 +48,7 @@ export const agenticowTools = [
|
|
|
121
48
|
branchPath: { type: 'string', description: 'Path to write the branch file' },
|
|
122
49
|
label: { type: 'string', description: 'Human-readable label for the branch (alnum + _.-:/@ only)' },
|
|
123
50
|
dimension: { type: 'integer', description: 'Vector dimension (required only when basePath does not exist yet)' },
|
|
51
|
+
nativeAnn: { type: 'boolean', description: 'Use the native Rust COW dual-graph ANN path (recall@10=1.0, query spans the COW boundary in one Rust call). Default false (exact JS chain-walk). Set true when the branch will be queried.', default: false },
|
|
124
52
|
},
|
|
125
53
|
required: ['basePath', 'branchPath', 'label'],
|
|
126
54
|
},
|
|
@@ -132,9 +60,10 @@ export const agenticowTools = [
|
|
|
132
60
|
const basePath = resolveMemoryPath(String(input.basePath));
|
|
133
61
|
const branchPath = resolveMemoryPath(String(input.branchPath));
|
|
134
62
|
const dim = input.dimension;
|
|
63
|
+
const nativeAnn = input.nativeAnn === true;
|
|
135
64
|
const base = await openWithLineage(api, basePath, dim);
|
|
136
65
|
try {
|
|
137
|
-
const branch = await base.fork(label, branchPath);
|
|
66
|
+
const branch = await base.fork(label, branchPath, { nativeAnn });
|
|
138
67
|
// Persist lineage manifests so the branch (and base) reopen with
|
|
139
68
|
// their COW chain intact. Without this, fork is in-memory only.
|
|
140
69
|
await branch.save?.(manifestFor(branchPath));
|
|
@@ -145,6 +74,7 @@ export const agenticowTools = [
|
|
|
145
74
|
basePath,
|
|
146
75
|
branchPath,
|
|
147
76
|
label,
|
|
77
|
+
nativeAnn,
|
|
148
78
|
};
|
|
149
79
|
}
|
|
150
80
|
finally {
|
|
@@ -152,6 +82,181 @@ export const agenticowTools = [
|
|
|
152
82
|
}
|
|
153
83
|
},
|
|
154
84
|
},
|
|
85
|
+
{
|
|
86
|
+
name: 'agenticow_ingest',
|
|
87
|
+
description: 'agenticow — write vectors (with optional text payloads) into an .rvf memory branch or base. Records: [{id?, vector, text?}] — id auto-assigns when omitted. This is the write half that makes a branch usable: agenticow_branch creates an empty COW child, but without ingest it has nothing to read back. Use when you have branched and must populate the branch (agenticow_branch alone leaves it empty). Editing the base directly is wrong when the writes are speculative — ingest into a branch, then promote only if validated. Persists via .agenticow.json lineage manifest.',
|
|
88
|
+
category: 'memory',
|
|
89
|
+
tags: ['agenticow', 'memory', 'cow', 'ingest', 'write'],
|
|
90
|
+
inputSchema: {
|
|
91
|
+
type: 'object',
|
|
92
|
+
properties: {
|
|
93
|
+
path: { type: 'string', description: 'Path to .rvf memory file (branch or base)' },
|
|
94
|
+
records: {
|
|
95
|
+
type: 'array',
|
|
96
|
+
description: 'Vectors to ingest: [{id?: number, vector: number[], text?: string}]',
|
|
97
|
+
items: {
|
|
98
|
+
type: 'object',
|
|
99
|
+
properties: {
|
|
100
|
+
id: { type: 'integer', description: 'Explicit id (auto-assigned when omitted)' },
|
|
101
|
+
vector: { type: 'array', items: { type: 'number' }, description: 'Embedding vector (length must equal the memory dimension)' },
|
|
102
|
+
text: { type: 'string', description: 'Optional payload surfaced on query hits' },
|
|
103
|
+
},
|
|
104
|
+
required: ['vector'],
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
dimension: { type: 'integer', description: 'Vector dimension (required only when path does not exist yet)' },
|
|
108
|
+
},
|
|
109
|
+
required: ['path', 'records'],
|
|
110
|
+
},
|
|
111
|
+
handler: async (input) => {
|
|
112
|
+
const api = await loadAgenticow();
|
|
113
|
+
if (!api)
|
|
114
|
+
return degradedResult('agenticow-not-found');
|
|
115
|
+
const path = resolveMemoryPath(String(input.path));
|
|
116
|
+
const records = input.records;
|
|
117
|
+
if (!Array.isArray(records) || records.length === 0) {
|
|
118
|
+
throw new Error('records must be a non-empty array of {id?, vector, text?}');
|
|
119
|
+
}
|
|
120
|
+
for (const r of records) {
|
|
121
|
+
if (!Array.isArray(r.vector) || r.vector.length === 0) {
|
|
122
|
+
throw new Error('each record requires a non-empty numeric vector');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const dim = input.dimension ?? records[0].vector.length;
|
|
126
|
+
const mem = await openWithLineage(api, path, dim);
|
|
127
|
+
try {
|
|
128
|
+
const result = await mem.ingest(records.map((r) => ({
|
|
129
|
+
...(typeof r.id === 'number' ? { id: r.id } : {}),
|
|
130
|
+
vector: r.vector,
|
|
131
|
+
...(r.text !== undefined ? { text: r.text } : {}),
|
|
132
|
+
})));
|
|
133
|
+
await mem.save?.(manifestFor(path));
|
|
134
|
+
return { success: true, path, ingested: result };
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
await mem.close?.();
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'agenticow_query',
|
|
143
|
+
description: 'agenticow — k-NN read across an .rvf memory branch\'s full COW lineage (parent ∪ edits, child wins), returning {id, distance, branch, text}. Read-only (no manifest write). The `branch` field on each hit tells you which lineage node the result came from — the read-through semantics that make branching useful. Use when you need to read from an agent/session branch without materializing a full copy. Re-opening the base and manually merging edits is wrong: query already spans the chain (and uses the single-call Rust path when the branch was forked with nativeAnn).',
|
|
144
|
+
category: 'memory',
|
|
145
|
+
tags: ['agenticow', 'memory', 'cow', 'query', 'read', 'search'],
|
|
146
|
+
inputSchema: {
|
|
147
|
+
type: 'object',
|
|
148
|
+
properties: {
|
|
149
|
+
path: { type: 'string', description: 'Path to .rvf memory file' },
|
|
150
|
+
vector: { type: 'array', items: { type: 'number' }, description: 'Query embedding vector' },
|
|
151
|
+
k: { type: 'integer', description: 'Number of nearest neighbors to return', default: 10 },
|
|
152
|
+
efSearch: { type: 'integer', description: 'HNSW efSearch per lineage store (higher = better recall, slower)' },
|
|
153
|
+
},
|
|
154
|
+
required: ['path', 'vector'],
|
|
155
|
+
},
|
|
156
|
+
handler: async (input) => {
|
|
157
|
+
const api = await loadAgenticow();
|
|
158
|
+
if (!api)
|
|
159
|
+
return degradedResult('agenticow-not-found');
|
|
160
|
+
const path = resolveMemoryPath(String(input.path));
|
|
161
|
+
const vector = input.vector;
|
|
162
|
+
if (!Array.isArray(vector) || vector.length === 0) {
|
|
163
|
+
throw new Error('vector must be a non-empty numeric array');
|
|
164
|
+
}
|
|
165
|
+
const k = typeof input.k === 'number' ? input.k : 10;
|
|
166
|
+
const opts = {};
|
|
167
|
+
if (typeof input.efSearch === 'number')
|
|
168
|
+
opts.efSearch = input.efSearch;
|
|
169
|
+
const mem = await openWithLineage(api, path);
|
|
170
|
+
try {
|
|
171
|
+
const hits = await mem.query(vector, k, opts);
|
|
172
|
+
return { success: true, path, k, hits };
|
|
173
|
+
}
|
|
174
|
+
finally {
|
|
175
|
+
await mem.close?.();
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: 'agenticow_diff',
|
|
181
|
+
description: 'agenticow — show what a branch changed relative to its lineage: {added, overridden, deleted} vector-id lists. Use when you are about to promote and want to preview the exact merge, or when auditing what a branch actually wrote. Diffing by re-querying is wrong because deletions (tombstones) are invisible to a read — diff() surfaces them explicitly. Requires the branch was opened with edit tracking (default on).',
|
|
182
|
+
category: 'memory',
|
|
183
|
+
tags: ['agenticow', 'memory', 'cow', 'diff'],
|
|
184
|
+
inputSchema: {
|
|
185
|
+
type: 'object',
|
|
186
|
+
properties: {
|
|
187
|
+
path: { type: 'string', description: 'Path to branch .rvf file' },
|
|
188
|
+
},
|
|
189
|
+
required: ['path'],
|
|
190
|
+
},
|
|
191
|
+
handler: async (input) => {
|
|
192
|
+
const api = await loadAgenticow();
|
|
193
|
+
if (!api)
|
|
194
|
+
return degradedResult('agenticow-not-found');
|
|
195
|
+
const path = resolveMemoryPath(String(input.path));
|
|
196
|
+
const mem = await openWithLineage(api, path);
|
|
197
|
+
try {
|
|
198
|
+
const diff = await mem.diff();
|
|
199
|
+
return { success: true, path, diff };
|
|
200
|
+
}
|
|
201
|
+
finally {
|
|
202
|
+
await mem.close?.();
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: 'agenticow_lineage',
|
|
208
|
+
description: 'agenticow — walk the COW chain of an .rvf memory file: an ordered list of nodes (role working|checkpoint|base, id, label, parent, createdAt, mutations, tombstones). Use when you need branch history — to find checkpoint ids for a targeted rollback, or to debug a promote. Guessing the chain from filenames is wrong — lineage is the authoritative structure the store maintains.',
|
|
209
|
+
category: 'memory',
|
|
210
|
+
tags: ['agenticow', 'memory', 'cow', 'lineage', 'history'],
|
|
211
|
+
inputSchema: {
|
|
212
|
+
type: 'object',
|
|
213
|
+
properties: {
|
|
214
|
+
path: { type: 'string', description: 'Path to .rvf memory file' },
|
|
215
|
+
},
|
|
216
|
+
required: ['path'],
|
|
217
|
+
},
|
|
218
|
+
handler: async (input) => {
|
|
219
|
+
const api = await loadAgenticow();
|
|
220
|
+
if (!api)
|
|
221
|
+
return degradedResult('agenticow-not-found');
|
|
222
|
+
const path = resolveMemoryPath(String(input.path));
|
|
223
|
+
const mem = await openWithLineage(api, path);
|
|
224
|
+
try {
|
|
225
|
+
const lineage = await mem.lineage();
|
|
226
|
+
return { success: true, path, lineage };
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
await mem.close?.();
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
name: 'agenticow_status',
|
|
235
|
+
description: 'agenticow — health/geometry of an .rvf memory file: {totalVectors, totalSegments, fileSize, currentEpoch, deadSpaceRatio, readOnly, chainDepth, dimension, metric}. Use when you need to check vector count before/after ingest, spot compaction pressure (deadSpaceRatio), or confirm the dimension before ingesting into a shared base. Pure read.',
|
|
236
|
+
category: 'memory',
|
|
237
|
+
tags: ['agenticow', 'memory', 'cow', 'status'],
|
|
238
|
+
inputSchema: {
|
|
239
|
+
type: 'object',
|
|
240
|
+
properties: {
|
|
241
|
+
path: { type: 'string', description: 'Path to .rvf memory file' },
|
|
242
|
+
},
|
|
243
|
+
required: ['path'],
|
|
244
|
+
},
|
|
245
|
+
handler: async (input) => {
|
|
246
|
+
const api = await loadAgenticow();
|
|
247
|
+
if (!api)
|
|
248
|
+
return degradedResult('agenticow-not-found');
|
|
249
|
+
const path = resolveMemoryPath(String(input.path));
|
|
250
|
+
const mem = await openWithLineage(api, path);
|
|
251
|
+
try {
|
|
252
|
+
const status = await mem.status();
|
|
253
|
+
return { success: true, path, status };
|
|
254
|
+
}
|
|
255
|
+
finally {
|
|
256
|
+
await mem.close?.();
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
},
|
|
155
260
|
{
|
|
156
261
|
name: 'agenticow_checkpoint',
|
|
157
262
|
description: 'agenticow — freeze a labelled restore point on an .rvf memory file. Subsequent edits stay in a fresh COW child; rollback returns here. Use when you are about to run an experimental Darwin tick or speculative agent edit that may need to be discarded. Relying on the working node alone is wrong because there is no "undo last N writes" semantics — without a checkpoint, a bad ingest contaminates the base. Persists via .agenticow.json lineage manifest so it survives close+reopen.',
|
|
@@ -191,6 +296,7 @@ export const agenticowTools = [
|
|
|
191
296
|
type: 'object',
|
|
192
297
|
properties: {
|
|
193
298
|
path: { type: 'string', description: 'Path to .rvf memory file' },
|
|
299
|
+
checkpointId: { type: 'string', description: 'Target checkpoint id from agenticow_lineage (omit to roll back to the most recent checkpoint)' },
|
|
194
300
|
},
|
|
195
301
|
required: ['path'],
|
|
196
302
|
},
|
|
@@ -199,9 +305,10 @@ export const agenticowTools = [
|
|
|
199
305
|
if (!api)
|
|
200
306
|
return degradedResult('agenticow-not-found');
|
|
201
307
|
const path = resolveMemoryPath(String(input.path));
|
|
308
|
+
const checkpointId = input.checkpointId ? String(input.checkpointId) : undefined;
|
|
202
309
|
const mem = await openWithLineage(api, path);
|
|
203
310
|
try {
|
|
204
|
-
const r = await mem.rollback();
|
|
311
|
+
const r = checkpointId ? await mem.rollback(checkpointId) : await mem.rollback();
|
|
205
312
|
await mem.save?.(manifestFor(path));
|
|
206
313
|
return { success: true, path, rolledBack: true, result: r };
|
|
207
314
|
}
|
|
@@ -27,6 +27,7 @@ export { autopilotTools } from './autopilot-tools.js';
|
|
|
27
27
|
export { metaharnessTools } from './metaharness-tools.js';
|
|
28
28
|
export { testgenTools } from './testgen-tools.js';
|
|
29
29
|
export { agenticowTools } from './agenticow-tools.js';
|
|
30
|
+
export { agenticowSpeculateTools } from './agenticow-speculate-tools.js';
|
|
30
31
|
export { agentbbsTools } from './agentbbs-tools.js';
|
|
31
32
|
export { businessPodTools } from './business-pod-tools.js';
|
|
32
33
|
export { httpFetchTools } from './http-fetch-tools.js';
|
|
@@ -29,6 +29,8 @@ export { metaharnessTools } from './metaharness-tools.js';
|
|
|
29
29
|
export { testgenTools } from './testgen-tools.js';
|
|
30
30
|
// agenticow@~0.2.3 — Copy-On-Write memory branching (162-byte branches)
|
|
31
31
|
export { agenticowTools } from './agenticow-tools.js';
|
|
32
|
+
// agenticow step 4 — speculative branch-and-promote (A/B memory exploration)
|
|
33
|
+
export { agenticowSpeculateTools } from './agenticow-speculate-tools.js';
|
|
32
34
|
// ADR-164 — AgentBBS federated business-domain BBS rooms (Phase 1)
|
|
33
35
|
export { agentbbsTools } from './agentbbs-tools.js';
|
|
34
36
|
// ADR-164 Phase 2 — Business-pod template validation
|