claude-flow 3.61.1 → 3.63.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 +1 -1
- package/package.json +1 -1
- package/v3/@claude-flow/cli/README.md +1 -1
- package/v3/@claude-flow/cli/catalog-manifest.json +3 -3
- 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 +344 -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/mcp-tools/rure-tools.js +59 -0
- package/v3/@claude-flow/cli/dist/src/missions/schemas.d.ts +12 -12
- package/v3/@claude-flow/cli/dist/src/rure/cuda-cubin.d.ts +7 -0
- package/v3/@claude-flow/cli/dist/src/rure/cuda-cubin.js +17 -2
- package/v3/@claude-flow/cli/dist/src/rure/cuda-occupancy.d.ts +59 -0
- package/v3/@claude-flow/cli/dist/src/rure/cuda-occupancy.js +69 -0
- package/v3/@claude-flow/cli/dist/src/rure/cuda-plan.d.ts +84 -0
- package/v3/@claude-flow/cli/dist/src/rure/cuda-plan.js +197 -0
- package/v3/@claude-flow/cli/dist/src/services/headless-worker-executor.js +56 -19
- 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/dist/src/services/worker-daemon.d.ts +1 -0
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +11 -2
- package/v3/@claude-flow/cli/package.json +1 -1
|
@@ -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
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/** Verified, advisory workflow learning. Caller labels never establish trust. */
|
|
2
|
+
import { createHash, createPublicKey, verify } from 'node:crypto';
|
|
3
|
+
import { accept } from './harness-benchmark.js';
|
|
4
|
+
export const TASK_TYPES = ['code-review', 'migration', 'research', 'test-generation', 'implementation', 'other'];
|
|
5
|
+
const trusted = new WeakSet();
|
|
6
|
+
const HASH = /^[a-f0-9]{64}$/;
|
|
7
|
+
const ID = /^[a-z][a-z0-9-]{0,39}$/;
|
|
8
|
+
const MODEL = /^[a-z][a-z0-9./-]{0,79}$/;
|
|
9
|
+
const object = (v) => {
|
|
10
|
+
if (!v || typeof v !== 'object' || Array.isArray(v))
|
|
11
|
+
throw new Error('Expected an object');
|
|
12
|
+
return v;
|
|
13
|
+
};
|
|
14
|
+
function exact(v, keys) {
|
|
15
|
+
if (Object.keys(v).length !== keys.length || Object.keys(v).some(k => !keys.includes(k)))
|
|
16
|
+
throw new Error('Unknown or missing learning fields');
|
|
17
|
+
}
|
|
18
|
+
const integer = (v, min, max) => typeof v === 'number' && Number.isSafeInteger(v) && v >= min && v <= max;
|
|
19
|
+
const unit = (v) => typeof v === 'number' && Number.isFinite(v) && v >= 0 && v <= 1;
|
|
20
|
+
export function canonical(v) {
|
|
21
|
+
if (Array.isArray(v))
|
|
22
|
+
return '[' + v.map(canonical).join(',') + ']';
|
|
23
|
+
if (v && typeof v === 'object')
|
|
24
|
+
return '{' + Object.keys(v).sort().map(k => JSON.stringify(k) + ':' + canonical(v[k])).join(',') + '}';
|
|
25
|
+
return JSON.stringify(v);
|
|
26
|
+
}
|
|
27
|
+
export const digest = (v) => createHash('sha256').update(canonical(v)).digest('hex');
|
|
28
|
+
/** Closed metadata contract: no prompts, arbitrary commands, transcripts or caller trust labels. */
|
|
29
|
+
export function observationOf(value) {
|
|
30
|
+
const v = object(value);
|
|
31
|
+
exact(v, ['v', 'attemptId', 'taskHash', 'sourceHash', 'artifactHash', 'policyHash', 'corpusHash', 'evaluatorHash', 'taskType', 'split', 'trial', 'strategy', 'budgetCents', 'costCents', 'durationMs', 'checks']);
|
|
32
|
+
if (v.v !== 1 || !TASK_TYPES.includes(v.taskType) || !['train', 'holdout'].includes(v.split) || !integer(v.trial, 0, 99))
|
|
33
|
+
throw new Error('Invalid learning cohort');
|
|
34
|
+
for (const key of ['attemptId', 'taskHash', 'sourceHash', 'artifactHash', 'policyHash', 'corpusHash', 'evaluatorHash'])
|
|
35
|
+
if (typeof v[key] !== 'string' || !HASH.test(v[key]))
|
|
36
|
+
throw new Error('Invalid evidence hash');
|
|
37
|
+
const strategy = object(v.strategy);
|
|
38
|
+
exact(strategy, ['id', 'model', 'workers', 'template']);
|
|
39
|
+
if (!ID.test(String(strategy.id)) || !ID.test(String(strategy.template)) || !MODEL.test(String(strategy.model)) || !integer(strategy.workers, 1, 64))
|
|
40
|
+
throw new Error('Invalid workflow strategy');
|
|
41
|
+
if (!integer(v.budgetCents, 1, 999_999_999) || (v.costCents !== null && !integer(v.costCents, 0, 999_999_999)) || (v.durationMs !== null && !integer(v.durationMs, 0, 86_400_000)))
|
|
42
|
+
throw new Error('Invalid measured cost or duration');
|
|
43
|
+
const checks = object(v.checks);
|
|
44
|
+
exact(checks, ['acceptance', 'safety', 'replay', 'adversarial', 'drift', 'driftThreshold', 'rollbackRate']);
|
|
45
|
+
if (['acceptance', 'safety', 'replay', 'adversarial'].some(k => typeof checks[k] !== 'boolean') || !unit(checks.drift) || !unit(checks.driftThreshold) || !unit(checks.rollbackRate))
|
|
46
|
+
throw new Error('Invalid verification measurements');
|
|
47
|
+
// Detach caller objects, including nested strategy/checks, before hashing or persistence.
|
|
48
|
+
return JSON.parse(JSON.stringify(v));
|
|
49
|
+
}
|
|
50
|
+
export function evidenceOf(value) {
|
|
51
|
+
const v = object(value);
|
|
52
|
+
if (Object.keys(v).some(k => !['observation', 'signature'].includes(k)))
|
|
53
|
+
throw new Error('Unknown evidence fields');
|
|
54
|
+
const observation = observationOf(v.observation);
|
|
55
|
+
// Canonical form only: the last base64 character of a 64-byte signature has 4 unused bits that the decoder ignores, so
|
|
56
|
+
// other spellings of the same bytes would verify yet digest differently and be stored as separate records.
|
|
57
|
+
if (v.signature !== undefined && (typeof v.signature !== 'string' || !/^[A-Za-z0-9+/]{86}==$/.test(v.signature)
|
|
58
|
+
|| Buffer.from(v.signature, 'base64').toString('base64') !== v.signature))
|
|
59
|
+
throw new Error('Invalid verifier signature');
|
|
60
|
+
return { observation, ...(v.signature ? { signature: v.signature } : {}) };
|
|
61
|
+
}
|
|
62
|
+
/** Public key is configured by the host, never accepted from an MCP argument or stored record. */
|
|
63
|
+
export function isVerified(evidence, publicKey) {
|
|
64
|
+
if (!publicKey || !evidence.signature)
|
|
65
|
+
return false;
|
|
66
|
+
try {
|
|
67
|
+
const key = createPublicKey(publicKey);
|
|
68
|
+
return key.asymmetricKeyType === 'ed25519' && verify(null, Buffer.from(canonical(evidence.observation)), key, Buffer.from(evidence.signature, 'base64'));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Revalidate every persisted record on each use; a JSON provenance label is not authority. */
|
|
75
|
+
export function trustedEvidence(records, publicKey) {
|
|
76
|
+
const out = [];
|
|
77
|
+
const ids = new Map();
|
|
78
|
+
for (const raw of records) {
|
|
79
|
+
const e = evidenceOf(raw);
|
|
80
|
+
if (!isVerified(e, publicKey) || e.observation.costCents === null || e.observation.durationMs === null)
|
|
81
|
+
continue;
|
|
82
|
+
const hash = digest(e.observation);
|
|
83
|
+
const previous = ids.get(e.observation.attemptId);
|
|
84
|
+
if (previous && previous !== hash)
|
|
85
|
+
throw new Error('Conflicting learning attempt');
|
|
86
|
+
if (previous)
|
|
87
|
+
continue;
|
|
88
|
+
ids.set(e.observation.attemptId, hash);
|
|
89
|
+
{
|
|
90
|
+
Object.freeze(e.observation.strategy);
|
|
91
|
+
Object.freeze(e.observation.checks);
|
|
92
|
+
Object.freeze(e.observation);
|
|
93
|
+
Object.freeze(e);
|
|
94
|
+
trusted.add(e);
|
|
95
|
+
out.push(e);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
export function manifestOf(value) {
|
|
101
|
+
const m = object(value);
|
|
102
|
+
exact(m, ['taskHashes', 'trials']);
|
|
103
|
+
if (!Array.isArray(m.taskHashes) || m.taskHashes.length < 2 || m.taskHashes.length > 100 || m.taskHashes.some(h => typeof h !== 'string' || !HASH.test(h)) || new Set(m.taskHashes).size !== m.taskHashes.length)
|
|
104
|
+
throw new Error('Manifest needs 2 to 100 unique task hashes');
|
|
105
|
+
if (!Array.isArray(m.trials) || m.trials.length < 3 || m.trials.length > 10 || m.trials.some(t => !integer(t, 0, 99)) || new Set(m.trials).size !== m.trials.length)
|
|
106
|
+
throw new Error('Manifest needs 3 to 10 unique trials');
|
|
107
|
+
return { taskHashes: [...m.taskHashes].sort(), trials: [...m.trials].sort((a, b) => a - b) };
|
|
108
|
+
}
|
|
109
|
+
export function scopeOf(value) {
|
|
110
|
+
const s = object(value);
|
|
111
|
+
exact(s, ['taskType', 'policyHash', 'evaluatorHash']);
|
|
112
|
+
if (!TASK_TYPES.includes(s.taskType) || !HASH.test(String(s.policyHash)) || !HASH.test(String(s.evaluatorHash)))
|
|
113
|
+
throw new Error('Invalid recommendation scope');
|
|
114
|
+
return { taskType: s.taskType, policyHash: s.policyHash, evaluatorHash: s.evaluatorHash };
|
|
115
|
+
}
|
|
116
|
+
const inScope = (o, s) => o.taskType === s.taskType && o.policyHash === s.policyHash && o.evaluatorHash === s.evaluatorHash;
|
|
117
|
+
const success = (o) => o.checks.acceptance && o.checks.safety ? 1 : 0;
|
|
118
|
+
const p95 = (values) => [...values].sort((a, b) => a - b)[Math.max(0, Math.ceil(values.length * .95) - 1)] ?? 0;
|
|
119
|
+
/** Training evidence only; holdout records and unsigned observations cannot influence a pick. */
|
|
120
|
+
export function recommend(records, scope, maxP95Ms = 86_400_000) {
|
|
121
|
+
if (records.some(e => !trusted.has(e)))
|
|
122
|
+
throw new Error('Host-verified evidence required');
|
|
123
|
+
if (!integer(maxP95Ms, 1, 86_400_000))
|
|
124
|
+
throw new Error('Invalid latency constraint');
|
|
125
|
+
const groups = new Map();
|
|
126
|
+
for (const e of records) {
|
|
127
|
+
if (e.observation.split !== 'train' || !inScope(e.observation, scope))
|
|
128
|
+
continue;
|
|
129
|
+
const key = canonical(e.observation.strategy);
|
|
130
|
+
groups.set(key, [...(groups.get(key) ?? []), e]);
|
|
131
|
+
}
|
|
132
|
+
const candidates = [...groups.values()].flatMap(rows => {
|
|
133
|
+
if (rows.length < 3 || new Set(rows.map(r => r.observation.taskHash)).size < 3)
|
|
134
|
+
return [];
|
|
135
|
+
const totalCost = rows.reduce((n, r) => n + (r.observation.costCents ?? 0), 0);
|
|
136
|
+
const latency = p95(rows.map(r => r.observation.durationMs));
|
|
137
|
+
if (totalCost <= 0 || latency > maxP95Ms || rows.some(r => !r.observation.checks.safety))
|
|
138
|
+
return [];
|
|
139
|
+
const successes = rows.reduce((n, r) => n + success(r.observation), 0);
|
|
140
|
+
if (successes === 0)
|
|
141
|
+
return [];
|
|
142
|
+
return [{ strategy: rows[0].observation.strategy, samples: rows.length, successes, successRate: successes / rows.length, successesPerDollar: successes * 100 / totalCost, p95Ms: latency, evidenceIds: rows.map(r => digest(r.observation)).sort() }];
|
|
143
|
+
}).sort((a, b) => b.successesPerDollar - a.successesPerDollar || a.p95Ms - b.p95Ms || canonical(a.strategy).localeCompare(canonical(b.strategy)));
|
|
144
|
+
return { advisory: true, selected: candidates[0] ?? null, candidates, abstained: candidates.length === 0, reason: candidates.length ? 'Ranked by measured training successes per dollar within exact policy/evaluator scope' : 'Insufficient trusted training evidence within scope and latency constraint', defaultsChanged: false };
|
|
145
|
+
}
|
|
146
|
+
/** Complete paired heldout data, equal admitted budgets, source identity and existing MetaHarness conjunction. */
|
|
147
|
+
export function evaluate(records, scope, manifest, baselineId, candidateId) {
|
|
148
|
+
if (records.some(e => !trusted.has(e)))
|
|
149
|
+
throw new Error('Host-verified evidence required');
|
|
150
|
+
if (!ID.test(baselineId) || !ID.test(candidateId) || baselineId === candidateId)
|
|
151
|
+
throw new Error('Distinct strategy IDs required');
|
|
152
|
+
const corpusHash = digest(manifest);
|
|
153
|
+
const relevant = records.filter(e => inScope(e.observation, scope) && [baselineId, candidateId].includes(e.observation.strategy.id));
|
|
154
|
+
const trainingTasks = new Set(records.filter(e => inScope(e.observation, scope) && e.observation.split === 'train').map(e => e.observation.taskHash));
|
|
155
|
+
if (manifest.taskHashes.some(h => trainingTasks.has(h)))
|
|
156
|
+
throw new Error('Training and heldout tasks overlap');
|
|
157
|
+
const rows = relevant.filter(e => e.observation.split === 'holdout' && e.observation.corpusHash === corpusHash);
|
|
158
|
+
for (const id of [baselineId, candidateId])
|
|
159
|
+
if (new Set(rows.filter(r => r.observation.strategy.id === id).map(r => canonical(r.observation.strategy))).size !== 1)
|
|
160
|
+
throw new Error('Missing or inconsistent strategy configuration');
|
|
161
|
+
const paired = [];
|
|
162
|
+
for (const taskHash of manifest.taskHashes)
|
|
163
|
+
for (const trial of manifest.trials) {
|
|
164
|
+
const pick = (id) => rows.filter(e => e.observation.taskHash === taskHash && e.observation.trial === trial && e.observation.strategy.id === id);
|
|
165
|
+
const base = pick(baselineId), candidate = pick(candidateId);
|
|
166
|
+
if (base.length !== 1 || candidate.length !== 1)
|
|
167
|
+
throw new Error('Missing or duplicate paired heldout result');
|
|
168
|
+
const b = base[0].observation, c = candidate[0].observation;
|
|
169
|
+
if (b.sourceHash !== c.sourceHash || b.budgetCents !== c.budgetCents)
|
|
170
|
+
throw new Error('Source identity or admitted budgets differ');
|
|
171
|
+
paired.push([b, c]);
|
|
172
|
+
}
|
|
173
|
+
const base = paired.map(p => p[0]), candidate = paired.map(p => p[1]);
|
|
174
|
+
const efficiency = (rows) => {
|
|
175
|
+
const cost = rows.reduce((n, r) => n + r.costCents, 0);
|
|
176
|
+
if (cost <= 0)
|
|
177
|
+
throw new Error('Positive measured cost required');
|
|
178
|
+
return rows.reduce((n, r) => n + success(r), 0) * 100 / cost;
|
|
179
|
+
};
|
|
180
|
+
const baselineEfficiency = efficiency(base), candidateEfficiency = efficiency(candidate);
|
|
181
|
+
if (baselineEfficiency <= 0)
|
|
182
|
+
throw new Error('Nonzero baseline success required for relative improvement');
|
|
183
|
+
const delta = candidateEfficiency / baselineEfficiency - 1;
|
|
184
|
+
const total = baselineEfficiency + candidateEfficiency;
|
|
185
|
+
const all = [...base, ...candidate];
|
|
186
|
+
const gate = accept({
|
|
187
|
+
heldOutScore: candidateEfficiency / total, baselineHeldOutScore: baselineEfficiency / total,
|
|
188
|
+
redblue: all.every(o => o.checks.adversarial) ? 'PASS' : 'FAIL',
|
|
189
|
+
drift: Math.max(...candidate.map(o => o.checks.drift)), driftThreshold: Math.min(...candidate.map(o => o.checks.driftThreshold)),
|
|
190
|
+
replayDeterministic: all.every(o => o.checks.replay), receiptCoverage: 1,
|
|
191
|
+
canaryRollbackRate: Math.max(...candidate.map(o => o.checks.rollbackRate)), baselineRollbackRate: Math.max(...base.map(o => o.checks.rollbackRate)),
|
|
192
|
+
});
|
|
193
|
+
const noRegression = paired.every(([b, c]) => success(c) >= success(b)) && all.every(o => o.checks.safety);
|
|
194
|
+
const baselineP95Ms = p95(base.map(o => o.durationMs)), candidateP95Ms = p95(candidate.map(o => o.durationMs));
|
|
195
|
+
return { advisory: true, eligible: gate.accept && delta >= .10 - Number.EPSILON && noRegression && candidateP95Ms <= baselineP95Ms, baselineP95Ms, candidateP95Ms, delta, baselineEfficiency, candidateEfficiency, pairedResults: paired.length, corpusHash, noRegression, metaharness: gate, defaultsChanged: false, evidenceIds: rows.map(r => digest(r.observation)).sort() };
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=managed-learning.js.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** Native memory runs behind a bounded child boundary, never in the MCP process. */
|
|
2
|
+
import { type ChildProcess } from 'node:child_process';
|
|
3
|
+
export declare const MANAGED_MEMORY_NAMESPACE = "managed-workflow-evidence";
|
|
4
|
+
export declare const MANAGED_MEMORY_LIMITS: {
|
|
5
|
+
readonly workers: 1;
|
|
6
|
+
readonly requestBytes: 80000;
|
|
7
|
+
readonly resultBytes: 200000;
|
|
8
|
+
readonly rows: 20;
|
|
9
|
+
readonly rowBytes: 8192;
|
|
10
|
+
readonly timeoutMs: 15000;
|
|
11
|
+
readonly idleMs: 5000;
|
|
12
|
+
};
|
|
13
|
+
export type MemoryRequest = {
|
|
14
|
+
operation: 'index';
|
|
15
|
+
id: string;
|
|
16
|
+
value: string;
|
|
17
|
+
taskType: string;
|
|
18
|
+
} | {
|
|
19
|
+
operation: 'recall';
|
|
20
|
+
taskType: string;
|
|
21
|
+
allowedIds: string[];
|
|
22
|
+
};
|
|
23
|
+
export type MemoryRow = {
|
|
24
|
+
key: string;
|
|
25
|
+
namespace: typeof MANAGED_MEMORY_NAMESPACE;
|
|
26
|
+
score: number;
|
|
27
|
+
content: string;
|
|
28
|
+
};
|
|
29
|
+
export type MemoryResult = {
|
|
30
|
+
operation: 'index';
|
|
31
|
+
durable: true;
|
|
32
|
+
hasEmbedding: boolean;
|
|
33
|
+
} | {
|
|
34
|
+
operation: 'recall';
|
|
35
|
+
available: boolean;
|
|
36
|
+
rows: MemoryRow[];
|
|
37
|
+
};
|
|
38
|
+
/** The optional fork callback is an in-process host test seam, not a serialized tool argument. */
|
|
39
|
+
export declare class ManagedMemoryClient {
|
|
40
|
+
private readonly slots;
|
|
41
|
+
private readonly timeoutMs;
|
|
42
|
+
private readonly idleMs;
|
|
43
|
+
private readonly forkWorker;
|
|
44
|
+
constructor(options?: {
|
|
45
|
+
timeoutMs?: number;
|
|
46
|
+
idleMs?: number;
|
|
47
|
+
forkWorker?: (root: string) => ChildProcess;
|
|
48
|
+
});
|
|
49
|
+
run(root: string, raw: unknown, authorize?: () => boolean): Promise<MemoryResult>;
|
|
50
|
+
stats(): {
|
|
51
|
+
workers: number;
|
|
52
|
+
busy: number;
|
|
53
|
+
};
|
|
54
|
+
close(): void;
|
|
55
|
+
private remove;
|
|
56
|
+
}
|
|
57
|
+
export declare const managedMemoryClient: ManagedMemoryClient;
|
|
58
|
+
export declare function nativeMemoryIndex(root: string, input: Omit<Extract<MemoryRequest, {
|
|
59
|
+
operation: 'index';
|
|
60
|
+
}>, 'operation'>, authorize?: () => boolean): Promise<{
|
|
61
|
+
operation: "index";
|
|
62
|
+
durable: true;
|
|
63
|
+
hasEmbedding: boolean;
|
|
64
|
+
}>;
|
|
65
|
+
export declare function nativeMemoryRecall(root: string, input: Omit<Extract<MemoryRequest, {
|
|
66
|
+
operation: 'recall';
|
|
67
|
+
}>, 'operation'>, authorize?: () => boolean): Promise<{
|
|
68
|
+
operation: "recall";
|
|
69
|
+
available: boolean;
|
|
70
|
+
rows: MemoryRow[];
|
|
71
|
+
}>;
|
|
72
|
+
//# sourceMappingURL=managed-memory-client.d.ts.map
|