refactor(athena): delete athena_council tool directory

Remove the entire custom tool implementation (constants, launcher, session-waiter, tool-helpers, tools, types, and all tests). Council members are now launched via the standard task tool.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
ismeth
2026-02-19 02:20:18 +01:00
committed by YeonGyu-Kim
parent 70f074f579
commit 1349948957
10 changed files with 0 additions and 686 deletions

View File

@@ -1,10 +0,0 @@
export const ATHENA_COUNCIL_TOOL_DESCRIPTION_TEMPLATE = `Execute Athena's multi-model council for exactly ONE member per call.
Pass members as a single-item array containing one member name or model ID. Athena should call this tool once per selected member.
This tool launches the selected member as a background task and returns task/session metadata immediately.
After launching ALL members, use background_wait(task_ids=[...all IDs...]) to wait for results. It blocks until the next member finishes and returns progress. Repeat with remaining IDs until all complete.
{members}
IMPORTANT: This tool is designed for Athena agent use only. It requires council configuration to be present.`

View File

@@ -1,51 +0,0 @@
import { describe, expect, test } from "bun:test"
import type { BackgroundManager } from "../../features/background-agent"
import type { BackgroundTask, LaunchInput } from "../../features/background-agent/types"
import { createCouncilLauncher } from "./council-launcher"
function createMockTask(id: string): BackgroundTask {
return {
id,
parentSessionID: "session-1",
parentMessageID: "message-1",
description: "test",
prompt: "test",
agent: "athena",
status: "running",
}
}
describe("createCouncilLauncher", () => {
//#given a council launch input with all fields
//#when launch is called
//#then all fields are forwarded to the background manager
test("forwards all launch input fields to background manager", async () => {
const capturedInputs: LaunchInput[] = []
const mockManager = {
launch: async (input: LaunchInput) => {
capturedInputs.push(input)
return createMockTask("bg-1")
},
getTask: () => undefined,
} as unknown as BackgroundManager
const launcher = createCouncilLauncher(mockManager)
await launcher.launch({
description: "Council member: test",
prompt: "Analyze this",
agent: "athena",
parentSessionID: "session-1",
parentMessageID: "message-1",
model: { providerID: "openai", modelID: "gpt-5.3-codex" },
})
expect(capturedInputs).toHaveLength(1)
expect(capturedInputs[0]?.description).toBe("Council member: test")
expect(capturedInputs[0]?.prompt).toBe("Analyze this")
expect(capturedInputs[0]?.agent).toBe("athena")
expect(capturedInputs[0]?.parentSessionID).toBe("session-1")
expect(capturedInputs[0]?.parentMessageID).toBe("message-1")
expect(capturedInputs[0]?.model).toEqual({ providerID: "openai", modelID: "gpt-5.3-codex" })
})
})

View File

@@ -1,18 +0,0 @@
import type { CouncilLauncher, CouncilLaunchInput } from "../../agents/athena/council-orchestrator"
import type { BackgroundManager } from "../../features/background-agent"
export function createCouncilLauncher(manager: BackgroundManager): CouncilLauncher {
return {
launch(input: CouncilLaunchInput) {
return manager.launch({
description: input.description,
prompt: input.prompt,
agent: input.agent,
parentSessionID: input.parentSessionID,
parentMessageID: input.parentMessageID,
parentAgent: input.parentAgent,
model: input.model,
})
},
}
}

View File

@@ -1 +0,0 @@
export { createAthenaCouncilTool } from "./tools"

View File

