From 597a9069bbffed4a199585ff5d2eb14d7d7fef29 Mon Sep 17 00:00:00 2001 From: ismeth Date: Tue, 17 Feb 2026 12:33:49 +0100 Subject: [PATCH] feat(athena): add dedicated council-member agent for multi-model council Replace oracle as the agent for council background tasks with a purpose-built council-member agent. This avoids coupling to oracle's config/prompt and provides proper read-only tool restrictions (deny write, edit, task, athena_council). - New council-member-agent.ts with analysis-oriented system prompt - Registered in agentSources (hidden from Sisyphus delegation table) - Added to type system, Zod schemas, display names, tool restrictions - Minimal model fallback (always overridden per council member at launch) - Council orchestrator now launches members as council-member agent Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus --- src/agents/athena/council-member-agent.ts | 24 +++++++++++++++++++ .../athena/council-orchestrator.test.ts | 2 +- src/agents/athena/council-orchestrator.ts | 2 +- src/agents/athena/index.ts | 1 + src/agents/builtin-agents.ts | 2 ++ src/agents/types.ts | 1 + src/config/schema/agent-names.ts | 2 ++ src/shared/agent-display-names.ts | 1 + src/shared/agent-tool-restrictions.ts | 8 +++++++ src/shared/model-requirements.ts | 5 ++++ 10 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 src/agents/athena/council-member-agent.ts diff --git a/src/agents/athena/council-member-agent.ts b/src/agents/athena/council-member-agent.ts new file mode 100644 index 00000000..ff5d6464 --- /dev/null +++ b/src/agents/athena/council-member-agent.ts @@ -0,0 +1,24 @@ +import type { AgentConfig } from "@opencode-ai/sdk" +import type { AgentMode } from "../types" + +const MODE: AgentMode = "subagent" + +const COUNCIL_MEMBER_PROMPT = `You are an independent code analyst in a multi-model council. Your role is to provide thorough, evidence-based analysis of the question you receive. + +## Instructions +- Search the codebase using available tools (Read, Grep, Glob, LSP) +- Report findings with evidence: file paths, line numbers, code snippets +- For each finding, state severity (critical/high/medium/low) and confidence (high/medium/low) +- Focus on real issues backed by evidence, not hypothetical concerns +- Be concise but thorough — quality over quantity` + +export function createCouncilMemberAgent(model: string): AgentConfig { + return { + description: "Independent code analyst for Athena multi-model council. Read-only, evidence-based analysis. (Council Member - OhMyOpenCode)", + mode: MODE, + model, + temperature: 0.1, + prompt: COUNCIL_MEMBER_PROMPT, + } as AgentConfig +} +createCouncilMemberAgent.mode = MODE diff --git a/src/agents/athena/council-orchestrator.test.ts b/src/agents/athena/council-orchestrator.test.ts index 706e8ef5..264ac3b7 100644 --- a/src/agents/athena/council-orchestrator.test.ts +++ b/src/agents/athena/council-orchestrator.test.ts @@ -65,7 +65,7 @@ describe("executeCouncil", () => { for (const launch of launches) { expect(launch.prompt).toBe(expectedPrompt) - expect(launch.agent).toBe("athena") + expect(launch.agent).toBe("council-member") } expect(launches[0]?.model).toEqual({ providerID: "openai", modelID: "gpt-5.3-codex" }) diff --git a/src/agents/athena/council-orchestrator.ts b/src/agents/athena/council-orchestrator.ts index cf143cff..3630fa42 100644 --- a/src/agents/athena/council-orchestrator.ts +++ b/src/agents/athena/council-orchestrator.ts @@ -75,7 +75,7 @@ async function launchMember( return launcher.launch({ description: `Council member: ${memberName}`, prompt, - agent: "athena", + agent: "council-member", parentSessionID, parentMessageID, parentAgent, diff --git a/src/agents/athena/index.ts b/src/agents/athena/index.ts index 1d67f30d..dfbee90d 100644 --- a/src/agents/athena/index.ts +++ b/src/agents/athena/index.ts @@ -1,5 +1,6 @@ export * from "./types" export * from "./agent" +export * from "./council-member-agent" export * from "./model-parser" export * from "./council-prompt" export * from "./council-orchestrator" diff --git a/src/agents/builtin-agents.ts b/src/agents/builtin-agents.ts index 43c9b1f3..0193d482 100644 --- a/src/agents/builtin-agents.ts +++ b/src/agents/builtin-agents.ts @@ -13,6 +13,7 @@ import { createAtlasAgent, atlasPromptMetadata } from "./atlas" import { createMomusAgent, momusPromptMetadata } from "./momus" import { createHephaestusAgent } from "./hephaestus" import { createAthenaAgent, ATHENA_PROMPT_METADATA } from "./athena/agent" +import { createCouncilMemberAgent } from "./athena/council-member-agent" import type { AvailableCategory } from "./dynamic-agent-prompt-builder" import { fetchAvailableModels, @@ -40,6 +41,7 @@ const agentSources: Partial> = { metis: createMetisAgent, momus: createMomusAgent, athena: createAthenaAgent, + "council-member": createCouncilMemberAgent, // Note: Atlas is handled specially in createBuiltinAgents() // because it needs OrchestratorContext, not just a model string atlas: createAtlasAgent as AgentFactory, diff --git a/src/agents/types.ts b/src/agents/types.ts index 029bbbc6..e634a6db 100644 --- a/src/agents/types.ts +++ b/src/agents/types.ts @@ -104,6 +104,7 @@ export type BuiltinAgentName = | "momus" | "atlas" | "athena" + | "council-member" export type OverridableAgentName = | "build" diff --git a/src/config/schema/agent-names.ts b/src/config/schema/agent-names.ts index b167e5bb..53a6701b 100644 --- a/src/config/schema/agent-names.ts +++ b/src/config/schema/agent-names.ts @@ -12,6 +12,7 @@ export const BuiltinAgentNameSchema = z.enum([ "momus", "atlas", "athena", + "council-member", ]) export const BuiltinSkillNameSchema = z.enum([ @@ -38,6 +39,7 @@ export const OverridableAgentNameSchema = z.enum([ "multimodal-looker", "atlas", "athena", + "council-member", ]) export const AgentNameSchema = BuiltinAgentNameSchema diff --git a/src/shared/agent-display-names.ts b/src/shared/agent-display-names.ts index 2e8a33ab..26ec999a 100644 --- a/src/shared/agent-display-names.ts +++ b/src/shared/agent-display-names.ts @@ -16,6 +16,7 @@ export const AGENT_DISPLAY_NAMES: Record = { librarian: "librarian", explore: "explore", "multimodal-looker": "multimodal-looker", + "council-member": "council-member", } /** diff --git a/src/shared/agent-tool-restrictions.ts b/src/shared/agent-tool-restrictions.ts index e26f418a..8a7ade8b 100644 --- a/src/shared/agent-tool-restrictions.ts +++ b/src/shared/agent-tool-restrictions.ts @@ -50,6 +50,14 @@ const AGENT_RESTRICTIONS: Record> = { }, athena: ATHENA_RESTRICTIONS, + + "council-member": { + write: false, + edit: false, + task: false, + call_omo_agent: false, + athena_council: false, + }, } function permissionToToolBooleans( diff --git a/src/shared/model-requirements.ts b/src/shared/model-requirements.ts index 50497059..40d4daff 100644 --- a/src/shared/model-requirements.ts +++ b/src/shared/model-requirements.ts @@ -100,6 +100,11 @@ export const AGENT_MODEL_REQUIREMENTS: Record = { { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" }, ], }, + "council-member": { + fallbackChain: [ + { providers: ["opencode"], model: "gpt-5-nano" }, + ], + }, } export const CATEGORY_MODEL_REQUIREMENTS: Record = {