wave 7: the prober reads its map — graph-context injection dies whole

The owner's zero-configuration ruling (2026-09-23): nothing configures
the graph beyond chat models. The prior-knowledge injection — its budget
dial, its runner, its marker part, its dock/dispatch entries — is
removed across all bands; the prober now reads the learner's persistent
knowledge map on demand through two gated tools instead:

- list_sessions: every birth session, names + kinds + counts + dates
  (graphSessionIndex, Platform's core seam)
- list_propositions(session): what was born there + the session's own
  latest understanding level + that evaluation's date
  (graphSessionPropositions) — names/levels/dating ONLY, no statement
  text (owner ruling: the map's organization is the prober's business,
  its prose is not)

Registered via createProberTools when chat-handler passes graphStore
(the graphToolsEnabled gate decided server-side, per-turn fresh); the
prober doctrine gains the map-review nudge at session start.

Also: runner's JobKind special-casing collapses (KIND_ROLE/KIND_RUNNERS
are plain records again), semantic-clusters.ts dies with its test, the
embedder dep's doc tells the wave-7 stub story, and ports.ts's
readClusterLabels/readNamingRuns surface stays untouched for Platform's
ledger-port interlude.

848/848 tests, typecheck/lint/build green.
This commit is contained in:
amosblomqvist
2026-09-23 15:24:48 +03:00
parent a72753b059
commit 6b13583e96
40 changed files with 373 additions and 1990 deletions
+75
View File
@@ -0,0 +1,75 @@
import { z } from "zod";
import { lhTool, type LhTool } from "../lh-tool.ts";
import { graphSessionIndex, graphSessionPropositions } from "../../core/graph.ts";
import type { ChatStore, GraphStore } from "../../core/ports.ts";
/**
* THE PROBER'S GRAPH TOOLS (wave 7, owner-locked 2026-09-23): context
* injection is dead — zero configuration beyond chat models. The prober
* reads the learner's persistent knowledge map through two tools instead,
* replacing the old injected block with on-demand eyes.
*
* Names + levels + dating ONLY — no statement text, no session contents
* (owner ruling: the map's ORGANIZATION is the prober's business, its
* prose is not). The data comes from Platform's pure core seams
* (graphSessionIndex / graphSessionPropositions) over the same ports the
* snapshot reads, so the prober and the learner's graph view can never
* disagree.
*
* Registered on the prober ONLY when the settings gate opens
* (graphToolsEnabled — the graph master switch AND the agent.graphTools
* toggle); the composition root decides, the factory just receives the
* store (absent = the gate is closed, no map tools — tests drive this).
*/
/** What the graph tools close over: the stores the session views read. */
export interface GraphToolContext {
chatStore: ChatStore;
graphStore: GraphStore;
}
export function proberGraphTools(ctx: GraphToolContext): Record<string, LhTool<any, any>> {
const deps = { chatStore: ctx.chatStore, graphStore: ctx.graphStore };
return {
list_sessions: lhTool({
name: "list_sessions",
description:
"List the sessions of the learner's persistent knowledge graph — every chat or imported-notes file that ever contributed a proposition, most recent first. The map is organized into birth-session clusters (propositions grouped by the session they were first learned in), and session names are those clusters' labels. " +
"Each line: the session's name, kind (chat/file), how many propositions were born in it, its dates, and its id — use that id with list_propositions. " +
"Reading this first shows what he has already covered and when, before you probe anything.",
schema: z.object({}),
execute: async (): Promise<string> => {
const sessions = graphSessionIndex(deps);
if (sessions.length === 0) return "The knowledge graph is empty — no session has contributed propositions yet.";
const lines = sessions.map((s, i) =>
`${i + 1}. ${s.name} (${s.kind}, ${s.propositions} proposition${s.propositions === 1 ? "" : "s"}, ` +
`born ${s.createdAt ?? "unknown date"}${s.updatedAt ? `, last active ${s.updatedAt}` : ""}) — id: ${s.id}`,
);
return `${sessions.length} session(s) in the knowledge graph, most recent first:\n${lines.join("\n")}`;
},
}),
list_propositions: lhTool({
name: "list_propositions",
description:
"List the propositions BORN in one knowledge-graph session — what was first learned there — each with his latest understanding level on the scale solid / shaky / accepted / resisted / not yet engaged, and the date that level was set. " +
"Names, levels, and dating only — no statement text, no session contents. " +
"The level is that session's own latest evaluation; other sessions may have moved it since, so cross-session levels can differ.",
schema: z.object({
session: z
.string()
.min(1)
.describe("The session's id exactly as list_sessions reported it (a chat id or a notes source id)"),
}),
execute: async ({ session }): Promise<string> => {
const propositions = graphSessionPropositions(deps, session);
if (propositions.length === 0) {
return `No propositions born in session "${session}". Call list_sessions for the current ids.`;
}
const lines = propositions.map(
(p) => `- ${p.name} — ${p.understanding ?? "never evaluated"}${p.evaluatedAt ? ` (as of ${p.evaluatedAt})` : ""}`,
);
return `${propositions.length} proposition(s) born in this session:\n${lines.join("\n")}`;
},
}),
};
}