@@ -1,108 +0,0 @@
/// <reference types="bun-types" />
import { describe, expect, test } from "bun:test"
import type { BackgroundManager } from "../../features/background-agent"
import { waitForCouncilSessions } from "./session-waiter"
describe("waitForCouncilSessions", () => {
test("resolves all sessions when tasks have sessionIDs immediately", async () => {
//#given
const launched = [
{ member: { model: "openai/gpt-5.3-codex", name: "GPT" }, taskId: "task-1" },
{ member: { model: "anthropic/claude-opus-4-6" }, taskId: "task-2" },
]
const manager = {
getTask: (id: string) => ({ sessionID: `ses-${id}` }),
} as unknown as BackgroundManager
//#when
const result = await waitForCouncilSessions(launched, manager)
//#then
expect(result.sessions).toHaveLength(2)
expect(result.timedOut).toBe(false)
expect(result.aborted).toBe(false)
expect(result.sessions[0].taskId).toBe("task-1")
expect(result.sessions[0].memberName).toBe("GPT")
expect(result.sessions[1].taskId).toBe("task-2")
expect(result.sessions[1].memberName).toBe("anthropic/claude-opus-4-6")
})
test("returns empty sessions for empty launched list", async () => {
//#given
const manager = { getTask: () => undefined } as unknown as BackgroundManager
//#when
const result = await waitForCouncilSessions([], manager)
//#then
expect(result.sessions).toHaveLength(0)
expect(result.timedOut).toBe(false)
expect(result.aborted).toBe(false)
})
test("sets aborted flag when abort signal fires", async () => {
//#given
const launched = [
{ member: { model: "openai/gpt-5.3-codex" }, taskId: "task-1" },
]
const manager = { getTask: () => undefined } as unknown as BackgroundManager
const controller = new AbortController()
// Abort immediately
controller.abort()
//#when
const result = await waitForCouncilSessions(launched, manager, controller.signal)
//#then
expect(result.sessions).toHaveLength(0)
expect(result.aborted).toBe(true)
expect(result.timedOut).toBe(false)
})
test("resolves partial sessions when some tasks get sessionIDs", async () => {
//#given
const launched = [
{ member: { model: "openai/gpt-5.3-codex", name: "GPT" }, taskId: "task-1" },
{ member: { model: "anthropic/claude-opus-4-6" }, taskId: "task-2" },
]
const controller = new AbortController()
let callCount = 0
const manager = {
getTask: (id: string) => {
callCount++
// Only task-1 gets a session, task-2 never does
if (id === "task-1") return { sessionID: "ses-task-1" }
return undefined
},
} as unknown as BackgroundManager
// Abort after a short delay to avoid waiting full 30s
setTimeout(() => controller.abort(), 200)
//#when
const result = await waitForCouncilSessions(launched, manager, controller.signal)
//#then
expect(result.sessions).toHaveLength(1)
expect(result.sessions[0].taskId).toBe("task-1")
expect(result.aborted).toBe(true)
})
test("uses member model as memberName when name is not provided", async () => {
//#given
const launched = [
{ member: { model: "google/gemini-3-pro" }, taskId: "task-1" },
]
const manager = {
getTask: () => ({ sessionID: "ses-1" }),
} as unknown as BackgroundManager
//#when
const result = await waitForCouncilSessions(launched, manager)
//#then
expect(result.sessions[0].memberName).toBe("google/gemini-3-pro")
expect(result.sessions[0].model).toBe("google/gemini-3-pro")
})
})

View File

@@ -1,67 +0,0 @@
import type { BackgroundManager } from "../../features/background-agent"
import type { CouncilLaunchedMember } from "../../agents/athena/types"
const WAIT_INTERVAL_MS = 100
const WAIT_TIMEOUT_MS = 30_000
interface CouncilSessionInfo {
taskId: string
memberName: string
model: string
sessionId: string
}
export interface CouncilSessionWaitResult {
sessions: CouncilSessionInfo[]
timedOut: boolean
aborted: boolean
}
/**
* Waits for background sessions to be created for launched council members.
* Returns session info for each member whose session became available within the timeout.
*/
export async function waitForCouncilSessions(
launched: CouncilLaunchedMember[],
manager: BackgroundManager,
abort?: AbortSignal
): Promise<CouncilSessionWaitResult> {
const results: CouncilSessionInfo[] = []
const pending = new Map(
launched.map((entry) => [entry.taskId, entry])
)
const deadline = Date.now() + WAIT_TIMEOUT_MS
let timedOut = false
let aborted = false
while (pending.size > 0 && Date.now() < deadline) {
if (abort?.aborted) {
aborted = true
break
}
for (const [taskId, entry] of pending) {
const task = manager.getTask(taskId)
if (task?.sessionID) {
results.push({
taskId,
memberName: entry.member.name ?? entry.member.model,
model: entry.member.model,
sessionId: task.sessionID,
})
pending.delete(taskId)
}
}
if (pending.size > 0) {
await new Promise((resolve) => setTimeout(resolve, WAIT_INTERVAL_MS))
}
}
if (pending.size > 0 && !aborted) {
timedOut = true
}
return { sessions: results, timedOut, aborted }
}

View File

