From 95f133ff6333ab9eec3c9cc9facc22022f6a47da Mon Sep 17 00:00:00 2001 From: ismeth Date: Thu, 12 Feb 2026 13:53:47 +0100 Subject: [PATCH] feat(03-01): implement synthesis contracts and formatter pipeline - Add synthesis result contracts with agreement, provenance, and Athena assessment fields\n- Add synthesis prompt builder and council-response formatter with failure-aware provenance output --- src/agents/athena/index.ts | 3 ++ src/agents/athena/synthesis-formatter.ts | 48 ++++++++++++++++++++++++ src/agents/athena/synthesis-prompt.ts | 44 ++++++++++++++++++++++ src/agents/athena/synthesis-types.ts | 31 +++++++++++++++ 4 files changed, 126 insertions(+) create mode 100644 src/agents/athena/synthesis-formatter.ts create mode 100644 src/agents/athena/synthesis-prompt.ts create mode 100644 src/agents/athena/synthesis-types.ts diff --git a/src/agents/athena/index.ts b/src/agents/athena/index.ts index 3788e9e4..82830b4d 100644 --- a/src/agents/athena/index.ts +++ b/src/agents/athena/index.ts @@ -3,4 +3,7 @@ export * from "./model-parser" export * from "./council-prompt" export * from "./council-orchestrator" export * from "./council-result-collector" +export * from "./synthesis-types" +export * from "./synthesis-prompt" +export * from "./synthesis-formatter" export * from "../../config/schema/athena" diff --git a/src/agents/athena/synthesis-formatter.ts b/src/agents/athena/synthesis-formatter.ts new file mode 100644 index 00000000..cff06e60 --- /dev/null +++ b/src/agents/athena/synthesis-formatter.ts @@ -0,0 +1,48 @@ +import type { CouncilExecutionResult } from "./types" + +export function formatCouncilResultsForSynthesis(result: CouncilExecutionResult): string { + const completedResponses = result.responses.filter((response) => response.status === "completed") + + if (completedResponses.length === 0) { + return [ + "# Council Responses for Synthesis", + `Question: ${result.question}`, + "No successful responses from council members.", + "Review failed member details below for provenance.", + ...result.responses.map((response) => { + const memberName = response.member.name ?? response.member.model + return [ + `## Member: ${memberName} (${response.status})`, + `Model: ${response.member.model}`, + `Status: ${response.status}`, + `Duration: ${response.durationMs}ms`, + `Error: ${response.error ?? "No error message provided"}`, + ].join("\n") + }), + ].join("\n\n") + } + + const sections = result.responses.map((response) => { + const memberName = response.member.name ?? response.member.model + const header = [ + `## Member: ${memberName} (${response.status})`, + `Model: ${response.member.model}`, + `Status: ${response.status}`, + `Duration: ${response.durationMs}ms`, + ] + + if (response.status === "completed") { + const responseBody = response.response?.trim() ? response.response : "No response content provided" + return [...header, "Response:", responseBody].join("\n") + } + + return [...header, `Error: ${response.error ?? "No error message provided"}`].join("\n") + }) + + return [ + "# Council Responses for Synthesis", + `Question: ${result.question}`, + `Completed responses: ${result.completedCount}/${result.totalMembers}`, + ...sections, + ].join("\n\n") +} diff --git a/src/agents/athena/synthesis-prompt.ts b/src/agents/athena/synthesis-prompt.ts new file mode 100644 index 00000000..0470955c --- /dev/null +++ b/src/agents/athena/synthesis-prompt.ts @@ -0,0 +1,44 @@ +export function buildSynthesisPrompt(formattedResponses: string, question: string, completedCount: number): string { + return `You are Athena, the synthesis lead for a multi-model council. Your job is to merge independent model outputs into a single, evidence-grounded synthesis. + +## Original Question +${question} + +## Council Responses +${formattedResponses} + +## Your Responsibilities +1. Identify distinct findings across all completed member responses. +2. Group findings that refer to the same underlying issue (semantic similarity, not exact wording). +3. Classify agreementLevel for each finding using ${completedCount} completed member(s): + - unanimous: all completed members reported the finding + - majority: more than 50% of completed members reported the finding + - minority: 2 or more members reported it, but not a majority + - solo: only 1 member reported it +4. Add AthenaAssessment for each finding: + - agrees: whether you agree with the finding + - rationale: concise reason for agreement or disagreement +5. Set isFalsePositiveRisk: + - true for solo findings (likely false positives unless strongly supported) + - false for findings reported by multiple members + +## Output Contract +Return JSON only with this shape: +{ + "findings": [ + { + "summary": "string", + "details": "string", + "agreementLevel": "unanimous | majority | minority | solo", + "reportedBy": ["model/name"], + "assessment": { + "agrees": true, + "rationale": "string" + }, + "isFalsePositiveRisk": false + } + ] +} + +The finding object must match the SynthesizedFinding type exactly. Keep findings concise, concrete, and tied to source responses.` +} diff --git a/src/agents/athena/synthesis-types.ts b/src/agents/athena/synthesis-types.ts new file mode 100644 index 00000000..4efb8235 --- /dev/null +++ b/src/agents/athena/synthesis-types.ts @@ -0,0 +1,31 @@ +import type { AgreementLevel, CouncilMemberConfig, CouncilMemberStatus } from "./types" + +export interface AthenaAssessment { + agrees: boolean + rationale: string +} + +export interface SynthesizedFinding { + summary: string + details: string + agreementLevel: AgreementLevel + reportedBy: string[] + assessment: AthenaAssessment + isFalsePositiveRisk: boolean +} + +export interface MemberProvenance { + member: CouncilMemberConfig + status: CouncilMemberStatus + rawResponse?: string + durationMs: number +} + +export interface SynthesisResult { + question: string + findings: SynthesizedFinding[] + memberProvenance: MemberProvenance[] + totalFindings: number + consensusCount: number + outlierCount: number +}