diff --git a/src/agents/AGENTS.md b/src/agents/AGENTS.md index 500f23ca5..65c02e15f 100644 --- a/src/agents/AGENTS.md +++ b/src/agents/AGENTS.md @@ -35,7 +35,7 @@ Agent factories following `createXXXAgent(model) → AgentConfig` pattern. Each | Atlas | task, call_omo_agent | | Momus | write, edit, task | | Athena | write, edit, call_omo_agent | -| Council-Member | write, edit, task, call_omo_agent, switch_agent, background_wait | +| Council-Member | write, edit, task, call_omo_agent, switch_agent, background_wait, prepare_council_prompt | ## STRUCTURE diff --git a/src/agents/athena/agent.ts b/src/agents/athena/agent.ts index 002ead7ca..ecdd39c5a 100644 --- a/src/agents/athena/agent.ts +++ b/src/agents/athena/agent.ts @@ -73,14 +73,17 @@ Step 2: Resolve the selected member list: - If user selected "All Members", resolve to every member from your available council members listed below. - Otherwise resolve to the explicitly selected member labels. -Step 3: Launch each selected member via the task tool with run_in_background=true: -- For each selected member, call the task tool with: +Step 3: Save the prompt, then launch members with short references: + +Step 3a: Call prepare_council_prompt with the user's original question as the prompt parameter. This saves it to a temp file and returns the file path. + +Step 3b: For each selected member, call the task tool with: - subagent_type: the exact member name from your available council members listed below (e.g., "Council: Claude Opus 4.6") - run_in_background: true - - prompt: the user's original question + - prompt: "Read for your instructions." (where is the file path from Step 3a) - load_skills: [] - description: the member name (e.g., "Council: Claude Opus 4.6") -- Launch ALL selected members FIRST (one task call per member, all in parallel) before collecting any results. +- Launch ALL selected members before collecting any results. - Track every returned task_id and member mapping. - IMPORTANT: Use EXACTLY the subagent_type names listed in your available council members below — they must match precisely. diff --git a/src/agents/athena/council-member-agent.ts b/src/agents/athena/council-member-agent.ts index c813acbfd..fa53f2cab 100644 --- a/src/agents/athena/council-member-agent.ts +++ b/src/agents/athena/council-member-agent.ts @@ -32,6 +32,7 @@ export function createCouncilMemberAgent(model: string): AgentConfig { "call_omo_agent", "switch_agent", "background_wait", + "prepare_council_prompt", ]) const base = { diff --git a/src/plugin-handlers/tool-config-handler.ts b/src/plugin-handlers/tool-config-handler.ts index b6de94def..2f9e5f635 100644 --- a/src/plugin-handlers/tool-config-handler.ts +++ b/src/plugin-handlers/tool-config-handler.ts @@ -26,6 +26,7 @@ export function applyToolConfig(params: { LspCodeActionResolve: false, "task_*": false, teammate: false, + prepare_council_prompt: false, ...(params.pluginConfig.experimental?.task_system ? { todowrite: false, todoread: false } : {}), @@ -106,6 +107,7 @@ export function applyToolConfig(params: { athena.permission = { ...athena.permission, task: "allow", + prepare_council_prompt: "allow", question: questionPermission, }; } diff --git a/src/plugin/tool-registry.ts b/src/plugin/tool-registry.ts index 10e05b904..dad7d2f5f 100644 --- a/src/plugin/tool-registry.ts +++ b/src/plugin/tool-registry.ts @@ -26,6 +26,7 @@ import { createTaskList, createTaskUpdateTool, createHashlineEditTool, + createPrepareCouncilPromptTool, } from "../tools" import { getMainSessionID } from "../features/claude-code-session-state" import { filterDisabledTools } from "../shared/disabled-tools" @@ -138,6 +139,7 @@ export function createToolRegistry(args: { interactive_bash, ...taskToolsRecord, ...hashlineToolsRecord, + prepare_council_prompt: createPrepareCouncilPromptTool(), } const filteredTools = filterDisabledTools(allTools, pluginConfig.disabled_tools) diff --git a/src/shared/agent-tool-restrictions.ts b/src/shared/agent-tool-restrictions.ts index bc4c35a9b..37192b358 100644 --- a/src/shared/agent-tool-restrictions.ts +++ b/src/shared/agent-tool-restrictions.ts @@ -63,6 +63,7 @@ const AGENT_RESTRICTIONS: Record> = { call_omo_agent: false, switch_agent: false, background_wait: false, + prepare_council_prompt: false, }, } diff --git a/src/tools/index.ts b/src/tools/index.ts index 5c85bc5d3..94b873bff 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -46,6 +46,7 @@ export { createTaskUpdateTool, } from "./task" export { createHashlineEditTool } from "./hashline-edit" +export { createPrepareCouncilPromptTool } from "./prepare-council-prompt" export function createBackgroundTools(manager: BackgroundManager, client: OpencodeClient): Record { const outputManager: BackgroundOutputManager = manager diff --git a/src/tools/prepare-council-prompt/index.ts b/src/tools/prepare-council-prompt/index.ts new file mode 100644 index 000000000..6a58bac4e --- /dev/null +++ b/src/tools/prepare-council-prompt/index.ts @@ -0,0 +1 @@ +export { createPrepareCouncilPromptTool } from "./tools" diff --git a/src/tools/prepare-council-prompt/tools.ts b/src/tools/prepare-council-prompt/tools.ts new file mode 100644 index 000000000..e2edf15fa --- /dev/null +++ b/src/tools/prepare-council-prompt/tools.ts @@ -0,0 +1,47 @@ +import { tool, type ToolDefinition } from "@opencode-ai/plugin" +import { writeFile, unlink } from "node:fs/promises" +import { join } from "node:path" +import { tmpdir } from "node:os" +import { log } from "../../shared/logger" + +const CLEANUP_DELAY_MS = 30 * 60 * 1000 + +export function createPrepareCouncilPromptTool(): ToolDefinition { + const description = `Save a council analysis prompt to a temp file so council members can read it. + +Athena-only tool. Saves the prompt once, then each council member task() call uses a short +"Read " instruction instead of repeating the full question. This keeps task() calls +fast and small. + +Returns the file path to reference in subsequent task() calls.` + + return tool({ + description, + args: { + prompt: tool.schema.string().describe("The full analysis prompt/question for council members"), + }, + async execute(args: { prompt: string }) { + if (!args.prompt?.trim()) { + return "Prompt cannot be empty." + } + + const filename = `athena-council-${crypto.randomUUID().slice(0, 8)}.md` + const filePath = join(tmpdir(), filename) + + await writeFile(filePath, args.prompt, "utf-8") + + setTimeout(() => { + unlink(filePath).catch(() => {}) + }, CLEANUP_DELAY_MS) + + log("[prepare-council-prompt] Saved prompt", { filePath, length: args.prompt.length }) + + return `Council prompt saved to: ${filePath} + +Use this path in each council member's task() call: +- prompt: "Read ${filePath} for your instructions." + +The file auto-deletes after 30 minutes.` + }, + }) +}