From 227a59a79679984b0b80a7c47f7dfc90b7ccaf4f Mon Sep 17 00:00:00 2001 From: ismeth Date: Sun, 1 Mar 2026 02:52:42 +0100 Subject: [PATCH] fix(athena): implement 10 prompt audit fixes from council review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix 1: Clarify 'restart at Step 3' → 'restart the council workflow from Step 3 (intent classification)' - Fix 2: Replace {council-session-name} with static references to council_finalize archive_dir - Fix 3: Add documenting comment for call_omo_agent in solo mode allowlist - Fix 4: Add delegation agent rationale comment to RUNTIME_GUIDANCE_BY_INTENT - Fix 5: Type-safe COUNCIL_INTENT_ADDENDUMS with Record - Fix 6: Clarify FREEFORM wording re COUNCIL_MEMBER_RESPONSE tag requirement - Fix 7: Remove dead Step 15 fallback from agent.ts - Fix 8: Add compound question intent precedence guidance - Fix 9: Fix AUDIT double-period typo - Fix 10: Add edge case test for extractCouncilResponse with literal tag text --- .../athena/athena-config-injection.test.ts | 1 - src/agents/athena/council-intent-addendums.ts | 8 ++-- src/agents/athena/council-member-agent.ts | 4 ++ src/agents/athena/council-runtime-guidance.ts | 41 +++++++++++-------- .../council-response-extractor.test.ts | 20 +++++++++ src/tools/prepare-council-prompt/tools.ts | 8 ++-- 6 files changed, 56 insertions(+), 26 deletions(-) diff --git a/src/agents/athena/athena-config-injection.test.ts b/src/agents/athena/athena-config-injection.test.ts index c11afc379..24e7be1fd 100644 --- a/src/agents/athena/athena-config-injection.test.ts +++ b/src/agents/athena/athena-config-injection.test.ts @@ -44,7 +44,6 @@ describe("Athena prompt config injection placeholders", () => { expect(athenaConfig.prompt).toContain("Step 12: Synthesize") expect(athenaConfig.prompt).toContain("Step 13: Determine follow-up path from council_finalize runtime guidance") expect(athenaConfig.prompt).toContain("Step 14: Execute the runtime guidance action flow") - expect(athenaConfig.prompt).toContain("Step 15: Fallback behavior if runtime guidance is missing") }) it("#then omits legacy mixed step labels", () => { diff --git a/src/agents/athena/council-intent-addendums.ts b/src/agents/athena/council-intent-addendums.ts index e0a7df705..f9006cc43 100644 --- a/src/agents/athena/council-intent-addendums.ts +++ b/src/agents/athena/council-intent-addendums.ts @@ -1,4 +1,6 @@ -export const COUNCIL_INTENT_ADDENDUMS: Record = { +import type { CouncilIntent } from "./council-runtime-guidance" + +export const COUNCIL_INTENT_ADDENDUMS: Record = { DIAGNOSE: ` ## Analysis Intent: DIAGNOSE @@ -63,7 +65,7 @@ You are conducting an **audit** — your goal is to find discrete issues, risks, - Severity determines priority: critical (blocks/breaks), high (significant risk), medium (should fix), low (nice to fix) - For each finding, provide the specific location (reference, section, or component where it occurs) - State your confidence: high (clear evidence), medium (likely but needs verification), low (suspicion, investigate further) -- **This is a broad sweep, not a targeted trace.**. +- **This is a broad sweep, not a targeted trace.** **Required output fields per finding:** - Title, Severity (critical/high/medium/low), Location, Confidence (high/medium/low) @@ -291,7 +293,7 @@ You are **surfacing genuine viewpoints and taking a stand** — your goal is to FREEFORM: ` ## Analysis Intent: FREEFORM -Respond naturally to the question below. No analytical framework is imposed. +Respond naturally to the question below. No analytical framework is imposed (apart from the required COUNCIL_MEMBER_RESPONSE tags for extraction). Use whatever structure serves the answer best. Analyze if analysis helps. Answer directly if a direct answer is better. Be conversational if the question is conversational. diff --git a/src/agents/athena/council-member-agent.ts b/src/agents/athena/council-member-agent.ts index 26d6b8f03..e83f30125 100644 --- a/src/agents/athena/council-member-agent.ts +++ b/src/agents/athena/council-member-agent.ts @@ -86,6 +86,10 @@ export function createCouncilMemberAgent(model: string): AgentConfig { "lsp_symbols", "lsp_diagnostics", "ast_grep_search", + // call_omo_agent is included in both solo and delegation modes. + // Solo mode restricts its use via prompt instruction (COUNCIL_SOLO_ADDENDUM) + // rather than tool-level restriction. This is intentional — tool-level + // restriction would require separate agent configs per mode. "call_omo_agent", "background_output", "background_wait", diff --git a/src/agents/athena/council-runtime-guidance.ts b/src/agents/athena/council-runtime-guidance.ts index 4a7ff3557..e273fa1b7 100644 --- a/src/agents/athena/council-runtime-guidance.ts +++ b/src/agents/athena/council-runtime-guidance.ts @@ -12,6 +12,11 @@ const VALID_INTENTS = [ export type CouncilIntent = (typeof VALID_INTENTS)[number] const RUNTIME_GUIDANCE_BY_INTENT: Record = { + // Delegation agent design rationale: + // - DIAGNOSE = single targeted fix → Hephaestus (direct implementation) / Sisyphus (orchestrated implementation) + // - AUDIT = multi-finding remediation → Atlas (todo-list orchestration) / Prometheus (phased planning) + // Other intents (PLAN, EVALUATE, EXPLAIN, CREATE, PERSPECTIVES, FREEFORM) are INFORMATIONAL + // and offer context-appropriate delegation options in their action paths. DIAGNOSE: ` Use DIAGNOSE synthesis. @@ -234,7 +239,7 @@ Question({ options: [ { label: "Execute full plan (Prometheus)", description: "Hand off all phases to Prometheus for execution" }, { label: "Execute selected phase (Prometheus)", description: "Choose one phase and execute only that phase first" }, - { label: "Write to document", description: "Write the plan under .sisyphus/athena/notes/{council-session-name}" }, + { label: "Write to document", description: "Save to .sisyphus/athena/notes/ (named after this council session)" }, { label: "Ask follow-up", description: "Ask another planning question" }, { label: "Done", description: "No further action needed" } ], @@ -258,8 +263,8 @@ Question({ 3) Execute selected action: - Execute full plan (Prometheus) -> switch_agent(agent="prometheus") with full synthesized plan. - Execute selected phase (Prometheus) -> switch_agent(agent="prometheus") with only the selected phase plus dependencies. -- Write to document -> write the document directly to ".sisyphus/athena/notes/{council-session-name}" and then report the exact path to the user. -- Ask follow-up -> ask user then restart at Step 3. +- Write to document -> write the document to the ".sisyphus/athena/notes/" directory using the council session name from the council_finalize archive_dir, then report the exact path to the user. +- Ask follow-up -> ask user then restart the council workflow from Step 3 (intent classification). - Done -> acknowledge and end. `, @@ -282,7 +287,7 @@ Question({ options: [ { label: "Adopt option -> create plan (Prometheus)", description: "Turn a selected option into an execution plan" }, { label: "Adopt option -> implement now", description: "Implement a selected option immediately" }, - { label: "Write to document", description: "Write under .sisyphus/athena/notes/{council-session-name}" }, + { label: "Write to document", description: "Save to .sisyphus/athena/notes/ (named after this council session)" }, { label: "Ask follow-up", description: "Ask another comparison question" }, { label: "Done", description: "No further action needed" } ], @@ -321,8 +326,8 @@ Question({ - Adopt option -> implement now + Hephaestus -> switch_agent(agent="hephaestus") with selected option. - Adopt option -> implement now + Sisyphus -> switch_agent(agent="sisyphus") with selected option. - Adopt option -> implement now + Sisyphus ultrawork -> switch_agent(agent="sisyphus") and prefix handoff context with "ultrawork ". -- Write to document -> write directly to ".sisyphus/athena/notes/{council-session-name}" and then report the exact path. -- Ask follow-up -> ask user then restart at Step 3. +- Write to document -> write the document to the ".sisyphus/athena/notes/" directory using the council session name from the council_finalize archive_dir, then report the exact path. +- Ask follow-up -> ask user then restart the council workflow from Step 3 (intent classification). - Done -> acknowledge and end. `, @@ -345,7 +350,7 @@ Question({ header: "Explanation Next Step", options: [ { label: "Convert to action plan (Prometheus)", description: "Turn insights into a phased plan" }, - { label: "Write to document", description: "Write under .sisyphus/athena/notes/{council-session-name}" }, + { label: "Write to document", description: "Save to .sisyphus/athena/notes/ (named after this council session)" }, { label: "Ask follow-up", description: "Ask another explanatory question" }, { label: "Done", description: "No further action needed" } ], @@ -355,8 +360,8 @@ Question({ 2) Execute selected action: - Convert to action plan (Prometheus) -> switch_agent(agent="prometheus") with synthesized explanation and target outcome. -- Write to document -> write directly to ".sisyphus/athena/notes/{council-session-name}" and then report the exact path. -- Ask follow-up -> ask user then restart at Step 3. +- Write to document -> write the document to the ".sisyphus/athena/notes/" directory using the council session name from the council_finalize archive_dir, then report the exact path. +- Ask follow-up -> ask user then restart the council workflow from Step 3 (intent classification). - Done -> acknowledge and end. `, @@ -405,7 +410,7 @@ Question({ { label: "Implement selected creation (Hephaestus)", description: "Direct implementation with Hephaestus" }, { label: "Implement selected creation (Sisyphus)", description: "Implementation with Sisyphus" }, { label: "Implement selected creation (Sisyphus ultrawork)", description: "Implementation with Sisyphus using ultrawork mode" }, - { label: "Write selected creation to document", description: "Write under .sisyphus/athena/notes/{council-session-name}" }, + { label: "Write selected creation to document", description: "Save to .sisyphus/athena/notes/ (named after this council session)" }, { label: "Ask follow-up", description: "Ask another creation-focused question" }, { label: "Done", description: "No further action needed" } ], @@ -417,8 +422,8 @@ Question({ - Implement selected creation (Hephaestus) -> switch_agent(agent="hephaestus") with only selected creation(s). - Implement selected creation (Sisyphus) -> switch_agent(agent="sisyphus") with only selected creation(s). - Implement selected creation (Sisyphus ultrawork) -> switch_agent(agent="sisyphus") and prefix handoff context with "ultrawork ", including only selected creation(s). -- Write selected creation to document -> write directly to ".sisyphus/athena/notes/{council-session-name}" and then report the exact path. -- Ask follow-up -> ask user then restart at Step 3. +- Write selected creation to document -> write the document to the ".sisyphus/athena/notes/" directory using the council session name from the council_finalize archive_dir, then report the exact path. +- Ask follow-up -> ask user then restart the council workflow from Step 3 (intent classification). - Done -> acknowledge and end. `, @@ -443,7 +448,7 @@ Question({ options: [ { label: "Commit to stance -> create plan (Prometheus)", description: "Turn a chosen stance into a phased plan" }, { label: "Commit to stance -> implement now", description: "Implement based on a chosen stance immediately" }, - { label: "Write to document", description: "Write under .sisyphus/athena/notes/{council-session-name}" }, + { label: "Write to document", description: "Save to .sisyphus/athena/notes/ (named after this council session)" }, { label: "Ask follow-up", description: "Ask another perspective question" }, { label: "Done", description: "No further action needed" } ], @@ -482,8 +487,8 @@ Question({ - Commit to stance -> implement now + Hephaestus -> switch_agent(agent="hephaestus") with selected stance. - Commit to stance -> implement now + Sisyphus -> switch_agent(agent="sisyphus") with selected stance. - Commit to stance -> implement now + Sisyphus ultrawork -> switch_agent(agent="sisyphus") and prefix handoff context with "ultrawork ". -- Write to document -> write directly to ".sisyphus/athena/notes/{council-session-name}" and then report the exact path. -- Ask follow-up -> ask user then restart at Step 3. +- Write to document -> write the document to the ".sisyphus/athena/notes/" directory using the council session name from the council_finalize archive_dir, then report the exact path. +- Ask follow-up -> ask user then restart the council workflow from Step 3 (intent classification). - Done -> acknowledge and end. `, @@ -506,7 +511,7 @@ Question({ options: [ { label: "Create plan (Prometheus)", description: "Turn the result into a phased execution plan" }, { label: "Implement now", description: "Implement directly from this result" }, - { label: "Write to document", description: "Write under .sisyphus/athena/notes/{council-session-name}" }, + { label: "Write to document", description: "Save to .sisyphus/athena/notes/ (named after this council session)" }, { label: "Ask follow-up", description: "Ask another question" }, { label: "Done", description: "No further action needed" } ], @@ -533,8 +538,8 @@ Question({ - Implement now + Hephaestus -> switch_agent(agent="hephaestus") with synthesized result. - Implement now + Sisyphus -> switch_agent(agent="sisyphus") with synthesized result. - Implement now + Sisyphus ultrawork -> switch_agent(agent="sisyphus") and prefix handoff context with "ultrawork ". -- Write to document -> write directly to ".sisyphus/athena/notes/{council-session-name}" and then report the exact path. -- Ask follow-up -> ask user then restart at Step 3. +- Write to document -> write the document to the ".sisyphus/athena/notes/" directory using the council session name from the council_finalize archive_dir, then report the exact path. +- Ask follow-up -> ask user then restart the council workflow from Step 3 (intent classification). - Done -> acknowledge and end. `, } diff --git a/src/tools/council-archive/council-response-extractor.test.ts b/src/tools/council-archive/council-response-extractor.test.ts index d86eeb54c..86c29dc2e 100644 --- a/src/tools/council-archive/council-response-extractor.test.ts +++ b/src/tools/council-archive/council-response-extractor.test.ts @@ -64,6 +64,26 @@ describe("extractCouncilResponse", () => { }) }) + describe("#given response body contains literal COUNCIL_MEMBER_RESPONSE tag text", () => { + it("#then extracts the actual tagged response, not the discussed tag", () => { + const text = [ + "Here is my exploration log where I discuss the tag format.", + "The system uses tags for extraction.", + "Now here is my actual response:", + "", + "## Finding 1: Tag discussion in body", + "The extractor uses lastIndexOf to find the opening tag.", + "", + ].join("\n") + const result = extractCouncilResponse(text) + expect(result).toEqual({ + has_response: true, + response_complete: true, + result: "## Finding 1: Tag discussion in body\nThe extractor uses lastIndexOf to find the opening tag.", + }) + }) + }) + describe("#given an empty string", () => { it("#then returns has_response false and null result", () => { const result = extractCouncilResponse("") diff --git a/src/tools/prepare-council-prompt/tools.ts b/src/tools/prepare-council-prompt/tools.ts index b0be30c18..e052d4ca8 100644 --- a/src/tools/prepare-council-prompt/tools.ts +++ b/src/tools/prepare-council-prompt/tools.ts @@ -3,7 +3,7 @@ import { randomUUID } from "node:crypto" import { writeFile, unlink, mkdir, readdir, stat } from "node:fs/promises" import { join } from "node:path" import { log } from "../../shared/logger" -import { COUNCIL_SOLO_ADDENDUM, COUNCIL_DELEGATION_ADDENDUM, COUNCIL_INTENT_ADDENDUMS } from "../../agents/athena" +import { COUNCIL_SOLO_ADDENDUM, COUNCIL_DELEGATION_ADDENDUM, COUNCIL_INTENT_ADDENDUMS, getValidCouncilIntents, type CouncilIntent } from "../../agents/athena" const CLEANUP_DELAY_MS = 30 * 60 * 1000 const COUNCIL_TMP_DIR = ".sisyphus/tmp" @@ -78,8 +78,8 @@ Returns the file path to reference in subsequent task() calls.` return `Invalid mode: "${args.mode}". Valid modes: "solo", "delegation".` } - const validIntents = Object.keys(COUNCIL_INTENT_ADDENDUMS) - if (args.intent !== undefined && !validIntents.includes(args.intent.toUpperCase())) { + const validIntents = getValidCouncilIntents() + if (args.intent !== undefined && !(validIntents as readonly string[]).includes(args.intent.toUpperCase())) { return `Invalid intent: "${args.intent}". Valid intents: ${validIntents.map((i) => `"${i}"`).join(", ")}.` } @@ -95,7 +95,7 @@ Returns the file path to reference in subsequent task() calls.` const filePath = join(tmpDir, filename) const modeAddendum = mode === "delegation" ? COUNCIL_DELEGATION_ADDENDUM : COUNCIL_SOLO_ADDENDUM - const intentAddendum = resolvedIntent ? (COUNCIL_INTENT_ADDENDUMS[resolvedIntent] ?? "") : "" + const intentAddendum = resolvedIntent ? (COUNCIL_INTENT_ADDENDUMS[resolvedIntent as CouncilIntent] ?? "") : "" const content = intentAddendum ? `${modeAddendum}\n\n${intentAddendum}\n\n## Analysis Question\n\n${args.prompt}` : `${modeAddendum}\n\n## Analysis Question\n\n${args.prompt}`