@@ -1,95 +0,0 @@
import type { CouncilConfig, CouncilMemberConfig } from "../../agents/athena/types"
import { ATHENA_COUNCIL_TOOL_DESCRIPTION_TEMPLATE } from "./constants"
function isCouncilConfigured(councilConfig: CouncilConfig | undefined): councilConfig is CouncilConfig {
return Boolean(councilConfig && councilConfig.members.length > 0)
}
interface FilterCouncilMembersResult {
members: CouncilMemberConfig[]
error?: string
}
function buildSingleMemberSelectionError(members: CouncilMemberConfig[]): string {
const availableNames = members.map((member) => member.name ?? member.model).join(", ")
return `athena_council runs one member per call. Pass exactly one member in members (single-item array). Available members: ${availableNames}.`
}
function filterCouncilMembers(
members: CouncilMemberConfig[],
selectedNames: string[] | undefined
): FilterCouncilMembersResult {
if (!selectedNames || selectedNames.length === 0) {
return {
members: [],
error: buildSingleMemberSelectionError(members),
}
}
const memberLookup = new Map<string, CouncilMemberConfig>()
members.forEach((member) => {
memberLookup.set(member.model.toLowerCase(), member)
if (member.name) {
memberLookup.set(member.name.toLowerCase(), member)
}
})
const unresolved: string[] = []
const filteredMembers: CouncilMemberConfig[] = []
const includedMembers = new Set<CouncilMemberConfig>()
selectedNames.forEach((selectedName) => {
const selectedKey = selectedName.toLowerCase()
const matchedMember = memberLookup.get(selectedKey)
if (!matchedMember) {
unresolved.push(selectedName)
return
}
if (includedMembers.has(matchedMember)) {
return
}
includedMembers.add(matchedMember)
filteredMembers.push(matchedMember)
})
if (unresolved.length > 0) {
const availableDescriptions = members
.map((member) => {
if (member.name) {
return `${member.name} (${member.model})`
}
return member.model
})
.join(", ")
return {
members: [],
error: `Unknown council members: ${unresolved.join(", ")}. Available: ${availableDescriptions}.`,
}
}
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}`)
}
function formatCouncilLaunchFailure(
failures: Array<{ member: { name?: string; model: string }; error: string }>
): string {
const failureLines = failures
.map((failure) => `- **${failure.member.name ?? failure.member.model}**: ${failure.error}`)
.join("\n")
return failureLines
? `Failed to launch council member.\n\n### Launch Failures\n\n${failureLines}`
: "Failed to launch council member."
}
export { isCouncilConfigured, filterCouncilMembers, buildSingleMemberSelectionError, buildToolDescription, formatCouncilLaunchFailure }

View File

