feat(athena): use Question tool TUI for council member selection with dynamic member list

This commit is contained in:
ismeth 2026-02-13 10:52:57 +01:00 committed by YeonGyu-Kim
parent a43d2bd98f
commit 9a69478d8e
4 changed files with 37 additions and 19 deletions

View File

@ -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

View File

@ -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.

View File

@ -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<string, unknown> }).args.question).toBeDefined()
expect((athenaCouncilTool as { args: Record<string, unknown> }).args.members).toBeDefined()
})

View File

@ -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