/** * Agent Forum v4 - Historian Agent * * Role: Prevent regression and historical repetition. * Inputs: sqlite-vec, Git Notes * Outputs: Contextual injection (passing historical context to other agents/pipeline) */ import { parseArgs } from "https://deno.land/std@0.224.0/cli/parse_args.ts"; import { Database } from "jsr:@db/sqlite"; import * as sqliteVec from "npm:sqlite-vec"; async function runCommand( cmd: string, args: string[], ): Promise<{ code: number; stdout: string; stderr: string }> { const command = new Deno.Command(cmd, { args, stdout: "piped", stderr: "piped", }); const { code, stdout, stderr } = await command.output(); const decoder = new TextDecoder(); return { code, stdout: decoder.decode(stdout).trim(), stderr: decoder.decode(stderr).trim(), }; } /** * Fetches Git Notes for a specific namespace across the git history. */ async function fetchGitNotes(namespace: string): Promise { console.log(` Fetching Git Notes for namespace: ${namespace}...`); // Use git log to show notes for the specified namespace const cmd = await runCommand("git", [ "log", `--show-notes=${namespace}`, "--pretty=format:%N", ]); if (cmd.code !== 0 || !cmd.stdout) { return []; } // Split by newlines and filter out empty lines (since %N can return empty if no note) const lines = cmd.stdout.split("\n").filter((line) => line.trim() !== ""); return lines; } async function gatherHistoricalContext() { console.log("-> Historian Agent: Gathering historical context..."); // 1. Construct search string from file changes const diffCmd = await runCommand("git", ["diff", "--name-only", "HEAD~1", "HEAD"]); const searchVectorString = diffCmd.stdout.split("\n").filter(Boolean).join(" "); console.log(` Search vector string derived from diff: "${searchVectorString}"`); // 2. Fetch from Vector DB console.log(" Extracting SQLite databases from meta-state branch..."); const extractedVectors: string[] = []; try { const checkDocs = await runCommand("git", ["ls-tree", "meta-state", "docs_graph.sqlite"]); const checkTelemetry = await runCommand("git", ["ls-tree", "meta-state", "telemetry_graph.sqlite"]); // Dummy vector string based on search string for PoC query, normally we would generate an embedding for searchVectorString const dummyVector = new Uint8Array(new Float32Array([0,0,0]).buffer); if (checkDocs.code === 0 && checkDocs.stdout.trim() !== "") { const tempDbPath = await Deno.makeTempFile({ suffix: ".sqlite" }); const dbFile = await Deno.open(tempDbPath, { write: true }); const cmd = new Deno.Command("git", { args: ["show", "meta-state:docs_graph.sqlite"], stdout: "piped" }); const output = await cmd.output(); await dbFile.write(output.stdout); dbFile.close(); const db = new Database(tempDbPath); db.enableLoadExtension = true; sqliteVec.load(db); db.enableLoadExtension = false; try { const results = db.prepare("SELECT text, vec_distance_cosine(embedding, ?) as similarity FROM docs ORDER BY similarity DESC LIMIT 3").all(dummyVector); for (const row of results) { extractedVectors.push(`Doc2Vec: ${row.text}`); } } catch (e) { console.warn(" ⚠️ Could not query docs_graph.sqlite:", e); } db.close(); await Deno.remove(tempDbPath); } if (checkTelemetry.code === 0 && checkTelemetry.stdout.trim() !== "") { const tempDbPath = await Deno.makeTempFile({ suffix: ".sqlite" }); const dbFile = await Deno.open(tempDbPath, { write: true }); const cmd = new Deno.Command("git", { args: ["show", "meta-state:telemetry_graph.sqlite"], stdout: "piped" }); const output = await cmd.output(); await dbFile.write(output.stdout); dbFile.close(); const db = new Database(tempDbPath); db.enableLoadExtension = true; sqliteVec.load(db); db.enableLoadExtension = false; try { // Assume telemetry table for PoC purposes const results = db.prepare("SELECT text, vec_distance_cosine(embedding, ?) as similarity FROM telemetry ORDER BY similarity DESC LIMIT 3").all(dummyVector); for (const row of results) { extractedVectors.push(`Telemetry2Vec: ${row.text}`); } } catch (e) { console.warn(" ⚠️ Could not query telemetry_graph.sqlite:", e); } db.close(); await Deno.remove(tempDbPath); } } catch (e) { console.warn(" ⚠️ Failed to query sqlite db:", e); } // 3. Fetch Git Notes (Reasoning, Telemetry, etc.) const reasoningNotes = await fetchGitNotes("reasoning"); const aiNotes = await fetchGitNotes("ai"); const combinedContext = { agent: "Historian", timestamp: Date.now(), extractedVectors: extractedVectors.length ? extractedVectors : ["No historical vector matches found."], historicalNotes: [...reasoningNotes, ...aiNotes], directive: "Ensure new Coder implementations avoid previously failed architectural patterns.", }; console.log("-> Historian Agent: Context gathered successfully."); // Provide contextual injection (e.g., via writing to a specific Git Note for the Coder) const jsonString = JSON.stringify(combinedContext); const ref = `refs/notes/historical_context`; const cmd = await runCommand("git", [ "notes", "--ref", ref, "add", "-f", "-m", jsonString, "HEAD", ]); if (cmd.code !== 0) { console.error( `⚠️ Failed to inject historical context to Git Notes:`, cmd.stderr, ); } else { console.log( "-> Contextual injection written to Git Notes (refs/notes/historical_context).", ); } } async function main() { const args = parseArgs(Deno.args, { boolean: ["run"], }); if (args["run"]) { await gatherHistoricalContext(); } else { console.log("Historian Agent installed. Run with --run flag."); } } if (import.meta.main) { await main(); }