From 5e71d84a69f530ca5f95f762b161d09d753da1ae Mon Sep 17 00:00:00 2001 From: ismeth Date: Tue, 3 Mar 2026 17:00:09 +0100 Subject: [PATCH] feat(athena): add bulk_launch config for council launch strategy New config: agents.athena.bulk_launch (boolean, default false) - false: launch members one-by-one via task() for TUI inspectability - true: launch all members at once via athena_council tool Prompt uses {BULK_LAUNCH_STEP_5_2} placeholder injected at runtime based on config value. Schema, caller, and tests updated. --- .../athena/athena-config-injection.test.ts | 4 ++ src/agents/athena/interactive-prompt.ts | 11 +-- src/config/schema/athena.test.ts | 72 +++++++++++++++++++ src/config/schema/athena.ts | 1 + 4 files changed, 78 insertions(+), 10 deletions(-) diff --git a/src/agents/athena/athena-config-injection.test.ts b/src/agents/athena/athena-config-injection.test.ts index 397f2de62..f45dc14b0 100644 --- a/src/agents/athena/athena-config-injection.test.ts +++ b/src/agents/athena/athena-config-injection.test.ts @@ -130,6 +130,10 @@ describe("Athena prompt config injection placeholders", () => { expect(athenaConfig.prompt).toContain("runtime guidance message injected by council_finalize") expect(athenaConfig.prompt).toContain("") }) + it("#then contains BULK_LAUNCH_STEP_5_2 placeholder", () => { + expect(athenaConfig.prompt).toContain("{BULK_LAUNCH_STEP_5_2}") + }) + }) }) }) diff --git a/src/agents/athena/interactive-prompt.ts b/src/agents/athena/interactive-prompt.ts index befc7c950..6c869f705 100644 --- a/src/agents/athena/interactive-prompt.ts +++ b/src/agents/athena/interactive-prompt.ts @@ -149,16 +149,7 @@ Bake the classified intent into your prepare_council_prompt call (Step 5.1). ## Step 5.1: Call prepare_council_prompt with the user's original question as the prompt parameter, the selected analysis mode, and the classified intent. This saves it to a temp file and returns the file path. Example: prepare_council_prompt({ prompt: "...", mode: "solo", intent: "EVALUATE" }) -## Step 5.2: 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 - - write_output_to_file: true - - prompt: "Read for your instructions." (where is the file path from Step 5.1) - - load_skills: [] - - description: the member name (e.g., "Council: Claude Opus 4.6") -- 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. +{BULK_LAUNCH_STEP_5_2} ### Step 6: Track progress with background_wait (metadata only): - After launching all members, call background_wait(task_ids=[...all task IDs...], timeout=${COUNCIL_DEFAULTS.BACKGROUND_WAIT_TIMEOUT_MS}). diff --git a/src/config/schema/athena.test.ts b/src/config/schema/athena.test.ts index a4532e852..23d35a453 100644 --- a/src/config/schema/athena.test.ts +++ b/src/config/schema/athena.test.ts @@ -693,3 +693,75 @@ describe("AthenaConfigSchema — non-interactive fields", () => { }) }) }) + +describe("AthenaConfigSchema — bulk_launch field", () => { + const validCouncil = { + members: [ + { model: "openai/gpt-5.3-codex", name: "council-opus" }, + { model: "anthropic/claude-opus-4-6", name: "council-gpt" }, + ], + } + + describe("#given bulk_launch field", () => { + describe("#when parsed with true", () => { + it("#then accepts the value", () => { + //#given + const config = { council: validCouncil, bulk_launch: true } + + //#when + const result = AthenaConfigSchema.safeParse(config) + + //#then + expect(result.success).toBe(true) + if (result.success) { + expect(result.data.bulk_launch).toBe(true) + } + }) + }) + + describe("#when parsed with false", () => { + it("#then accepts the value", () => { + //#given + const config = { council: validCouncil, bulk_launch: false } + + //#when + const result = AthenaConfigSchema.safeParse(config) + + //#then + expect(result.success).toBe(true) + if (result.success) { + expect(result.data.bulk_launch).toBe(false) + } + }) + }) + + describe("#when parsed without bulk_launch", () => { + it("#then defaults to false", () => { + //#given + const config = { council: validCouncil } + + //#when + const result = AthenaConfigSchema.safeParse(config) + + //#then + expect(result.success).toBe(true) + if (result.success) { + expect(result.data.bulk_launch).toBe(false) + } + }) + }) + + describe("#when parsed with a non-boolean value", () => { + it("#then rejects the value", () => { + //#given + const config = { council: validCouncil, bulk_launch: "yes" } + + //#when + const result = AthenaConfigSchema.safeParse(config) + + //#then + expect(result.success).toBe(false) + }) + }) + }) +}) diff --git a/src/config/schema/athena.ts b/src/config/schema/athena.ts index 6e8eb5b34..3aaa20780 100644 --- a/src/config/schema/athena.ts +++ b/src/config/schema/athena.ts @@ -34,6 +34,7 @@ export type CouncilConfig = z.infer export const AthenaConfigSchema = z.object({ council: CouncilConfigSchema, + bulk_launch: z.boolean().default(false).optional(), non_interactive_mode: z.enum(["delegation", "solo"]).default("delegation").optional(), non_interactive_members: z.enum(["all", "custom"]).default("all").optional(), non_interactive_member_list: z.array(z.string()).optional(),