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.
This commit is contained in:
@@ -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("runtime guidance message injected by council_finalize")
|
||||||
expect(athenaConfig.prompt).toContain("<athena_runtime_guidance>")
|
expect(athenaConfig.prompt).toContain("<athena_runtime_guidance>")
|
||||||
})
|
})
|
||||||
|
it("#then contains BULK_LAUNCH_STEP_5_2 placeholder", () => {
|
||||||
|
expect(athenaConfig.prompt).toContain("{BULK_LAUNCH_STEP_5_2}")
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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.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:
|
{BULK_LAUNCH_STEP_5_2}
|
||||||
- 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 <path> for your instructions." (where <path> 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.
|
|
||||||
|
|
||||||
### Step 6: Track progress with background_wait (metadata only):
|
### 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}).
|
- After launching all members, call background_wait(task_ids=[...all task IDs...], timeout=${COUNCIL_DEFAULTS.BACKGROUND_WAIT_TIMEOUT_MS}).
|
||||||
|
|||||||
@@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export type CouncilConfig = z.infer<typeof CouncilConfigSchema>
|
|||||||
|
|
||||||
export const AthenaConfigSchema = z.object({
|
export const AthenaConfigSchema = z.object({
|
||||||
council: CouncilConfigSchema,
|
council: CouncilConfigSchema,
|
||||||
|
bulk_launch: z.boolean().default(false).optional(),
|
||||||
non_interactive_mode: z.enum(["delegation", "solo"]).default("delegation").optional(),
|
non_interactive_mode: z.enum(["delegation", "solo"]).default("delegation").optional(),
|
||||||
non_interactive_members: z.enum(["all", "custom"]).default("all").optional(),
|
non_interactive_members: z.enum(["all", "custom"]).default("all").optional(),
|
||||||
non_interactive_member_list: z.array(z.string()).optional(),
|
non_interactive_member_list: z.array(z.string()).optional(),
|
||||||
|
|||||||
Reference in New Issue
Block a user