From f0d0658eae0ced86ac0b1136f5d37eecc879564d Mon Sep 17 00:00:00 2001 From: ismeth Date: Fri, 20 Feb 2026 14:40:44 +0100 Subject: [PATCH] fix(athena): provider-aware config + better council error messages Use parsed.providerID/modelID in council member description instead of raw model string (eliminates dead variable). Track skipped members with reasons and surface them in the missing-council guard prompt so users see why their council failed to register. --- src/agents/builtin-agents.ts | 8 +++---- .../builtin-agents/athena-council-guard.ts | 15 ++++++++---- .../builtin-agents/council-member-agents.ts | 23 ++++++++++++++----- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/agents/builtin-agents.ts b/src/agents/builtin-agents.ts index 15847433..c1cc7cf9 100644 --- a/src/agents/builtin-agents.ts +++ b/src/agents/builtin-agents.ts @@ -203,8 +203,8 @@ export async function createBuiltinAgents( result["atlas"] = atlasConfig } - if (councilConfig && councilConfig.members.length >= 2 && result["athena"]) { - const { agents: councilAgents, registeredKeys } = registerCouncilMemberAgents(councilConfig) + if (councilConfig?.members && councilConfig.members.length >= 2 && result["athena"]) { + const { agents: councilAgents, registeredKeys, skippedMembers } = registerCouncilMemberAgents(councilConfig) for (const [key, config] of Object.entries(councilAgents)) { result[key] = config } @@ -217,9 +217,9 @@ export async function createBuiltinAgents( prompt: (result["athena"].prompt ?? "") + councilTaskInstructions, } } else { - result["athena"] = appendMissingCouncilPrompt(result["athena"]) + result["athena"] = appendMissingCouncilPrompt(result["athena"], skippedMembers) } - } else if (councilConfig && councilConfig.members.length >= 2 && !result["athena"]) { + } else if (councilConfig?.members && councilConfig.members.length >= 2 && !result["athena"]) { log("[builtin-agents] Skipping council member registration — Athena is disabled") } else if (result["athena"]) { result["athena"] = appendMissingCouncilPrompt(result["athena"]) diff --git a/src/agents/builtin-agents/athena-council-guard.ts b/src/agents/builtin-agents/athena-council-guard.ts index 565bd7cd..7feabf46 100644 --- a/src/agents/builtin-agents/athena-council-guard.ts +++ b/src/agents/builtin-agents/athena-council-guard.ts @@ -42,9 +42,16 @@ After informing the user, **end your turn**. Do NOT try to work around this by u * Replaces Athena's prompt with a guard that tells the user to configure council members. * Used when Athena is registered but no valid council config exists. */ -export function appendMissingCouncilPrompt(athenaConfig: AgentConfig): AgentConfig { - return { - ...athenaConfig, - prompt: (athenaConfig.prompt ?? "") + MISSING_COUNCIL_PROMPT, +export function appendMissingCouncilPrompt( + athenaConfig: AgentConfig, + skippedMembers?: Array<{ name: string; reason: string }>, +): AgentConfig { + let prompt = (athenaConfig.prompt ?? "") + MISSING_COUNCIL_PROMPT + + if (skippedMembers && skippedMembers.length > 0) { + const skipDetails = skippedMembers.map((m) => `- **${m.name}**: ${m.reason}`).join("\n") + prompt += `\n\n### Why Council Failed\n\nThe following members were skipped:\n${skipDetails}` } + + return { ...athenaConfig, prompt } } diff --git a/src/agents/builtin-agents/council-member-agents.ts b/src/agents/builtin-agents/council-member-agents.ts index df69715a..746eb879 100644 --- a/src/agents/builtin-agents/council-member-agents.ts +++ b/src/agents/builtin-agents/council-member-agents.ts @@ -19,25 +19,33 @@ function getCouncilMemberAgentKey(member: CouncilMemberConfig): string { * Each member becomes a separate agent callable via task(subagent_type="Council: "). * Returns a record of agent keys to configs and the list of registered keys. */ +type SkippedMember = { name: string; reason: string } + export function registerCouncilMemberAgents( councilConfig: CouncilConfig -): { agents: Record; registeredKeys: string[] } { +): { agents: Record; registeredKeys: string[]; skippedMembers: SkippedMember[] } { const agents: Record = {} const registeredKeys: string[] = [] + const skippedMembers: SkippedMember[] = [] for (const member of councilConfig.members) { const parsed = parseModelString(member.model) if (!parsed) { + skippedMembers.push({ + name: member.name, + reason: `Invalid model format: '${member.model}' (expected 'provider/model-id')`, + }) log("[council-member-agents] Skipping member with invalid model", { model: member.model }) continue } const key = getCouncilMemberAgentKey(member) - const config = createCouncilMemberAgent(member.model) - - const description = `Council member: ${member.name} (${member.model}). Independent read-only code analyst for Athena council. (OhMyOpenCode)` if (agents[key]) { + skippedMembers.push({ + name: member.name, + reason: `Duplicate name: '${member.name}' already registered`, + }) log("[council-member-agents] Skipping duplicate council member name", { name: member.name, model: member.model, @@ -46,6 +54,9 @@ export function registerCouncilMemberAgents( continue } + const config = createCouncilMemberAgent(member.model) + const description = `Council member: ${member.name} (${parsed.providerID}/${parsed.modelID}). Independent read-only code analyst for Athena council. (OhMyOpenCode)` + agents[key] = { ...config, description, @@ -65,8 +76,8 @@ 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: [], skippedMembers } } - return { agents, registeredKeys } + return { agents, registeredKeys, skippedMembers } }