Phase 22 — Internal Knowledge Library (KB)

Meta-layer over Phase 19 playbook_memory. Phase 19 answers "which
WORKERS worked for this event"; KB answers "which CONFIG worked for
this playbook signature" — model choice, budget hints, pathway notes,
error corrections.

tests/multi-agent/kb.ts:
- computeSignature(): stable sha256 hash of the (kind, role, count,
  city, state) tuple sequence. Same scenario shape → same sig.
- indexRun(): extracts sig, embeds spec digest via sidecar, appends
  outcome record, upserts signature to data/_kb/signatures.jsonl.
- findNeighbors(): cosine-ranks the k most-similar signatures from
  prior runs for a target spec.
- detectErrorCorrections(): scans outcomes for same-sig fail→succeed
  pairs, diffs the model set, logs to error_corrections.jsonl.
- recommendFor(): feeds target digest + k-NN neighbors + recent
  corrections to the overview model, gets back a structured JSON
  recommendation (top_models, budget_hints, pathway_notes), appends
  to pathway_recommendations.jsonl. JSON-shape constrained so the
  executor can inherit it mechanically.
- loadRecommendation(): at scenario start, pulls newest rec matching
  this sig (or nearest).

scenario.ts:
- Reads KB recommendation at startup (alongside prior lessons).
- Injects pathway_notes into guidanceFor() executor context.
- After retrospective, indexes the run + synthesizes next rec.

Cold-start behavior: first run with no history writes a low-confidence
"no prior data" rec so the signal that something was attempted is
captured. Second run gets "low confidence, 0 neighbors" until a third
distinct sig gives the embedder something to compare against — hence
the upcoming scenario generator.

VERIFIED:
- data/_kb/ populated after one scenario run: 1 outcome (sig=4674…,
  4/5 ok, 16 turns total), 1 signature, 2 recs (cold + post-run).
- Recommendation JSON-parsed cleanly from gpt-oss:20b overview model.

PRD Phase 22 added with file layout, cycle description, and the
rationale for file-based MVP → Rust port progression that matches
how Phase 21 primitives shipped.

What's NOT here yet (batched follow-ups per J's request, tested
between each):
- Lift the k=10 hybrid_search cap to adaptive k=max(count*5, 20)
- Scenario generator to bulk-populate KB with varied signatures
- Rust re-weighting: push playbook_memory success signal INTO
  hybrid_search scoring, not just post-hoc boost
This commit is contained in:
root
2026-04-20 20:27:12 -05:00
parent 0c4868c191
commit 9c1400d738
3 changed files with 529 additions and 2 deletions
+48 -1
View File
@@ -35,6 +35,7 @@ import {
reviewerPrompt,
GATEWAY,
} from "./agent.ts";
import { indexRun, recommendFor, loadRecommendation, type PathwayRecommendation } from "./kb.ts";
import { mkdir, writeFile, appendFile } from "node:fs/promises";
import { join } from "node:path";
@@ -154,6 +155,7 @@ interface ScenarioContext {
results: EventResult[];
gap_signals: Array<{ event: string; category: string; detail: string }>;
prior_lessons: PriorLesson[];
pathway_rec?: PathwayRecommendation | null;
}
interface PriorLesson {
@@ -580,7 +582,14 @@ CAST(reliability AS DOUBLE) > 0.7.`;
).join("\n")
: "";
return `${schemaLock}\n\nEVENT FOCUS:\n${base}${priorHint}`;
// Phase 22 pathway recommendation — if the KB synthesized a "best
// path" from neighbor runs, inject it as concrete pre-run guidance.
// Keep terse; the full rationale lives in the KB file.
const pathwayHint = ctx.pathway_rec && ctx.pathway_rec.pathway_notes
? `\n\nKB PATHWAY RECOMMENDATION (synthesized from ${ctx.pathway_rec.neighbors_consulted.length} neighbor runs, confidence=${ctx.pathway_rec.confidence}):\n${ctx.pathway_rec.pathway_notes.slice(0, 600)}`
: "";
return `${schemaLock}\n\nEVENT FOCUS:\n${base}${priorHint}${pathwayHint}`;
}
// =================== Artifact generation ===================
@@ -1101,6 +1110,7 @@ async function writeRetrospective(ctx: ScenarioContext): Promise<void> {
// =================== Main driver ===================
async function main() {
const runStart = Date.now();
const specPath = process.argv[2];
const spec: ScenarioSpec = specPath
? JSON.parse(await Bun.file(specPath).text())
@@ -1112,6 +1122,18 @@ async function main() {
const prior_lessons = await loadPriorLessons(spec);
// Phase 22 KB — load any pathway recommendation for this signature.
// The recommender is called at END of prior runs and synthesizes
// configuration + pathway notes from nearest-neighbor history.
// Nothing on first run (cold start); populates over time.
const pathwayRec = await loadRecommendation(spec).catch(() => null);
if (pathwayRec) {
console.log(`▶ KB recommendation loaded: confidence=${pathwayRec.confidence} from ${pathwayRec.neighbors_consulted.length} neighbors`);
if (pathwayRec.pathway_notes) {
console.log(` pathway: ${pathwayRec.pathway_notes.slice(0, 120)}${pathwayRec.pathway_notes.length > 120 ? "…" : ""}`);
}
}
const ctx: ScenarioContext = {
spec,
out_dir,
@@ -1119,6 +1141,7 @@ async function main() {
results: [],
gap_signals: [],
prior_lessons,
pathway_rec: pathwayRec,
};
// Initialize output files
@@ -1254,6 +1277,30 @@ async function main() {
await writeRetrospective(ctx);
// Phase 22 KB — index this run + synthesize recommendation for next
// time this signature (or similar ones) show up. Event-driven cycle:
// run ends → KB updates → next run reads rec at startup.
try {
const elapsed = (Date.now() - runStart) / 1000;
const { sig_hash } = await indexRun(out_dir, spec, {
executor: EXECUTOR_MODEL,
reviewer: REVIEWER_MODEL,
overview: OVERVIEW_MODEL,
overview_cloud: OVERVIEW_CLOUD,
}, elapsed);
console.log(`▶ KB indexed: sig=${sig_hash} (${elapsed.toFixed(1)}s)`);
const newRec = await recommendFor(spec, {
overview_model: OVERVIEW_MODEL,
cloud: OVERVIEW_CLOUD,
k: 5,
});
if (newRec) {
console.log(`▶ KB recommendation written: confidence=${newRec.confidence} (${newRec.neighbors_consulted.length} neighbors consulted)`);
}
} catch (e) {
console.log(` (KB update skipped: ${(e as Error).message})`);
}
const okCount = ctx.results.filter(r => r.ok).length;
if (okCount < ctx.results.length) {
console.log(`\n⚠ ${okCount}/${ctx.results.length} events succeeded. See ${out_dir}/report.md for gaps.`);