feat(confidence-engine): add completed episode reasoning seam

This commit is contained in:
2026-09-01 09:28:02 +01:00
parent ab655e2222
commit c89cc51ae6
2 changed files with 264 additions and 0 deletions
+106
View File
@@ -31,6 +31,8 @@ import {
validateGraphReferences,
} from "./utils.js";
import { validateFindings, produceFindingInformedSummary } from "./finding-helpers.js";
import { prepareCompletedEpisode } from "./episode-preparation.js";
import { buildEpisodeAwareGraphPrompt } from "./prompt-builder-episode.js";
function toValidationErrors(error) {
return (
@@ -1106,3 +1108,107 @@ async function updateCaseWithDependencies(body, dependencies = {}) {
}),
};
}
// ── Episode-aware reconsideration seam ─────────────────────
/**
* Reason over a completed focused episode and return a parsed GraphUpdateProposal.
*
* This is the smallest sibling to updateCase that reuses existing provider,
* model, and parsing mechanics — only the prompt path differs.
*/
export async function reconsiderCompletedEpisode(epiParams, deps = {}) {
const { situationGraph: epiGraph, targetNodeId, contributions, findings, episode: rawEpisode } = epiParams;
// Accept prepared episode or produce it deterministically
let episode;
if (rawEpisode) {
episode = rawEpisode;
} else if (epiParams?.turns || epiParams?.eligibleCanonicalFindings) {
// Already a prepared episode passed as first positional arg
episode = epiParams;
} else {
episode = prepareCompletedEpisode(epiParams);
}
if (!episode?.turns && !episode?.eligibleCanonicalFindings) {
return {
success: false,
stage: "input_validation",
error: "Invalid prepared episode — requires turns or findings",
statusCode: 400,
};
}
const getModelName = deps.getModelName;
const buildEpisodePrompt = deps.buildEpisodePrompt;
const parseProposal = deps.parseProposal;
const provider = epiParams.provider ?? deps.provider;
let modelName = null;
let rawResponse;
const startedAt = Date.now();
try {
modelName = deps.modelName ?? (deps.config ? deps.config.OLLAMA_MODEL : null) ?? getModelName?.() ?? null;
const promptBuilder = buildEpisodePrompt ?? buildEpisodeAwareGraphPrompt;
const prompt = promptBuilder({ episode });
const prov = provider ?? getProvider();
rawResponse = await prov.generateReconstruction(prompt, modelName);
} catch (error) {
return {
success: false,
stage: "provider",
error: "Graph update proposal generation failed",
providerErrors: [
sanitiseErrorMessage(
error,
"Provider failed to generate graph update proposal from episode",
),
],
diagnostics: {
promptVersion: null,
modelName,
responseDurationMs: Date.now() - startedAt,
normalisationsApplied: [],
},
statusCode: 502,
};
}
const parser = parseProposal ?? parseGraphUpdateProposal;
const parsedProposal = parser(rawResponse);
const responseDurationMs = Date.now() - startedAt;
if (!parsedProposal.success) {
return {
success: false,
stage: "proposal_validation",
error: "Invalid graph update proposal",
proposalErrors: parsedProposal.errors,
diagnostics: {
promptVersion: null,
modelName,
responseDurationMs,
normalisationsApplied: parsedProposal.normalisationsApplied,
},
statusCode: 502,
};
}
return {
success: true,
stage: "proposal_ready",
proposal: parsedProposal.proposal,
diagnostics: {
promptVersion: null,
modelName,
responseDurationMs,
normalisationsApplied: parsedProposal.normalisationsApplied,
},
};
}