From 9a69478d8e9b63626135a0ab95c9da5415531b63 Mon Sep 17 00:00:00 2001 From: ismeth Date: Fri, 13 Feb 2026 10:52:57 +0100 Subject: [PATCH] feat(athena): use Question tool TUI for council member selection with dynamic member list --- src/agents/athena/agent.ts | 32 ++++++++++++++++---------- src/tools/athena-council/constants.ts | 4 ++-- src/tools/athena-council/tools.test.ts | 7 +++--- src/tools/athena-council/tools.ts | 13 +++++++++-- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/agents/athena/agent.ts b/src/agents/athena/agent.ts index f603902a..13490498 100644 --- a/src/agents/athena/agent.ts +++ b/src/agents/athena/agent.ts @@ -32,19 +32,27 @@ export const ATHENA_PROMPT_METADATA: AgentPromptMetadata = { const ATHENA_SYSTEM_PROMPT = `You are Athena, a multi-model council orchestrator. You do NOT analyze code yourself. Your ONLY job is to send the user's question to your council of AI models, then synthesize their responses. -## CRITICAL: Council Member Selection +## CRITICAL: Council Member Selection (Your First Action) -Before calling athena_council, you MUST ask the user which council members to consult. Present the choice like this: +Before calling athena_council, you MUST present a multi-select prompt using the Question tool so the user can choose which council members to consult. The athena_council tool description lists all available members. -"I'll consult the council on this. Which members should I ask? -1. **All members** (default) — consult everyone -2. **Select specific members** — choose from: [list member names from config] +Use the Question tool like this: -Reply with member names, numbers, or just say 'all'." +Question({ + questions: [{ + question: "Which council members should I consult?", + header: "Council Members", + options: [ + { label: "All Members", description: "Consult all configured council members" }, + ...one option per member from the athena_council tool description's "Available council members" list + ], + multiple: true + }] +}) -**Shortcut:** If the user already specified models in their message (e.g., "ask GPT and Claude about X"), skip the selection prompt and call athena_council directly with those members. - -**Shortcut:** If the user says "all", "everyone", "the whole council", or similar — call athena_council without the members parameter (uses all). +**Shortcut — skip the Question tool if:** +- The user already specified models in their message (e.g., "ask GPT and Claude about X") → call athena_council directly with those members. +- The user says "all", "everyone", "the whole council" → call athena_council without the members parameter. DO NOT: - Read files yourself @@ -57,7 +65,7 @@ You are an ORCHESTRATOR, not an analyst. Your council members do the analysis. Y ## Workflow -Step 1: Ask the user which council members to consult (see above). Once the user responds (or if they pre-specified members), call athena_council with the user's question. Pass selected member names in the members parameter, or omit members to use all configured council members. +Step 1: Present the Question tool multi-select for council member selection (see above). Once the user responds, call athena_council with the user's question. If the user selected specific members, pass their names in the members parameter. If the user selected "All Members", omit the members parameter. Step 2: After athena_council returns, synthesize all council member responses: - Group findings by agreement level: unanimous, majority, minority, solo @@ -77,7 +85,7 @@ When the user confirms planning, output: 3. Tell the user: "Switch to Prometheus to start planning. It will see this conversation and can ask you questions." ## Constraints -- Ask which members to consult BEFORE calling athena_council (unless user pre-specified). +- Use the Question tool for member selection BEFORE calling athena_council (unless user pre-specified). - Do NOT write or edit files directly. - Do NOT delegate without explicit user confirmation. - Do NOT ignore solo finding false-positive warnings. @@ -93,7 +101,7 @@ export function createAthenaAgent(model: string): AgentConfig { mode: MODE, model, temperature: 0.1, - ...restrictions, + permission: { ...restrictions.permission, question: "allow" as const }, prompt: ATHENA_SYSTEM_PROMPT, color: "#1F8EFA", } as AgentConfig diff --git a/src/tools/athena-council/constants.ts b/src/tools/athena-council/constants.ts index 55689ace..00e7f724 100644 --- a/src/tools/athena-council/constants.ts +++ b/src/tools/athena-council/constants.ts @@ -1,8 +1,8 @@ -export const ATHENA_COUNCIL_TOOL_DESCRIPTION = `Execute Athena's multi-model council. Sends the question to all configured council members in parallel and returns their collected responses. +export const ATHENA_COUNCIL_TOOL_DESCRIPTION_TEMPLATE = `Execute Athena's multi-model council. Sends the question to all configured council members in parallel and returns their collected responses. Optionally pass a members array of member names or model IDs to consult only specific council members. If omitted, all configured members are consulted. -This tool reads council member configuration from the plugin config (agents.athena.council.members). Each member runs as an independent background agent with their configured model, variant, and temperature. +{members} Returns council member responses with status, response text, and timing. Use this output for synthesis. diff --git a/src/tools/athena-council/tools.test.ts b/src/tools/athena-council/tools.test.ts index 166213a2..7c678e4d 100644 --- a/src/tools/athena-council/tools.test.ts +++ b/src/tools/athena-council/tools.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test" import type { BackgroundManager } from "../../features/background-agent" -import { ATHENA_COUNCIL_TOOL_DESCRIPTION } from "./constants" +import { ATHENA_COUNCIL_TOOL_DESCRIPTION_TEMPLATE } from "./constants" import { createAthenaCouncilTool, filterCouncilMembers } from "./tools" const mockManager = { @@ -127,8 +127,9 @@ describe("createAthenaCouncilTool", () => { councilConfig: { members: [{ model: "openai/gpt-5.3-codex" }] }, }) - // #then - expect(athenaCouncilTool.description).toBe(ATHENA_COUNCIL_TOOL_DESCRIPTION) + // #then - description should be dynamic and include the member model + expect(athenaCouncilTool.description).toContain("openai/gpt-5.3-codex") + expect(athenaCouncilTool.description).toContain("Available council members:") expect((athenaCouncilTool as { args: Record }).args.question).toBeDefined() expect((athenaCouncilTool as { args: Record }).args.members).toBeDefined() }) diff --git a/src/tools/athena-council/tools.ts b/src/tools/athena-council/tools.ts index 0560a7ff..6eaa937d 100644 --- a/src/tools/athena-council/tools.ts +++ b/src/tools/athena-council/tools.ts @@ -2,7 +2,7 @@ import { tool, type ToolDefinition } from "@opencode-ai/plugin" import { executeCouncil } from "../../agents/athena/council-orchestrator" import type { CouncilConfig, CouncilMemberConfig, CouncilMemberResponse } from "../../agents/athena/types" import type { BackgroundManager, BackgroundTask, BackgroundTaskStatus } from "../../features/background-agent" -import { ATHENA_COUNCIL_TOOL_DESCRIPTION } from "./constants" +import { ATHENA_COUNCIL_TOOL_DESCRIPTION_TEMPLATE } from "./constants" import { createCouncilLauncher } from "./council-launcher" import type { AthenaCouncilToolArgs } from "./types" @@ -152,14 +152,23 @@ export function filterCouncilMembers( return { members: filteredMembers } } +function buildToolDescription(councilConfig: CouncilConfig | undefined): string { + const memberList = councilConfig?.members.length + ? councilConfig.members.map((m) => `- ${m.name ?? m.model}`).join("\n") + : "No members configured." + + return ATHENA_COUNCIL_TOOL_DESCRIPTION_TEMPLATE.replace("{members}", `Available council members:\n${memberList}`) +} + export function createAthenaCouncilTool(args: { backgroundManager: BackgroundManager councilConfig: CouncilConfig | undefined }): ToolDefinition { const { backgroundManager, councilConfig } = args + const description = buildToolDescription(councilConfig) return tool({ - description: ATHENA_COUNCIL_TOOL_DESCRIPTION, + description, args: { question: tool.schema.string().describe("The question to send to all council members"), members: tool.schema