fix(athena): harden council registration with duplicate detection and count validation

Three registration improvements: gate council member registration on Athena enablement, throw on duplicate council member keys instead of silent overwrite, and disable council mode when valid members drop below 2 after model parsing.

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 13:53:30 +01:00 committed by YeonGyu-Kim
parent 7b6d3206ce
commit c1bf455b63
3 changed files with 81 additions and 2 deletions

View File

@ -19,6 +19,7 @@ import {
fetchAvailableModels,
readConnectedProvidersCache,
readProviderModelsCache,
log,
} from "../shared"
import { CATEGORY_DESCRIPTIONS } from "../tools/delegate-task/constants"
import { mergeCategories } from "../shared/merge-categories"
@ -201,13 +202,13 @@ export async function createBuiltinAgents(
result["atlas"] = atlasConfig
}
if (councilConfig && councilConfig.members.length >= 2) {
if (councilConfig && councilConfig.members.length >= 2 && result["athena"]) {
const { agents: councilAgents, registeredKeys } = registerCouncilMemberAgents(councilConfig)
for (const [key, config] of Object.entries(councilAgents)) {
result[key] = config
}
if (result["athena"] && registeredKeys.length > 0) {
if (registeredKeys.length > 0) {
const memberList = registeredKeys.map((key) => `- "${key}"`).join("\n")
const councilTaskInstructions = `\n\n## Registered Council Members (use these as subagent_type in task calls)\n\n${memberList}`
result["athena"] = {
@ -215,6 +216,8 @@ export async function createBuiltinAgents(
prompt: (result["athena"].prompt ?? "") + councilTaskInstructions,
}
}
} else if (councilConfig && councilConfig.members.length >= 2 && !result["athena"]) {
log("[builtin-agents] Skipping council member registration — Athena is disabled")
}
return result

View File

@ -0,0 +1,63 @@
import { describe, expect, test } from "bun:test"
import { registerCouncilMemberAgents } from "./council-member-agents"
describe("council-member-agents", () => {
test("throws on duplicate model without name", () => {
//#given
const config = {
members: [
{ model: "openai/gpt-5.3-codex" },
{ model: "openai/gpt-5.3-codex" },
],
}
//#when + #then
expect(() => registerCouncilMemberAgents(config)).toThrow("already registered")
})
test("registers different models without error", () => {
//#given
const config = {
members: [
{ model: "openai/gpt-5.3-codex" },
{ model: "anthropic/claude-opus-4-6" },
],
}
//#when
const result = registerCouncilMemberAgents(config)
//#then
expect(result.registeredKeys).toHaveLength(2)
expect(result.registeredKeys).toContain("Council: GPT 5.3 Codex")
expect(result.registeredKeys).toContain("Council: Claude Opus 4.6")
})
test("allows same model with different names", () => {
//#given
const config = {
members: [
{ model: "openai/gpt-5.3-codex", name: "GPT Codex" },
{ model: "openai/gpt-5.3-codex", name: "Codex GPT" },
],
}
//#when
const result = registerCouncilMemberAgents(config)
//#then
expect(result.registeredKeys).toHaveLength(2)
expect(result.agents).toHaveProperty("Council: GPT Codex")
expect(result.agents).toHaveProperty("Council: Codex GPT")
})
test("returns empty when valid members below 2", () => {
//#given - one valid model, one invalid (no slash separator)
const config = {
members: [
{ model: "openai/gpt-5.3-codex" },
{ model: "invalid-no-slash" },
],
}
//#when
const result = registerCouncilMemberAgents(config)
//#then
expect(result.registeredKeys).toHaveLength(0)
expect(result.agents).toEqual({})
})
})

View File

@ -71,11 +71,19 @@ export function registerCouncilMemberAgents(
const friendlyName = member.name ?? humanizeModelId(member.model)
const description = `Council member: ${friendlyName} (${member.model}). Independent read-only code analyst for Athena council. (OhMyOpenCode)`
if (agents[key]) {
const existingModel = agents[key].model ?? "unknown"
throw new Error(
`Council member key "${key}" is already registered (model: ${existingModel}). Use distinct "name" fields to avoid collisions.`
)
}
agents[key] = {
...config,
description,
model: member.model,
...(member.variant ? { variant: member.variant } : {}),
...(member.temperature !== undefined ? { temperature: member.temperature } : {}),
}
registeredKeys.push(key)
@ -87,5 +95,10 @@ export function registerCouncilMemberAgents(
})
}
if (registeredKeys.length < 2) {
log("[council-member-agents] Fewer than 2 valid council members after model parsing — disabling council mode")
return { agents: {}, registeredKeys: [] }
}
return { agents, registeredKeys }
}