refactor(athena): remove council-orchestrator and council-prompt modules
Delete the orchestrator that launched council members via the custom athena_council tool. This logic is now replaced by standard task() calls from Athena's prompt. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1,228 +0,0 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { buildCouncilPrompt } from "./council-prompt"
|
||||
import { executeCouncil } from "./council-orchestrator"
|
||||
import type { CouncilConfig } from "./types"
|
||||
|
||||
interface MockLaunchInput {
|
||||
description: string
|
||||
prompt: string
|
||||
agent: string
|
||||
parentSessionID: string
|
||||
parentMessageID: string
|
||||
parentAgent?: string
|
||||
model?: { providerID: string; modelID: string; variant?: string }
|
||||
}
|
||||
|
||||
function createMockTask(id: string, launch: MockLaunchInput) {
|
||||
return {
|
||||
id,
|
||||
status: "pending" as const,
|
||||
parentSessionID: launch.parentSessionID,
|
||||
parentMessageID: launch.parentMessageID,
|
||||
description: launch.description,
|
||||
prompt: launch.prompt,
|
||||
agent: launch.agent,
|
||||
}
|
||||
}
|
||||
|
||||
describe("executeCouncil", () => {
|
||||
//#given a council with 3 members and a question
|
||||
//#when executeCouncil is called
|
||||
//#then all members are launched with the same prompt and parsed model ids
|
||||
test("launches all members with identical prompt and model params", async () => {
|
||||
const launches: MockLaunchInput[] = []
|
||||
const launcher = {
|
||||
launch: async (input: MockLaunchInput) => {
|
||||
launches.push(input)
|
||||
return createMockTask(`task-${launches.length}`, input)
|
||||
},
|
||||
}
|
||||
|
||||
const council: CouncilConfig = {
|
||||
members: [
|
||||
{ model: "openai/gpt-5.3-codex", name: "openai" },
|
||||
{ model: "anthropic/claude-sonnet-4-5", name: "anthropic" },
|
||||
{ model: "google/gemini-3-pro", name: "google" },
|
||||
],
|
||||
}
|
||||
|
||||
const question = "How can we improve the retry strategy?"
|
||||
const result = await executeCouncil({
|
||||
question,
|
||||
council,
|
||||
launcher,
|
||||
parentSessionID: "session-1",
|
||||
parentMessageID: "message-1",
|
||||
parentAgent: "sisyphus",
|
||||
})
|
||||
|
||||
const expectedPrompt = buildCouncilPrompt(question)
|
||||
|
||||
expect(launches).toHaveLength(3)
|
||||
expect(result.launched).toHaveLength(3)
|
||||
expect(result.failures).toHaveLength(0)
|
||||
expect(result.totalMembers).toBe(3)
|
||||
|
||||
for (const launch of launches) {
|
||||
expect(launch.prompt).toBe(expectedPrompt)
|
||||
expect(launch.agent).toBe("council-member")
|
||||
}
|
||||
|
||||
expect(launches[0]?.model).toEqual({ providerID: "openai", modelID: "gpt-5.3-codex" })
|
||||
expect(launches[1]?.model).toEqual({ providerID: "anthropic", modelID: "claude-sonnet-4-5" })
|
||||
expect(launches[2]?.model).toEqual({ providerID: "google", modelID: "gemini-3-pro" })
|
||||
})
|
||||
|
||||
//#given a council with 3 members where 1 launch throws
|
||||
//#when executeCouncil is called
|
||||
//#then launch failures are captured separately from successful launches
|
||||
test("captures launch failures separately from successful launches", async () => {
|
||||
const launcher = {
|
||||
launch: async (input: MockLaunchInput) => {
|
||||
if (input.model?.providerID === "anthropic") {
|
||||
throw new Error("Provider unavailable")
|
||||
}
|
||||
return createMockTask(`task-${input.model?.providerID}`, input)
|
||||
},
|
||||
}
|
||||
|
||||
const result = await executeCouncil({
|
||||
question: "Find race condition risks",
|
||||
council: {
|
||||
members: [
|
||||
{ model: "openai/gpt-5.3-codex" },
|
||||
{ model: "anthropic/claude-sonnet-4-5" },
|
||||
{ model: "google/gemini-3-pro" },
|
||||
],
|
||||
},
|
||||
launcher,
|
||||
parentSessionID: "session-1",
|
||||
parentMessageID: "message-1",
|
||||
})
|
||||
|
||||
expect(result.launched).toHaveLength(2)
|
||||
expect(result.failures).toHaveLength(1)
|
||||
expect(result.totalMembers).toBe(3)
|
||||
expect(result.failures[0]?.member.model).toBe("anthropic/claude-sonnet-4-5")
|
||||
expect(result.failures[0]?.error).toContain("Launch failed")
|
||||
})
|
||||
|
||||
//#given a council where all launches throw
|
||||
//#when executeCouncil is called
|
||||
//#then all members appear as failures with zero launched
|
||||
test("returns all failures when every launch throws", async () => {
|
||||
const launcher = {
|
||||
launch: async () => {
|
||||
throw new Error("Model unavailable")
|
||||
},
|
||||
}
|
||||
|
||||
const result = await executeCouncil({
|
||||
question: "Analyze unknown module",
|
||||
council: {
|
||||
members: [
|
||||
{ model: "openai/gpt-5.3-codex" },
|
||||
{ model: "anthropic/claude-sonnet-4-5" },
|
||||
],
|
||||
},
|
||||
launcher,
|
||||
parentSessionID: "session-1",
|
||||
parentMessageID: "message-1",
|
||||
})
|
||||
|
||||
expect(result.launched).toHaveLength(0)
|
||||
expect(result.failures).toHaveLength(2)
|
||||
expect(result.totalMembers).toBe(2)
|
||||
expect(result.failures.every((f) => f.error.includes("Launch failed"))).toBe(true)
|
||||
})
|
||||
|
||||
//#given a council with one invalid model string
|
||||
//#when executeCouncil is called
|
||||
//#then invalid member becomes a failure while others still launch
|
||||
test("handles invalid model strings without crashing council execution", async () => {
|
||||
const launches: MockLaunchInput[] = []
|
||||
const launcher = {
|
||||
launch: async (input: MockLaunchInput) => {
|
||||
launches.push(input)
|
||||
return createMockTask(`task-${launches.length}`, input)
|
||||
},
|
||||
}
|
||||
|
||||
const result = await executeCouncil({
|
||||
question: "Audit dependency graph",
|
||||
council: {
|
||||
members: [
|
||||
{ model: "invalid-model" },
|
||||
{ model: "openai/gpt-5.3-codex" },
|
||||
],
|
||||
},
|
||||
launcher,
|
||||
parentSessionID: "session-1",
|
||||
parentMessageID: "message-1",
|
||||
})
|
||||
|
||||
expect(launches).toHaveLength(1)
|
||||
expect(result.launched).toHaveLength(1)
|
||||
expect(result.failures).toHaveLength(1)
|
||||
expect(result.failures.find((f) => f.member.model === "invalid-model")?.error).toContain("Launch failed")
|
||||
})
|
||||
|
||||
//#given members with per-member variant
|
||||
//#when executeCouncil is called
|
||||
//#then launch receives variant in model for each corresponding member
|
||||
test("passes member variant to launch input model", async () => {
|
||||
const launches: MockLaunchInput[] = []
|
||||
const launcher = {
|
||||
launch: async (input: MockLaunchInput) => {
|
||||
launches.push(input)
|
||||
return createMockTask(`task-${launches.length}`, input)
|
||||
},
|
||||
}
|
||||
|
||||
await executeCouncil({
|
||||
question: "Compare architecture options",
|
||||
council: {
|
||||
members: [
|
||||
{ model: "openai/gpt-5.3-codex", variant: "high" },
|
||||
{ model: "anthropic/claude-sonnet-4-5" },
|
||||
],
|
||||
},
|
||||
launcher,
|
||||
parentSessionID: "session-1",
|
||||
parentMessageID: "message-1",
|
||||
})
|
||||
|
||||
expect(launches).toHaveLength(2)
|
||||
expect(launches[0]?.model?.variant).toBe("high")
|
||||
expect(launches[1]?.model?.variant).toBeUndefined()
|
||||
})
|
||||
|
||||
//#given launched members
|
||||
//#when executeCouncil returns
|
||||
//#then each launched member has a taskId for background_output retrieval
|
||||
test("returns task IDs for background_output retrieval", async () => {
|
||||
const launcher = {
|
||||
launch: async (input: MockLaunchInput) =>
|
||||
createMockTask(`bg_${input.model?.providerID}`, input),
|
||||
}
|
||||
|
||||
const result = await executeCouncil({
|
||||
question: "Review error handling",
|
||||
council: {
|
||||
members: [
|
||||
{ model: "openai/gpt-5.3-codex", name: "OpenAI" },
|
||||
{ model: "google/gemini-3-pro", name: "Gemini" },
|
||||
],
|
||||
},
|
||||
launcher,
|
||||
parentSessionID: "session-1",
|
||||
parentMessageID: "message-1",
|
||||
})
|
||||
|
||||
expect(result.launched).toHaveLength(2)
|
||||
expect(result.launched[0]?.taskId).toBe("bg_openai")
|
||||
expect(result.launched[0]?.member.name).toBe("OpenAI")
|
||||
expect(result.launched[1]?.taskId).toBe("bg_google")
|
||||
expect(result.launched[1]?.member.name).toBe("Gemini")
|
||||
})
|
||||
})
|
||||
@@ -1,94 +0,0 @@
|
||||
import type { LaunchInput, BackgroundTask } from "../../features/background-agent/types"
|
||||
import { log } from "../../shared/logger"
|
||||
import { buildCouncilPrompt } from "./council-prompt"
|
||||
import { parseModelString } from "./model-parser"
|
||||
import type { CouncilConfig, CouncilLaunchFailure, CouncilLaunchedMember, CouncilLaunchResult, CouncilMemberConfig } from "./types"
|
||||
|
||||
export type CouncilLaunchInput = LaunchInput
|
||||
|
||||
export interface CouncilLauncher {
|
||||
launch(input: CouncilLaunchInput): Promise<BackgroundTask>
|
||||
}
|
||||
|
||||
export interface CouncilExecutionInput {
|
||||
question: string
|
||||
council: CouncilConfig
|
||||
launcher: CouncilLauncher
|
||||
parentSessionID: string
|
||||
parentMessageID: string
|
||||
parentAgent?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches all council members in parallel and returns launch outcomes.
|
||||
* Does NOT wait for task completion — actual results are collected by the
|
||||
* agent via background_output calls after this returns.
|
||||
*/
|
||||
export async function executeCouncil(input: CouncilExecutionInput): Promise<CouncilLaunchResult> {
|
||||
const { question, council, launcher, parentSessionID, parentMessageID, parentAgent } = input
|
||||
|
||||
log("[athena-council] Executing council", { memberCount: council.members.length })
|
||||
|
||||
const prompt = buildCouncilPrompt(question)
|
||||
|
||||
const launchResults = await Promise.allSettled(
|
||||
council.members.map((member) =>
|
||||
launchMember(member, prompt, launcher, parentSessionID, parentMessageID, parentAgent)
|
||||
)
|
||||
)
|
||||
|
||||
const launched: CouncilLaunchedMember[] = []
|
||||
const failures: CouncilLaunchFailure[] = []
|
||||
|
||||
launchResults.forEach((result, index) => {
|
||||
const member = council.members[index]
|
||||
|
||||
if (result.status === "fulfilled") {
|
||||
launched.push({ member, taskId: result.value.id })
|
||||
return
|
||||
}
|
||||
|
||||
failures.push({
|
||||
member,
|
||||
error: `Launch failed: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`,
|
||||
})
|
||||
})
|
||||
|
||||
log("[athena-council] Council execution complete", { launched: launched.length, failures: failures.length })
|
||||
|
||||
return {
|
||||
question,
|
||||
launched,
|
||||
failures,
|
||||
totalMembers: council.members.length,
|
||||
}
|
||||
}
|
||||
|
||||
async function launchMember(
|
||||
member: CouncilMemberConfig,
|
||||
prompt: string,
|
||||
launcher: CouncilLauncher,
|
||||
parentSessionID: string,
|
||||
parentMessageID: string,
|
||||
parentAgent: string | undefined
|
||||
): Promise<BackgroundTask> {
|
||||
const parsedModel = parseModelString(member.model)
|
||||
if (!parsedModel) {
|
||||
throw new Error(`Invalid model string: "${member.model}"`)
|
||||
}
|
||||
|
||||
const memberName = member.name ?? member.model
|
||||
return launcher.launch({
|
||||
description: `Council member: ${memberName}`,
|
||||
prompt,
|
||||
agent: "council-member",
|
||||
parentSessionID,
|
||||
parentMessageID,
|
||||
parentAgent,
|
||||
model: {
|
||||
providerID: parsedModel.providerID,
|
||||
modelID: parsedModel.modelID,
|
||||
...(member.variant ? { variant: member.variant } : {}),
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
export function buildCouncilPrompt(question: string): string {
|
||||
return `You are a council member in a multi-model analysis council. Your role is to provide independent, thorough analysis of the question below.
|
||||
|
||||
## Your Role
|
||||
- You are one of several AI models analyzing the same question independently
|
||||
- Your analysis should be thorough and evidence-based
|
||||
- You are read-only - you cannot modify any files, only analyze
|
||||
- Focus on finding real issues, not hypothetical ones
|
||||
|
||||
## Instructions
|
||||
1. Analyze the question carefully
|
||||
2. Search the codebase thoroughly using available tools (Read, Grep, Glob, LSP)
|
||||
3. Report your findings with evidence (file paths, line numbers, code snippets)
|
||||
4. For each finding, state:
|
||||
- What the issue/observation is
|
||||
- Where it is (file path, line number)
|
||||
- Why it matters (severity: critical/high/medium/low)
|
||||
- Your confidence level (high/medium/low)
|
||||
5. Be concise but thorough - quality over quantity
|
||||
|
||||
## Question
|
||||
|
||||
${question}`
|
||||
}
|
||||
Reference in New Issue
Block a user