From 5707672a09b383dbf2141d50979ca59f4e9bdb2b Mon Sep 17 00:00:00 2001 From: amosblomqvist Date: Tue, 29 Sep 2026 04:10:38 +0300 Subject: [PATCH] =?UTF-8?q?auditor=20grounding=20=E2=80=94=20a=20prescribe?= =?UTF-8?q?d,=20bounded=20exploration=20procedure=20+=20born-in=20find=20p?= =?UTF-8?q?ointers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Observed live: the auditor looped find for a very long time, never touching list_sessions/list_propositions. Root cause was the prompt: its only procedure text was 'For each proposition, search the graph (its key words, its synonyms, its phrasings)' — a per-proposition find loop with no starting point, no sense of the space's extent, and no termination rule. And find returned id+name+statement with no session pointer, so results never composed with the listing tools (owner diagnosis, 2026-09-29). - AUDITOR_INSTRUCTIONS now prescribes the process: (1) list_sessions ONCE — the graph is small and finite, this shows its entire extent; (2) read plausibly-related sessions with list_propositions (full text) — every realistic candidate seen in a few calls; (3) find as the targeted sweep; (4) SUBMIT once related sessions are read — never re-search seen ground - find hits now carry 'born in: ' — results point at sessions the auditor can read whole (legacy no-provenance nodes name none) - identity doctrine untouched: equivalence bias, linkTo mechanics, verdicts-only deliverable --- src/agent/subagents/extraction.ts | 18 ++++++++++++++---- src/agent/tools/graph.ts | 15 +++++++++++++-- tests/graph-tools.test.ts | 4 +++- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/agent/subagents/extraction.ts b/src/agent/subagents/extraction.ts index b88935c..8b65e33 100644 --- a/src/agent/subagents/extraction.ts +++ b/src/agent/subagents/extraction.ts @@ -466,12 +466,22 @@ has read a source (a teaching session, or the author's personal notes) and repor follows/supports edges. You NEVER see the source itself — the extractor is fully trusted, and you do not re-check its work. Your job is IDENTITY: connecting those findings to the CURRENT knowledge graph, preparing the merge. -You explore the graph through your tools: find (glob over names + statements + aliases), list_sessions, and -list_propositions (full statement text, per session). The graph may be large — search, don't assume. +You explore the graph through your tools: list_sessions, list_propositions, and find (glob over names + +statements + aliases; each hit also says which session it was born in). The graph is SMALL and FINITE — a handful of +sessions — and your job is done as soon as you have SEEN the plausible candidates. FOLLOW THIS PROCEDURE: + +1. GROUND yourself first: call list_sessions ONCE. This one call shows the entire space — every session that ever + contributed propositions, and how many. Now you know the extent of what you are searching; nothing is hidden. +2. READ the plausibly-related sessions: for each session whose topic could overlap these findings, call + list_propositions — every proposition born there, with full statement text. A few calls and you have read + every realistic identity candidate in the graph. +3. find is your targeted sweep: use it for phrasings, synonyms, and neighboring ideas the session reads made you + wonder about — its hits point back at sessions you can read whole. +4. SUBMIT. Once every plausibly-related session has been read (and stray finds come back empty), you are DONE. + Do not re-search ground you have already seen; more patterns over the same nodes are wasted work. IDENTITY — links, per proposition: -- For each proposition, search the graph (its key words, its synonyms, its phrasings). Does an EXISTING node already - state this same proposition? +- For each proposition: does an EXISTING node — one you have now SEEN — already state this same proposition? - If yes: add { "id": , "linkTo": } to "links". The session's phrasings join its aliases and its provenance; the node keeps its existing name. - If no: add nothing — a new node is created with the extractor's name as its label. diff --git a/src/agent/tools/graph.ts b/src/agent/tools/graph.ts index fcf703c..34a92c8 100644 --- a/src/agent/tools/graph.ts +++ b/src/agent/tools/graph.ts @@ -123,7 +123,8 @@ export function auditorGraphTools(ctx: GraphToolContext): Record => { const nodes = getGraph(deps.graphStore).nodes; @@ -131,7 +132,17 @@ export function auditorGraphTools(ctx: GraphToolContext): Record globMatch(query, n.name) || globMatch(query, n.statement) || n.aliases.some((a) => globMatch(query, a)), ); if (hits.length === 0) return `No existing proposition matches "${query}".`; - const lines = hits.map((n) => `- ${n.id} :: ${n.name}\n statement: ${n.statement}`); + // BORN-IN POINTER (owner, 2026-09-29): a hit names its birth + // session(s) — find results now compose with list_propositions + // (read the whole session the hit came from) instead of being + // dead ends. Legacy no-provenance nodes name none. + const lines = hits.map((n) => { + const born = [...new Set(n.provenance.map((pt) => pt.chatId))]; + return ( + `- ${n.id} :: ${n.name}\n statement: ${n.statement}` + + (born.length > 0 ? `\n born in: ${born.join(", ")}` : "") + ); + }); return `${hits.length} match(es):\n${lines.join("\n")}`; }), }, diff --git a/tests/graph-tools.test.ts b/tests/graph-tools.test.ts index bf3b126..40dd19b 100644 --- a/tests/graph-tools.test.ts +++ b/tests/graph-tools.test.ts @@ -188,12 +188,14 @@ describe("auditor graph tools", () => { ]; const { chatStore, graphStore } = fakeStores(nodes); - it("find: glob over names + statements + aliases, returning id + name + statement", async () => { + it("find: glob over names + statements + aliases, returning id + name + statement + BIRTH SESSION (owner, 2026-09-29)", async () => { const tools = auditorGraphTools({ chatStore, graphStore }); const out = (await tools.find!.execute!("tc", { query: "*will to power*" })).details as string; assert.match(out, /1 match\(es\):/); assert.match(out, /- n1 :: Will to power/); assert.match(out, /statement: The will to power is Nietzsche's central drive\./); + // The born-in pointer: hits compose with list_propositions. + assert.match(out, /born in: c1/); // Statements and aliases hit too — and a bare term gets implicit stars. const byStatement = (await tools.find!.execute!("tc", { query: "living again" })).details as string; assert.match(byStatement, /n2 :: Eternal recurrence/);