@tangle-network/agent-knowledge 1.12.0 → 2.0.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 +32 -12
- package/dist/benchmarks/index.d.ts +1 -1
- package/dist/benchmarks/index.js +1 -1
- package/dist/chunk-QID2QVR5.js +2119 -0
- package/dist/chunk-QID2QVR5.js.map +1 -0
- package/dist/{chunk-ULXZI235.js → chunk-RIHIYCX2.js} +65 -67
- package/dist/chunk-RIHIYCX2.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/{index-Cj5jRXOz.d.ts → index-BLxw1I_F.d.ts} +4 -1
- package/dist/index.d.ts +178 -32
- package/dist/index.js +1371 -337
- package/dist/index.js.map +1 -1
- package/package.json +10 -4
- package/dist/chunk-ULXZI235.js.map +0 -1
- package/dist/chunk-W6VWYTNH.js +0 -958
- package/dist/chunk-W6VWYTNH.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -9,35 +9,53 @@ import {
|
|
|
9
9
|
SourceAnchorSchema,
|
|
10
10
|
SourceRecordSchema,
|
|
11
11
|
WIKILINK_REGEX,
|
|
12
|
+
acquireDurableFileLock,
|
|
12
13
|
addSourcePath,
|
|
13
14
|
addSourceText,
|
|
15
|
+
applyKnowledgeFileTransaction,
|
|
14
16
|
applyKnowledgeWriteBlocks,
|
|
15
17
|
applyKnowledgeWriteBlocksFile,
|
|
18
|
+
assertKnowledgeMutationPath,
|
|
16
19
|
buildKnowledgeGraph,
|
|
17
20
|
buildKnowledgeIndex,
|
|
18
21
|
explainKnowledgeTarget,
|
|
19
22
|
extractWikilinks,
|
|
23
|
+
finishKnowledgeFileTransaction,
|
|
20
24
|
formatFrontmatter,
|
|
21
25
|
initKnowledgeBase,
|
|
22
26
|
inspectKnowledgeIndex,
|
|
27
|
+
inspectPendingKnowledgeMutation,
|
|
28
|
+
isMissingFile,
|
|
23
29
|
isSafeKnowledgePath,
|
|
24
30
|
isScaffoldPath,
|
|
31
|
+
knowledgeFileTransactionPlanHash,
|
|
25
32
|
layoutFor,
|
|
26
33
|
lintKnowledgeIndex,
|
|
34
|
+
listRegularFilesWithinRoot,
|
|
27
35
|
loadKnowledgePages,
|
|
28
36
|
loadSourceRegistry,
|
|
29
37
|
mediaTypeFor,
|
|
30
38
|
normalizeLinkTarget,
|
|
31
39
|
parseFrontmatter,
|
|
32
40
|
parseKnowledgeWriteBlocks,
|
|
41
|
+
prepareKnowledgeFileTransaction,
|
|
42
|
+
readRegularFileWithinRoot,
|
|
43
|
+
recoverPendingKnowledgeMutation,
|
|
44
|
+
renameDurable,
|
|
45
|
+
rollbackKnowledgeFileTransaction,
|
|
33
46
|
sourceRegistryPath,
|
|
34
47
|
stringMetadata,
|
|
35
48
|
textSourceAdapter,
|
|
36
49
|
validateKnowledgeIndex,
|
|
50
|
+
withKnowledgeMutation,
|
|
51
|
+
withKnowledgeRead,
|
|
52
|
+
withSafeDirectory,
|
|
53
|
+
writeFileDurableWithinRoot,
|
|
37
54
|
writeJson,
|
|
55
|
+
writeJsonDurableWithinRoot,
|
|
38
56
|
writeKnowledgeIndex,
|
|
39
57
|
writeSourceRegistry
|
|
40
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-QID2QVR5.js";
|
|
41
59
|
import {
|
|
42
60
|
AgentMemoryHitSchema,
|
|
43
61
|
AgentMemoryKindSchema,
|
|
@@ -101,7 +119,7 @@ import {
|
|
|
101
119
|
scoreMemoryBenchmarkArtifact,
|
|
102
120
|
scoreRetrievalArtifact,
|
|
103
121
|
summarizeKnowledgeBenchmarkCampaign
|
|
104
|
-
} from "./chunk-
|
|
122
|
+
} from "./chunk-RIHIYCX2.js";
|
|
105
123
|
import {
|
|
106
124
|
reciprocalRankFusion,
|
|
107
125
|
searchKnowledge,
|
|
@@ -133,7 +151,7 @@ async function fetchWithRetry(url, init, opts) {
|
|
|
133
151
|
await res.text().catch(() => "");
|
|
134
152
|
const backoff = opts.retryBaseMs * 2 ** attempt;
|
|
135
153
|
const jitter = backoff * (0.75 + Math.random() * 0.5);
|
|
136
|
-
await new Promise((
|
|
154
|
+
await new Promise((resolve2) => setTimeout(resolve2, jitter));
|
|
137
155
|
}
|
|
138
156
|
if (lastRes) return lastRes;
|
|
139
157
|
throw new RouterError(0, "fetchWithRetry produced no response");
|
|
@@ -1284,24 +1302,34 @@ function createKnowledgeEvent(input) {
|
|
|
1284
1302
|
}
|
|
1285
1303
|
|
|
1286
1304
|
// src/freshness.ts
|
|
1287
|
-
import {
|
|
1288
|
-
import {
|
|
1305
|
+
import { readFile } from "fs/promises";
|
|
1306
|
+
import { join } from "path";
|
|
1307
|
+
import { z } from "zod";
|
|
1308
|
+
var freshnessRecordSchema = z.object({
|
|
1309
|
+
workspaceId: z.string().min(1),
|
|
1310
|
+
sourceId: z.string().min(1),
|
|
1311
|
+
lastRefreshedAt: z.iso.datetime(),
|
|
1312
|
+
contentHash: z.string().min(1).optional()
|
|
1313
|
+
}).strict();
|
|
1314
|
+
var freshnessFileSchema = z.object({ records: z.record(z.string(), freshnessRecordSchema) }).strict();
|
|
1289
1315
|
function createFileSystemFreshnessStore(options) {
|
|
1290
1316
|
const path = join(options.root, ".agent-knowledge", "freshness.json");
|
|
1291
|
-
let writeQueue = Promise.resolve();
|
|
1292
1317
|
const read = async () => {
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1318
|
+
return withKnowledgeRead(options.root, async () => {
|
|
1319
|
+
try {
|
|
1320
|
+
return freshnessFileSchema.parse(JSON.parse(await readFile(path, "utf8"))).records;
|
|
1321
|
+
} catch (error) {
|
|
1322
|
+
if (isMissingFile(error)) return {};
|
|
1323
|
+
throw error;
|
|
1324
|
+
}
|
|
1325
|
+
});
|
|
1300
1326
|
};
|
|
1301
1327
|
const write = async (records) => {
|
|
1302
|
-
await
|
|
1303
|
-
|
|
1304
|
-
|
|
1328
|
+
await writeJsonDurableWithinRoot(
|
|
1329
|
+
options.root,
|
|
1330
|
+
".agent-knowledge/freshness.json",
|
|
1331
|
+
freshnessFileSchema.parse({ records })
|
|
1332
|
+
);
|
|
1305
1333
|
};
|
|
1306
1334
|
return {
|
|
1307
1335
|
async last(key) {
|
|
@@ -1310,17 +1338,16 @@ function createFileSystemFreshnessStore(options) {
|
|
|
1310
1338
|
return record ? new Date(record.lastRefreshedAt) : null;
|
|
1311
1339
|
},
|
|
1312
1340
|
async mark(input) {
|
|
1313
|
-
|
|
1341
|
+
await withKnowledgeMutation(options.root, async () => {
|
|
1314
1342
|
const records = await read();
|
|
1315
1343
|
records[buildKey(input)] = {
|
|
1316
1344
|
workspaceId: input.workspaceId,
|
|
1317
1345
|
sourceId: input.sourceId,
|
|
1318
1346
|
lastRefreshedAt: input.when.toISOString(),
|
|
1319
|
-
contentHash: input.contentHash
|
|
1347
|
+
...input.contentHash === void 0 ? {} : { contentHash: input.contentHash }
|
|
1320
1348
|
};
|
|
1321
1349
|
await write(records);
|
|
1322
1350
|
});
|
|
1323
|
-
await writeQueue;
|
|
1324
1351
|
},
|
|
1325
1352
|
async stale(input) {
|
|
1326
1353
|
const last = await this.last(input);
|
|
@@ -2056,7 +2083,6 @@ function lensDistribution(set = investmentThesisSet) {
|
|
|
2056
2083
|
}
|
|
2057
2084
|
|
|
2058
2085
|
// src/investment-thesis-task.ts
|
|
2059
|
-
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
2060
2086
|
import { join as join2 } from "path";
|
|
2061
2087
|
|
|
2062
2088
|
// src/material-facts-metric.ts
|
|
@@ -2371,23 +2397,23 @@ function thesisSynthesisMessages(input, kbText) {
|
|
|
2371
2397
|
];
|
|
2372
2398
|
}
|
|
2373
2399
|
async function writeThesisPage(root, input, thesis) {
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
""
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2400
|
+
return withKnowledgeMutation(root, async () => {
|
|
2401
|
+
const relativePath = `knowledge/thesis-${slugify(input.ticker)}.md`;
|
|
2402
|
+
const body = [
|
|
2403
|
+
"---",
|
|
2404
|
+
`title: Investment thesis \u2014 ${input.company} (${input.ticker})`,
|
|
2405
|
+
`ticker: ${input.ticker}`,
|
|
2406
|
+
`cutoff: ${input.cutoff}`,
|
|
2407
|
+
"kind: investment-thesis",
|
|
2408
|
+
"---",
|
|
2409
|
+
`# Investment thesis \u2014 ${input.company} (${input.ticker}), as of ${input.cutoff}`,
|
|
2410
|
+
"",
|
|
2411
|
+
thesis.trim(),
|
|
2412
|
+
""
|
|
2413
|
+
].join("\n");
|
|
2414
|
+
await writeFileDurableWithinRoot(root, relativePath, body, { encoding: "utf8" });
|
|
2415
|
+
return join2(root, relativePath);
|
|
2416
|
+
});
|
|
2391
2417
|
}
|
|
2392
2418
|
async function runInvestmentThesisTask(input, options) {
|
|
2393
2419
|
const worker = createWebResearchWorker({ ...options.workerOptions, router: options.router });
|
|
@@ -2410,8 +2436,16 @@ async function runInvestmentThesisTask(input, options) {
|
|
|
2410
2436
|
}
|
|
2411
2437
|
|
|
2412
2438
|
// src/kb-improvement.ts
|
|
2413
|
-
import {
|
|
2414
|
-
import {
|
|
2439
|
+
import { createHash } from "crypto";
|
|
2440
|
+
import { cp, lstat, mkdir, mkdtemp, rm, stat } from "fs/promises";
|
|
2441
|
+
import { tmpdir } from "os";
|
|
2442
|
+
import { dirname, isAbsolute, join as join3, relative, resolve, sep } from "path";
|
|
2443
|
+
import {
|
|
2444
|
+
canonicalJson,
|
|
2445
|
+
contentHash,
|
|
2446
|
+
validateRunRecord
|
|
2447
|
+
} from "@tangle-network/agent-eval";
|
|
2448
|
+
import { z as z2 } from "zod";
|
|
2415
2449
|
|
|
2416
2450
|
// src/rag-eval.ts
|
|
2417
2451
|
var defaultThresholds = {
|
|
@@ -3453,6 +3487,198 @@ function assertNotAborted(signal) {
|
|
|
3453
3487
|
}
|
|
3454
3488
|
|
|
3455
3489
|
// src/kb-improvement.ts
|
|
3490
|
+
var digestSchema = z2.string().regex(/^[a-f0-9]{64}$/);
|
|
3491
|
+
var runIdSchema = z2.string().min(1).max(2048);
|
|
3492
|
+
var safePathSegmentSchema = z2.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/);
|
|
3493
|
+
var improvementStatusSchema = z2.enum([
|
|
3494
|
+
"running",
|
|
3495
|
+
"candidate-ready",
|
|
3496
|
+
"promoted",
|
|
3497
|
+
"rejected",
|
|
3498
|
+
"blocked"
|
|
3499
|
+
]);
|
|
3500
|
+
var runRecordSchema = z2.custom((value) => {
|
|
3501
|
+
try {
|
|
3502
|
+
validateRunRecord(value);
|
|
3503
|
+
return true;
|
|
3504
|
+
} catch {
|
|
3505
|
+
return false;
|
|
3506
|
+
}
|
|
3507
|
+
}, "invalid agent-eval RunRecord");
|
|
3508
|
+
var deterministicMetricProvenanceSchema = z2.object({
|
|
3509
|
+
evaluator: z2.string().min(1),
|
|
3510
|
+
version: z2.string().min(1),
|
|
3511
|
+
method: z2.literal("deterministic")
|
|
3512
|
+
}).strict();
|
|
3513
|
+
var measuredMetricProvenanceSchema = z2.object({
|
|
3514
|
+
evaluator: z2.string().min(1),
|
|
3515
|
+
version: z2.string().min(1),
|
|
3516
|
+
method: z2.enum(["sampled", "composite"]),
|
|
3517
|
+
corpusHash: digestSchema,
|
|
3518
|
+
runRecords: z2.array(runRecordSchema).min(1)
|
|
3519
|
+
}).strict();
|
|
3520
|
+
var modelMetricProvenanceSchema = z2.object({
|
|
3521
|
+
evaluator: z2.string().min(1),
|
|
3522
|
+
version: z2.string().min(1),
|
|
3523
|
+
method: z2.literal("model"),
|
|
3524
|
+
model: z2.string().min(1),
|
|
3525
|
+
corpusHash: digestSchema,
|
|
3526
|
+
runRecords: z2.array(runRecordSchema).min(1)
|
|
3527
|
+
}).strict();
|
|
3528
|
+
var improvementMetricSchema = z2.object({
|
|
3529
|
+
score: z2.number().finite().min(0).max(1),
|
|
3530
|
+
passed: z2.boolean(),
|
|
3531
|
+
dimensions: z2.record(z2.string(), z2.number().finite()).optional(),
|
|
3532
|
+
notes: z2.string().optional(),
|
|
3533
|
+
provenance: z2.discriminatedUnion("method", [
|
|
3534
|
+
deterministicMetricProvenanceSchema,
|
|
3535
|
+
measuredMetricProvenanceSchema,
|
|
3536
|
+
modelMetricProvenanceSchema
|
|
3537
|
+
])
|
|
3538
|
+
}).strict().superRefine((metric, context) => {
|
|
3539
|
+
if (metric.provenance.method === "deterministic") return;
|
|
3540
|
+
const actualCorpusHash = contentHash(metric.provenance.runRecords);
|
|
3541
|
+
if (actualCorpusHash !== metric.provenance.corpusHash) {
|
|
3542
|
+
context.addIssue({
|
|
3543
|
+
code: "custom",
|
|
3544
|
+
path: ["provenance", "corpusHash"],
|
|
3545
|
+
message: "metric corpus hash must bind the complete RunRecord array"
|
|
3546
|
+
});
|
|
3547
|
+
}
|
|
3548
|
+
});
|
|
3549
|
+
var candidateRecordSchema = z2.object({
|
|
3550
|
+
iteration: z2.number().int().positive(),
|
|
3551
|
+
candidateId: safePathSegmentSchema,
|
|
3552
|
+
baseHash: digestSchema,
|
|
3553
|
+
candidateHash: digestSchema.optional(),
|
|
3554
|
+
evidenceHash: digestSchema.optional(),
|
|
3555
|
+
promotionPlanHash: digestSchema.optional(),
|
|
3556
|
+
status: improvementStatusSchema,
|
|
3557
|
+
createdAt: z2.iso.datetime(),
|
|
3558
|
+
updatedAt: z2.iso.datetime()
|
|
3559
|
+
}).strict();
|
|
3560
|
+
var KnowledgeImprovementRunStateSchema = z2.object({
|
|
3561
|
+
schemaVersion: z2.literal(1),
|
|
3562
|
+
runId: runIdSchema,
|
|
3563
|
+
root: z2.string().min(1),
|
|
3564
|
+
goal: z2.string().min(1),
|
|
3565
|
+
status: improvementStatusSchema,
|
|
3566
|
+
baseHash: digestSchema,
|
|
3567
|
+
createdAt: z2.iso.datetime(),
|
|
3568
|
+
updatedAt: z2.iso.datetime(),
|
|
3569
|
+
ownerId: z2.string().min(1).optional(),
|
|
3570
|
+
candidates: z2.array(candidateRecordSchema),
|
|
3571
|
+
promotedCandidateId: safePathSegmentSchema.optional(),
|
|
3572
|
+
blockedReason: z2.string().min(1).optional()
|
|
3573
|
+
}).strict().superRefine((state, context) => {
|
|
3574
|
+
const candidateIds = /* @__PURE__ */ new Set();
|
|
3575
|
+
for (const [index, candidate] of state.candidates.entries()) {
|
|
3576
|
+
if (candidateIds.has(candidate.candidateId)) {
|
|
3577
|
+
context.addIssue({
|
|
3578
|
+
code: "custom",
|
|
3579
|
+
path: ["candidates", index, "candidateId"],
|
|
3580
|
+
message: "candidate ids must be unique within an improvement run"
|
|
3581
|
+
});
|
|
3582
|
+
}
|
|
3583
|
+
candidateIds.add(candidate.candidateId);
|
|
3584
|
+
if (candidate.baseHash !== state.baseHash) {
|
|
3585
|
+
context.addIssue({
|
|
3586
|
+
code: "custom",
|
|
3587
|
+
path: ["candidates", index, "baseHash"],
|
|
3588
|
+
message: "candidate base hash must match its improvement run"
|
|
3589
|
+
});
|
|
3590
|
+
}
|
|
3591
|
+
if (candidate.status === "candidate-ready") {
|
|
3592
|
+
if (!candidate.candidateHash || !candidate.evidenceHash || !candidate.promotionPlanHash) {
|
|
3593
|
+
context.addIssue({
|
|
3594
|
+
code: "custom",
|
|
3595
|
+
path: ["candidates", index],
|
|
3596
|
+
message: "ready candidates require content, evidence, and promotion-plan identities"
|
|
3597
|
+
});
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
if (candidate.status === "promoted" && (!candidate.candidateHash || !candidate.evidenceHash || !candidate.promotionPlanHash)) {
|
|
3601
|
+
context.addIssue({
|
|
3602
|
+
code: "custom",
|
|
3603
|
+
path: ["candidates", index],
|
|
3604
|
+
message: "promoted candidates require content, evidence, and promotion-plan identities"
|
|
3605
|
+
});
|
|
3606
|
+
}
|
|
3607
|
+
if (candidate.status === "promoted" && Boolean(candidate.evidenceHash) !== Boolean(candidate.promotionPlanHash)) {
|
|
3608
|
+
context.addIssue({
|
|
3609
|
+
code: "custom",
|
|
3610
|
+
path: ["candidates", index],
|
|
3611
|
+
message: "promoted candidate evidence and promotion-plan identities must appear together"
|
|
3612
|
+
});
|
|
3613
|
+
}
|
|
3614
|
+
if (candidate.status === "promoted" && state.status !== "promoted") {
|
|
3615
|
+
context.addIssue({
|
|
3616
|
+
code: "custom",
|
|
3617
|
+
path: ["candidates", index, "status"],
|
|
3618
|
+
message: "only a promoted run may contain a promoted candidate"
|
|
3619
|
+
});
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
if (state.status === "promoted") {
|
|
3623
|
+
const promoted = state.candidates.filter(
|
|
3624
|
+
(candidate) => candidate.candidateId === state.promotedCandidateId
|
|
3625
|
+
);
|
|
3626
|
+
if (promoted.length !== 1 || promoted[0]?.status !== "promoted") {
|
|
3627
|
+
context.addIssue({
|
|
3628
|
+
code: "custom",
|
|
3629
|
+
path: ["promotedCandidateId"],
|
|
3630
|
+
message: "promoted state must identify exactly one promoted candidate"
|
|
3631
|
+
});
|
|
3632
|
+
}
|
|
3633
|
+
} else if (state.promotedCandidateId !== void 0) {
|
|
3634
|
+
context.addIssue({
|
|
3635
|
+
code: "custom",
|
|
3636
|
+
path: ["promotedCandidateId"],
|
|
3637
|
+
message: "only a promoted run may identify a promoted candidate"
|
|
3638
|
+
});
|
|
3639
|
+
}
|
|
3640
|
+
if (state.status === "candidate-ready" && state.candidates.filter((candidate) => candidate.status === "candidate-ready").length !== 1) {
|
|
3641
|
+
context.addIssue({
|
|
3642
|
+
code: "custom",
|
|
3643
|
+
path: ["status"],
|
|
3644
|
+
message: "candidate-ready state must contain exactly one ready candidate"
|
|
3645
|
+
});
|
|
3646
|
+
}
|
|
3647
|
+
if (state.status === "blocked" && state.blockedReason === void 0) {
|
|
3648
|
+
context.addIssue({
|
|
3649
|
+
code: "custom",
|
|
3650
|
+
path: ["blockedReason"],
|
|
3651
|
+
message: "blocked state must include a reason"
|
|
3652
|
+
});
|
|
3653
|
+
}
|
|
3654
|
+
});
|
|
3655
|
+
var KnowledgeImprovementEvidenceSchema = z2.object({
|
|
3656
|
+
schemaVersion: z2.literal(1),
|
|
3657
|
+
kind: z2.literal("knowledge-improvement-evidence"),
|
|
3658
|
+
runId: runIdSchema,
|
|
3659
|
+
candidateId: safePathSegmentSchema,
|
|
3660
|
+
iteration: z2.number().int().positive(),
|
|
3661
|
+
goalHash: digestSchema,
|
|
3662
|
+
baseHash: digestSchema,
|
|
3663
|
+
candidateHash: digestSchema,
|
|
3664
|
+
promotionPlanHash: digestSchema,
|
|
3665
|
+
validation: z2.unknown(),
|
|
3666
|
+
readiness: z2.unknown().nullable(),
|
|
3667
|
+
kbQuality: z2.unknown(),
|
|
3668
|
+
evaluation: improvementMetricSchema,
|
|
3669
|
+
lifecycle: z2.unknown().nullable()
|
|
3670
|
+
}).strict();
|
|
3671
|
+
var KnowledgeImprovementCandidateRefSchema = z2.object({
|
|
3672
|
+
schemaVersion: z2.literal(1),
|
|
3673
|
+
kind: z2.literal("knowledge-improvement-candidate"),
|
|
3674
|
+
runId: runIdSchema,
|
|
3675
|
+
candidateId: safePathSegmentSchema,
|
|
3676
|
+
goalHash: digestSchema,
|
|
3677
|
+
baseHash: digestSchema,
|
|
3678
|
+
candidateHash: digestSchema,
|
|
3679
|
+
evidenceHash: digestSchema,
|
|
3680
|
+
promotionPlanHash: digestSchema
|
|
3681
|
+
}).strict();
|
|
3456
3682
|
var DEFAULT_LEASE_TTL_MS = 15 * 60 * 1e3;
|
|
3457
3683
|
var UPDATE_PHASES = [
|
|
3458
3684
|
"knowledge-acquisition",
|
|
@@ -3468,33 +3694,159 @@ function knowledgeImprovementRunId(root, goal) {
|
|
|
3468
3694
|
return stableId("kimpr", `${root}:${goal}`);
|
|
3469
3695
|
}
|
|
3470
3696
|
function knowledgeImprovementRunDir(root, runId) {
|
|
3471
|
-
|
|
3697
|
+
const parsedRunId = runIdSchema.parse(runId);
|
|
3698
|
+
const safeRunId = safePathSegmentSchema.safeParse(parsedRunId);
|
|
3699
|
+
const runSegment = safeRunId.success ? safeRunId.data : `${slugify(parsedRunId).slice(0, 72)}-${sha256(parsedRunId).slice(0, 16)}`;
|
|
3700
|
+
const improvementsDir = join3(layoutFor(root).cacheDir, "improvements");
|
|
3701
|
+
const runDir = join3(improvementsDir, runSegment);
|
|
3702
|
+
const resolvedImprovementsDir = resolve(improvementsDir);
|
|
3703
|
+
const resolvedRunDir = resolve(runDir);
|
|
3704
|
+
if (!resolvedRunDir.startsWith(`${resolvedImprovementsDir}${sep}`)) {
|
|
3705
|
+
throw new Error("knowledge improvement run directory escaped its root");
|
|
3706
|
+
}
|
|
3707
|
+
return runDir;
|
|
3708
|
+
}
|
|
3709
|
+
async function withKnowledgeImprovementRun(root, runId, create, use) {
|
|
3710
|
+
const runDir = knowledgeImprovementRunDir(root, runId);
|
|
3711
|
+
const relativePath = descendantPath(root, runDir);
|
|
3712
|
+
if (!relativePath) throw new Error("knowledge improvement run directory escaped its root");
|
|
3713
|
+
return withSafeDirectory(root, relativePath, create, async (openedRunDir) => {
|
|
3714
|
+
const result = await use(openedRunDir);
|
|
3715
|
+
const openedIdentity = await stat(openedRunDir);
|
|
3716
|
+
const currentIdentity = await withSafeDirectory(
|
|
3717
|
+
root,
|
|
3718
|
+
relativePath,
|
|
3719
|
+
false,
|
|
3720
|
+
(currentRunDir) => stat(currentRunDir)
|
|
3721
|
+
);
|
|
3722
|
+
if (openedIdentity.dev !== currentIdentity.dev || openedIdentity.ino !== currentIdentity.ino) {
|
|
3723
|
+
throw new Error("knowledge improvement run directory changed during use");
|
|
3724
|
+
}
|
|
3725
|
+
return result;
|
|
3726
|
+
});
|
|
3472
3727
|
}
|
|
3473
|
-
async function loadKnowledgeImprovementState(root, runId
|
|
3728
|
+
async function loadKnowledgeImprovementState(root, runId) {
|
|
3729
|
+
const expectedRunId = runIdSchema.parse(runId);
|
|
3474
3730
|
try {
|
|
3475
|
-
return
|
|
3476
|
-
|
|
3477
|
-
|
|
3731
|
+
return await withKnowledgeImprovementRun(
|
|
3732
|
+
root,
|
|
3733
|
+
expectedRunId,
|
|
3734
|
+
false,
|
|
3735
|
+
(runDir) => loadKnowledgeImprovementStateFromRun(root, expectedRunId, runDir)
|
|
3736
|
+
);
|
|
3737
|
+
} catch (error) {
|
|
3738
|
+
if (isMissingFile(error)) return null;
|
|
3739
|
+
throw error;
|
|
3478
3740
|
}
|
|
3479
3741
|
}
|
|
3742
|
+
async function loadKnowledgeImprovementStateFromRun(root, runId, runDir) {
|
|
3743
|
+
const stateFile = await readRegularFileWithinRoot(runDir, "state.json");
|
|
3744
|
+
const raw = JSON.parse(stateFile.bytes.toString("utf8"));
|
|
3745
|
+
const state = KnowledgeImprovementRunStateSchema.parse(raw);
|
|
3746
|
+
if (state.runId !== runId) {
|
|
3747
|
+
throw new Error("knowledge improvement state does not match the requested run");
|
|
3748
|
+
}
|
|
3749
|
+
if (resolve(state.root) !== resolve(root)) {
|
|
3750
|
+
throw new Error("knowledge improvement state does not match the requested root");
|
|
3751
|
+
}
|
|
3752
|
+
for (const candidate of state.candidates) {
|
|
3753
|
+
if (candidate.status === "running") await assertCandidateWorkspace(runDir, candidate);
|
|
3754
|
+
}
|
|
3755
|
+
return state;
|
|
3756
|
+
}
|
|
3757
|
+
function knowledgeImprovementCandidateRef(result) {
|
|
3758
|
+
if (!result.candidate) throw new Error("knowledge improvement result has no candidate");
|
|
3759
|
+
return candidateRefFor(result.runId, result.state, result.candidate);
|
|
3760
|
+
}
|
|
3761
|
+
async function withKnowledgeImprovementCandidate(options, use) {
|
|
3762
|
+
assertExactCandidatePlatform();
|
|
3763
|
+
const candidateRef = KnowledgeImprovementCandidateRefSchema.parse(options.candidate);
|
|
3764
|
+
return withKnowledgeImprovementRun(options.root, candidateRef.runId, false, async (runDir) => {
|
|
3765
|
+
const state = await loadKnowledgeImprovementStateFromRun(
|
|
3766
|
+
options.root,
|
|
3767
|
+
candidateRef.runId,
|
|
3768
|
+
runDir
|
|
3769
|
+
);
|
|
3770
|
+
return withMeasuredCandidateSnapshot(
|
|
3771
|
+
options.root,
|
|
3772
|
+
runDir,
|
|
3773
|
+
state,
|
|
3774
|
+
candidateRef,
|
|
3775
|
+
(resolved) => withIsolatedCandidateCopy(
|
|
3776
|
+
resolved.root,
|
|
3777
|
+
candidateRef.candidateHash,
|
|
3778
|
+
(root) => use({
|
|
3779
|
+
root,
|
|
3780
|
+
candidate: candidateRef,
|
|
3781
|
+
evaluation: resolved.evidence.evaluation
|
|
3782
|
+
})
|
|
3783
|
+
)
|
|
3784
|
+
);
|
|
3785
|
+
});
|
|
3786
|
+
}
|
|
3787
|
+
async function promoteKnowledgeCandidate(options) {
|
|
3788
|
+
assertExactCandidatePlatform();
|
|
3789
|
+
const candidateRef = Object.freeze(
|
|
3790
|
+
KnowledgeImprovementCandidateRefSchema.parse(options.candidate)
|
|
3791
|
+
);
|
|
3792
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
3793
|
+
return withKnowledgeImprovementRun(options.root, candidateRef.runId, false, async (runDir) => {
|
|
3794
|
+
const lease = await acquireRunLease(runDir, {
|
|
3795
|
+
ownerId: options.ownerId ?? `pid-${process.pid}`,
|
|
3796
|
+
ttlMs: options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS
|
|
3797
|
+
});
|
|
3798
|
+
try {
|
|
3799
|
+
lease.assertOwned();
|
|
3800
|
+
const state = await loadKnowledgeImprovementStateFromRun(
|
|
3801
|
+
options.root,
|
|
3802
|
+
candidateRef.runId,
|
|
3803
|
+
runDir
|
|
3804
|
+
);
|
|
3805
|
+
return await promoteReadyCandidate({
|
|
3806
|
+
root: options.root,
|
|
3807
|
+
runDir,
|
|
3808
|
+
state,
|
|
3809
|
+
candidateRef,
|
|
3810
|
+
leaseTtlMs: options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS,
|
|
3811
|
+
assertRunOwned: lease.assertOwned,
|
|
3812
|
+
now,
|
|
3813
|
+
onState: options.onState
|
|
3814
|
+
});
|
|
3815
|
+
} finally {
|
|
3816
|
+
await lease.release();
|
|
3817
|
+
}
|
|
3818
|
+
});
|
|
3819
|
+
}
|
|
3480
3820
|
async function improveKnowledgeBase(options) {
|
|
3821
|
+
assertExactCandidatePlatform();
|
|
3481
3822
|
assertKnowledgeImprovementOptions(options);
|
|
3482
3823
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
3483
|
-
const runId =
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3824
|
+
const runId = runIdSchema.parse(
|
|
3825
|
+
options.runId ?? knowledgeImprovementRunId(options.root, options.goal)
|
|
3826
|
+
);
|
|
3827
|
+
return withKnowledgeImprovementRun(
|
|
3828
|
+
options.root,
|
|
3829
|
+
runId,
|
|
3830
|
+
true,
|
|
3831
|
+
(runDir) => improveKnowledgeBaseInRun(options, runId, runDir, now)
|
|
3832
|
+
);
|
|
3833
|
+
}
|
|
3834
|
+
async function improveKnowledgeBaseInRun(options, runId, runDir, now) {
|
|
3487
3835
|
const lease = await acquireRunLease(runDir, {
|
|
3488
3836
|
ownerId: options.ownerId ?? `pid-${process.pid}`,
|
|
3489
|
-
ttlMs: options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS
|
|
3490
|
-
now
|
|
3837
|
+
ttlMs: options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS
|
|
3491
3838
|
});
|
|
3492
3839
|
try {
|
|
3493
|
-
|
|
3840
|
+
lease.assertOwned();
|
|
3841
|
+
let state = options.resume === false ? null : await loadKnowledgeImprovementStateFromRun(options.root, runId, runDir).catch((error) => {
|
|
3842
|
+
if (isMissingFile(error)) return null;
|
|
3843
|
+
throw error;
|
|
3844
|
+
});
|
|
3494
3845
|
if (!state) {
|
|
3495
3846
|
const baseHash = await hashKnowledgeBase(options.root);
|
|
3847
|
+
await createBaselineSnapshot(runDir, options.root, baseHash);
|
|
3496
3848
|
state = {
|
|
3497
|
-
|
|
3849
|
+
schemaVersion: 1,
|
|
3498
3850
|
runId,
|
|
3499
3851
|
root: options.root,
|
|
3500
3852
|
goal: options.goal,
|
|
@@ -3508,29 +3860,65 @@ async function improveKnowledgeBase(options) {
|
|
|
3508
3860
|
await saveState(runDir, state, options.onState);
|
|
3509
3861
|
await appendLedger(runDir, { type: "run.created", runId, baseHash });
|
|
3510
3862
|
}
|
|
3863
|
+
if (state.goal !== options.goal) {
|
|
3864
|
+
throw new Error("knowledge improvement state does not match the requested goal");
|
|
3865
|
+
}
|
|
3866
|
+
const promotedCandidateId = state.promotedCandidateId;
|
|
3867
|
+
const promotedCandidate = state.status === "promoted" ? state.candidates.find((candidate2) => candidate2.candidateId === promotedCandidateId) : void 0;
|
|
3868
|
+
if (state.status === "promoted" && !promotedCandidate) {
|
|
3869
|
+
throw new Error("promoted knowledge state has no promoted candidate");
|
|
3870
|
+
}
|
|
3871
|
+
const resumablePromotion = state.candidates.find(
|
|
3872
|
+
(candidate2) => (candidate2.status === "candidate-ready" || candidate2.status === "promoted") && candidate2.evidenceHash !== void 0 && candidate2.promotionPlanHash !== void 0
|
|
3873
|
+
);
|
|
3874
|
+
const resumableCandidateRef = resumablePromotion ? candidateRefFor(runId, state, resumablePromotion) : void 0;
|
|
3875
|
+
await withKnowledgeMutation(options.root, () => void 0, {
|
|
3876
|
+
resumeTransaction: resumableCandidateRef ? {
|
|
3877
|
+
purpose: promotionTransactionPurpose(resumableCandidateRef),
|
|
3878
|
+
validate: (transaction) => assertPromotionTransaction(transaction, resumableCandidateRef)
|
|
3879
|
+
} : void 0
|
|
3880
|
+
});
|
|
3881
|
+
await ensureBaselineSnapshot(runDir, options.root, state.baseHash);
|
|
3511
3882
|
if (state.status === "promoted") {
|
|
3512
|
-
|
|
3883
|
+
const promoted = promotedCandidate;
|
|
3884
|
+
return await promoteReadyCandidate({
|
|
3885
|
+
root: options.root,
|
|
3886
|
+
runDir,
|
|
3887
|
+
state,
|
|
3888
|
+
candidateRef: candidateRefFor(runId, state, promoted),
|
|
3889
|
+
leaseTtlMs: options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS,
|
|
3890
|
+
assertRunOwned: lease.assertOwned,
|
|
3891
|
+
now,
|
|
3892
|
+
onState: options.onState
|
|
3893
|
+
});
|
|
3513
3894
|
}
|
|
3514
3895
|
if (state.status === "blocked") {
|
|
3515
|
-
return { runId,
|
|
3896
|
+
return { runId, state, promoted: false, blocked: true };
|
|
3516
3897
|
}
|
|
3517
3898
|
const maxCandidates = Math.max(1, options.maxCandidates ?? 1);
|
|
3518
3899
|
let candidate = findActiveCandidate(state);
|
|
3900
|
+
let lastRejectedCandidate;
|
|
3901
|
+
let lastRejectedEvaluation;
|
|
3519
3902
|
let lifecycle;
|
|
3520
|
-
while (candidate
|
|
3903
|
+
while (candidate || state.candidates.length < maxCandidates) {
|
|
3521
3904
|
if (!candidate) {
|
|
3522
|
-
const
|
|
3523
|
-
if (
|
|
3905
|
+
const currentHash = await hashKnowledgeBase(options.root);
|
|
3906
|
+
if (currentHash !== state.baseHash) {
|
|
3524
3907
|
state = await blockRun(
|
|
3525
3908
|
runDir,
|
|
3526
3909
|
state,
|
|
3527
|
-
`base changed before candidate creation: expected ${state.baseHash}, got ${
|
|
3910
|
+
`base changed before candidate creation: expected ${state.baseHash}, got ${currentHash}`,
|
|
3528
3911
|
options.onState,
|
|
3529
3912
|
now
|
|
3530
3913
|
);
|
|
3531
|
-
return { runId,
|
|
3914
|
+
return { runId, state, promoted: false, blocked: true };
|
|
3532
3915
|
}
|
|
3533
|
-
|
|
3916
|
+
const activeState = state;
|
|
3917
|
+
candidate = await withBaselineSnapshot(
|
|
3918
|
+
runDir,
|
|
3919
|
+
activeState.baseHash,
|
|
3920
|
+
(baselineRoot) => createCandidateWorkspace(runDir, activeState, baselineRoot, now)
|
|
3921
|
+
);
|
|
3534
3922
|
state.candidates.push(candidate);
|
|
3535
3923
|
state.status = "running";
|
|
3536
3924
|
state.updatedAt = now().toISOString();
|
|
@@ -3542,79 +3930,377 @@ async function improveKnowledgeBase(options) {
|
|
|
3542
3930
|
iteration: candidate.iteration
|
|
3543
3931
|
});
|
|
3544
3932
|
}
|
|
3545
|
-
|
|
3546
|
-
candidate
|
|
3547
|
-
|
|
3548
|
-
|
|
3933
|
+
const measured = await measureCandidate(runId, runDir, state, candidate, options, now);
|
|
3934
|
+
candidate = measured.candidate;
|
|
3935
|
+
const evaluation = measured.evaluation;
|
|
3936
|
+
lifecycle = measured.lifecycle;
|
|
3937
|
+
if (evaluation.passed) {
|
|
3938
|
+
candidate.status = "candidate-ready";
|
|
3939
|
+
candidate.updatedAt = now().toISOString();
|
|
3940
|
+
state.status = "candidate-ready";
|
|
3941
|
+
state.updatedAt = now().toISOString();
|
|
3942
|
+
await saveState(runDir, state, options.onState);
|
|
3943
|
+
await appendLedger(runDir, {
|
|
3944
|
+
type: "candidate.ready",
|
|
3945
|
+
runId,
|
|
3946
|
+
candidateId: candidate.candidateId
|
|
3947
|
+
});
|
|
3948
|
+
break;
|
|
3949
|
+
}
|
|
3950
|
+
candidate.status = "rejected";
|
|
3951
|
+
state.status = "running";
|
|
3549
3952
|
state.updatedAt = now().toISOString();
|
|
3550
3953
|
await saveState(runDir, state, options.onState);
|
|
3551
3954
|
await appendLedger(runDir, {
|
|
3552
|
-
type: "candidate.
|
|
3955
|
+
type: "candidate.rejected",
|
|
3553
3956
|
runId,
|
|
3554
3957
|
candidateId: candidate.candidateId
|
|
3555
3958
|
});
|
|
3959
|
+
lastRejectedCandidate = candidate;
|
|
3960
|
+
lastRejectedEvaluation = evaluation;
|
|
3961
|
+
candidate = void 0;
|
|
3556
3962
|
}
|
|
3557
3963
|
if (!candidate) {
|
|
3558
3964
|
state.status = "rejected";
|
|
3559
3965
|
state.updatedAt = now().toISOString();
|
|
3560
3966
|
await saveState(runDir, state, options.onState);
|
|
3561
|
-
return {
|
|
3562
|
-
}
|
|
3563
|
-
candidate = await evaluateCandidate(runDir, state, candidate, lifecycle, options, now);
|
|
3564
|
-
state.updatedAt = now().toISOString();
|
|
3565
|
-
await saveState(runDir, state, options.onState);
|
|
3566
|
-
if (!candidate.evaluation?.passed) {
|
|
3567
|
-
candidate.status = "rejected";
|
|
3568
|
-
state.status = "rejected";
|
|
3569
|
-
state.updatedAt = now().toISOString();
|
|
3570
|
-
await saveState(runDir, state, options.onState);
|
|
3571
|
-
await appendLedger(runDir, {
|
|
3572
|
-
type: "candidate.rejected",
|
|
3967
|
+
return {
|
|
3573
3968
|
runId,
|
|
3574
|
-
candidateId: candidate.candidateId
|
|
3575
|
-
});
|
|
3576
|
-
return { runId, runDir, state, candidate, lifecycle, promoted: false, blocked: false };
|
|
3577
|
-
}
|
|
3578
|
-
if (options.promote === false) {
|
|
3579
|
-
state.status = "candidate-ready";
|
|
3580
|
-
state.updatedAt = now().toISOString();
|
|
3581
|
-
await saveState(runDir, state, options.onState);
|
|
3582
|
-
return { runId, runDir, state, candidate, lifecycle, promoted: false, blocked: false };
|
|
3583
|
-
}
|
|
3584
|
-
const currentHash = await hashKnowledgeBase(options.root);
|
|
3585
|
-
if (currentHash !== state.baseHash) {
|
|
3586
|
-
state = await blockRun(
|
|
3587
|
-
runDir,
|
|
3588
3969
|
state,
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
runId,
|
|
3596
|
-
candidateId: candidate.candidateId,
|
|
3597
|
-
reason: state.blockedReason
|
|
3598
|
-
});
|
|
3599
|
-
return { runId, runDir, state, candidate, lifecycle, promoted: false, blocked: true };
|
|
3970
|
+
candidate: lastRejectedCandidate,
|
|
3971
|
+
...lastRejectedEvaluation ? { evaluation: lastRejectedEvaluation } : {},
|
|
3972
|
+
lifecycle,
|
|
3973
|
+
promoted: false,
|
|
3974
|
+
blocked: false
|
|
3975
|
+
};
|
|
3600
3976
|
}
|
|
3601
|
-
await
|
|
3602
|
-
|
|
3603
|
-
candidate.updatedAt = now().toISOString();
|
|
3604
|
-
state.status = "promoted";
|
|
3605
|
-
state.promotedCandidateId = candidate.candidateId;
|
|
3606
|
-
state.updatedAt = now().toISOString();
|
|
3607
|
-
await saveState(runDir, state, options.onState);
|
|
3608
|
-
await appendLedger(runDir, {
|
|
3609
|
-
type: "candidate.promoted",
|
|
3977
|
+
const evidence = await assertCandidateEvidence(runDir, candidateRefFor(runId, state, candidate));
|
|
3978
|
+
return {
|
|
3610
3979
|
runId,
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3980
|
+
state,
|
|
3981
|
+
candidate,
|
|
3982
|
+
evaluation: evidence.evaluation,
|
|
3983
|
+
lifecycle,
|
|
3984
|
+
promoted: false,
|
|
3985
|
+
blocked: false
|
|
3986
|
+
};
|
|
3614
3987
|
} finally {
|
|
3615
3988
|
await lease.release();
|
|
3616
3989
|
}
|
|
3617
3990
|
}
|
|
3991
|
+
async function promoteReadyCandidate(input) {
|
|
3992
|
+
const { candidateRef, runDir, state } = input;
|
|
3993
|
+
assertStateIdentity(input.root, candidateRef, state);
|
|
3994
|
+
const candidate = state.candidates.find((entry) => entry.candidateId === candidateRef.candidateId);
|
|
3995
|
+
if (!candidate || canonicalJson(candidateRefFor(candidateRef.runId, state, candidate)) !== canonicalJson(candidateRef)) {
|
|
3996
|
+
throw new Error("knowledge candidate approval does not match the measured candidate");
|
|
3997
|
+
}
|
|
3998
|
+
const purpose = promotionTransactionPurpose(candidateRef);
|
|
3999
|
+
return withKnowledgeMutation(
|
|
4000
|
+
input.root,
|
|
4001
|
+
async (mutationLock) => {
|
|
4002
|
+
input.assertRunOwned();
|
|
4003
|
+
const transactionRoot = mutationLock.transactionRoot;
|
|
4004
|
+
let pending = null;
|
|
4005
|
+
const currentHash = await hashKnowledgeBase(input.root);
|
|
4006
|
+
if (state.status === "promoted" && state.promotedCandidateId !== candidate.candidateId) {
|
|
4007
|
+
throw new Error(
|
|
4008
|
+
`knowledge run already promoted '${state.promotedCandidateId ?? "unknown"}'`
|
|
4009
|
+
);
|
|
4010
|
+
}
|
|
4011
|
+
if (state.status === "promoted" && currentHash !== candidateRef.candidateHash) {
|
|
4012
|
+
throw new Error(
|
|
4013
|
+
`promoted knowledge base changed: expected ${candidateRef.candidateHash}, got ${currentHash}`
|
|
4014
|
+
);
|
|
4015
|
+
}
|
|
4016
|
+
if (state.status !== "promoted" && state.status !== "candidate-ready") {
|
|
4017
|
+
throw new Error(
|
|
4018
|
+
`knowledge candidate is not ready for promotion: run=${state.status}, candidate=${candidate.status}`
|
|
4019
|
+
);
|
|
4020
|
+
}
|
|
4021
|
+
if (currentHash !== state.baseHash && currentHash !== candidateRef.candidateHash) {
|
|
4022
|
+
return await blockPromotion(
|
|
4023
|
+
input,
|
|
4024
|
+
candidate,
|
|
4025
|
+
`base changed before promotion: expected ${state.baseHash}, got ${currentHash}`
|
|
4026
|
+
);
|
|
4027
|
+
}
|
|
4028
|
+
if (currentHash !== candidateRef.candidateHash) {
|
|
4029
|
+
pending = await withMeasuredCandidateSnapshot(
|
|
4030
|
+
input.root,
|
|
4031
|
+
runDir,
|
|
4032
|
+
state,
|
|
4033
|
+
candidateRef,
|
|
4034
|
+
(resolved) => withBaselineSnapshot(runDir, state.baseHash, async (baselineRoot) => {
|
|
4035
|
+
const plan = await promotionPlanEntries(baselineRoot, resolved.root);
|
|
4036
|
+
if (knowledgeFileTransactionPlanHash(plan) !== candidateRef.promotionPlanHash) {
|
|
4037
|
+
throw new Error("knowledge candidate promotion plan changed after approval");
|
|
4038
|
+
}
|
|
4039
|
+
return prepareKnowledgeFileTransaction({
|
|
4040
|
+
root: input.root,
|
|
4041
|
+
transactionRoot,
|
|
4042
|
+
purpose,
|
|
4043
|
+
mutations: await promotionMutations(resolved.root, plan),
|
|
4044
|
+
includeUnchanged: true,
|
|
4045
|
+
now: input.now
|
|
4046
|
+
});
|
|
4047
|
+
})
|
|
4048
|
+
);
|
|
4049
|
+
if (!pending) {
|
|
4050
|
+
throw new Error("knowledge promotion plan unexpectedly contained no file changes");
|
|
4051
|
+
}
|
|
4052
|
+
try {
|
|
4053
|
+
assertPromotionTransaction(pending, candidateRef);
|
|
4054
|
+
} catch (error) {
|
|
4055
|
+
try {
|
|
4056
|
+
await rollbackKnowledgeFileTransaction({
|
|
4057
|
+
root: input.root,
|
|
4058
|
+
transactionRoot,
|
|
4059
|
+
transaction: pending,
|
|
4060
|
+
beforeCommit() {
|
|
4061
|
+
mutationLock.assertOwned();
|
|
4062
|
+
input.assertRunOwned();
|
|
4063
|
+
}
|
|
4064
|
+
});
|
|
4065
|
+
await finishKnowledgeFileTransaction({
|
|
4066
|
+
root: input.root,
|
|
4067
|
+
transactionRoot,
|
|
4068
|
+
transaction: pending,
|
|
4069
|
+
assertOwned() {
|
|
4070
|
+
mutationLock.assertOwned();
|
|
4071
|
+
input.assertRunOwned();
|
|
4072
|
+
}
|
|
4073
|
+
});
|
|
4074
|
+
} catch (cleanupError) {
|
|
4075
|
+
throw new AggregateError(
|
|
4076
|
+
[error, cleanupError],
|
|
4077
|
+
"invalid knowledge promotion transaction could not be removed"
|
|
4078
|
+
);
|
|
4079
|
+
}
|
|
4080
|
+
throw error;
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
try {
|
|
4084
|
+
if (pending) {
|
|
4085
|
+
await applyKnowledgeFileTransaction({
|
|
4086
|
+
root: input.root,
|
|
4087
|
+
transactionRoot,
|
|
4088
|
+
transaction: pending,
|
|
4089
|
+
beforeCommit() {
|
|
4090
|
+
mutationLock.assertOwned();
|
|
4091
|
+
input.assertRunOwned();
|
|
4092
|
+
}
|
|
4093
|
+
});
|
|
4094
|
+
}
|
|
4095
|
+
mutationLock.assertOwned();
|
|
4096
|
+
input.assertRunOwned();
|
|
4097
|
+
if (await hashKnowledgeBase(input.root) !== candidateRef.candidateHash) {
|
|
4098
|
+
throw new Error("promoted knowledge content does not match the approved candidate");
|
|
4099
|
+
}
|
|
4100
|
+
await writeKnowledgeIndex(input.root);
|
|
4101
|
+
} catch (error) {
|
|
4102
|
+
if (!pending) throw error;
|
|
4103
|
+
try {
|
|
4104
|
+
mutationLock.assertOwned();
|
|
4105
|
+
input.assertRunOwned();
|
|
4106
|
+
} catch (ownershipError) {
|
|
4107
|
+
throw new AggregateError(
|
|
4108
|
+
[error, ownershipError],
|
|
4109
|
+
"knowledge promotion lost its lock and left the transaction pending"
|
|
4110
|
+
);
|
|
4111
|
+
}
|
|
4112
|
+
try {
|
|
4113
|
+
await rollbackKnowledgeFileTransaction({
|
|
4114
|
+
root: input.root,
|
|
4115
|
+
transactionRoot,
|
|
4116
|
+
transaction: pending,
|
|
4117
|
+
beforeCommit() {
|
|
4118
|
+
mutationLock.assertOwned();
|
|
4119
|
+
input.assertRunOwned();
|
|
4120
|
+
}
|
|
4121
|
+
});
|
|
4122
|
+
await finishKnowledgeFileTransaction({
|
|
4123
|
+
root: input.root,
|
|
4124
|
+
transactionRoot,
|
|
4125
|
+
transaction: pending,
|
|
4126
|
+
assertOwned() {
|
|
4127
|
+
mutationLock.assertOwned();
|
|
4128
|
+
input.assertRunOwned();
|
|
4129
|
+
}
|
|
4130
|
+
});
|
|
4131
|
+
await writeKnowledgeIndex(input.root);
|
|
4132
|
+
} catch (rollbackError) {
|
|
4133
|
+
throw new AggregateError(
|
|
4134
|
+
[error, rollbackError],
|
|
4135
|
+
"knowledge promotion failed and could not restore the previous files"
|
|
4136
|
+
);
|
|
4137
|
+
}
|
|
4138
|
+
throw error;
|
|
4139
|
+
}
|
|
4140
|
+
candidate.status = "promoted";
|
|
4141
|
+
candidate.updatedAt = input.now().toISOString();
|
|
4142
|
+
state.status = "promoted";
|
|
4143
|
+
state.promotedCandidateId = candidate.candidateId;
|
|
4144
|
+
state.updatedAt = input.now().toISOString();
|
|
4145
|
+
await saveState(runDir, state, input.onState);
|
|
4146
|
+
await ensurePromotionEvent(runDir, candidateRef);
|
|
4147
|
+
if (pending) {
|
|
4148
|
+
await finishKnowledgeFileTransaction({
|
|
4149
|
+
root: input.root,
|
|
4150
|
+
transactionRoot,
|
|
4151
|
+
transaction: pending,
|
|
4152
|
+
assertOwned() {
|
|
4153
|
+
mutationLock.assertOwned();
|
|
4154
|
+
input.assertRunOwned();
|
|
4155
|
+
}
|
|
4156
|
+
});
|
|
4157
|
+
}
|
|
4158
|
+
return promotionResult(input, candidate, true, false);
|
|
4159
|
+
},
|
|
4160
|
+
{
|
|
4161
|
+
staleMs: input.leaseTtlMs,
|
|
4162
|
+
resumeTransaction: {
|
|
4163
|
+
purpose,
|
|
4164
|
+
validate: (transaction) => assertPromotionTransaction(transaction, candidateRef)
|
|
4165
|
+
}
|
|
4166
|
+
}
|
|
4167
|
+
);
|
|
4168
|
+
}
|
|
4169
|
+
async function blockPromotion(input, candidate, reason) {
|
|
4170
|
+
await blockRun(input.runDir, input.state, reason, input.onState, input.now);
|
|
4171
|
+
await appendLedger(input.runDir, {
|
|
4172
|
+
type: "promotion.blocked",
|
|
4173
|
+
runId: input.candidateRef.runId,
|
|
4174
|
+
candidateId: candidate.candidateId,
|
|
4175
|
+
reason
|
|
4176
|
+
});
|
|
4177
|
+
return promotionResult(input, candidate, false, true);
|
|
4178
|
+
}
|
|
4179
|
+
function promotionResult(input, candidate, promoted, blocked) {
|
|
4180
|
+
return {
|
|
4181
|
+
runId: input.candidateRef.runId,
|
|
4182
|
+
state: input.state,
|
|
4183
|
+
candidate,
|
|
4184
|
+
...input.lifecycle ? { lifecycle: input.lifecycle } : {},
|
|
4185
|
+
promoted,
|
|
4186
|
+
blocked
|
|
4187
|
+
};
|
|
4188
|
+
}
|
|
4189
|
+
function candidateRefFor(runId, state, candidate) {
|
|
4190
|
+
if (!candidate.candidateHash) {
|
|
4191
|
+
throw new Error(`knowledge candidate '${candidate.candidateId}' has no content hash`);
|
|
4192
|
+
}
|
|
4193
|
+
if (!candidate.evidenceHash) {
|
|
4194
|
+
throw new Error(`knowledge candidate '${candidate.candidateId}' has no evidence hash`);
|
|
4195
|
+
}
|
|
4196
|
+
if (!candidate.promotionPlanHash) {
|
|
4197
|
+
throw new Error(`knowledge candidate '${candidate.candidateId}' has no promotion plan hash`);
|
|
4198
|
+
}
|
|
4199
|
+
if (candidate.status !== "candidate-ready" && candidate.status !== "promoted") {
|
|
4200
|
+
throw new Error(`knowledge candidate '${candidate.candidateId}' is not ready`);
|
|
4201
|
+
}
|
|
4202
|
+
return Object.freeze({
|
|
4203
|
+
schemaVersion: 1,
|
|
4204
|
+
kind: "knowledge-improvement-candidate",
|
|
4205
|
+
runId,
|
|
4206
|
+
candidateId: candidate.candidateId,
|
|
4207
|
+
goalHash: sha256(state.goal),
|
|
4208
|
+
baseHash: candidate.baseHash,
|
|
4209
|
+
candidateHash: candidate.candidateHash,
|
|
4210
|
+
evidenceHash: candidate.evidenceHash,
|
|
4211
|
+
promotionPlanHash: candidate.promotionPlanHash
|
|
4212
|
+
});
|
|
4213
|
+
}
|
|
4214
|
+
async function withMeasuredCandidateSnapshot(liveRoot, runDir, state, candidateRef, use) {
|
|
4215
|
+
assertStateIdentity(liveRoot, candidateRef, state);
|
|
4216
|
+
const candidate = state.candidates.find((entry) => entry.candidateId === candidateRef.candidateId);
|
|
4217
|
+
if (!candidate) {
|
|
4218
|
+
throw new Error(`knowledge candidate '${candidateRef.candidateId}' does not exist`);
|
|
4219
|
+
}
|
|
4220
|
+
const expectedRef = candidateRefFor(candidateRef.runId, state, candidate);
|
|
4221
|
+
if (canonicalJson(expectedRef) !== canonicalJson(candidateRef)) {
|
|
4222
|
+
throw new Error("knowledge candidate approval does not match the measured candidate");
|
|
4223
|
+
}
|
|
4224
|
+
const evidence = await assertCandidateEvidence(runDir, candidateRef);
|
|
4225
|
+
const relativePath = join3(
|
|
4226
|
+
"candidates",
|
|
4227
|
+
candidate.candidateId,
|
|
4228
|
+
"snapshots",
|
|
4229
|
+
candidateRef.candidateHash
|
|
4230
|
+
);
|
|
4231
|
+
return withSafeDirectory(runDir, relativePath, false, async (root) => {
|
|
4232
|
+
if (await hashKnowledgeBase(root) !== candidateRef.candidateHash) {
|
|
4233
|
+
throw new Error("knowledge candidate snapshot changed after approval");
|
|
4234
|
+
}
|
|
4235
|
+
const result = await use({ root, candidate, evidence });
|
|
4236
|
+
if (await hashKnowledgeBase(root) !== candidateRef.candidateHash) {
|
|
4237
|
+
throw new Error("knowledge candidate snapshot changed during use");
|
|
4238
|
+
}
|
|
4239
|
+
return result;
|
|
4240
|
+
});
|
|
4241
|
+
}
|
|
4242
|
+
async function withIsolatedCandidateCopy(sourceRoot, expectedHash, use) {
|
|
4243
|
+
const isolationRoot = await mkdtemp(join3(tmpdir(), "agent-knowledge-candidate-"));
|
|
4244
|
+
const candidateRoot = join3(isolationRoot, "candidate");
|
|
4245
|
+
try {
|
|
4246
|
+
await copyKnowledgeWorkspace(sourceRoot, candidateRoot);
|
|
4247
|
+
if (await hashKnowledgeBase(candidateRoot) !== expectedHash) {
|
|
4248
|
+
throw new Error("isolated knowledge candidate does not match its approved content");
|
|
4249
|
+
}
|
|
4250
|
+
const result = await use(candidateRoot);
|
|
4251
|
+
if (await hashKnowledgeBase(candidateRoot) !== expectedHash) {
|
|
4252
|
+
throw new Error("knowledge candidate snapshot changed during use");
|
|
4253
|
+
}
|
|
4254
|
+
return result;
|
|
4255
|
+
} finally {
|
|
4256
|
+
await rm(isolationRoot, { recursive: true, force: true });
|
|
4257
|
+
}
|
|
4258
|
+
}
|
|
4259
|
+
async function assertCandidateEvidence(runDir, candidate) {
|
|
4260
|
+
const evidence = KnowledgeImprovementEvidenceSchema.parse(
|
|
4261
|
+
JSON.parse(
|
|
4262
|
+
(await readRegularFileWithinRoot(
|
|
4263
|
+
runDir,
|
|
4264
|
+
candidateEvidenceRelativePath(candidate.candidateId)
|
|
4265
|
+
)).bytes.toString("utf8")
|
|
4266
|
+
)
|
|
4267
|
+
);
|
|
4268
|
+
const actualHash = contentHash(evidence);
|
|
4269
|
+
if (actualHash !== candidate.evidenceHash) {
|
|
4270
|
+
throw new Error(
|
|
4271
|
+
`knowledge candidate evidence changed after approval: expected ${candidate.evidenceHash}, got ${actualHash}`
|
|
4272
|
+
);
|
|
4273
|
+
}
|
|
4274
|
+
if (evidence.runId !== candidate.runId || evidence.candidateId !== candidate.candidateId || evidence.goalHash !== candidate.goalHash || evidence.baseHash !== candidate.baseHash || evidence.candidateHash !== candidate.candidateHash || evidence.promotionPlanHash !== candidate.promotionPlanHash || evidence.evaluation.passed !== true) {
|
|
4275
|
+
throw new Error("knowledge candidate evidence does not match the approved candidate");
|
|
4276
|
+
}
|
|
4277
|
+
return evidence;
|
|
4278
|
+
}
|
|
4279
|
+
function assertStateIdentity(root, candidateRef, state) {
|
|
4280
|
+
if (state.runId !== candidateRef.runId) {
|
|
4281
|
+
throw new Error("knowledge candidate run identity does not match persisted state");
|
|
4282
|
+
}
|
|
4283
|
+
if (resolve(state.root) !== resolve(root)) {
|
|
4284
|
+
throw new Error("knowledge candidate root does not match persisted state");
|
|
4285
|
+
}
|
|
4286
|
+
if (sha256(state.goal) !== candidateRef.goalHash) {
|
|
4287
|
+
throw new Error("knowledge candidate goal does not match persisted state");
|
|
4288
|
+
}
|
|
4289
|
+
if (state.baseHash !== candidateRef.baseHash) {
|
|
4290
|
+
throw new Error("knowledge candidate base does not match persisted state");
|
|
4291
|
+
}
|
|
4292
|
+
}
|
|
4293
|
+
async function assertCandidateWorkspace(runDir, candidate) {
|
|
4294
|
+
await withCandidateWorkspace(runDir, candidate, () => void 0);
|
|
4295
|
+
}
|
|
4296
|
+
async function withCandidateWorkspace(runDir, candidate, use) {
|
|
4297
|
+
return withSafeDirectory(
|
|
4298
|
+
runDir,
|
|
4299
|
+
join3("candidates", safePathSegmentSchema.parse(candidate.candidateId), "workspace"),
|
|
4300
|
+
false,
|
|
4301
|
+
use
|
|
4302
|
+
);
|
|
4303
|
+
}
|
|
3618
4304
|
function assertKnowledgeImprovementOptions(options) {
|
|
3619
4305
|
if (options.step && options.knowledgeResearch?.step) {
|
|
3620
4306
|
throw new Error("improveKnowledgeBase accepts either step or knowledgeResearch.step, not both");
|
|
@@ -3629,43 +4315,84 @@ function assertKnowledgeImprovementOptions(options) {
|
|
|
3629
4315
|
);
|
|
3630
4316
|
}
|
|
3631
4317
|
}
|
|
3632
|
-
async function
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
4318
|
+
async function measureCandidate(runId, runDir, state, candidate, options, now) {
|
|
4319
|
+
return withCandidateWorkspace(runDir, candidate, async (candidateRoot) => {
|
|
4320
|
+
const currentCandidateHash = await hashKnowledgeBase(candidateRoot);
|
|
4321
|
+
if (candidate.status === "candidate-ready" && candidate.candidateHash === currentCandidateHash && candidate.evidenceHash !== void 0 && candidate.promotionPlanHash !== void 0) {
|
|
4322
|
+
const evidence = await assertCandidateEvidence(
|
|
4323
|
+
runDir,
|
|
4324
|
+
candidateRefFor(runId, state, candidate)
|
|
4325
|
+
);
|
|
4326
|
+
return { candidate, evaluation: evidence.evaluation };
|
|
4327
|
+
}
|
|
4328
|
+
clearCandidateMeasurement(candidate);
|
|
4329
|
+
const lifecycles = [];
|
|
4330
|
+
if (candidate.status === "running") {
|
|
4331
|
+
const updateLifecycle = await runCandidateUpdateLifecycle(
|
|
4332
|
+
runId,
|
|
4333
|
+
candidate,
|
|
4334
|
+
candidateRoot,
|
|
4335
|
+
options,
|
|
4336
|
+
now
|
|
4337
|
+
);
|
|
4338
|
+
if (updateLifecycle) lifecycles.push(updateLifecycle);
|
|
4339
|
+
}
|
|
4340
|
+
return withFrozenCandidateWorkspace(runDir, candidate, candidateRoot, async (snapshot) => {
|
|
4341
|
+
const evaluationLifecycle = await runCandidateEvaluationLifecycle(
|
|
4342
|
+
runDir,
|
|
4343
|
+
candidate,
|
|
4344
|
+
snapshot.root,
|
|
4345
|
+
options,
|
|
4346
|
+
now
|
|
4347
|
+
);
|
|
4348
|
+
if (evaluationLifecycle) lifecycles.push(evaluationLifecycle);
|
|
4349
|
+
const lifecycle = mergeLifecycleResults(options.goal, lifecycles);
|
|
4350
|
+
const measured = await evaluateCandidate(
|
|
4351
|
+
runDir,
|
|
4352
|
+
state,
|
|
4353
|
+
candidate,
|
|
4354
|
+
snapshot,
|
|
4355
|
+
lifecycle,
|
|
4356
|
+
options,
|
|
4357
|
+
now
|
|
4358
|
+
);
|
|
4359
|
+
return { ...measured, ...lifecycle ? { lifecycle } : {} };
|
|
3664
4360
|
});
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
4361
|
+
});
|
|
4362
|
+
}
|
|
4363
|
+
async function runCandidateUpdateLifecycle(runId, candidate, candidateRoot, options, now) {
|
|
4364
|
+
if (!shouldRunUpdateStage(options)) return void 0;
|
|
4365
|
+
const lifecycle = await runRagKnowledgeImprovementLoop({
|
|
4366
|
+
goal: options.goal,
|
|
4367
|
+
acquireKnowledge: options.acquireKnowledge,
|
|
4368
|
+
knowledgeResearch: candidateKnowledgeResearchOptions(candidateRoot, options),
|
|
4369
|
+
updateKnowledge: candidateUpdateHook(runId, candidate, candidateRoot, options),
|
|
4370
|
+
enabledPhases: selectedStagePhases(options, UPDATE_PHASES),
|
|
4371
|
+
requiredPhases: selectedStageRequiredPhases(options, UPDATE_PHASES),
|
|
4372
|
+
signal: options.signal,
|
|
4373
|
+
now
|
|
4374
|
+
});
|
|
4375
|
+
return lifecycle;
|
|
4376
|
+
}
|
|
4377
|
+
async function runCandidateEvaluationLifecycle(runDir, candidate, candidateRoot, options, now) {
|
|
4378
|
+
if (!shouldRunEvaluationStage(options)) return void 0;
|
|
4379
|
+
const candidateIndex = await buildKnowledgeIndex(candidateRoot);
|
|
4380
|
+
const lifecycle = await runRagKnowledgeImprovementLoop({
|
|
4381
|
+
goal: options.goal,
|
|
4382
|
+
retrieval: options.retrieval ? {
|
|
4383
|
+
...options.retrieval,
|
|
4384
|
+
index: candidateIndex,
|
|
4385
|
+
runDir: options.retrieval.runDir ?? join3(runDir, "retrieval", candidate.candidateId)
|
|
4386
|
+
} : void 0,
|
|
4387
|
+
diagnose: options.diagnose,
|
|
4388
|
+
evaluateAnswers: options.evaluateAnswers,
|
|
4389
|
+
promote: options.decidePromotion,
|
|
4390
|
+
enabledPhases: selectedStagePhases(options, EVALUATION_PHASES),
|
|
4391
|
+
requiredPhases: selectedStageRequiredPhases(options, EVALUATION_PHASES),
|
|
4392
|
+
signal: options.signal,
|
|
4393
|
+
now
|
|
4394
|
+
});
|
|
4395
|
+
return lifecycle;
|
|
3669
4396
|
}
|
|
3670
4397
|
function candidateKnowledgeResearchOptions(candidateRoot, options) {
|
|
3671
4398
|
if (!options.step && !options.knowledgeResearch) return void 0;
|
|
@@ -3682,16 +4409,16 @@ function candidateKnowledgeResearchOptions(candidateRoot, options) {
|
|
|
3682
4409
|
readiness: rest.readiness ?? options.readiness
|
|
3683
4410
|
};
|
|
3684
4411
|
}
|
|
3685
|
-
function candidateUpdateHook(runId, candidate, options) {
|
|
4412
|
+
function candidateUpdateHook(runId, candidate, candidateRoot, options) {
|
|
3686
4413
|
if (!options.updateKnowledge) return void 0;
|
|
3687
4414
|
return (input) => options.updateKnowledge({
|
|
3688
4415
|
...input,
|
|
3689
4416
|
runId,
|
|
3690
4417
|
iteration: candidate.iteration,
|
|
3691
4418
|
candidateId: candidate.candidateId,
|
|
3692
|
-
root:
|
|
4419
|
+
root: candidateRoot,
|
|
3693
4420
|
baselineRoot: options.root,
|
|
3694
|
-
candidateRoot
|
|
4421
|
+
candidateRoot,
|
|
3695
4422
|
baseHash: candidate.baseHash
|
|
3696
4423
|
});
|
|
3697
4424
|
}
|
|
@@ -3716,20 +4443,6 @@ function selectedStagePhases(options, stagePhases) {
|
|
|
3716
4443
|
function selectedStageRequiredPhases(options, stagePhases) {
|
|
3717
4444
|
return (options.requiredPhases ?? []).filter((phase) => stagePhases.includes(phase));
|
|
3718
4445
|
}
|
|
3719
|
-
function recordLifecycle(candidate, stage, lifecycle) {
|
|
3720
|
-
const record = {
|
|
3721
|
-
stage,
|
|
3722
|
-
phases: lifecycle.phases,
|
|
3723
|
-
findingCount: lifecycle.findings.length,
|
|
3724
|
-
retrievalWinnerConfig: lifecycle.retrieval?.winnerConfig,
|
|
3725
|
-
answerQuality: lifecycle.answerQuality,
|
|
3726
|
-
promotionDecision: lifecycle.promotion
|
|
3727
|
-
};
|
|
3728
|
-
candidate.lifecycle = [...candidate.lifecycle ?? [], record];
|
|
3729
|
-
candidate.retrievalWinnerConfig = lifecycle.retrieval?.winnerConfig ?? candidate.retrievalWinnerConfig;
|
|
3730
|
-
candidate.answerQuality = lifecycle.answerQuality ?? candidate.answerQuality;
|
|
3731
|
-
candidate.promotionDecision = lifecycle.promotion ?? candidate.promotionDecision;
|
|
3732
|
-
}
|
|
3733
4446
|
function mergeLifecycleResults(goal, lifecycles) {
|
|
3734
4447
|
if (lifecycles.length === 0) return void 0;
|
|
3735
4448
|
return {
|
|
@@ -3749,54 +4462,88 @@ function lastDefined(values) {
|
|
|
3749
4462
|
}
|
|
3750
4463
|
return void 0;
|
|
3751
4464
|
}
|
|
3752
|
-
async function evaluateCandidate(runDir, state, candidate, lifecycle, options, now) {
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
4465
|
+
async function evaluateCandidate(runDir, state, candidate, snapshot, lifecycle, options, now) {
|
|
4466
|
+
return withBaselineSnapshot(runDir, state.baseHash, async (baselineRoot) => {
|
|
4467
|
+
const [baselineIndex, candidateIndex] = await Promise.all([
|
|
4468
|
+
buildKnowledgeIndex(baselineRoot),
|
|
4469
|
+
buildKnowledgeIndex(snapshot.root)
|
|
4470
|
+
]);
|
|
4471
|
+
const validation = validateKnowledgeIndex(candidateIndex, { strict: options.strict });
|
|
4472
|
+
const readiness = readinessFor(options, candidateIndex);
|
|
4473
|
+
const kbQuality = scoreKnowledgeBaseIndex(candidateIndex, {
|
|
4474
|
+
strict: options.strict,
|
|
4475
|
+
...options.kbQuality
|
|
4476
|
+
});
|
|
4477
|
+
const candidateHash = snapshot.hash;
|
|
4478
|
+
const metric = options.evaluate?.({
|
|
4479
|
+
runId: state.runId,
|
|
4480
|
+
iteration: candidate.iteration,
|
|
4481
|
+
root: options.root,
|
|
4482
|
+
baselineRoot,
|
|
4483
|
+
candidateRoot: snapshot.root,
|
|
4484
|
+
baselineIndex,
|
|
4485
|
+
candidateIndex,
|
|
4486
|
+
baseHash: state.baseHash,
|
|
4487
|
+
candidateHash,
|
|
4488
|
+
validation,
|
|
4489
|
+
readiness,
|
|
4490
|
+
kbQuality,
|
|
4491
|
+
lifecycle,
|
|
4492
|
+
signal: options.signal
|
|
4493
|
+
}) ?? defaultKnowledgeImprovementMetric(
|
|
4494
|
+
validation,
|
|
4495
|
+
readiness,
|
|
4496
|
+
options.readinessSpecs,
|
|
4497
|
+
kbQuality,
|
|
4498
|
+
lifecycle
|
|
4499
|
+
);
|
|
4500
|
+
const evaluation = applyLifecycleFailures(normalizeMetric(await metric), lifecycle);
|
|
4501
|
+
const measuredHash = await hashKnowledgeBase(snapshot.root);
|
|
4502
|
+
if (measuredHash !== candidateHash) {
|
|
4503
|
+
throw new Error(
|
|
4504
|
+
`knowledge candidate changed during evaluation: expected ${candidateHash}, got ${measuredHash}`
|
|
4505
|
+
);
|
|
4506
|
+
}
|
|
4507
|
+
candidate.candidateHash = candidateHash;
|
|
4508
|
+
candidate.promotionPlanHash = knowledgeFileTransactionPlanHash(
|
|
4509
|
+
await promotionPlanEntries(baselineRoot, snapshot.root)
|
|
4510
|
+
);
|
|
4511
|
+
const evidence = KnowledgeImprovementEvidenceSchema.parse(
|
|
4512
|
+
JSON.parse(
|
|
4513
|
+
JSON.stringify({
|
|
4514
|
+
schemaVersion: 1,
|
|
4515
|
+
kind: "knowledge-improvement-evidence",
|
|
4516
|
+
runId: state.runId,
|
|
4517
|
+
candidateId: candidate.candidateId,
|
|
4518
|
+
iteration: candidate.iteration,
|
|
4519
|
+
goalHash: sha256(state.goal),
|
|
4520
|
+
baseHash: candidate.baseHash,
|
|
4521
|
+
candidateHash,
|
|
4522
|
+
promotionPlanHash: candidate.promotionPlanHash,
|
|
4523
|
+
validation,
|
|
4524
|
+
readiness: readiness ?? null,
|
|
4525
|
+
kbQuality,
|
|
4526
|
+
evaluation,
|
|
4527
|
+
lifecycle: lifecycle ?? null
|
|
4528
|
+
})
|
|
4529
|
+
)
|
|
4530
|
+
);
|
|
4531
|
+
candidate.evidenceHash = contentHash(evidence);
|
|
4532
|
+
candidate.updatedAt = now().toISOString();
|
|
4533
|
+
await writeJsonDurableWithinRoot(
|
|
4534
|
+
runDir,
|
|
4535
|
+
candidateEvidenceRelativePath(candidate.candidateId),
|
|
4536
|
+
evidence
|
|
4537
|
+
);
|
|
4538
|
+
await appendLedger(runDir, {
|
|
4539
|
+
type: "candidate.evaluated",
|
|
4540
|
+
runId: state.runId,
|
|
4541
|
+
candidateId: candidate.candidateId,
|
|
4542
|
+
score: evaluation.score,
|
|
4543
|
+
passed: evaluation.passed
|
|
4544
|
+
});
|
|
4545
|
+
return { candidate, evaluation };
|
|
3798
4546
|
});
|
|
3799
|
-
return candidate;
|
|
3800
4547
|
}
|
|
3801
4548
|
function defaultKnowledgeImprovementMetric(validation, readiness, readinessSpecs, kbQuality, lifecycle) {
|
|
3802
4549
|
const blockingMissing = readiness?.report.blockingMissingRequirements.length ?? 0;
|
|
@@ -3814,15 +4561,18 @@ function defaultKnowledgeImprovementMetric(validation, readiness, readinessSpecs
|
|
|
3814
4561
|
const failedReasons = [
|
|
3815
4562
|
validation.ok ? void 0 : "candidate validation failed",
|
|
3816
4563
|
kbQuality.ok ? void 0 : "candidate KB quality check failed",
|
|
3817
|
-
blockingMissing === 0 ? void 0 : `${blockingMissing}/${blockingTotal} blocking knowledge requirements still missing
|
|
3818
|
-
lifecycle?.answerQuality && !lifecycle.answerQuality.passed ? "answer quality failed" : void 0,
|
|
3819
|
-
lifecycle?.promotion && !lifecycle.promotion.promoted ? `promotion decision held: ${lifecycle.promotion.reason}` : void 0
|
|
4564
|
+
blockingMissing === 0 ? void 0 : `${blockingMissing}/${blockingTotal} blocking knowledge requirements still missing`
|
|
3820
4565
|
].filter((reason) => Boolean(reason));
|
|
3821
4566
|
return {
|
|
3822
4567
|
score: average2(Object.values(dimensions)),
|
|
3823
4568
|
passed: failedReasons.length === 0,
|
|
3824
4569
|
dimensions,
|
|
3825
|
-
notes: failedReasons.length === 0 ? "candidate passed configured checks" : failedReasons.join("; ")
|
|
4570
|
+
notes: failedReasons.length === 0 ? "candidate passed configured checks" : failedReasons.join("; "),
|
|
4571
|
+
provenance: {
|
|
4572
|
+
evaluator: "@tangle-network/agent-knowledge/default-knowledge-improvement-metric",
|
|
4573
|
+
version: "1",
|
|
4574
|
+
method: "deterministic"
|
|
4575
|
+
}
|
|
3826
4576
|
};
|
|
3827
4577
|
}
|
|
3828
4578
|
function applyLifecycleFailures(metric, lifecycle) {
|
|
@@ -3839,33 +4589,126 @@ function applyLifecycleFailures(metric, lifecycle) {
|
|
|
3839
4589
|
};
|
|
3840
4590
|
}
|
|
3841
4591
|
function normalizeMetric(metric) {
|
|
3842
|
-
|
|
3843
|
-
throw new Error(`knowledge improvement score must be in [0, 1], got ${String(metric.score)}`);
|
|
3844
|
-
}
|
|
3845
|
-
return { ...metric, passed: Boolean(metric.passed) };
|
|
4592
|
+
return improvementMetricSchema.parse(metric);
|
|
3846
4593
|
}
|
|
3847
4594
|
async function createCandidateWorkspace(runDir, state, root, now) {
|
|
3848
4595
|
const iteration = state.candidates.length + 1;
|
|
3849
4596
|
const candidateId = stableId("kcand", `${state.runId}:${iteration}:${now().toISOString()}`);
|
|
3850
|
-
const candidateRoot =
|
|
4597
|
+
const candidateRoot = candidateWorkspacePath(runDir, candidateId);
|
|
3851
4598
|
await copyKnowledgeWorkspace(root, candidateRoot);
|
|
3852
4599
|
const createdAt = now().toISOString();
|
|
3853
4600
|
return {
|
|
3854
4601
|
iteration,
|
|
3855
4602
|
candidateId,
|
|
3856
|
-
candidateRoot,
|
|
3857
4603
|
baseHash: state.baseHash,
|
|
3858
4604
|
status: "running",
|
|
3859
4605
|
createdAt,
|
|
3860
4606
|
updatedAt: createdAt
|
|
3861
4607
|
};
|
|
3862
4608
|
}
|
|
4609
|
+
function candidateWorkspacePath(runDir, candidateId) {
|
|
4610
|
+
return join3(runDir, "candidates", safePathSegmentSchema.parse(candidateId), "workspace");
|
|
4611
|
+
}
|
|
4612
|
+
function baselineSnapshotPath(runDir) {
|
|
4613
|
+
return join3(runDir, "baseline");
|
|
4614
|
+
}
|
|
4615
|
+
async function createBaselineSnapshot(runDir, root, expectedHash) {
|
|
4616
|
+
const target = baselineSnapshotPath(runDir);
|
|
4617
|
+
try {
|
|
4618
|
+
await assertBaselineSnapshot(runDir, expectedHash);
|
|
4619
|
+
return;
|
|
4620
|
+
} catch (error) {
|
|
4621
|
+
if (!isMissingFile(error)) throw error;
|
|
4622
|
+
}
|
|
4623
|
+
const preparation = await mkdtemp(join3(runDir, "baseline-prepare-"));
|
|
4624
|
+
let activated = false;
|
|
4625
|
+
try {
|
|
4626
|
+
await copyKnowledgeWorkspace(root, preparation);
|
|
4627
|
+
const actualHash = await hashKnowledgeBase(preparation);
|
|
4628
|
+
if (actualHash !== expectedHash) {
|
|
4629
|
+
throw new Error(
|
|
4630
|
+
`knowledge base changed while baseline was frozen: expected ${expectedHash}, got ${actualHash}`
|
|
4631
|
+
);
|
|
4632
|
+
}
|
|
4633
|
+
await renameDurable(preparation, target);
|
|
4634
|
+
activated = true;
|
|
4635
|
+
} finally {
|
|
4636
|
+
if (!activated) await rm(preparation, { recursive: true, force: true });
|
|
4637
|
+
}
|
|
4638
|
+
}
|
|
4639
|
+
async function ensureBaselineSnapshot(runDir, root, expectedHash) {
|
|
4640
|
+
try {
|
|
4641
|
+
await assertBaselineSnapshot(runDir, expectedHash);
|
|
4642
|
+
} catch (error) {
|
|
4643
|
+
if (!isMissingFile(error)) throw error;
|
|
4644
|
+
const liveHash = await hashKnowledgeBase(root);
|
|
4645
|
+
if (liveHash !== expectedHash) {
|
|
4646
|
+
throw new Error(
|
|
4647
|
+
"knowledge improvement baseline snapshot is missing and cannot be reconstructed"
|
|
4648
|
+
);
|
|
4649
|
+
}
|
|
4650
|
+
await createBaselineSnapshot(runDir, root, expectedHash);
|
|
4651
|
+
}
|
|
4652
|
+
}
|
|
4653
|
+
async function assertBaselineSnapshot(runDir, expectedHash) {
|
|
4654
|
+
await withBaselineSnapshot(runDir, expectedHash, () => void 0);
|
|
4655
|
+
}
|
|
4656
|
+
async function withBaselineSnapshot(runDir, expectedHash, use) {
|
|
4657
|
+
return withSafeDirectory(runDir, "baseline", false, async (baselineRoot) => {
|
|
4658
|
+
const actualHash = await hashKnowledgeBase(baselineRoot);
|
|
4659
|
+
if (actualHash !== expectedHash) {
|
|
4660
|
+
throw new Error(
|
|
4661
|
+
`knowledge improvement baseline changed: expected ${expectedHash}, got ${actualHash}`
|
|
4662
|
+
);
|
|
4663
|
+
}
|
|
4664
|
+
return use(baselineRoot);
|
|
4665
|
+
});
|
|
4666
|
+
}
|
|
4667
|
+
async function withFrozenCandidateWorkspace(runDir, candidate, candidateRoot, use) {
|
|
4668
|
+
const snapshotsPath = join3(
|
|
4669
|
+
"candidates",
|
|
4670
|
+
safePathSegmentSchema.parse(candidate.candidateId),
|
|
4671
|
+
"snapshots"
|
|
4672
|
+
);
|
|
4673
|
+
return withSafeDirectory(runDir, snapshotsPath, true, async (snapshotsDir) => {
|
|
4674
|
+
const preparation = await mkdtemp(join3(snapshotsDir, "prepare-"));
|
|
4675
|
+
let activated = false;
|
|
4676
|
+
try {
|
|
4677
|
+
await copyKnowledgeWorkspace(candidateRoot, preparation);
|
|
4678
|
+
const hash = await hashKnowledgeBase(preparation);
|
|
4679
|
+
try {
|
|
4680
|
+
const result = await withSafeDirectory(snapshotsDir, hash, false, async (existing) => {
|
|
4681
|
+
if (await hashKnowledgeBase(existing) !== hash) {
|
|
4682
|
+
throw new Error("knowledge candidate snapshot does not match its content identity");
|
|
4683
|
+
}
|
|
4684
|
+
return use({ root: existing, hash });
|
|
4685
|
+
});
|
|
4686
|
+
await rm(preparation, { recursive: true, force: true });
|
|
4687
|
+
activated = true;
|
|
4688
|
+
return result;
|
|
4689
|
+
} catch (error) {
|
|
4690
|
+
if (!isMissingFile(error)) throw error;
|
|
4691
|
+
}
|
|
4692
|
+
await renameDurable(preparation, join3(snapshotsDir, hash));
|
|
4693
|
+
activated = true;
|
|
4694
|
+
return withSafeDirectory(snapshotsDir, hash, false, (root) => use({ root, hash }));
|
|
4695
|
+
} finally {
|
|
4696
|
+
if (!activated) await rm(preparation, { recursive: true, force: true });
|
|
4697
|
+
}
|
|
4698
|
+
});
|
|
4699
|
+
}
|
|
4700
|
+
function clearCandidateMeasurement(candidate) {
|
|
4701
|
+
delete candidate.candidateHash;
|
|
4702
|
+
delete candidate.evidenceHash;
|
|
4703
|
+
delete candidate.promotionPlanHash;
|
|
4704
|
+
}
|
|
3863
4705
|
function findActiveCandidate(state) {
|
|
3864
4706
|
return [...state.candidates].reverse().find((candidate) => candidate.status === "candidate-ready" || candidate.status === "running");
|
|
3865
4707
|
}
|
|
3866
4708
|
async function copyKnowledgeWorkspace(sourceRoot, targetRoot) {
|
|
3867
4709
|
await rm(targetRoot, { recursive: true, force: true });
|
|
3868
|
-
await
|
|
4710
|
+
await mkdir(join3(targetRoot, "knowledge"), { recursive: true });
|
|
4711
|
+
await mkdir(join3(targetRoot, "raw", "sources"), { recursive: true });
|
|
3869
4712
|
await copyIfExists(join3(sourceRoot, "knowledge"), join3(targetRoot, "knowledge"));
|
|
3870
4713
|
await copyIfExists(join3(sourceRoot, "raw"), join3(targetRoot, "raw"));
|
|
3871
4714
|
await copyIfExists(
|
|
@@ -3874,106 +4717,180 @@ async function copyKnowledgeWorkspace(sourceRoot, targetRoot) {
|
|
|
3874
4717
|
);
|
|
3875
4718
|
await writeKnowledgeIndex(targetRoot);
|
|
3876
4719
|
}
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
4720
|
+
function promotionTransactionPurpose(candidate) {
|
|
4721
|
+
return `knowledge-promotion:${contentHash(candidate)}`;
|
|
4722
|
+
}
|
|
4723
|
+
async function promotionPlanEntries(baselineRoot, candidateRoot) {
|
|
4724
|
+
const [before, after] = await Promise.all([
|
|
4725
|
+
knowledgeHashEntries(baselineRoot),
|
|
4726
|
+
knowledgeHashEntries(candidateRoot)
|
|
4727
|
+
]);
|
|
4728
|
+
const beforeByPath = new Map(before.map((entry) => [entry.path, entry]));
|
|
4729
|
+
const afterByPath = new Map(after.map((entry) => [entry.path, entry]));
|
|
4730
|
+
const paths = [
|
|
4731
|
+
.../* @__PURE__ */ new Set([...before.map((entry) => entry.path), ...after.map((entry) => entry.path)])
|
|
4732
|
+
].sort((left, right) => left.localeCompare(right));
|
|
4733
|
+
return paths.map((path) => {
|
|
4734
|
+
assertKnowledgeMutationPath(path);
|
|
4735
|
+
const beforeEntry = beforeByPath.get(path);
|
|
4736
|
+
const afterEntry = afterByPath.get(path);
|
|
4737
|
+
return {
|
|
4738
|
+
path,
|
|
4739
|
+
beforeHash: beforeEntry?.transactionHash ?? null,
|
|
4740
|
+
afterHash: afterEntry?.transactionHash ?? null,
|
|
4741
|
+
...beforeEntry ? { beforeMode: beforeEntry.mode } : {},
|
|
4742
|
+
...afterEntry ? { afterMode: afterEntry.mode } : {}
|
|
4743
|
+
};
|
|
4744
|
+
});
|
|
4745
|
+
}
|
|
4746
|
+
async function promotionMutations(candidateRoot, plan) {
|
|
4747
|
+
return Promise.all(
|
|
4748
|
+
plan.map(async (entry) => {
|
|
4749
|
+
if (entry.afterHash === null) return { path: entry.path, content: null };
|
|
4750
|
+
const file = await readRegularFileWithinRoot(candidateRoot, entry.path);
|
|
4751
|
+
const actualHash = createHash("sha256").update(file.bytes).digest("hex");
|
|
4752
|
+
if (actualHash !== entry.afterHash || file.mode !== entry.afterMode) {
|
|
4753
|
+
throw new Error(`knowledge candidate file changed before promotion: ${entry.path}`);
|
|
4754
|
+
}
|
|
4755
|
+
return { path: entry.path, content: file.bytes, mode: file.mode };
|
|
4756
|
+
})
|
|
3884
4757
|
);
|
|
3885
|
-
await writeKnowledgeIndex(root);
|
|
3886
4758
|
}
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
4759
|
+
function assertPromotionTransaction(transaction, candidate) {
|
|
4760
|
+
const actualPlanHash = knowledgeFileTransactionPlanHash(transaction.entries);
|
|
4761
|
+
if (actualPlanHash !== candidate.promotionPlanHash) {
|
|
4762
|
+
throw new Error(
|
|
4763
|
+
`knowledge promotion plan changed after approval: expected ${candidate.promotionPlanHash}, got ${actualPlanHash}`
|
|
4764
|
+
);
|
|
3892
4765
|
}
|
|
3893
|
-
if (options.replace) await rm(target, { recursive: true, force: true });
|
|
3894
|
-
await mkdir3(dirname2(target), { recursive: true });
|
|
3895
|
-
await cp(source, target, { recursive: true });
|
|
3896
4766
|
}
|
|
3897
|
-
async function
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
4767
|
+
async function ensurePromotionEvent(runDir, candidateRef) {
|
|
4768
|
+
if (await hasPromotionEvent(runDir, candidateRef)) return;
|
|
4769
|
+
await appendLedger(runDir, {
|
|
4770
|
+
type: "candidate.promoted",
|
|
4771
|
+
runId: candidateRef.runId,
|
|
4772
|
+
candidateId: candidateRef.candidateId,
|
|
4773
|
+
candidateHash: candidateRef.candidateHash,
|
|
4774
|
+
evidenceHash: candidateRef.evidenceHash,
|
|
4775
|
+
promotionPlanHash: candidateRef.promotionPlanHash
|
|
4776
|
+
});
|
|
4777
|
+
}
|
|
4778
|
+
async function hasPromotionEvent(runDir, candidateRef) {
|
|
4779
|
+
let matched = false;
|
|
4780
|
+
for (const row of await loadKnowledgeImprovementEventsFromRun(runDir)) {
|
|
4781
|
+
if (row.type !== "candidate.promoted" || row.candidateId !== candidateRef.candidateId) continue;
|
|
4782
|
+
if (row.runId !== candidateRef.runId || row.candidateHash !== candidateRef.candidateHash || row.evidenceHash !== candidateRef.evidenceHash || row.promotionPlanHash !== candidateRef.promotionPlanHash) {
|
|
4783
|
+
throw new Error("persisted knowledge promotion event conflicts with the approved candidate");
|
|
4784
|
+
}
|
|
4785
|
+
matched = true;
|
|
3901
4786
|
}
|
|
3902
|
-
|
|
3903
|
-
entries.push(...await hashFileEntry(root, sourceRegistry));
|
|
3904
|
-
entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
3905
|
-
return sha256(JSON.stringify(entries));
|
|
4787
|
+
return matched;
|
|
3906
4788
|
}
|
|
3907
|
-
async function
|
|
3908
|
-
const
|
|
4789
|
+
async function loadKnowledgeImprovementEvents(root, runId) {
|
|
4790
|
+
const parsedRunId = runIdSchema.parse(runId);
|
|
3909
4791
|
try {
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
if (entry.isDirectory()) out.push(...await hashTreeEntries(root, rel));
|
|
3920
|
-
else if (entry.isFile()) out.push(...await hashFileEntry(root, rel));
|
|
4792
|
+
return await withKnowledgeImprovementRun(
|
|
4793
|
+
root,
|
|
4794
|
+
parsedRunId,
|
|
4795
|
+
false,
|
|
4796
|
+
(runDir) => loadKnowledgeImprovementEventsFromRun(runDir)
|
|
4797
|
+
);
|
|
4798
|
+
} catch (error) {
|
|
4799
|
+
if (isMissingFile(error)) return [];
|
|
4800
|
+
throw error;
|
|
3921
4801
|
}
|
|
3922
|
-
return out;
|
|
3923
4802
|
}
|
|
3924
|
-
async function
|
|
4803
|
+
async function loadKnowledgeImprovementEventsFromRun(runDir) {
|
|
4804
|
+
const events = [];
|
|
3925
4805
|
try {
|
|
3926
|
-
const
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
4806
|
+
for (const file of await listRegularFilesWithinRoot(runDir, "events")) {
|
|
4807
|
+
const name = file.path.slice("events/".length);
|
|
4808
|
+
if (name.includes("/") || !name.endsWith(".json")) {
|
|
4809
|
+
throw new Error(`knowledge event store contains an unsupported entry: ${name}`);
|
|
4810
|
+
}
|
|
4811
|
+
events.push(parseKnowledgeImprovementEvent(JSON.parse(file.bytes.toString("utf8"))));
|
|
4812
|
+
}
|
|
4813
|
+
} catch (error) {
|
|
4814
|
+
if (!isMissingFile(error)) throw error;
|
|
4815
|
+
}
|
|
4816
|
+
const unique2 = /* @__PURE__ */ new Map();
|
|
4817
|
+
for (const event of events) {
|
|
4818
|
+
const { at: _at, ...semantic } = event;
|
|
4819
|
+
unique2.set(contentHash(semantic), event);
|
|
3930
4820
|
}
|
|
4821
|
+
return [...unique2.values()].sort((left, right) => left.at.localeCompare(right.at));
|
|
3931
4822
|
}
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
const
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
4823
|
+
function parseKnowledgeImprovementEvent(value) {
|
|
4824
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4825
|
+
throw new Error("knowledge improvement event is not an object");
|
|
4826
|
+
}
|
|
4827
|
+
const event = value;
|
|
4828
|
+
if (typeof event.at !== "string" || typeof event.type !== "string") {
|
|
4829
|
+
throw new Error("knowledge improvement event is missing at or type");
|
|
4830
|
+
}
|
|
4831
|
+
return event;
|
|
4832
|
+
}
|
|
4833
|
+
async function copyIfExists(source, target) {
|
|
4834
|
+
let sourceStat;
|
|
3942
4835
|
try {
|
|
3943
|
-
|
|
4836
|
+
sourceStat = await lstat(source);
|
|
4837
|
+
} catch (error) {
|
|
4838
|
+
if (isMissingFile(error)) return;
|
|
4839
|
+
throw error;
|
|
4840
|
+
}
|
|
4841
|
+
if (!sourceStat.isDirectory() && !sourceStat.isFile()) {
|
|
4842
|
+
throw new Error(`knowledge surface contains an unsupported filesystem entry: ${source}`);
|
|
4843
|
+
}
|
|
4844
|
+
await mkdir(dirname(target), { recursive: true });
|
|
4845
|
+
await cp(source, target, { recursive: sourceStat.isDirectory(), dereference: false });
|
|
4846
|
+
}
|
|
4847
|
+
async function hashKnowledgeBase(root) {
|
|
4848
|
+
return withKnowledgeRead(root, () => hashKnowledgeBaseUnlocked(root));
|
|
4849
|
+
}
|
|
4850
|
+
async function hashKnowledgeBaseUnlocked(root) {
|
|
4851
|
+
const entries = await knowledgeHashEntries(root);
|
|
4852
|
+
return sha256(JSON.stringify(entries.map(({ path, hash, mode }) => ({ path, hash, mode }))));
|
|
4853
|
+
}
|
|
4854
|
+
async function knowledgeHashEntries(root) {
|
|
4855
|
+
const entries = [];
|
|
4856
|
+
for (const rel of ["knowledge", "raw"]) {
|
|
3944
4857
|
try {
|
|
3945
|
-
await
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
4858
|
+
for (const file of await listRegularFilesWithinRoot(root, rel)) {
|
|
4859
|
+
entries.push(knowledgeFileIdentity(file.path, file.bytes, file.mode));
|
|
4860
|
+
}
|
|
4861
|
+
} catch (error) {
|
|
4862
|
+
if (!isMissingFile(error)) throw error;
|
|
3949
4863
|
}
|
|
4864
|
+
}
|
|
4865
|
+
const sourceRegistry = relative(root, layoutFor(root).sourceRegistryPath).replace(/\\/g, "/");
|
|
4866
|
+
try {
|
|
4867
|
+
const file = await readRegularFileWithinRoot(root, sourceRegistry);
|
|
4868
|
+
entries.push(knowledgeFileIdentity(sourceRegistry, file.bytes, file.mode));
|
|
3950
4869
|
} catch (error) {
|
|
3951
|
-
|
|
3952
|
-
if (code !== "EEXIST") throw error;
|
|
3953
|
-
const existing = await readLease(path);
|
|
3954
|
-
if (existing && new Date(existing.expiresAt).getTime() > now.getTime()) {
|
|
3955
|
-
throw new Error(
|
|
3956
|
-
`knowledge improvement run is locked by ${existing.ownerId} until ${existing.expiresAt}`
|
|
3957
|
-
);
|
|
3958
|
-
}
|
|
3959
|
-
await rm(path, { force: true });
|
|
3960
|
-
return acquireRunLease(runDir, options);
|
|
4870
|
+
if (!isMissingFile(error)) throw error;
|
|
3961
4871
|
}
|
|
4872
|
+
entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
4873
|
+
return entries;
|
|
4874
|
+
}
|
|
4875
|
+
function knowledgeFileIdentity(path, bytes, mode) {
|
|
3962
4876
|
return {
|
|
3963
|
-
ownerId: options.ownerId,
|
|
3964
4877
|
path,
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
}
|
|
4878
|
+
hash: sha256(bytes.toString("base64")),
|
|
4879
|
+
transactionHash: createHash("sha256").update(bytes).digest("hex"),
|
|
4880
|
+
mode
|
|
3969
4881
|
};
|
|
3970
4882
|
}
|
|
3971
|
-
async function
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
}
|
|
4883
|
+
async function acquireRunLease(runDir, options) {
|
|
4884
|
+
const path = join3(runDir, "run.lock.durable");
|
|
4885
|
+
const acquired = await acquireDurableFileLock(runDir, {
|
|
4886
|
+
lockfilePath: path,
|
|
4887
|
+
staleMs: options.ttlMs
|
|
4888
|
+
});
|
|
4889
|
+
return {
|
|
4890
|
+
ownerId: options.ownerId,
|
|
4891
|
+
assertOwned: acquired.assertOwned,
|
|
4892
|
+
release: acquired.release
|
|
4893
|
+
};
|
|
3977
4894
|
}
|
|
3978
4895
|
async function blockRun(runDir, state, reason, onState, now) {
|
|
3979
4896
|
state.status = "blocked";
|
|
@@ -3983,24 +4900,51 @@ async function blockRun(runDir, state, reason, onState, now) {
|
|
|
3983
4900
|
return state;
|
|
3984
4901
|
}
|
|
3985
4902
|
async function saveState(runDir, state, onState) {
|
|
3986
|
-
await
|
|
4903
|
+
await writeJsonDurableWithinRoot(
|
|
4904
|
+
runDir,
|
|
4905
|
+
"state.json",
|
|
4906
|
+
KnowledgeImprovementRunStateSchema.parse(state)
|
|
4907
|
+
);
|
|
3987
4908
|
await onState?.(state);
|
|
3988
4909
|
}
|
|
3989
4910
|
async function appendLedger(runDir, value) {
|
|
3990
|
-
const
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
4911
|
+
const type = value.type;
|
|
4912
|
+
if (typeof type !== "string" || type.length === 0) {
|
|
4913
|
+
throw new Error("knowledge improvement event requires a type");
|
|
4914
|
+
}
|
|
4915
|
+
const relativePath = join3("events", `${contentHash(value)}.json`).replace(/\\/g, "/");
|
|
4916
|
+
try {
|
|
4917
|
+
const file = await readRegularFileWithinRoot(runDir, relativePath);
|
|
4918
|
+
const existing = parseKnowledgeImprovementEvent(JSON.parse(file.bytes.toString("utf8")));
|
|
4919
|
+
const { at: _at, ...semantic } = existing;
|
|
4920
|
+
if (canonicalJson(semantic) !== canonicalJson(value)) {
|
|
4921
|
+
throw new Error("knowledge improvement event identity conflicts with durable content");
|
|
4922
|
+
}
|
|
4923
|
+
return;
|
|
4924
|
+
} catch (error) {
|
|
4925
|
+
if (!isMissingFile(error)) throw error;
|
|
4926
|
+
}
|
|
4927
|
+
await writeJsonDurableWithinRoot(runDir, relativePath, {
|
|
4928
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4929
|
+
...value
|
|
4930
|
+
});
|
|
3994
4931
|
}
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4932
|
+
function candidateEvidenceRelativePath(candidateId) {
|
|
4933
|
+
return join3("candidates", safePathSegmentSchema.parse(candidateId), "evidence.json").replace(
|
|
4934
|
+
/\\/g,
|
|
4935
|
+
"/"
|
|
4936
|
+
);
|
|
4937
|
+
}
|
|
4938
|
+
function descendantPath(root, path) {
|
|
4939
|
+
const value = relative(resolve(root), resolve(path)).replace(/\\/g, "/");
|
|
4940
|
+
if (value === "" || value === ".." || value.startsWith("../") || isAbsolute(value))
|
|
4941
|
+
return void 0;
|
|
4942
|
+
return value;
|
|
4001
4943
|
}
|
|
4002
|
-
function
|
|
4003
|
-
|
|
4944
|
+
function assertExactCandidatePlatform() {
|
|
4945
|
+
if (process.platform !== "linux") {
|
|
4946
|
+
throw new Error("exact knowledge candidate workflows require Linux directory descriptors");
|
|
4947
|
+
}
|
|
4004
4948
|
}
|
|
4005
4949
|
function average2(values) {
|
|
4006
4950
|
const finite = values.filter(Number.isFinite);
|
|
@@ -4009,8 +4953,7 @@ function average2(values) {
|
|
|
4009
4953
|
}
|
|
4010
4954
|
|
|
4011
4955
|
// src/kb-store.ts
|
|
4012
|
-
import {
|
|
4013
|
-
import { dirname as dirname3, join as join4 } from "path";
|
|
4956
|
+
import { z as z3 } from "zod";
|
|
4014
4957
|
var MemoryKbStore = class {
|
|
4015
4958
|
sources = /* @__PURE__ */ new Map();
|
|
4016
4959
|
pages = /* @__PURE__ */ new Map();
|
|
@@ -4062,33 +5005,115 @@ var MemoryKbStore = class {
|
|
|
4062
5005
|
return out.slice(-(query.limit ?? out.length)).map(clone);
|
|
4063
5006
|
}
|
|
4064
5007
|
};
|
|
4065
|
-
var
|
|
5008
|
+
var knowledgeEventsSchema = z3.array(KnowledgeEventSchema);
|
|
5009
|
+
var FileSystemKbStore = class {
|
|
4066
5010
|
constructor(dir) {
|
|
4067
|
-
super();
|
|
4068
5011
|
this.dir = dir;
|
|
4069
5012
|
}
|
|
4070
5013
|
dir;
|
|
5014
|
+
async putSource(source) {
|
|
5015
|
+
const parsed = SourceRecordSchema.parse(source);
|
|
5016
|
+
await this.updateIndex((index) => ({
|
|
5017
|
+
...index,
|
|
5018
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5019
|
+
sources: [parsed, ...index.sources.filter((entry) => entry.id !== parsed.id)]
|
|
5020
|
+
}));
|
|
5021
|
+
}
|
|
5022
|
+
async getSource(id) {
|
|
5023
|
+
return withKnowledgeRead(this.dir, async () => {
|
|
5024
|
+
const index = await this.readIndex();
|
|
5025
|
+
return clone(index?.sources.find((source) => source.id === id) ?? null);
|
|
5026
|
+
});
|
|
5027
|
+
}
|
|
5028
|
+
async listSources() {
|
|
5029
|
+
return withKnowledgeRead(this.dir, async () => clone((await this.readIndex())?.sources ?? []));
|
|
5030
|
+
}
|
|
5031
|
+
async putPage(page) {
|
|
5032
|
+
const parsed = KnowledgePageSchema.parse(page);
|
|
5033
|
+
await this.updateIndex((index) => {
|
|
5034
|
+
const pages = [parsed, ...index.pages.filter((entry) => entry.id !== parsed.id)];
|
|
5035
|
+
return {
|
|
5036
|
+
...index,
|
|
5037
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5038
|
+
pages,
|
|
5039
|
+
graph: buildKnowledgeGraph(pages)
|
|
5040
|
+
};
|
|
5041
|
+
});
|
|
5042
|
+
}
|
|
5043
|
+
async getPage(idOrPath) {
|
|
5044
|
+
return withKnowledgeRead(this.dir, async () => {
|
|
5045
|
+
const index = await this.readIndex();
|
|
5046
|
+
return clone(
|
|
5047
|
+
index?.pages.find((page) => page.id === idOrPath || page.path === idOrPath) ?? null
|
|
5048
|
+
);
|
|
5049
|
+
});
|
|
5050
|
+
}
|
|
5051
|
+
async listPages() {
|
|
5052
|
+
return withKnowledgeRead(this.dir, async () => clone((await this.readIndex())?.pages ?? []));
|
|
5053
|
+
}
|
|
4071
5054
|
async putIndex(index) {
|
|
4072
|
-
|
|
4073
|
-
await
|
|
5055
|
+
const parsed = KnowledgeIndexSchema.parse(index);
|
|
5056
|
+
await withKnowledgeMutation(
|
|
5057
|
+
this.dir,
|
|
5058
|
+
() => writeJsonDurableWithinRoot(this.dir, "index.json", parsed)
|
|
5059
|
+
);
|
|
4074
5060
|
}
|
|
4075
5061
|
async getIndex() {
|
|
4076
|
-
|
|
4077
|
-
return JSON.parse(await readFile3(join4(this.dir, "index.json"), "utf8"));
|
|
4078
|
-
} catch {
|
|
4079
|
-
return await super.getIndex();
|
|
4080
|
-
}
|
|
5062
|
+
return withKnowledgeRead(this.dir, () => this.readIndex());
|
|
4081
5063
|
}
|
|
4082
5064
|
async putEvent(event) {
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
5065
|
+
const parsed = KnowledgeEventSchema.parse(event);
|
|
5066
|
+
await withKnowledgeMutation(this.dir, async () => {
|
|
5067
|
+
const current = await this.readEvents();
|
|
5068
|
+
const next = [...current.filter((entry) => entry.id !== parsed.id), parsed].sort(
|
|
5069
|
+
(a, b) => a.createdAt.localeCompare(b.createdAt)
|
|
5070
|
+
);
|
|
5071
|
+
await writeJsonDurableWithinRoot(this.dir, "events.json", next);
|
|
5072
|
+
});
|
|
5073
|
+
}
|
|
5074
|
+
async listEvents(query = {}) {
|
|
5075
|
+
return withKnowledgeRead(this.dir, async () => {
|
|
5076
|
+
let events = await this.readEvents();
|
|
5077
|
+
if (query.type) events = events.filter((event) => event.type === query.type);
|
|
5078
|
+
if (query.target) events = events.filter((event) => event.target === query.target);
|
|
5079
|
+
return clone(events.slice(-(query.limit ?? events.length)));
|
|
5080
|
+
});
|
|
5081
|
+
}
|
|
5082
|
+
async updateIndex(change) {
|
|
5083
|
+
await withKnowledgeMutation(this.dir, async () => {
|
|
5084
|
+
const current = await this.readIndex() ?? emptyIndex2(this.dir);
|
|
5085
|
+
const next = KnowledgeIndexSchema.parse(change(current));
|
|
5086
|
+
await writeJsonDurableWithinRoot(this.dir, "index.json", next);
|
|
5087
|
+
});
|
|
5088
|
+
}
|
|
5089
|
+
async readIndex() {
|
|
5090
|
+
return readJsonFile(
|
|
5091
|
+
this.dir,
|
|
5092
|
+
"index.json",
|
|
5093
|
+
KnowledgeIndexSchema
|
|
5094
|
+
);
|
|
5095
|
+
}
|
|
5096
|
+
async readEvents() {
|
|
5097
|
+
return await readJsonFile(this.dir, "events.json", knowledgeEventsSchema) ?? [];
|
|
4086
5098
|
}
|
|
4087
5099
|
};
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
5100
|
+
function emptyIndex2(root) {
|
|
5101
|
+
return {
|
|
5102
|
+
root,
|
|
5103
|
+
generatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
5104
|
+
sources: [],
|
|
5105
|
+
pages: [],
|
|
5106
|
+
graph: { nodes: [], edges: [] }
|
|
5107
|
+
};
|
|
5108
|
+
}
|
|
5109
|
+
async function readJsonFile(root, relativePath, schema) {
|
|
5110
|
+
try {
|
|
5111
|
+
const file = await readRegularFileWithinRoot(root, relativePath);
|
|
5112
|
+
return schema.parse(JSON.parse(file.bytes.toString("utf8")));
|
|
5113
|
+
} catch (error) {
|
|
5114
|
+
if (error?.code === "ENOENT") return null;
|
|
5115
|
+
throw error;
|
|
5116
|
+
}
|
|
4092
5117
|
}
|
|
4093
5118
|
function clone(value) {
|
|
4094
5119
|
return value == null ? value : JSON.parse(JSON.stringify(value));
|
|
@@ -4330,11 +5355,11 @@ async function evaluateKnowledgeBaseReadiness(options) {
|
|
|
4330
5355
|
// src/release.ts
|
|
4331
5356
|
import {
|
|
4332
5357
|
evaluateReleaseConfidence,
|
|
4333
|
-
validateRunRecord
|
|
5358
|
+
validateRunRecord as validateRunRecord2
|
|
4334
5359
|
} from "@tangle-network/agent-eval";
|
|
4335
5360
|
function knowledgeReleaseReport(input) {
|
|
4336
5361
|
const baselineRuns = input.baselineRuns ?? [];
|
|
4337
|
-
const runRecords = [...input.candidateRuns, ...baselineRuns].map(
|
|
5362
|
+
const runRecords = [...input.candidateRuns, ...baselineRuns].map(validateRunRecord2);
|
|
4338
5363
|
const scorecard = evaluateReleaseConfidence({
|
|
4339
5364
|
target: "agent-knowledge-base",
|
|
4340
5365
|
candidateId: input.candidateId,
|
|
@@ -4782,6 +5807,9 @@ export {
|
|
|
4782
5807
|
KnowledgeEventSchema,
|
|
4783
5808
|
KnowledgeGraphEdgeSchema,
|
|
4784
5809
|
KnowledgeGraphNodeSchema,
|
|
5810
|
+
KnowledgeImprovementCandidateRefSchema,
|
|
5811
|
+
KnowledgeImprovementEvidenceSchema,
|
|
5812
|
+
KnowledgeImprovementRunStateSchema,
|
|
4785
5813
|
KnowledgeIndexSchema,
|
|
4786
5814
|
KnowledgePageSchema,
|
|
4787
5815
|
KnowledgeProposalParseError,
|
|
@@ -4860,18 +5888,21 @@ export {
|
|
|
4860
5888
|
initKnowledgeBase,
|
|
4861
5889
|
innerHtmlById,
|
|
4862
5890
|
inspectKnowledgeIndex,
|
|
5891
|
+
inspectPendingKnowledgeMutation,
|
|
4863
5892
|
investmentThesisSet,
|
|
4864
5893
|
isKnowledgeMemoryBenchmarkCase,
|
|
4865
5894
|
isSafeKnowledgePath,
|
|
4866
5895
|
isScaffoldPath,
|
|
4867
5896
|
kbIndexToText,
|
|
4868
5897
|
knowledgeBenchmarkJudge,
|
|
5898
|
+
knowledgeImprovementCandidateRef,
|
|
4869
5899
|
knowledgeImprovementRunDir,
|
|
4870
5900
|
knowledgeImprovementRunId,
|
|
4871
5901
|
knowledgeReleaseReport,
|
|
4872
5902
|
layoutFor,
|
|
4873
5903
|
lensDistribution,
|
|
4874
5904
|
lintKnowledgeIndex,
|
|
5905
|
+
loadKnowledgeImprovementEvents,
|
|
4875
5906
|
loadKnowledgeImprovementState,
|
|
4876
5907
|
loadKnowledgePages,
|
|
4877
5908
|
loadSourceRegistry,
|
|
@@ -4888,10 +5919,12 @@ export {
|
|
|
4888
5919
|
parseKnowledgeBenchmarkQrels,
|
|
4889
5920
|
parseKnowledgeWriteBlocks,
|
|
4890
5921
|
politeFetch,
|
|
5922
|
+
promoteKnowledgeCandidate,
|
|
4891
5923
|
proposeFromFinding,
|
|
4892
5924
|
proposeFromFindings,
|
|
4893
5925
|
ragAnswerQualityJudge,
|
|
4894
5926
|
reciprocalRankFusion,
|
|
5927
|
+
recoverPendingKnowledgeMutation,
|
|
4895
5928
|
renderKnowledgeBenchmarkReportMarkdown,
|
|
4896
5929
|
renderMemoryContext,
|
|
4897
5930
|
resetRetrievalHoldoutRegistry,
|
|
@@ -4936,6 +5969,7 @@ export {
|
|
|
4936
5969
|
triageSource,
|
|
4937
5970
|
validateKnowledgeIndex,
|
|
4938
5971
|
withCitedClaim,
|
|
5972
|
+
withKnowledgeImprovementCandidate,
|
|
4939
5973
|
writeJson,
|
|
4940
5974
|
writeKnowledgeIndex,
|
|
4941
5975
|
writeSourceRegistry
|