claude-flow 3.62.0 → 3.63.1
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/.claude/.proven-config-version +1 -0
- package/.claude/helpers/.helpers-version +1 -0
- package/.claude/helpers/helpers.manifest.json +13 -0
- package/.claude/proven-config.json +42 -0
- package/.claude/settings.local.json +8 -0
- package/.claude-plugin/marketplace.json +3 -3
- package/README.md +2 -2
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/node_modules/@claude-flow/security/dist/input-validator.d.ts +6 -6
- package/package.json +1 -1
- package/v3/@claude-flow/cli/README.md +2 -2
- package/v3/@claude-flow/cli/catalog-manifest.json +3 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentbbs-tools.js +7 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/managed-agent-tools.d.ts +1 -20
- package/v3/@claude-flow/cli/dist/src/mcp-tools/managed-agent-tools.js +347 -108
- package/v3/@claude-flow/cli/dist/src/mcp-tools/managed-learning-tool.d.ts +4 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/managed-learning-tool.js +46 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +6 -3
- package/v3/@claude-flow/cli/dist/src/missions/schemas.d.ts +12 -12
- package/v3/@claude-flow/cli/dist/src/ruvector/diskann-backend.d.ts +78 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/diskann-backend.js +310 -0
- package/v3/@claude-flow/cli/dist/src/services/flywheel-transaction.js +5 -0
- package/v3/@claude-flow/cli/dist/src/services/managed-learning-loop.d.ts +79 -0
- package/v3/@claude-flow/cli/dist/src/services/managed-learning-loop.js +76 -0
- package/v3/@claude-flow/cli/dist/src/services/managed-learning-store.d.ts +187 -0
- package/v3/@claude-flow/cli/dist/src/services/managed-learning-store.js +238 -0
- package/v3/@claude-flow/cli/dist/src/services/managed-learning.d.ts +108 -0
- package/v3/@claude-flow/cli/dist/src/services/managed-learning.js +197 -0
- package/v3/@claude-flow/cli/dist/src/services/managed-memory-client.d.ts +72 -0
- package/v3/@claude-flow/cli/dist/src/services/managed-memory-client.js +236 -0
- package/v3/@claude-flow/cli/dist/src/services/managed-memory-worker.d.ts +56 -0
- package/v3/@claude-flow/cli/dist/src/services/managed-memory-worker.js +161 -0
- package/v3/@claude-flow/cli/dist/src/services/managed-run-collector.d.ts +93 -0
- package/v3/@claude-flow/cli/dist/src/services/managed-run-collector.js +181 -0
- package/v3/@claude-flow/cli/dist/src/services/managed-run-store.d.ts +52 -0
- package/v3/@claude-flow/cli/dist/src/services/managed-run-store.js +119 -0
- package/v3/@claude-flow/cli/dist/src/services/managed-workflows.d.ts +101 -0
- package/v3/@claude-flow/cli/dist/src/services/managed-workflows.js +183 -0
- package/v3/@claude-flow/cli/package.json +1 -1
- package/v3/@claude-flow/shared/dist/events/event-store.test.d.ts +8 -0
- package/v3/@claude-flow/shared/dist/events/event-store.test.js +293 -0
- package/v3/@claude-flow/shared/dist/hooks/hooks.test.d.ts +9 -0
- package/v3/@claude-flow/shared/dist/hooks/hooks.test.js +322 -0
- package/v3/@claude-flow/shared/dist/hooks/verify-exports.test.d.ts +9 -0
- package/v3/@claude-flow/shared/dist/hooks/verify-exports.test.js +93 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** Bounded, replayable feedback cycle. Evaluation is never promotion authority. */
|
|
2
|
+
import { digest, evidenceOf, isVerified, manifestOf, scopeOf, trustedEvidence } from './managed-learning.js';
|
|
3
|
+
import { evaluateLearning, indexLearning, recallLearning, recordLearningBatch, recommendLearning } from './managed-learning-store.js';
|
|
4
|
+
function inputOf(value) {
|
|
5
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
6
|
+
throw new Error('Expected a managed cycle');
|
|
7
|
+
const v = value;
|
|
8
|
+
if (Object.keys(v).some(k => !['scope', 'evidence', 'index', 'maxP95Ms', 'evaluation'].includes(k)) || !Array.isArray(v.evidence) || v.evidence.length > 24)
|
|
9
|
+
throw new Error('Managed cycle accepts at most 24 signed outcomes');
|
|
10
|
+
if (v.index !== undefined && typeof v.index !== 'boolean')
|
|
11
|
+
throw new Error('Invalid indexing request');
|
|
12
|
+
if (v.maxP95Ms !== undefined && (typeof v.maxP95Ms !== 'number' || !Number.isFinite(v.maxP95Ms) || v.maxP95Ms <= 0 || v.maxP95Ms > 86_400_000))
|
|
13
|
+
throw new Error('Invalid latency constraint');
|
|
14
|
+
if (v.evaluation !== undefined) {
|
|
15
|
+
const e = v.evaluation;
|
|
16
|
+
if (!e || typeof e !== 'object' || Array.isArray(e) || Object.keys(e).length !== 3 || Object.keys(e).some(k => !['manifest', 'baselineId', 'candidateId'].includes(k)) || typeof e.baselineId !== 'string' || typeof e.candidateId !== 'string' || !/^[a-z][a-z0-9-]{0,39}$/.test(e.baselineId) || !/^[a-z][a-z0-9-]{0,39}$/.test(e.candidateId) || e.baselineId === e.candidateId)
|
|
17
|
+
throw new Error('Invalid evaluation request');
|
|
18
|
+
manifestOf(e.manifest);
|
|
19
|
+
}
|
|
20
|
+
scopeOf(v.scope);
|
|
21
|
+
return structuredClone(v);
|
|
22
|
+
}
|
|
23
|
+
/** authorize is host-owned and rechecked across asynchronous boundaries. */
|
|
24
|
+
export async function cycleLearning(root, value, publicKey, authorize) {
|
|
25
|
+
const started = performance.now(), input = inputOf(value), scope = scopeOf(input.scope);
|
|
26
|
+
const rows = input.evidence.map(evidenceOf);
|
|
27
|
+
const check = () => { if (!authorize() || !publicKey)
|
|
28
|
+
throw new Error('Host learning policy or verifier changed'); };
|
|
29
|
+
check();
|
|
30
|
+
// Reject the whole batch before any write; unsigned input cannot start a cycle.
|
|
31
|
+
for (const e of rows)
|
|
32
|
+
if (!isVerified(e, publicKey) || e.observation.costCents === null || e.observation.durationMs === null || e.observation.taskType !== scope.taskType || e.observation.policyHash !== scope.policyHash || e.observation.evaluatorHash !== scope.evaluatorHash)
|
|
33
|
+
throw new Error('Every cycle outcome must be authenticated, measured and in scope');
|
|
34
|
+
const trusted = trustedEvidence(rows, publicKey).sort((a, b) => digest(a.observation).localeCompare(digest(b.observation)));
|
|
35
|
+
if (input.index && trusted.filter(e => e.observation.split === 'train').length > 6)
|
|
36
|
+
throw new Error('Indexing cycles accept at most six training outcomes; split the batch');
|
|
37
|
+
const cycleId = digest({ scope, evidence: trusted.map(e => digest(e.observation)).sort(), evaluation: input.evaluation ?? null, index: input.index ?? false, maxP95Ms: input.maxP95Ms ?? null });
|
|
38
|
+
const recorded = [], duplicates = [], indexed = [], deferred = [];
|
|
39
|
+
const phases = ['admitted'];
|
|
40
|
+
// One atomic batch avoids per-row sync/summary work. Later native failures
|
|
41
|
+
// leave a resumable signed batch, never a claimed indexed outcome.
|
|
42
|
+
check();
|
|
43
|
+
for (const r of trusted.length ? recordLearningBatch(root, trusted, publicKey) : []) {
|
|
44
|
+
(r.duplicate ? duplicates : recorded).push(r.id);
|
|
45
|
+
}
|
|
46
|
+
phases.push('recorded');
|
|
47
|
+
let memory;
|
|
48
|
+
if (input.index) {
|
|
49
|
+
const training = trusted.filter(e => e.observation.split === 'train');
|
|
50
|
+
for (const e of training.slice(0, 6)) {
|
|
51
|
+
check();
|
|
52
|
+
const r = await indexLearning(root, digest(e.observation), publicKey, authorize);
|
|
53
|
+
check();
|
|
54
|
+
if (r.indexed)
|
|
55
|
+
indexed.push(r.id);
|
|
56
|
+
else
|
|
57
|
+
deferred.push(r.id);
|
|
58
|
+
}
|
|
59
|
+
phases.push('indexed');
|
|
60
|
+
check();
|
|
61
|
+
memory = await recallLearning(root, scope, publicKey, authorize);
|
|
62
|
+
check();
|
|
63
|
+
phases.push('recalled');
|
|
64
|
+
}
|
|
65
|
+
check();
|
|
66
|
+
const recommendation = recommendLearning(root, scope, publicKey, input.maxP95Ms);
|
|
67
|
+
phases.push('recommended');
|
|
68
|
+
check();
|
|
69
|
+
const evaluation = input.evaluation ? evaluateLearning(root, { scope, ...input.evaluation }, publicKey) : null;
|
|
70
|
+
if (evaluation)
|
|
71
|
+
phases.push('evaluated');
|
|
72
|
+
return { cycleId, phases, recorded, duplicates, indexed, deferred, recommendation, evaluation,
|
|
73
|
+
memory: memory ? { available: memory.available, recalled: memory.evidence.length, backend: memory.backend } : null,
|
|
74
|
+
elapsedMs: performance.now() - started, advisory: true, defaultsChanged: false, promotionRequested: false };
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=managed-learning-loop.js.map
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { type Evidence } from './managed-learning.js';
|
|
2
|
+
export declare const LEARNING_PATH = ".claude-flow/managed-learning/state.json";
|
|
3
|
+
export declare function learningStatus(root: string, publicKey?: string): {
|
|
4
|
+
version: number;
|
|
5
|
+
updatedMs: number;
|
|
6
|
+
observations: number;
|
|
7
|
+
verified: number;
|
|
8
|
+
quarantined: number;
|
|
9
|
+
training: number;
|
|
10
|
+
holdout: number;
|
|
11
|
+
verifierConfigured: boolean;
|
|
12
|
+
lastRecommendation: {
|
|
13
|
+
selected: {
|
|
14
|
+
evidenceIds: string[];
|
|
15
|
+
strategy: {
|
|
16
|
+
id: string;
|
|
17
|
+
model: string;
|
|
18
|
+
workers: number;
|
|
19
|
+
template: string;
|
|
20
|
+
};
|
|
21
|
+
samples: number;
|
|
22
|
+
successes: number;
|
|
23
|
+
successRate: number;
|
|
24
|
+
successesPerDollar: number;
|
|
25
|
+
p95Ms: number;
|
|
26
|
+
} | null;
|
|
27
|
+
candidates: {
|
|
28
|
+
evidenceIds: string[];
|
|
29
|
+
strategy: {
|
|
30
|
+
id: string;
|
|
31
|
+
model: string;
|
|
32
|
+
workers: number;
|
|
33
|
+
template: string;
|
|
34
|
+
};
|
|
35
|
+
samples: number;
|
|
36
|
+
successes: number;
|
|
37
|
+
successRate: number;
|
|
38
|
+
successesPerDollar: number;
|
|
39
|
+
p95Ms: number;
|
|
40
|
+
}[];
|
|
41
|
+
advisory: boolean;
|
|
42
|
+
abstained: boolean;
|
|
43
|
+
reason: string;
|
|
44
|
+
defaultsChanged: boolean;
|
|
45
|
+
} | null;
|
|
46
|
+
lastEvaluation: {
|
|
47
|
+
evidenceIds: string[];
|
|
48
|
+
advisory: boolean;
|
|
49
|
+
eligible: boolean;
|
|
50
|
+
baselineP95Ms: number;
|
|
51
|
+
candidateP95Ms: number;
|
|
52
|
+
delta: number;
|
|
53
|
+
baselineEfficiency: number;
|
|
54
|
+
candidateEfficiency: number;
|
|
55
|
+
pairedResults: number;
|
|
56
|
+
corpusHash: string;
|
|
57
|
+
noRegression: boolean;
|
|
58
|
+
metaharness: import("./harness-benchmark.js").AcceptResult;
|
|
59
|
+
defaultsChanged: boolean;
|
|
60
|
+
} | null;
|
|
61
|
+
advisory: boolean;
|
|
62
|
+
defaultsChanged: boolean;
|
|
63
|
+
};
|
|
64
|
+
/** Validate all input before acquiring the writer lock; publish the batch atomically. */
|
|
65
|
+
export declare function recordLearningBatch(root: string, inputs: unknown[], publicKey?: string): {
|
|
66
|
+
stored: boolean;
|
|
67
|
+
duplicate: boolean;
|
|
68
|
+
id: string;
|
|
69
|
+
verified: boolean;
|
|
70
|
+
}[];
|
|
71
|
+
export declare function recordLearning(root: string, input: unknown, publicKey?: string): {
|
|
72
|
+
stored: boolean;
|
|
73
|
+
duplicate: boolean;
|
|
74
|
+
id: string;
|
|
75
|
+
verified: boolean;
|
|
76
|
+
};
|
|
77
|
+
export declare function recommendLearning(root: string, scope: unknown, publicKey?: string, maxP95Ms?: number): {
|
|
78
|
+
advisory: boolean;
|
|
79
|
+
selected: {
|
|
80
|
+
strategy: {
|
|
81
|
+
id: string;
|
|
82
|
+
model: string;
|
|
83
|
+
workers: number;
|
|
84
|
+
template: string;
|
|
85
|
+
};
|
|
86
|
+
samples: number;
|
|
87
|
+
successes: number;
|
|
88
|
+
successRate: number;
|
|
89
|
+
successesPerDollar: number;
|
|
90
|
+
p95Ms: number;
|
|
91
|
+
evidenceIds: string[];
|
|
92
|
+
};
|
|
93
|
+
candidates: {
|
|
94
|
+
strategy: {
|
|
95
|
+
id: string;
|
|
96
|
+
model: string;
|
|
97
|
+
workers: number;
|
|
98
|
+
template: string;
|
|
99
|
+
};
|
|
100
|
+
samples: number;
|
|
101
|
+
successes: number;
|
|
102
|
+
successRate: number;
|
|
103
|
+
successesPerDollar: number;
|
|
104
|
+
p95Ms: number;
|
|
105
|
+
evidenceIds: string[];
|
|
106
|
+
}[];
|
|
107
|
+
abstained: boolean;
|
|
108
|
+
reason: string;
|
|
109
|
+
defaultsChanged: boolean;
|
|
110
|
+
};
|
|
111
|
+
export declare function evaluateLearning(root: string, input: {
|
|
112
|
+
scope: unknown;
|
|
113
|
+
manifest: unknown;
|
|
114
|
+
baselineId: string;
|
|
115
|
+
candidateId: string;
|
|
116
|
+
}, publicKey?: string): {
|
|
117
|
+
advisory: boolean;
|
|
118
|
+
eligible: boolean;
|
|
119
|
+
baselineP95Ms: number;
|
|
120
|
+
candidateP95Ms: number;
|
|
121
|
+
delta: number;
|
|
122
|
+
baselineEfficiency: number;
|
|
123
|
+
candidateEfficiency: number;
|
|
124
|
+
pairedResults: number;
|
|
125
|
+
corpusHash: string;
|
|
126
|
+
noRegression: boolean;
|
|
127
|
+
metaharness: import("./harness-benchmark.js").AcceptResult;
|
|
128
|
+
defaultsChanged: boolean;
|
|
129
|
+
evidenceIds: string[];
|
|
130
|
+
};
|
|
131
|
+
/** Export only verified training evidence to native append-only AgentDB/RuVector storage. */
|
|
132
|
+
export declare function indexLearning(root: string, id: string, publicKey?: string, authorize?: () => boolean): Promise<{
|
|
133
|
+
indexed: boolean;
|
|
134
|
+
durable: boolean;
|
|
135
|
+
namespace: string;
|
|
136
|
+
id: string;
|
|
137
|
+
hasEmbedding: boolean;
|
|
138
|
+
advisory: boolean;
|
|
139
|
+
}>;
|
|
140
|
+
/** Memory similarity supplies recall evidence, never authority or a cherry-picked quality denominator. */
|
|
141
|
+
export declare function recallLearning(root: string, input: unknown, publicKey?: string, authorize?: () => boolean): Promise<{
|
|
142
|
+
available: boolean;
|
|
143
|
+
backend: string;
|
|
144
|
+
evidence: {
|
|
145
|
+
id: string;
|
|
146
|
+
similarity: number;
|
|
147
|
+
strategy: Evidence["observation"]["strategy"];
|
|
148
|
+
sourceHash: string;
|
|
149
|
+
}[];
|
|
150
|
+
recommendation: {
|
|
151
|
+
advisory: boolean;
|
|
152
|
+
selected: {
|
|
153
|
+
strategy: {
|
|
154
|
+
id: string;
|
|
155
|
+
model: string;
|
|
156
|
+
workers: number;
|
|
157
|
+
template: string;
|
|
158
|
+
};
|
|
159
|
+
samples: number;
|
|
160
|
+
successes: number;
|
|
161
|
+
successRate: number;
|
|
162
|
+
successesPerDollar: number;
|
|
163
|
+
p95Ms: number;
|
|
164
|
+
evidenceIds: string[];
|
|
165
|
+
};
|
|
166
|
+
candidates: {
|
|
167
|
+
strategy: {
|
|
168
|
+
id: string;
|
|
169
|
+
model: string;
|
|
170
|
+
workers: number;
|
|
171
|
+
template: string;
|
|
172
|
+
};
|
|
173
|
+
samples: number;
|
|
174
|
+
successes: number;
|
|
175
|
+
successRate: number;
|
|
176
|
+
successesPerDollar: number;
|
|
177
|
+
p95Ms: number;
|
|
178
|
+
evidenceIds: string[];
|
|
179
|
+
}[];
|
|
180
|
+
abstained: boolean;
|
|
181
|
+
reason: string;
|
|
182
|
+
defaultsChanged: boolean;
|
|
183
|
+
};
|
|
184
|
+
advisory: boolean;
|
|
185
|
+
similarityIsVerification: boolean;
|
|
186
|
+
}>;
|
|
187
|
+
//# sourceMappingURL=managed-learning-store.d.ts.map
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/** Project-local bounded evidence ledger. Local files are revalidated, never trusted by label. */
|
|
2
|
+
import { constants, closeSync, existsSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { nativeMemoryIndex, nativeMemoryRecall } from './managed-memory-client.js';
|
|
6
|
+
import { digest, evidenceOf, evaluate, isVerified, manifestOf, recommend, scopeOf, trustedEvidence } from './managed-learning.js';
|
|
7
|
+
export const LEARNING_PATH = '.claude-flow/managed-learning/state.json';
|
|
8
|
+
const MAX_BYTES = 2_000_000;
|
|
9
|
+
function directory(root, create = false) {
|
|
10
|
+
let dir = resolve(root);
|
|
11
|
+
if (!lstatSync(dir).isDirectory())
|
|
12
|
+
throw new Error('Learning root must be a directory');
|
|
13
|
+
for (const part of ['.claude-flow', 'managed-learning']) {
|
|
14
|
+
dir = join(dir, part);
|
|
15
|
+
if (!existsSync(dir)) {
|
|
16
|
+
if (create)
|
|
17
|
+
mkdirSync(dir, { mode: 0o700 });
|
|
18
|
+
else
|
|
19
|
+
return join(resolve(root), '.claude-flow', 'managed-learning');
|
|
20
|
+
}
|
|
21
|
+
if (existsSync(dir) && (!lstatSync(dir).isDirectory() || lstatSync(dir).isSymbolicLink()))
|
|
22
|
+
throw new Error('Learning directory must not be a symlink');
|
|
23
|
+
}
|
|
24
|
+
return dir;
|
|
25
|
+
}
|
|
26
|
+
function read(dir) {
|
|
27
|
+
const path = join(dir, 'state.json');
|
|
28
|
+
if (!existsSync(path))
|
|
29
|
+
return { version: 1, records: [], updatedMs: 0 };
|
|
30
|
+
if (!lstatSync(path).isFile() || lstatSync(path).isSymbolicLink())
|
|
31
|
+
throw new Error('Learning state must be a regular file');
|
|
32
|
+
const fd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
33
|
+
let text;
|
|
34
|
+
try {
|
|
35
|
+
const stat = fstatSync(fd);
|
|
36
|
+
if (!stat.isFile() || stat.size > MAX_BYTES)
|
|
37
|
+
throw new Error('Learning state exceeds read limit');
|
|
38
|
+
// Fixed buffer also bounds a file that grows after fstat.
|
|
39
|
+
const buffer = Buffer.alloc(MAX_BYTES + 1);
|
|
40
|
+
const size = readSync(fd, buffer, 0, buffer.length, 0);
|
|
41
|
+
if (size > MAX_BYTES)
|
|
42
|
+
throw new Error('Learning state exceeds read limit');
|
|
43
|
+
text = buffer.subarray(0, size).toString('utf8');
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
closeSync(fd);
|
|
47
|
+
}
|
|
48
|
+
const value = JSON.parse(text);
|
|
49
|
+
if (value.version !== 1 || !Array.isArray(value.records) || value.records.length > 500 || !Number.isFinite(value.updatedMs))
|
|
50
|
+
throw new Error('Invalid learning state');
|
|
51
|
+
value.records = value.records.map(evidenceOf);
|
|
52
|
+
// Summaries are display-only; never rehydrate them as routing or promotion authority.
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
function syncDirectory(dir) { const fd = openSync(dir, constants.O_RDONLY); try {
|
|
56
|
+
fsyncSync(fd);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
closeSync(fd);
|
|
60
|
+
} }
|
|
61
|
+
function mutate(root, action, publicKey) {
|
|
62
|
+
const dir = directory(root, true);
|
|
63
|
+
const lockPath = join(dir, 'writer.lock');
|
|
64
|
+
const lock = openSync(lockPath, 'wx', 0o600);
|
|
65
|
+
const temporary = join(dir, `${randomUUID()}.tmp`);
|
|
66
|
+
try {
|
|
67
|
+
syncDirectory(dir); // Refuse unsupported durability semantics before changing the ledger.
|
|
68
|
+
const state = read(dir), result = action(state);
|
|
69
|
+
state.updatedMs = Date.now();
|
|
70
|
+
const payload = JSON.stringify(state);
|
|
71
|
+
if (Buffer.byteLength(payload) > MAX_BYTES)
|
|
72
|
+
throw new Error('Learning ledger is full');
|
|
73
|
+
const fd = openSync(temporary, 'wx', 0o600);
|
|
74
|
+
try {
|
|
75
|
+
writeFileSync(fd, payload);
|
|
76
|
+
fsyncSync(fd);
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
closeSync(fd);
|
|
80
|
+
}
|
|
81
|
+
renameSync(temporary, join(dir, 'state.json'));
|
|
82
|
+
syncDirectory(dir);
|
|
83
|
+
// The console reads only this bounded advisory projection, never the full evidence ledger.
|
|
84
|
+
const summary = summaryOf(state, publicKey);
|
|
85
|
+
const summaryText = JSON.stringify(summary);
|
|
86
|
+
if (Buffer.byteLength(summaryText) > 64_000)
|
|
87
|
+
throw new Error('Learning summary limit exceeded');
|
|
88
|
+
const summaryFd = openSync(temporary, 'wx', 0o600);
|
|
89
|
+
try {
|
|
90
|
+
writeFileSync(summaryFd, summaryText);
|
|
91
|
+
fsyncSync(summaryFd);
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
closeSync(summaryFd);
|
|
95
|
+
}
|
|
96
|
+
renameSync(temporary, join(dir, 'summary.json'));
|
|
97
|
+
syncDirectory(dir);
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
if (existsSync(temporary))
|
|
102
|
+
unlinkSync(temporary);
|
|
103
|
+
closeSync(lock);
|
|
104
|
+
unlinkSync(lockPath);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function summaryOf(state, publicKey) {
|
|
108
|
+
const verified = trustedEvidence(state.records, publicKey);
|
|
109
|
+
let last = null;
|
|
110
|
+
let evaluation = null;
|
|
111
|
+
try {
|
|
112
|
+
if (state.recommendationInput)
|
|
113
|
+
last = recommend(verified, scopeOf(state.recommendationInput.scope), state.recommendationInput.maxP95Ms);
|
|
114
|
+
}
|
|
115
|
+
catch { /* invalidated advice */ }
|
|
116
|
+
try {
|
|
117
|
+
if (state.evaluationInput) {
|
|
118
|
+
const e = state.evaluationInput;
|
|
119
|
+
evaluation = evaluate(verified, scopeOf(e.scope), manifestOf(e.manifest), e.baselineId, e.candidateId);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch { /* invalidated confirmation */ }
|
|
123
|
+
const compact = (r) => ({ ...r, evidenceIds: r.evidenceIds.slice(0, 6) });
|
|
124
|
+
return { version: 1, updatedMs: state.updatedMs, observations: state.records.length, verified: verified.length, quarantined: state.records.length - verified.length, training: verified.filter(e => e.observation.split === 'train').length, holdout: verified.filter(e => e.observation.split === 'holdout').length, verifierConfigured: !!publicKey,
|
|
125
|
+
lastRecommendation: last ? { ...last, selected: last.selected ? compact(last.selected) : null, candidates: last.candidates.slice(0, 6).map(compact) } : null,
|
|
126
|
+
lastEvaluation: evaluation ? { ...evaluation, evidenceIds: evaluation.evidenceIds.slice(0, 6) } : null,
|
|
127
|
+
advisory: true, defaultsChanged: false };
|
|
128
|
+
}
|
|
129
|
+
export function learningStatus(root, publicKey) { return summaryOf(read(directory(root)), publicKey); }
|
|
130
|
+
function applyRecord(state, evidence, publicKey) {
|
|
131
|
+
const verified = isVerified(evidence, publicKey);
|
|
132
|
+
const previous = state.records.find(e => e.observation.attemptId === evidence.observation.attemptId && isVerified(e, publicKey));
|
|
133
|
+
if (previous && verified && digest(previous.observation) !== digest(evidence.observation))
|
|
134
|
+
throw new Error('Conflicting learning attempt; immutable verified observations cannot be overwritten');
|
|
135
|
+
const duplicate = state.records.find(e => digest(e) === digest(evidence));
|
|
136
|
+
if (duplicate)
|
|
137
|
+
return { stored: false, duplicate: true, id: digest(evidence.observation), verified };
|
|
138
|
+
const unsigned = state.records.find(e => !e.signature && digest(e.observation) === digest(evidence.observation));
|
|
139
|
+
if (unsigned && verified) {
|
|
140
|
+
unsigned.signature = evidence.signature;
|
|
141
|
+
return { stored: true, duplicate: false, id: digest(evidence.observation), verified: true };
|
|
142
|
+
}
|
|
143
|
+
if (!verified && state.records.filter(e => !isVerified(e, publicKey)).length >= 100)
|
|
144
|
+
throw new Error('Learning quarantine is full');
|
|
145
|
+
if (state.records.length >= 500)
|
|
146
|
+
throw new Error('Learning ledger is full');
|
|
147
|
+
state.records.push(evidence);
|
|
148
|
+
return { stored: true, duplicate: false, id: digest(evidence.observation), verified };
|
|
149
|
+
}
|
|
150
|
+
/** Validate all input before acquiring the writer lock; publish the batch atomically. */
|
|
151
|
+
export function recordLearningBatch(root, inputs, publicKey) {
|
|
152
|
+
if (!Array.isArray(inputs) || inputs.length < 1 || inputs.length > 24)
|
|
153
|
+
throw new Error('Learning batch must contain 1 to 24 records');
|
|
154
|
+
const evidence = inputs.map(evidenceOf);
|
|
155
|
+
if (evidence.some(e => e.signature && !isVerified(e, publicKey)))
|
|
156
|
+
throw new Error('Untrusted or mismatched verifier receipt');
|
|
157
|
+
return mutate(root, state => evidence.map(e => applyRecord(state, e, publicKey)), publicKey);
|
|
158
|
+
}
|
|
159
|
+
export function recordLearning(root, input, publicKey) {
|
|
160
|
+
return recordLearningBatch(root, [input], publicKey)[0];
|
|
161
|
+
}
|
|
162
|
+
export function recommendLearning(root, scope, publicKey, maxP95Ms) {
|
|
163
|
+
return mutate(root, state => {
|
|
164
|
+
const result = recommend(trustedEvidence(state.records, publicKey), scopeOf(scope), maxP95Ms);
|
|
165
|
+
state.recommendationInput = { scope: scopeOf(scope), maxP95Ms };
|
|
166
|
+
state.lastRecommendation = result;
|
|
167
|
+
return result;
|
|
168
|
+
}, publicKey);
|
|
169
|
+
}
|
|
170
|
+
export function evaluateLearning(root, input, publicKey) {
|
|
171
|
+
return mutate(root, state => {
|
|
172
|
+
const result = evaluate(trustedEvidence(state.records, publicKey), scopeOf(input.scope), manifestOf(input.manifest), input.baselineId, input.candidateId);
|
|
173
|
+
const cohort = result.corpusHash, experiment = digest({ scope: scopeOf(input.scope), baselineId: input.baselineId, candidateId: input.candidateId });
|
|
174
|
+
if (state.confirmations?.[cohort] && state.confirmations[cohort] !== experiment)
|
|
175
|
+
throw new Error('Heldout corpus is already bound to another confirmation experiment');
|
|
176
|
+
state.confirmations = { ...(state.confirmations ?? {}), [cohort]: experiment };
|
|
177
|
+
state.evaluationInput = { ...input, scope: scopeOf(input.scope), manifest: manifestOf(input.manifest) };
|
|
178
|
+
state.lastEvaluation = result;
|
|
179
|
+
return result;
|
|
180
|
+
}, publicKey);
|
|
181
|
+
}
|
|
182
|
+
/** Export only verified training evidence to native append-only AgentDB/RuVector storage. */
|
|
183
|
+
export async function indexLearning(root, id, publicKey, authorize = () => true) {
|
|
184
|
+
if (!/^[a-f0-9]{64}$/.test(id))
|
|
185
|
+
throw new Error('Invalid evidence ID');
|
|
186
|
+
const evidence = trustedEvidence(read(directory(root)).records, publicKey).find(e => digest(e.observation) === id && e.observation.split === 'train');
|
|
187
|
+
if (!evidence)
|
|
188
|
+
throw new Error('Verified training evidence required for indexing');
|
|
189
|
+
if (!authorize())
|
|
190
|
+
throw new Error('Managed memory policy denied');
|
|
191
|
+
const result = await nativeMemoryIndex(root, { id, value: JSON.stringify(evidence), taskType: evidence.observation.taskType }, authorize);
|
|
192
|
+
if (!authorize())
|
|
193
|
+
throw new Error('Managed memory policy changed; admitted write may have completed');
|
|
194
|
+
if (!result.durable)
|
|
195
|
+
throw new Error('Native evidence index did not confirm durable persistence');
|
|
196
|
+
return { indexed: result.hasEmbedding, durable: true, namespace: 'managed-workflow-evidence', id, hasEmbedding: result.hasEmbedding, advisory: true };
|
|
197
|
+
}
|
|
198
|
+
/** Memory similarity supplies recall evidence, never authority or a cherry-picked quality denominator. */
|
|
199
|
+
export async function recallLearning(root, input, publicKey, authorize = () => true) {
|
|
200
|
+
const scope = scopeOf(input);
|
|
201
|
+
const local = trustedEvidence(read(directory(root)).records, publicKey).filter(e => e.observation.split === 'train' && e.observation.taskType === scope.taskType && e.observation.policyHash === scope.policyHash && e.observation.evaluatorHash === scope.evaluatorHash);
|
|
202
|
+
const allowed = new Set(local.map(e => digest(e.observation)));
|
|
203
|
+
let result;
|
|
204
|
+
if (!authorize())
|
|
205
|
+
throw new Error('Managed memory policy denied');
|
|
206
|
+
try {
|
|
207
|
+
result = await nativeMemoryRecall(root, { taskType: scope.taskType, allowedIds: [...allowed] }, authorize);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
if (!authorize())
|
|
211
|
+
throw new Error('Managed memory policy denied');
|
|
212
|
+
return { available: false, backend: 'ruflo-memory-child', evidence: [], recommendation: recommend(local, scope), advisory: true, similarityIsVerification: false };
|
|
213
|
+
}
|
|
214
|
+
if (!authorize())
|
|
215
|
+
throw new Error('Managed memory policy denied');
|
|
216
|
+
if (!result.available)
|
|
217
|
+
return { available: false, backend: 'ruflo-memory-child', evidence: [], recommendation: recommend(local, scope), advisory: true, similarityIsVerification: false };
|
|
218
|
+
const seen = new Set();
|
|
219
|
+
const evidence = [];
|
|
220
|
+
for (const hit of result.rows.slice(0, 20)) {
|
|
221
|
+
if (hit.namespace !== 'managed-workflow-evidence' || !allowed.has(hit.key) || seen.has(hit.key) || !Number.isFinite(hit.score))
|
|
222
|
+
continue;
|
|
223
|
+
seen.add(hit.key);
|
|
224
|
+
try {
|
|
225
|
+
// The child hydrates exact native rows, not display previews. The MCP
|
|
226
|
+
// parent still authenticates every receipt and joins the local cohort.
|
|
227
|
+
if (typeof hit.content !== 'string' || Buffer.byteLength(hit.content) > 8192)
|
|
228
|
+
continue;
|
|
229
|
+
const receipt = evidenceOf(JSON.parse(hit.content));
|
|
230
|
+
if (!isVerified(receipt, publicKey) || digest(receipt.observation) !== hit.key)
|
|
231
|
+
continue;
|
|
232
|
+
evidence.push({ id: hit.key, similarity: hit.score, strategy: receipt.observation.strategy, sourceHash: receipt.observation.sourceHash });
|
|
233
|
+
}
|
|
234
|
+
catch { /* A missing or malformed row is not verified recall evidence. */ }
|
|
235
|
+
}
|
|
236
|
+
return { available: true, backend: 'ruflo-memory-child', evidence, recommendation: recommend(local, scope), advisory: true, similarityIsVerification: false };
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=managed-learning-store.js.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export declare const TASK_TYPES: readonly ["code-review", "migration", "research", "test-generation", "implementation", "other"];
|
|
2
|
+
export type Observation = {
|
|
3
|
+
v: 1;
|
|
4
|
+
attemptId: string;
|
|
5
|
+
taskHash: string;
|
|
6
|
+
sourceHash: string;
|
|
7
|
+
artifactHash: string;
|
|
8
|
+
policyHash: string;
|
|
9
|
+
corpusHash: string;
|
|
10
|
+
evaluatorHash: string;
|
|
11
|
+
taskType: typeof TASK_TYPES[number];
|
|
12
|
+
split: 'train' | 'holdout';
|
|
13
|
+
trial: number;
|
|
14
|
+
strategy: {
|
|
15
|
+
id: string;
|
|
16
|
+
model: string;
|
|
17
|
+
workers: number;
|
|
18
|
+
template: string;
|
|
19
|
+
};
|
|
20
|
+
budgetCents: number;
|
|
21
|
+
costCents: number | null;
|
|
22
|
+
durationMs: number | null;
|
|
23
|
+
checks: {
|
|
24
|
+
acceptance: boolean;
|
|
25
|
+
safety: boolean;
|
|
26
|
+
replay: boolean;
|
|
27
|
+
adversarial: boolean;
|
|
28
|
+
drift: number;
|
|
29
|
+
driftThreshold: number;
|
|
30
|
+
rollbackRate: number;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
export type Evidence = {
|
|
34
|
+
observation: Observation;
|
|
35
|
+
signature?: string;
|
|
36
|
+
};
|
|
37
|
+
export type Manifest = {
|
|
38
|
+
taskHashes: string[];
|
|
39
|
+
trials: number[];
|
|
40
|
+
};
|
|
41
|
+
export declare function canonical(v: unknown): string;
|
|
42
|
+
export declare const digest: (v: unknown) => string;
|
|
43
|
+
/** Closed metadata contract: no prompts, arbitrary commands, transcripts or caller trust labels. */
|
|
44
|
+
export declare function observationOf(value: unknown): Observation;
|
|
45
|
+
export declare function evidenceOf(value: unknown): Evidence;
|
|
46
|
+
/** Public key is configured by the host, never accepted from an MCP argument or stored record. */
|
|
47
|
+
export declare function isVerified(evidence: Evidence, publicKey?: string): boolean;
|
|
48
|
+
/** Revalidate every persisted record on each use; a JSON provenance label is not authority. */
|
|
49
|
+
export declare function trustedEvidence(records: unknown[], publicKey?: string): Evidence[];
|
|
50
|
+
export declare function manifestOf(value: unknown): Manifest;
|
|
51
|
+
export type Scope = {
|
|
52
|
+
taskType: Observation['taskType'];
|
|
53
|
+
policyHash: string;
|
|
54
|
+
evaluatorHash: string;
|
|
55
|
+
};
|
|
56
|
+
export declare function scopeOf(value: unknown): Scope;
|
|
57
|
+
/** Training evidence only; holdout records and unsigned observations cannot influence a pick. */
|
|
58
|
+
export declare function recommend(records: Evidence[], scope: Scope, maxP95Ms?: number): {
|
|
59
|
+
advisory: boolean;
|
|
60
|
+
selected: {
|
|
61
|
+
strategy: {
|
|
62
|
+
id: string;
|
|
63
|
+
model: string;
|
|
64
|
+
workers: number;
|
|
65
|
+
template: string;
|
|
66
|
+
};
|
|
67
|
+
samples: number;
|
|
68
|
+
successes: number;
|
|
69
|
+
successRate: number;
|
|
70
|
+
successesPerDollar: number;
|
|
71
|
+
p95Ms: number;
|
|
72
|
+
evidenceIds: string[];
|
|
73
|
+
};
|
|
74
|
+
candidates: {
|
|
75
|
+
strategy: {
|
|
76
|
+
id: string;
|
|
77
|
+
model: string;
|
|
78
|
+
workers: number;
|
|
79
|
+
template: string;
|
|
80
|
+
};
|
|
81
|
+
samples: number;
|
|
82
|
+
successes: number;
|
|
83
|
+
successRate: number;
|
|
84
|
+
successesPerDollar: number;
|
|
85
|
+
p95Ms: number;
|
|
86
|
+
evidenceIds: string[];
|
|
87
|
+
}[];
|
|
88
|
+
abstained: boolean;
|
|
89
|
+
reason: string;
|
|
90
|
+
defaultsChanged: boolean;
|
|
91
|
+
};
|
|
92
|
+
/** Complete paired heldout data, equal admitted budgets, source identity and existing MetaHarness conjunction. */
|
|
93
|
+
export declare function evaluate(records: Evidence[], scope: Scope, manifest: Manifest, baselineId: string, candidateId: string): {
|
|
94
|
+
advisory: boolean;
|
|
95
|
+
eligible: boolean;
|
|
96
|
+
baselineP95Ms: number;
|
|
97
|
+
candidateP95Ms: number;
|
|
98
|
+
delta: number;
|
|
99
|
+
baselineEfficiency: number;
|
|
100
|
+
candidateEfficiency: number;
|
|
101
|
+
pairedResults: number;
|
|
102
|
+
corpusHash: string;
|
|
103
|
+
noRegression: boolean;
|
|
104
|
+
metaharness: import("./harness-benchmark.js").AcceptResult;
|
|
105
|
+
defaultsChanged: boolean;
|
|
106
|
+
evidenceIds: string[];
|
|
107
|
+
};
|
|
108
|
+
//# sourceMappingURL=managed-learning.d.ts.map
|