extraction cleanup: events ledger dies, markdown source view, auditor-as-linker (2026-09-27 rulings)
The graph becomes raw reasoning chains only. Quiz outcomes and understanding observations are no longer formalized; assessment moves to the future practice module. Events ledger removed end-to-end: LedgerEvent/LedgerEventKind/ UnderstandingLevel/ExtractionAttribution/AuditResult.dropped and snapshot nodeObservations deleted from contracts; quizEvent builder and the events step of mergeFindings gone; appendEvent/readEvents dropped from the port and store; LEDGER_FILES loses events.jsonl with a one-time purge of a stale file next to the nodes ledger; graph-deletion and the delete dialog lose event purge + quiz-answer impact counts; GraphSessionProposition becomes a pure birth list. Whole-session markdown source view: new core/session-markdown.ts is the shared formatting engine (id-anchored speaker blocks, quiz/ recall/question blocks, tool one-liners) — the extractor's prompt view and the future user-facing export render from one engine. Coverage fingerprints deliberately stay on the old line format (content-hash semantics; re-formatting never re-opens covered history). The whole session goes to the extractor — no phase cut. Auditor refocused as the identity linker: never sees the source; explores the graph via new auditor-specific tools in agent/tools/graph.ts (find — glob over name/statement/aliases; list_sessions; list_propositions with full statement text; no timestamps) instead of an injected full-graph dump; equivalence bias — paraphrases merge, only genuinely distinct propositions stay apart; no drop channel, the extractor is fully trusted; may draw bridging edges (typed follows/supports) between existing nodes and the batch, resolved by the merger with chat-scoped message-less provenance. Agentic loop, maxTurns 16. UI: the node detail card gains an edges section above the sessions list (follows-from / supported-by / what-follows / what-it-supports, rendered only when present, click-to-select neighbors); the understanding-note chips are gone. Prober list_propositions reworded to what a session covered — names only, no levels. docs/architecture.md + README updated to the as-built pipeline. Gates: typecheck, 1059/1059, lint, build.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Type } from "typebox";
|
||||
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
import { serverExecute } from "../tool-execute.ts";
|
||||
import { graphSessionIndex, graphSessionPropositions } from "../../core/graph.ts";
|
||||
import { getGraph, graphSessionIndex, graphSessionPropositions } from "../../core/graph.ts";
|
||||
import type { ChatStore, GraphStore } from "../../core/ports.ts";
|
||||
|
||||
/**
|
||||
@@ -63,18 +63,107 @@ export function proberGraphTools(ctx: GraphToolContext): Record<string, AgentToo
|
||||
name: "list_propositions",
|
||||
label: "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.",
|
||||
"List the propositions BORN in one knowledge-graph session — what was covered there. " +
|
||||
"Names only — no statement text, no session contents (owner ruling 2026-09-23: the map's ORGANIZATION is your business, its prose is not).",
|
||||
parameters: listPropositionsInput,
|
||||
execute: serverExecute("list_propositions", listPropositionsInput, 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})` : ""}`,
|
||||
const lines = propositions.map((p) => `- ${p.name}`);
|
||||
return `${propositions.length} proposition(s) covered in this session:\n${lines.join("\n")}`;
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ─── the auditor's graph tools (2026-09-27 relink) ───────────────────────────
|
||||
|
||||
/**
|
||||
* THE AUDITOR'S TOOLSET — the extraction auditor explores the graph to
|
||||
* place new propositions instead of receiving it injected (owner ruling
|
||||
* 2026-09-27: a full-graph dump bloats context as the graph grows; the
|
||||
* auditor looks up identity candidates itself). Auditor-specific
|
||||
* versions, deliberately different from the prober's:
|
||||
*
|
||||
* find glob over node names + statements + aliases — the
|
||||
* identity workhorse; returns id + name + statement
|
||||
* (statement text is ESSENTIAL for identity work,
|
||||
* unlike the prober's names-only rule).
|
||||
* list_sessions the birth-session index, no timestamps.
|
||||
* list_propositions full proposition text (same reasoning as find).
|
||||
*
|
||||
* No timestamps anywhere: identity work needs content, not chronology.
|
||||
* Read-only, pure core seams, same ports the snapshot reads.
|
||||
*/
|
||||
|
||||
/** Glob match — `*` spans anything, case-insensitive. `*needle*` is the
|
||||
* expected shape; a bare term gets implicit surrounding stars. */
|
||||
function globMatch(pattern: string, text: string): boolean {
|
||||
const raw = pattern.includes("*") ? pattern : `*${pattern}*`;
|
||||
const re = new RegExp(
|
||||
`^${raw.replace(/[.*+?^${}()|[\]\\]/g, (ch) => (ch === "*" ? "[\\s\\S]*" : `\\${ch}`))}$`,
|
||||
"i",
|
||||
);
|
||||
return re.test(text);
|
||||
}
|
||||
|
||||
export function auditorGraphTools(ctx: GraphToolContext): Record<string, AgentTool<any>> {
|
||||
const deps = { chatStore: ctx.chatStore, graphStore: ctx.graphStore };
|
||||
const findInput = Type.Object({
|
||||
query: Type.String({
|
||||
minLength: 1,
|
||||
description: "Glob pattern over node names, statements, and aliases — e.g. *will to power*. A bare term gets implicit surrounding *.",
|
||||
}),
|
||||
});
|
||||
return {
|
||||
find: {
|
||||
name: "find",
|
||||
label: "find",
|
||||
description:
|
||||
"Search the knowledge graph for existing propositions — glob over names, statements, and aliases. " +
|
||||
"Returns each match's id, name, and full statement. This is how you decide whether a new proposition IS an existing one: search its key words, its synonyms, its phrasings.",
|
||||
parameters: findInput,
|
||||
execute: serverExecute("find", findInput, async ({ query }): Promise<string> => {
|
||||
const nodes = getGraph(deps.graphStore).nodes;
|
||||
const hits = nodes.filter(
|
||||
(n) => 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}`);
|
||||
return `${hits.length} match(es):\n${lines.join("\n")}`;
|
||||
}),
|
||||
},
|
||||
list_sessions: {
|
||||
name: "list_sessions",
|
||||
label: "list_sessions",
|
||||
description:
|
||||
"List the knowledge graph's birth sessions — every chat or imported-notes file that ever contributed a proposition. " +
|
||||
"Each line: name, kind, proposition count, id. Use the id with list_propositions to see what a session covered.",
|
||||
parameters: Type.Object({}),
|
||||
execute: serverExecute("list_sessions", Type.Object({}), 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"}) — id: ${s.id}`);
|
||||
return `${sessions.length} session(s):\n${lines.join("\n")}`;
|
||||
}),
|
||||
},
|
||||
list_propositions: {
|
||||
name: "list_propositions",
|
||||
label: "list_propositions",
|
||||
description:
|
||||
"List the propositions BORN in one session, with FULL statement text — the content-level view for identity work " +
|
||||
"(deciding whether a new proposition is really one of these).",
|
||||
parameters: listPropositionsInput,
|
||||
execute: serverExecute("list_propositions", listPropositionsInput, 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 nodes = new Map(getGraph(deps.graphStore).nodes.map((n) => [n.id, n]));
|
||||
const lines = propositions.map((p) => {
|
||||
const node = nodes.get(p.nodeId);
|
||||
return `- ${p.nodeId} :: ${p.name}\n statement: ${node?.statement ?? "(unknown)"}`;
|
||||
});
|
||||
return `${propositions.length} proposition(s) born in this session:\n${lines.join("\n")}`;
|
||||
}),
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user