@@ -1,205 +0,0 @@
/// <reference types="bun-types" />
import { describe, expect, test } from "bun:test"
import type { BackgroundManager } from "../../features/background-agent"
import type { BackgroundTask } from "../../features/background-agent/types"
import { createAthenaCouncilTool } from "./tools"
import { filterCouncilMembers } from "./tool-helpers"
const mockManager = {
getTask: () => undefined,
launch: async () => {
throw new Error("launch should not be called in config validation tests")
},
} as unknown as BackgroundManager
const mockToolContext = {
sessionID: "session-1",
messageID: "message-1",
agent: "athena",
abort: new AbortController().signal,
}
const configuredMembers = [
{ name: "Claude", model: "anthropic/claude-sonnet-4-5" },
{ name: "GPT", model: "openai/gpt-5.3-codex" },
{ model: "google/gemini-3-pro" },
]
function createRunningTask(id: string, sessionID = `ses-${id}`): BackgroundTask {
return {
id,
parentSessionID: "session-1",
parentMessageID: "message-1",
description: `Council member task ${id}`,
prompt: "prompt",
agent: "council-member",
status: "running",
sessionID,
}
}
describe("filterCouncilMembers", () => {
test("returns selection error when selection is undefined", () => {
const result = filterCouncilMembers(configuredMembers, undefined)
expect(result.members).toEqual([])
expect(result.error).toBe(
"athena_council runs one member per call. Pass exactly one member in members (single-item array). Available members: Claude, GPT, google/gemini-3-pro."
)
})
test("returns selection error when selection is empty", () => {
const result = filterCouncilMembers(configuredMembers, [])
expect(result.members).toEqual([])
expect(result.error).toBe(
"athena_council runs one member per call. Pass exactly one member in members (single-item array). Available members: Claude, GPT, google/gemini-3-pro."
)
})
test("filters members using case-insensitive name and model matching", () => {
const result = filterCouncilMembers(configuredMembers, ["gpt", "GOOGLE/GEMINI-3-PRO"])
expect(result.members).toEqual([configuredMembers[1], configuredMembers[2]])
expect(result.error).toBeUndefined()
})
test("returns helpful error when selected members are not configured", () => {
const result = filterCouncilMembers(configuredMembers, ["mistral", "xai/grok-3"])
expect(result.members).toEqual([])
expect(result.error).toBe(
"Unknown council members: mistral, xai/grok-3. Available: Claude (anthropic/claude-sonnet-4-5), GPT (openai/gpt-5.3-codex), google/gemini-3-pro."
)
})
test("deduplicates when same member is selected by both name and model", () => {
const result = filterCouncilMembers(configuredMembers, ["Claude", "anthropic/claude-sonnet-4-5"])
expect(result.members).toEqual([configuredMembers[0]])
expect(result.error).toBeUndefined()
})
})
describe("createAthenaCouncilTool", () => {
test("returns error when councilConfig is undefined", async () => {
const athenaCouncilTool = createAthenaCouncilTool({
backgroundManager: mockManager,
councilConfig: undefined,
})
const result = await athenaCouncilTool.execute({ question: "How should we proceed?" }, mockToolContext)
expect(result).toBe("Athena council is not configured. Add council members to agents.athena.council.members in .opencode/oh-my-opencode.jsonc.")
})
test("returns error when councilConfig has empty members", async () => {
const athenaCouncilTool = createAthenaCouncilTool({
backgroundManager: mockManager,
councilConfig: { members: [] },
})
const result = await athenaCouncilTool.execute({ question: "Any concerns?" }, mockToolContext)
expect(result).toBe("Athena council is not configured. Add council members to agents.athena.council.members in .opencode/oh-my-opencode.jsonc.")
})
test("returns helpful error when members contains invalid names", async () => {
const athenaCouncilTool = createAthenaCouncilTool({
backgroundManager: mockManager,
councilConfig: { members: configuredMembers },
})
const result = await athenaCouncilTool.execute(
{ question: "Who should investigate this?", members: ["unknown-model"] },
mockToolContext
)
expect(result).toBe("Unknown council members: unknown-model. Available: Claude (anthropic/claude-sonnet-4-5), GPT (openai/gpt-5.3-codex), google/gemini-3-pro.")
})
test("returns selection error when members are omitted", async () => {
const athenaCouncilTool = createAthenaCouncilTool({
backgroundManager: mockManager,
councilConfig: { members: configuredMembers },
})
const result = await athenaCouncilTool.execute({ question: "How should we proceed?" }, mockToolContext)
expect(result).toBe(
"athena_council runs one member per call. Pass exactly one member in members (single-item array). Available members: Claude, GPT, google/gemini-3-pro."
)
})
test("returns selection error when multiple members are provided", async () => {
const athenaCouncilTool = createAthenaCouncilTool({
backgroundManager: mockManager,
councilConfig: { members: configuredMembers },
})
const result = await athenaCouncilTool.execute(
{ question: "How should we proceed?", members: ["Claude", "GPT"] },
mockToolContext
)
expect(result).toBe(
"athena_council runs one member per call. Pass exactly one member in members (single-item array). Available members: Claude, GPT, google/gemini-3-pro."
)
})
test("launches selected member and returns background task metadata", async () => {
let launchCount = 0
const taskStore = new Map<string, BackgroundTask>()
const launchManager = {
launch: async () => {
launchCount += 1
const task = createRunningTask(`bg-${launchCount}`)
taskStore.set(task.id, task)
return task
},
getTask: (id: string) => taskStore.get(id),
} as unknown as BackgroundManager
const athenaCouncilTool = createAthenaCouncilTool({
backgroundManager: launchManager,
councilConfig: { members: configuredMembers },
})
const result = await athenaCouncilTool.execute(
{ question: "Who should investigate this?", members: ["GPT"] },
mockToolContext
)
expect(launchCount).toBe(1)
expect(result).toContain("Council member launched in background.")
expect(result).toContain("Task ID: bg-1")
expect(result).toContain("Session ID: ses-bg-1")
expect(result).toContain("Member: GPT")
expect(result).toContain("Model: openai/gpt-5.3-codex")
expect(result).toContain("Status: running")
expect(result).toContain("background_output")
expect(result).toContain("task_id=\"bg-1\"")
expect(result).toContain("<task_metadata>")
expect(result).toContain("session_id: ses-bg-1")
})
test("returns launch failure details when selected member fails", async () => {
const launchManager = {
launch: async () => {
throw new Error("provider outage")
},
getTask: () => undefined,
} as unknown as BackgroundManager
const athenaCouncilTool = createAthenaCouncilTool({
backgroundManager: launchManager,
councilConfig: { members: configuredMembers },
})
const result = await athenaCouncilTool.execute(
{ question: "Any concerns?", members: ["GPT"] },
mockToolContext
)
expect(result).toContain("Failed to launch council member.")
expect(result).toContain("### Launch Failures")
expect(result).toContain("**GPT**")
expect(result).toContain("provider outage")
})
})

