slim the submit schemas: evidence and messages die; stringified-payload heal at the pre-validation seam

OpenRouter log diagnosis (owner, 2026-09-28): extraction time was
dominated by tool-validation retries. The failing call had the whole
propositions array emitted as ONE JSON-STRING ('propositions.0: must
be object'), a truncated placeholder artifact inside it, and
word-salad statements late in the array — payload-size collapse.
Field audit: 'evidence' was never persisted anywhere and its
faithfulness-checking consumer died with the fully-trusted ruling;
'messages' fed messageId granularity that NOTHING downstream reads,
and the model sent [] anyway (chat-level provenance in practice).
'aliases' stays (optional now): the auditor's find tool globs them.

Schema: propositions = {id, name, statement, aliases?}; edges =
{from, to, type}. Prompts lose the evidence/messages doctrine and
[id]-anchor guidance, gain a compact faithfulness line;
transcriptView renders anchorless. The auditor echo instruction
drops the evidence/messages mention, so its payload halves too.

Heal: healStringifiedArrays on the submit tool's prepareArguments
(pi-agent-core's sanctioned pre-TypeBox shim) parses a stringified
propositions/edges back into arrays — the LaTeX-restore doctrine:
heal weak-model wire behavior at the seam. Test drives the real
agent-loop seam with a stringified submit.
This commit is contained in:
amosblomqvist
2026-09-28 19:04:39 +03:00
parent 14a7f0de1d
commit bd9694b0e4
7 changed files with 131 additions and 125 deletions
+8 -10
View File
@@ -172,7 +172,7 @@ export interface CoverageEntry {
* of work): the extractor is the ONLY stage that sees the raw source
* (whole session as readable markdown, or raw notes) and extracts the
* reasoning chains — propositions (lonely relevant ones included) plus
* follows/supports edges, with verbatim evidence; the auditor NEVER sees
* follows/supports edges; the auditor NEVER sees
* the source — its job is preparing the merge: identity verdicts
* (link-or-create, merging equivalent forms of the same proposition) via
* graph-exploration tools (find / list_sessions / list_propositions), and
@@ -187,7 +187,7 @@ export interface CoverageEntry {
/** One proposed proposition — a node candidate (or a re-encounter of one). */
export interface ExtractedProposition {
/** Extractor-local id ("p1", …) referenced by edges + attribution. */
/** Extractor-local id ("p1", …) referenced by edges. */
id: string;
/** A very short label — a few words, a compact handle like a glossary
* headword. Not a sentence, needn't be unique or self-contained: the
@@ -202,10 +202,12 @@ export interface ExtractedProposition {
statement: string;
/** Verbatim phrasings the session used. */
aliases: string[];
/** The message ids where this proposition came up — provenance. */
messages: string[];
/** Verbatim transcript quotes backing the proposition. */
evidence: string[];
/* Submit-schema slim (owner, 2026-09-28): `messages` and `evidence`
* are GONE. Provenance is chat-level (the run's source key); the
* verbatim-quote fields were never persisted and weak models burned
* their retries on them ("propositions.0: must be object" — the
* stringified-payload collapse). The extractor reports propositions
* + structure, nothing else. */
}
/** One proposed relation. Endpoints are extractor-local proposition ids
@@ -219,10 +221,6 @@ export interface ExtractedEdge {
/** Proposition id (or graph node id) that follows / is supported. */
to: string;
type: EdgeType;
/** The message ids where this relation was asserted — provenance. */
messages: string[];
/** Verbatim transcript quotes backing the relation. */
evidence: string[];
}
/** The extractor's output shape: the raw reasoning chains. */
+10 -12
View File
@@ -85,16 +85,11 @@ export interface MergeSource {
coverage?: CoverageEntry[];
}
/** (chatId, messageIds) → provenance points; order-preserving, deduped. */
function pointsOf(chatId: string, messages: string[]): ProvenancePoint[] {
const seen = new Set<string>();
const out: ProvenancePoint[] = [];
for (const messageId of messages) {
if (messageId.length === 0 || seen.has(messageId)) continue;
seen.add(messageId);
out.push({ chatId, messageId });
}
return out;
/** The run's chat-level provenance point (submit-schema slim, 2026-09-28:
* propositions no longer carry message ids — the model never reported
* them reliably anyway — so every birth from a run shares one point). */
function pointOf(chatId: string): ProvenancePoint[] {
return [{ chatId, messageId: "" }];
}
const samePoint = (a: ProvenancePoint, b: ProvenancePoint): boolean =>
@@ -140,7 +135,7 @@ export function mergeFindings(chatId: string, audit: AuditResult, deps: GraphDep
for (const p of audit.propositions) {
if (mapping.has(p.id)) continue; // already resolved for this chat — idempotent
const points = pointsOf(chatId, p.messages);
const points = pointOf(chatId);
let graphNodeId: string;
if (p.linkTo !== undefined && existingIds.has(p.linkTo)) {
// Identity verdict: this proposition IS that node. Append a link
@@ -206,7 +201,10 @@ export function mergeFindings(chatId: string, audit: AuditResult, deps: GraphDep
from,
to,
type: edge.type,
provenance: bridged ? [{ chatId, messageId: "" }] : pointsOf(chatId, edge.messages),
// bridged vs transcript edges both carry the run's chat-level
// point now; the distinction lives in the endpoint resolution
// above (mapping vs existing node id), not in the provenance.
provenance: pointOf(chatId),
});
assertedByChat.add(key);
stats.edges++;
+5 -3
View File
@@ -4,9 +4,11 @@
* The extractor's view of a teaching session and the future user-facing
* session export share this engine: a session is rendered as clean,
* readable markdown — speaker headers, quiz/check-in blocks, tool notes —
* with the message-id anchors preserved (extraction provenance depends on
* the extractor reporting the `[id]` tags it saw; `anchors: false` mints
* the same document without them, for the user export).
* with optional message-id anchors. Since the submit-schema slim
* (2026-09-28) propositions no longer report message ids — the extractor
* renders anchorless and provenance is chat-level — so the anchors today
* serve only the future user-facing export; `anchors: false` mints the
* same document without them.
*
* Pure core: native messages in (LhMessage), string out. Content parity
* with the coverage line builder (core/coverage.ts messageViewLines) is