From 149d065d68582f83a20b6e9e6e7cda141f667885 Mon Sep 17 00:00:00 2001 From: ismeth Date: Tue, 3 Mar 2026 00:04:31 +0100 Subject: [PATCH] feat(athena): split into athena (interactive) and athena-junior (subagent) Split the Athena council orchestrator into two distinct agents: - athena: primary mode, interactive prompt with Question tool and switch_agent - athena-junior: subagent mode, non-interactive prompt with structured JSON output Key changes: - Create athena-junior-agent.ts factory (mode=subagent, denies question+call_omo_agent) - Revert athena agent.ts to primary mode (no env var switching) - Make buildAthenaRuntimeGuidance mode-aware (strips action_paths for non-interactive) - Add mode parameter to council_finalize tool - Register athena-junior in builtin-agents, tool-config, model-requirements - Replace "athena" with "athena-junior" in call_omo_agent ALLOWED_AGENTS - Update all tests for new architecture Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus --- src/agents/athena/agent.test.ts | 129 ++++++++++-------- .../athena/athena-config-injection.test.ts | 47 ++++++- src/agents/athena/athena-junior-agent.ts | 40 ++++++ .../athena/council-runtime-guidance.test.ts | 62 ++++++++- src/agents/athena/council-runtime-guidance.ts | 17 ++- src/agents/athena/index.ts | 3 +- src/agents/athena/non-interactive-prompt.ts | 2 +- src/plugin-handlers/tool-config-handler.ts | 10 ++ src/shared/agent-tool-restrictions.ts | 5 + src/shared/model-requirements.ts | 10 ++ src/tools/call-omo-agent/constants.ts | 2 +- src/tools/call-omo-agent/tools.test.ts | 4 +- .../create-council-finalize.ts | 8 +- src/tools/council-archive/types.ts | 1 + 14 files changed, 270 insertions(+), 70 deletions(-) create mode 100644 src/agents/athena/athena-junior-agent.ts diff --git a/src/agents/athena/agent.test.ts b/src/agents/athena/agent.test.ts index 26fa9c3a1..b462bb534 100644 --- a/src/agents/athena/agent.test.ts +++ b/src/agents/athena/agent.test.ts @@ -1,27 +1,14 @@ /// -import { afterEach, beforeEach, describe, expect, it } from "bun:test" +import { describe, expect, it } from "bun:test" import { createAthenaAgent } from "./agent" +import { createAthenaJuniorAgent } from "./athena-junior-agent" describe("createAthenaAgent", () => { - const originalEnv = process.env.OPENCODE_CLI_RUN_MODE - - beforeEach(() => { - delete process.env.OPENCODE_CLI_RUN_MODE - }) - - afterEach(() => { - if (originalEnv !== undefined) { - process.env.OPENCODE_CLI_RUN_MODE = originalEnv - } else { - delete process.env.OPENCODE_CLI_RUN_MODE - } - }) - describe("#given the agent mode", () => { describe("#when accessing the static mode property", () => { - it("#then equals 'all' to support both primary and subagent contexts", () => { - expect(createAthenaAgent.mode).toBe("all") + it("#then equals 'primary'", () => { + expect(createAthenaAgent.mode).toBe("primary") }) }) }) @@ -43,23 +30,27 @@ describe("createAthenaAgent", () => { expect(config.permission).toBeDefined() }) - it("#then includes a description", () => { + it("#then includes a description containing synthesis strategist", () => { const config = createAthenaAgent("anthropic/claude-opus-4-6") expect(config.description).toContain("synthesis strategist") }) }) }) - describe("#given default invocation (no CLI mode)", () => { - describe("#when OPENCODE_CLI_RUN_MODE is not set", () => { - it("#then selects the interactive prompt", () => { + describe("#given the interactive prompt", () => { + describe("#when checking prompt content", () => { + it("#then contains Question tool references", () => { const config = createAthenaAgent("anthropic/claude-opus-4-6") expect(config.prompt).toContain("Question tool") }) - it("#then contains interactive workflow sections", () => { + it("#then contains Step 2: Council setup section", () => { + const config = createAthenaAgent("anthropic/claude-opus-4-6") + expect(config.prompt).toContain("Step 2: Council setup") + }) + + it("#then contains agent_handoff section", () => { const config = createAthenaAgent("anthropic/claude-opus-4-6") - expect(config.prompt).toContain("Step 2: Council setup (default flow before launch).") expect(config.prompt).toContain("") }) @@ -67,40 +58,68 @@ describe("createAthenaAgent", () => { const config = createAthenaAgent("anthropic/claude-opus-4-6") expect(config.prompt).not.toContain("") }) - }) - }) - describe("#given CLI run mode", () => { - describe("#when OPENCODE_CLI_RUN_MODE is 'true'", () => { - beforeEach(() => { - process.env.OPENCODE_CLI_RUN_MODE = "true" - }) - - it("#then selects the non-interactive prompt", () => { + it("#then does not contain NEVER use the Question tool constraint", () => { const config = createAthenaAgent("anthropic/claude-opus-4-6") - expect(config.prompt).toContain("") - }) - - it("#then contains non-interactive constraints", () => { - const config = createAthenaAgent("anthropic/claude-opus-4-6") - expect(config.prompt).toContain("NEVER use the Question tool") - }) - - it("#then does not contain interactive agent handoff section", () => { - const config = createAthenaAgent("anthropic/claude-opus-4-6") - expect(config.prompt).not.toContain("") - }) - }) - - describe("#when OPENCODE_CLI_RUN_MODE is 'false'", () => { - beforeEach(() => { - process.env.OPENCODE_CLI_RUN_MODE = "false" - }) - - it("#then selects the interactive prompt", () => { - const config = createAthenaAgent("anthropic/claude-opus-4-6") - expect(config.prompt).toContain("Question tool") - expect(config.prompt).not.toContain("") + expect(config.prompt).not.toContain("NEVER use the Question tool") + }) + }) + }) +}) + +describe("createAthenaJuniorAgent", () => { + describe("#given the agent mode", () => { + describe("#when accessing the static mode property", () => { + it("#then equals 'subagent'", () => { + expect(createAthenaJuniorAgent.mode).toBe("subagent") + }) + }) + }) + + describe("#given the agent config", () => { + describe("#when creating the agent with a model", () => { + it("#then returns config with the specified model", () => { + const config = createAthenaJuniorAgent("anthropic/claude-opus-4-6") + expect(config.model).toBe("anthropic/claude-opus-4-6") + }) + + it("#then sets temperature to 0.1", () => { + const config = createAthenaJuniorAgent("anthropic/claude-opus-4-6") + expect(config.temperature).toBe(0.1) + }) + + it("#then includes tool restrictions denying call_omo_agent and question", () => { + const config = createAthenaJuniorAgent("anthropic/claude-opus-4-6") + expect(config.permission).toBeDefined() + }) + + it("#then includes a description containing Non-interactive", () => { + const config = createAthenaJuniorAgent("anthropic/claude-opus-4-6") + expect(config.description).toContain("Non-interactive") + }) + }) + }) + + describe("#given the non-interactive prompt", () => { + describe("#when checking prompt content", () => { + it("#then contains athena_council_result output contract", () => { + const config = createAthenaJuniorAgent("anthropic/claude-opus-4-6") + expect(config.prompt).toContain("") + }) + + it("#then contains NEVER use the Question tool constraint", () => { + const config = createAthenaJuniorAgent("anthropic/claude-opus-4-6") + expect(config.prompt).toContain("NEVER use the Question tool") + }) + + it("#then does not contain agent_handoff section", () => { + const config = createAthenaJuniorAgent("anthropic/claude-opus-4-6") + expect(config.prompt).not.toContain("") + }) + + it("#then does not contain switch_agent references", () => { + const config = createAthenaJuniorAgent("anthropic/claude-opus-4-6") + expect(config.prompt).not.toContain("switch_agent") }) }) }) diff --git a/src/agents/athena/athena-config-injection.test.ts b/src/agents/athena/athena-config-injection.test.ts index c8d5cbaff..d6b362572 100644 --- a/src/agents/athena/athena-config-injection.test.ts +++ b/src/agents/athena/athena-config-injection.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "bun:test" import { ATHENA_PROMPT_METADATA, createAthenaAgent } from "./agent" +import { ATHENA_JUNIOR_PROMPT_METADATA } from "./athena-junior-agent" import { ATHENA_NON_INTERACTIVE_PROMPT } from "./non-interactive-prompt" describe("Athena prompt config injection placeholders", () => { @@ -136,24 +137,62 @@ describe("Athena prompt config injection placeholders", () => { describe("Athena prompt metadata", () => { describe("#given ATHENA_PROMPT_METADATA", () => { describe("#when checking triggers", () => { - it("#then includes a Non-interactive council trigger", () => { + it("#then does NOT include a Non-interactive council trigger", () => { const hasNonInteractiveTrigger = ATHENA_PROMPT_METADATA.triggers.some((t) => t.domain.includes("Non-interactive"), ) + expect(hasNonInteractiveTrigger).toBe(false) + }) + }) + + describe("#when checking useWhen entries", () => { + it("#then does NOT include an entry mentioning oh-my-opencode run", () => { + const hasCLIEntry = ATHENA_PROMPT_METADATA.useWhen.some((entry) => + entry.includes("oh-my-opencode run"), + ) + expect(hasCLIEntry).toBe(false) + }) + + it("#then does NOT include an entry mentioning structured council output", () => { + const hasStructuredEntry = ATHENA_PROMPT_METADATA.useWhen.some((entry) => + entry.includes("structured") || entry.includes("agent-to-agent"), + ) + expect(hasStructuredEntry).toBe(false) + }) + }) + + describe("#when checking avoidWhen entries", () => { + it("#then includes an entry referencing athena-junior", () => { + const hasAthenaJuniorRef = ATHENA_PROMPT_METADATA.avoidWhen?.some((entry) => + entry.includes("athena-junior"), + ) + expect(hasAthenaJuniorRef).toBe(true) + }) + }) + }) +}) + +describe("Athena-Junior prompt metadata", () => { + describe("#given ATHENA_JUNIOR_PROMPT_METADATA", () => { + describe("#when checking triggers", () => { + it("#then includes a Non-interactive council trigger", () => { + const hasNonInteractiveTrigger = ATHENA_JUNIOR_PROMPT_METADATA.triggers.some((t) => + t.domain.includes("Non-interactive"), + ) expect(hasNonInteractiveTrigger).toBe(true) }) }) describe("#when checking useWhen entries", () => { it("#then includes an entry mentioning oh-my-opencode run", () => { - const hasCLIEntry = ATHENA_PROMPT_METADATA.useWhen.some((entry) => + const hasCLIEntry = ATHENA_JUNIOR_PROMPT_METADATA.useWhen.some((entry) => entry.includes("oh-my-opencode run"), ) expect(hasCLIEntry).toBe(true) }) - it("#then includes an entry mentioning structured council output", () => { - const hasStructuredEntry = ATHENA_PROMPT_METADATA.useWhen.some((entry) => + it("#then includes an entry mentioning structured or agent-to-agent", () => { + const hasStructuredEntry = ATHENA_JUNIOR_PROMPT_METADATA.useWhen.some((entry) => entry.includes("structured") || entry.includes("agent-to-agent"), ) expect(hasStructuredEntry).toBe(true) diff --git a/src/agents/athena/athena-junior-agent.ts b/src/agents/athena/athena-junior-agent.ts new file mode 100644 index 000000000..020bb2593 --- /dev/null +++ b/src/agents/athena/athena-junior-agent.ts @@ -0,0 +1,40 @@ +import type { AgentConfig } from "@opencode-ai/sdk" +import type { AgentMode, AgentPromptMetadata } from "../types" +import { createAgentToolRestrictions } from "../../shared/permission-compat" +import { ATHENA_NON_INTERACTIVE_PROMPT } from "./non-interactive-prompt" + +const MODE: AgentMode = "subagent" + +export const ATHENA_JUNIOR_PROMPT_METADATA: AgentPromptMetadata = { + category: "advisor", + cost: "EXPENSIVE", + promptAlias: "Athena-Junior", + triggers: [ + { domain: "Non-interactive council", trigger: "Agent needs multi-model analysis without user interaction" }, + { domain: "Programmatic synthesis", trigger: "Need structured council output for automated processing" }, + ], + useWhen: [ + "CLI invocation via oh-my-opencode run needing structured council output", + "Agent-to-agent invocation where structured JSON is required", + "Automated pipelines needing multi-model consensus", + ], + avoidWhen: [ + "Interactive sessions where user can confirm actions (use athena instead)", + "Single-model questions that do not need council synthesis", + ], +} + +export function createAthenaJuniorAgent(model: string): AgentConfig { + const restrictions = createAgentToolRestrictions(["call_omo_agent", "question"]) + return { + description: + "Non-interactive council orchestrator for programmatic multi-model synthesis. Returns structured JSON without user interaction. (Athena-Junior - OhMyOpenCode)", + mode: MODE, + model, + temperature: 0.1, + permission: restrictions.permission, + prompt: ATHENA_NON_INTERACTIVE_PROMPT, + color: "#1F8EFA", + } +} +createAthenaJuniorAgent.mode = MODE diff --git a/src/agents/athena/council-runtime-guidance.test.ts b/src/agents/athena/council-runtime-guidance.test.ts index 1fb556cd0..3f9b0b3a8 100644 --- a/src/agents/athena/council-runtime-guidance.test.ts +++ b/src/agents/athena/council-runtime-guidance.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "bun:test" -import { resolveCouncilIntent, buildAthenaRuntimeGuidance, getValidCouncilIntents } from "./council-runtime-guidance" +import { resolveCouncilIntent, buildAthenaRuntimeGuidance, getValidCouncilIntents, type CouncilGuidanceMode } from "./council-runtime-guidance" describe("council-runtime-guidance", () => { describe("resolveCouncilIntent", () => { @@ -116,7 +116,7 @@ describe("council-runtime-guidance", () => { }) }) - describe("#when building guidance for each intent", () => { + describe("#when building guidance for each intent in interactive mode (default)", () => { const allIntents = ["DIAGNOSE", "AUDIT", "PLAN", "EVALUATE", "EXPLAIN", "CREATE", "PERSPECTIVES", "FREEFORM"] as const for (const intent of allIntents) { @@ -125,7 +125,7 @@ describe("council-runtime-guidance", () => { expect(result).toContain("runtime_synthesis_rules") }) - it(`#then ${intent} guidance contains runtime_action_paths`, () => { + it(`#then ${intent} guidance contains runtime_action_paths in interactive mode`, () => { const result = buildAthenaRuntimeGuidance(intent) expect(result).toContain("runtime_action_paths") }) @@ -139,6 +139,62 @@ describe("council-runtime-guidance", () => { }) }) + describe("#given non-interactive mode", () => { + const allIntents = ["DIAGNOSE", "AUDIT", "PLAN", "EVALUATE", "EXPLAIN", "CREATE", "PERSPECTIVES", "FREEFORM"] as const + + describe("#when building guidance with non-interactive mode", () => { + for (const intent of allIntents) { + it(`#then ${intent} guidance contains runtime_synthesis_rules`, () => { + const result = buildAthenaRuntimeGuidance(intent, "non-interactive") + expect(result).toContain("runtime_synthesis_rules") + }) + + it(`#then ${intent} guidance does NOT contain runtime_action_paths`, () => { + const result = buildAthenaRuntimeGuidance(intent, "non-interactive") + expect(result).not.toContain("runtime_action_paths") + }) + + it(`#then ${intent} guidance contains mode: non-interactive`, () => { + const result = buildAthenaRuntimeGuidance(intent, "non-interactive") + expect(result).toContain("mode: non-interactive") + }) + + it(`#then ${intent} guidance contains source: council_finalize`, () => { + const result = buildAthenaRuntimeGuidance(intent, "non-interactive") + expect(result).toContain("source: council_finalize") + }) + } + }) + + describe("#when building guidance with explicit interactive mode", () => { + for (const intent of allIntents) { + it(`#then ${intent} guidance contains both synthesis_rules and action_paths`, () => { + const result = buildAthenaRuntimeGuidance(intent, "interactive") + expect(result).toContain("runtime_synthesis_rules") + expect(result).toContain("runtime_action_paths") + }) + + it(`#then ${intent} guidance contains mode: interactive`, () => { + const result = buildAthenaRuntimeGuidance(intent, "interactive") + expect(result).toContain("mode: interactive") + }) + } + }) + }) + + describe("CouncilGuidanceMode type", () => { + describe("#given the type is imported", () => { + describe("#when used as a type annotation", () => { + it("#then accepts interactive and non-interactive values", () => { + const interactive: CouncilGuidanceMode = "interactive" + const nonInteractive: CouncilGuidanceMode = "non-interactive" + expect(interactive).toBe("interactive") + expect(nonInteractive).toBe("non-interactive") + }) + }) + }) + }) + describe("getValidCouncilIntents", () => { describe("#given the function is called", () => { describe("#when retrieving valid intents", () => { diff --git a/src/agents/athena/council-runtime-guidance.ts b/src/agents/athena/council-runtime-guidance.ts index be0372aa5..0c878b256 100644 --- a/src/agents/athena/council-runtime-guidance.ts +++ b/src/agents/athena/council-runtime-guidance.ts @@ -50,12 +50,25 @@ export function resolveCouncilIntent(intent?: string): CouncilIntent | null { : null } -export function buildAthenaRuntimeGuidance(intent: CouncilIntent): string { +export type CouncilGuidanceMode = "interactive" | "non-interactive" + +export function buildAthenaRuntimeGuidance(intent: CouncilIntent, mode: CouncilGuidanceMode = "interactive"): string { + let guidanceContent = RUNTIME_GUIDANCE_BY_INTENT[intent].trim() + + if (mode === "non-interactive") { + guidanceContent = stripActionPaths(guidanceContent) + } + return [ "", "source: council_finalize", `intent: ${intent}`, - RUNTIME_GUIDANCE_BY_INTENT[intent].trim(), + `mode: ${mode}`, + guidanceContent, "", ].join("\n\n") } + +function stripActionPaths(guidance: string): string { + return guidance.replace(/[\s\S]*?<\/runtime_action_paths>/g, "").trim() +} diff --git a/src/agents/athena/index.ts b/src/agents/athena/index.ts index ef4e1ef7f..1bc1dfca8 100644 --- a/src/agents/athena/index.ts +++ b/src/agents/athena/index.ts @@ -1,4 +1,5 @@ export { createAthenaAgent, ATHENA_PROMPT_METADATA } from "./agent" +export { createAthenaJuniorAgent, ATHENA_JUNIOR_PROMPT_METADATA } from "./athena-junior-agent" export { createCouncilMemberAgent, COUNCIL_MEMBER_PROMPT, COUNCIL_SOLO_ADDENDUM, COUNCIL_DELEGATION_ADDENDUM } from "./council-member-agent" export { COUNCIL_INTENT_ADDENDUMS } from "./council-intent-addendums" export { @@ -6,7 +7,7 @@ export { getValidCouncilIntents, resolveCouncilIntent, } from "./council-runtime-guidance" -export type { CouncilIntent } from "./council-runtime-guidance" +export type { CouncilIntent, CouncilGuidanceMode } from "./council-runtime-guidance" export { COUNCIL_DEFAULTS } from "./constants" export { ATHENA_INTERACTIVE_PROMPT } from "./interactive-prompt" export { ATHENA_NON_INTERACTIVE_PROMPT } from "./non-interactive-prompt" diff --git a/src/agents/athena/non-interactive-prompt.ts b/src/agents/athena/non-interactive-prompt.ts index b50093a61..a0d83ae2b 100644 --- a/src/agents/athena/non-interactive-prompt.ts +++ b/src/agents/athena/non-interactive-prompt.ts @@ -72,7 +72,7 @@ Launch ALL members before collecting results. Track every task_id. - Repeat until ALL members reach terminal state. ### Step 6: Collect results with council_finalize. -- Call: council_finalize(task_ids=[...], name="{topic-slug}", intent="{intent}", question="{original question}", prompt_file="{path from Step 4.1}") +- Call: council_finalize(task_ids=[...], name="{topic-slug}", intent="{intent}", question="{original question}", prompt_file="{path from Step 4.1}", mode="non-interactive") - council_finalize extracts responses, writes archives, returns structured JSON with archive_dir and members array. - Read each member's archive_file using Read tool for synthesis input. diff --git a/src/plugin-handlers/tool-config-handler.ts b/src/plugin-handlers/tool-config-handler.ts index 2fd8f22c4..4344e39d3 100644 --- a/src/plugin-handlers/tool-config-handler.ts +++ b/src/plugin-handlers/tool-config-handler.ts @@ -136,6 +136,16 @@ export function applyToolConfig(params: { question: questionPermission, }; } + const athenaJunior = agentByKey(params.agentResult, "athena-junior"); + if (athenaJunior) { + athenaJunior.permission = { + ...athenaJunior.permission, + task: "allow", + prepare_council_prompt: "allow", + council_finalize: "allow", + question: "deny", + }; + } params.config.permission = { webfetch: "allow", diff --git a/src/shared/agent-tool-restrictions.ts b/src/shared/agent-tool-restrictions.ts index f807e8bf2..b05985d80 100644 --- a/src/shared/agent-tool-restrictions.ts +++ b/src/shared/agent-tool-restrictions.ts @@ -51,6 +51,11 @@ const AGENT_RESTRICTIONS: Record> = { call_omo_agent: false, }, + "athena-junior": { + call_omo_agent: false, + question: false, + }, + // NOTE: Athena/council tool restrictions are also defined in: // - src/agents/athena/agent.ts (AgentConfig permission format) // - src/agents/athena/council-member-agent.ts (AgentConfig permission format — allow-list) diff --git a/src/shared/model-requirements.ts b/src/shared/model-requirements.ts index 3ba5e69ff..111488ea7 100644 --- a/src/shared/model-requirements.ts +++ b/src/shared/model-requirements.ts @@ -190,6 +190,16 @@ export const AGENT_MODEL_REQUIREMENTS: Record = { { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" }, ], }, + "athena-junior": { + fallbackChain: [ + { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" }, + { providers: ["opencode"], model: "kimi-k2.5-free" }, + { providers: ["zai-coding-plan"], model: "glm-4.7" }, + { providers: ["opencode"], model: "glm-4.7-free" }, + { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" }, + { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" }, + ], + }, "council-member": { fallbackChain: [ { providers: ["opencode"], model: "gpt-5-nano" }, diff --git a/src/tools/call-omo-agent/constants.ts b/src/tools/call-omo-agent/constants.ts index 831c112e6..ec11e902d 100644 --- a/src/tools/call-omo-agent/constants.ts +++ b/src/tools/call-omo-agent/constants.ts @@ -6,7 +6,7 @@ export const ALLOWED_AGENTS = [ "metis", "momus", "multimodal-looker", - "athena", + "athena-junior", ] as const export const CALL_OMO_AGENT_DESCRIPTION = `Spawn explore/librarian agent or custom agents. run_in_background REQUIRED (true=async with task_id, false=sync). diff --git a/src/tools/call-omo-agent/tools.test.ts b/src/tools/call-omo-agent/tools.test.ts index 657170fce..f658c90cd 100644 --- a/src/tools/call-omo-agent/tools.test.ts +++ b/src/tools/call-omo-agent/tools.test.ts @@ -512,7 +512,7 @@ describe("createCallOmoAgent", () => { expect(result).toContain("background_task.maxDepth=3") }) - test("should accept athena as a valid agent type", async () => { + test("should accept athena-junior as a valid agent type", async () => { //#given const toolDef = createCallOmoAgent(mockCtx, mockBackgroundManager, []) const executeFunc = toolDef.execute as Function @@ -522,7 +522,7 @@ describe("createCallOmoAgent", () => { { description: "Test", prompt: "Test prompt", - subagent_type: "athena", + subagent_type: "athena-junior", run_in_background: true, }, { sessionID: "test", messageID: "msg", agent: "test", abort: new AbortController().signal } diff --git a/src/tools/council-archive/create-council-finalize.ts b/src/tools/council-archive/create-council-finalize.ts index 23b1baa88..c81519a9d 100644 --- a/src/tools/council-archive/create-council-finalize.ts +++ b/src/tools/council-archive/create-council-finalize.ts @@ -11,6 +11,7 @@ import { resolveCouncilIntent, COUNCIL_DEFAULTS, } from "../../agents/athena" +import type { CouncilGuidanceMode } from "../../agents/athena" import type { CouncilFinalizeArgs, CouncilMemberResult, CouncilFinalizeResult } from "./types" export function createCouncilFinalize( @@ -30,6 +31,10 @@ export function createCouncilFinalize( .describe(`Classified question intent used for runtime Athena guidance injection. Valid intents: ${getValidCouncilIntents().join(", ")}`), question: tool.schema.string().optional().describe("Original user question that triggered the council"), prompt_file: tool.schema.string().optional().describe("Path to the council prompt temp file (will be moved into the archive)"), + mode: tool.schema + .string() + .optional() + .describe('Council guidance mode: "interactive" (default) includes action paths with Question tool, "non-interactive" strips action paths for programmatic use'), }, async execute(args: CouncilFinalizeArgs, toolContext) { const resolvedIntent = resolveCouncilIntent(args.intent) @@ -154,7 +159,8 @@ export function createCouncilFinalize( members, } - const guidance = buildAthenaRuntimeGuidance(resolvedIntent) + const resolvedMode = (args.mode === "non-interactive" ? "non-interactive" : "interactive") as CouncilGuidanceMode + const guidance = buildAthenaRuntimeGuidance(resolvedIntent, resolvedMode) return JSON.stringify(result, null, 2) + "\n\n" + guidance }, }) diff --git a/src/tools/council-archive/types.ts b/src/tools/council-archive/types.ts index eea6fc1f0..909826983 100644 --- a/src/tools/council-archive/types.ts +++ b/src/tools/council-archive/types.ts @@ -4,6 +4,7 @@ export interface CouncilFinalizeArgs { intent: string question?: string prompt_file?: string + mode?: string } export interface CouncilMemberResult {