View File

@@ -1,118 +0,0 @@
import { tool, type ToolDefinition } from "@opencode-ai/plugin"
import { executeCouncil } from "../../agents/athena/council-orchestrator"
import type { CouncilConfig } from "../../agents/athena/types"
import type { BackgroundManager } from "../../features/background-agent"
import { createCouncilLauncher } from "./council-launcher"
import { waitForCouncilSessions } from "./session-waiter"
import type { AthenaCouncilToolArgs, AthenaCouncilToolContext } from "./types"
import { storeToolMetadata } from "../../features/tool-metadata-store"
import { log } from "../../shared/logger"
import {
isCouncilConfigured,
filterCouncilMembers,
buildSingleMemberSelectionError,
buildToolDescription,
formatCouncilLaunchFailure,
} from "./tool-helpers"
export function createAthenaCouncilTool(args: {
backgroundManager: BackgroundManager
councilConfig: CouncilConfig | undefined
}): ToolDefinition {
const { backgroundManager, councilConfig } = args
const description = buildToolDescription(councilConfig)
return tool({
description,
args: {
question: tool.schema.string().describe("The question to send to all council members"),
members: tool.schema
.array(tool.schema.string())
.optional()
.describe("Single-item list containing exactly one council member name or model ID."),
},
async execute(toolArgs: AthenaCouncilToolArgs, toolContext: AthenaCouncilToolContext) {
if (!isCouncilConfigured(councilConfig)) {
return "Athena council is not configured. Add council members to agents.athena.council.members in .opencode/oh-my-opencode.jsonc."
}
const filteredMembers = filterCouncilMembers(councilConfig.members, toolArgs.members)
if (filteredMembers.error) {
return filteredMembers.error
}
if (filteredMembers.members.length !== 1) {
return buildSingleMemberSelectionError(councilConfig.members)
}
const execution = await executeCouncil({
question: toolArgs.question,
council: { members: filteredMembers.members },
launcher: createCouncilLauncher(backgroundManager),
parentSessionID: toolContext.sessionID,
parentMessageID: toolContext.messageID,
parentAgent: toolContext.agent,
})
if (execution.launched.length === 0) {
return formatCouncilLaunchFailure(execution.failures)
}
const launched = execution.launched[0]
const launchedMemberName = launched?.member.name ?? launched?.member.model
const launchedMemberModel = launched?.member.model ?? "unknown"
const launchedTaskId = launched?.taskId ?? "unknown"
log("[athena-council] Launching council member", { member: launchedMemberName, model: launchedMemberModel, taskId: launchedTaskId })
const waitResult = await waitForCouncilSessions(execution.launched, backgroundManager, toolContext.abort)
const launchedSession = waitResult.sessions.find((session) => session.taskId === launchedTaskId)
const sessionId = launchedSession?.sessionId ?? "pending"
let statusNote = ""
if (waitResult.timedOut) {
statusNote = "\nNote: Session creation timed out. The task is still running — use background_output to check status."
} else if (waitResult.aborted) {
statusNote = "\nNote: Session wait was aborted. The task may still be running."
}
log("[athena-council] Session resolved", { taskId: launchedTaskId, sessionId })
if (toolContext.metadata) {
const memberMetadata = {
title: `Council: ${launchedMemberName}`,
metadata: {
sessionId,
agent: "council-member",
model: launchedMemberModel,
description: `Council member: ${launchedMemberName}`,
},
}
try {
await toolContext.metadata(memberMetadata)
if (toolContext.callID) {
storeToolMetadata(toolContext.sessionID, toolContext.callID, memberMetadata)
}
} catch (error) {
log("[athena-council] Metadata storage failed (best-effort)", { error: error instanceof Error ? error.message : String(error) })
}
}
return `Council member launched in background.
Task ID: ${launchedTaskId}
Session ID: ${sessionId}
Member: ${launchedMemberName}
Model: ${launchedMemberModel}
Status: running${statusNote}
Use \`background_output\` with task_id="${launchedTaskId}" to collect this member's result.
- block=true: Wait for completion and return the result
- full_session=true: Include full session messages when needed
<task_metadata>
session_id: ${sessionId}
</task_metadata>`
},
})
}

View File

@@ -1,13 +0,0 @@
export interface AthenaCouncilToolArgs {
question: string
members?: string[]
}
export interface AthenaCouncilToolContext {
sessionID: string
messageID: string
agent: string
abort: AbortSignal
metadata?: (input: { title?: string; metadata?: Record<string, unknown> }) => void | Promise<void>
callID?: string
}