fix(agents): exclude subagents from UI model selection override

Subagents (explore, librarian, oracle, etc.) now use their own fallback
chain instead of inheriting the UI-selected model. This fixes the issue
where explore agent was incorrectly using Opus instead of Haiku.

- Add AgentMode type and static mode property to AgentFactory
- Each agent declares its own mode via factory.mode = MODE pattern
- createBuiltinAgents() checks source.mode before passing uiSelectedModel
This commit is contained in:
justsisyphus 2026-01-30 13:49:40 +09:00
parent 67aeb9cb8c
commit 3b5d18e6bf
9 changed files with 49 additions and 18 deletions

View File

@ -1,5 +1,7 @@
import type { AgentConfig } from "@opencode-ai/sdk" import type { AgentConfig } from "@opencode-ai/sdk"
import type { AgentPromptMetadata } from "./types" import type { AgentMode, AgentPromptMetadata } from "./types"
const MODE: AgentMode = "primary"
import type { AvailableAgent, AvailableSkill, AvailableCategory } from "./dynamic-agent-prompt-builder" import type { AvailableAgent, AvailableSkill, AvailableCategory } from "./dynamic-agent-prompt-builder"
import { buildCategorySkillsDelegationGuide } from "./dynamic-agent-prompt-builder" import { buildCategorySkillsDelegationGuide } from "./dynamic-agent-prompt-builder"
import type { CategoryConfig } from "../config/schema" import type { CategoryConfig } from "../config/schema"
@ -530,7 +532,7 @@ export function createAtlasAgent(ctx: OrchestratorContext): AgentConfig {
return { return {
description: description:
"Orchestrates work via delegate_task() to complete ALL tasks in a todo list until fully done. (Atlas - OhMyOpenCode)", "Orchestrates work via delegate_task() to complete ALL tasks in a todo list until fully done. (Atlas - OhMyOpenCode)",
mode: "primary" as const, mode: MODE,
...(ctx.model ? { model: ctx.model } : {}), ...(ctx.model ? { model: ctx.model } : {}),
temperature: 0.1, temperature: 0.1,
prompt: buildDynamicOrchestratorPrompt(ctx), prompt: buildDynamicOrchestratorPrompt(ctx),
@ -539,6 +541,7 @@ export function createAtlasAgent(ctx: OrchestratorContext): AgentConfig {
...restrictions, ...restrictions,
} as AgentConfig } as AgentConfig
} }
createAtlasAgent.mode = MODE
export const atlasPromptMetadata: AgentPromptMetadata = { export const atlasPromptMetadata: AgentPromptMetadata = {
category: "advisor", category: "advisor",

View File

@ -1,7 +1,9 @@
import type { AgentConfig } from "@opencode-ai/sdk" import type { AgentConfig } from "@opencode-ai/sdk"
import type { AgentPromptMetadata } from "./types" import type { AgentMode, AgentPromptMetadata } from "./types"
import { createAgentToolRestrictions } from "../shared/permission-compat" import { createAgentToolRestrictions } from "../shared/permission-compat"
const MODE: AgentMode = "subagent"
export const EXPLORE_PROMPT_METADATA: AgentPromptMetadata = { export const EXPLORE_PROMPT_METADATA: AgentPromptMetadata = {
category: "exploration", category: "exploration",
cost: "FREE", cost: "FREE",
@ -34,7 +36,7 @@ export function createExploreAgent(model: string): AgentConfig {
return { return {
description: description:
'Contextual grep for codebases. Answers "Where is X?", "Which file has Y?", "Find the code that does Z". Fire multiple in parallel for broad searches. Specify thoroughness: "quick" for basic, "medium" for moderate, "very thorough" for comprehensive analysis. (Explore - OhMyOpenCode)', 'Contextual grep for codebases. Answers "Where is X?", "Which file has Y?", "Find the code that does Z". Fire multiple in parallel for broad searches. Specify thoroughness: "quick" for basic, "medium" for moderate, "very thorough" for comprehensive analysis. (Explore - OhMyOpenCode)',
mode: "subagent" as const, mode: MODE,
model, model,
temperature: 0.1, temperature: 0.1,
...restrictions, ...restrictions,
@ -119,4 +121,4 @@ Use the right tool for the job:
Flood with parallel calls. Cross-validate findings across multiple tools.`, Flood with parallel calls. Cross-validate findings across multiple tools.`,
} }
} }
createExploreAgent.mode = MODE

View File

@ -325,4 +325,4 @@ grep_app_searchGitHub(query: "useQuery")
`, `,
} }
} }
createLibrarianAgent.mode = MODE

View File

@ -1,7 +1,9 @@
import type { AgentConfig } from "@opencode-ai/sdk" import type { AgentConfig } from "@opencode-ai/sdk"
import type { AgentPromptMetadata } from "./types" import type { AgentMode, AgentPromptMetadata } from "./types"
import { createAgentToolRestrictions } from "../shared/permission-compat" import { createAgentToolRestrictions } from "../shared/permission-compat"
const MODE: AgentMode = "subagent"
/** /**
* Metis - Plan Consultant Agent * Metis - Plan Consultant Agent
* *
@ -311,7 +313,7 @@ export function createMetisAgent(model: string): AgentConfig {
return { return {
description: description:
"Pre-planning consultant that analyzes requests to identify hidden intentions, ambiguities, and AI failure points. (Metis - OhMyOpenCode)", "Pre-planning consultant that analyzes requests to identify hidden intentions, ambiguities, and AI failure points. (Metis - OhMyOpenCode)",
mode: "subagent" as const, mode: MODE,
model, model,
temperature: 0.3, temperature: 0.3,
...metisRestrictions, ...metisRestrictions,
@ -319,7 +321,7 @@ export function createMetisAgent(model: string): AgentConfig {
thinking: { type: "enabled", budgetTokens: 32000 }, thinking: { type: "enabled", budgetTokens: 32000 },
} as AgentConfig } as AgentConfig
} }
createMetisAgent.mode = MODE
export const metisPromptMetadata: AgentPromptMetadata = { export const metisPromptMetadata: AgentPromptMetadata = {
category: "advisor", category: "advisor",

View File

@ -1,8 +1,10 @@
import type { AgentConfig } from "@opencode-ai/sdk" import type { AgentConfig } from "@opencode-ai/sdk"
import type { AgentPromptMetadata } from "./types" import type { AgentMode, AgentPromptMetadata } from "./types"
import { isGptModel } from "./types" import { isGptModel } from "./types"
import { createAgentToolRestrictions } from "../shared/permission-compat" import { createAgentToolRestrictions } from "../shared/permission-compat"
const MODE: AgentMode = "subagent"
/** /**
* Momus - Plan Reviewer Agent * Momus - Plan Reviewer Agent
* *
@ -400,7 +402,7 @@ export function createMomusAgent(model: string): AgentConfig {
const base = { const base = {
description: description:
"Expert reviewer for evaluating work plans against rigorous clarity, verifiability, and completeness standards. (Momus - OhMyOpenCode)", "Expert reviewer for evaluating work plans against rigorous clarity, verifiability, and completeness standards. (Momus - OhMyOpenCode)",
mode: "subagent" as const, mode: MODE,
model, model,
temperature: 0.1, temperature: 0.1,
...restrictions, ...restrictions,
@ -413,7 +415,7 @@ export function createMomusAgent(model: string): AgentConfig {
return { ...base, thinking: { type: "enabled", budgetTokens: 32000 } } as AgentConfig return { ...base, thinking: { type: "enabled", budgetTokens: 32000 } } as AgentConfig
} }
createMomusAgent.mode = MODE
export const momusPromptMetadata: AgentPromptMetadata = { export const momusPromptMetadata: AgentPromptMetadata = {
category: "advisor", category: "advisor",

View File

@ -1,7 +1,9 @@
import type { AgentConfig } from "@opencode-ai/sdk" import type { AgentConfig } from "@opencode-ai/sdk"
import type { AgentPromptMetadata } from "./types" import type { AgentMode, AgentPromptMetadata } from "./types"
import { createAgentToolAllowlist } from "../shared/permission-compat" import { createAgentToolAllowlist } from "../shared/permission-compat"
const MODE: AgentMode = "subagent"
export const MULTIMODAL_LOOKER_PROMPT_METADATA: AgentPromptMetadata = { export const MULTIMODAL_LOOKER_PROMPT_METADATA: AgentPromptMetadata = {
category: "utility", category: "utility",
cost: "CHEAP", cost: "CHEAP",
@ -15,7 +17,7 @@ export function createMultimodalLookerAgent(model: string): AgentConfig {
return { return {
description: description:
"Analyze media files (PDFs, images, diagrams) that require interpretation beyond raw text. Extracts specific information or summaries from documents, describes visual content. Use when you need analyzed/extracted data rather than literal file contents. (Multimodal-Looker - OhMyOpenCode)", "Analyze media files (PDFs, images, diagrams) that require interpretation beyond raw text. Extracts specific information or summaries from documents, describes visual content. Use when you need analyzed/extracted data rather than literal file contents. (Multimodal-Looker - OhMyOpenCode)",
mode: "subagent" as const, mode: MODE,
model, model,
temperature: 0.1, temperature: 0.1,
...restrictions, ...restrictions,
@ -53,4 +55,4 @@ Response rules:
Your output goes straight to the main agent for continued work.`, Your output goes straight to the main agent for continued work.`,
} }
} }
createMultimodalLookerAgent.mode = MODE

View File

@ -1,5 +1,8 @@
import type { AgentConfig } from "@opencode-ai/sdk" import type { AgentConfig } from "@opencode-ai/sdk"
import type { AgentMode } from "./types"
import { isGptModel } from "./types" import { isGptModel } from "./types"
const MODE: AgentMode = "primary"
import type { AvailableAgent, AvailableTool, AvailableSkill, AvailableCategory } from "./dynamic-agent-prompt-builder" import type { AvailableAgent, AvailableTool, AvailableSkill, AvailableCategory } from "./dynamic-agent-prompt-builder"
import { import {
buildKeyTriggersSection, buildKeyTriggersSection,
@ -434,7 +437,7 @@ export function createSisyphusAgent(
const base = { const base = {
description: description:
"Powerful AI orchestrator. Plans obsessively with todos, assesses search complexity before exploration, delegates strategically via category+skills combinations. Uses explore for internal code (parallel-friendly), librarian for external docs. (Sisyphus - OhMyOpenCode)", "Powerful AI orchestrator. Plans obsessively with todos, assesses search complexity before exploration, delegates strategically via category+skills combinations. Uses explore for internal code (parallel-friendly), librarian for external docs. (Sisyphus - OhMyOpenCode)",
mode: "primary" as const, mode: MODE,
model, model,
maxTokens: 64000, maxTokens: 64000,
prompt, prompt,
@ -448,3 +451,4 @@ export function createSisyphusAgent(
return { ...base, thinking: { type: "enabled", budgetTokens: 32000 } } return { ...base, thinking: { type: "enabled", budgetTokens: 32000 } }
} }
createSisyphusAgent.mode = MODE

View File

@ -1,6 +1,20 @@
import type { AgentConfig } from "@opencode-ai/sdk" import type { AgentConfig } from "@opencode-ai/sdk"
export type AgentFactory = (model: string) => AgentConfig /**
* Agent mode determines UI model selection behavior:
* - "primary": Respects user's UI-selected model (sisyphus, atlas)
* - "subagent": Uses own fallback chain, ignores UI selection (oracle, explore, etc.)
* - "all": Available in both contexts (OpenCode compatibility)
*/
export type AgentMode = "primary" | "subagent" | "all"
/**
* Agent factory function with static mode property.
* Mode is exposed as static property for pre-instantiation access.
*/
export type AgentFactory = ((model: string) => AgentConfig) & {
mode: AgentMode
}
/** /**
* Agent category for grouping in Sisyphus prompt sections * Agent category for grouping in Sisyphus prompt sections

View File

@ -225,8 +225,10 @@ export async function createBuiltinAgents(
const override = findCaseInsensitive(agentOverrides, agentName) const override = findCaseInsensitive(agentOverrides, agentName)
const requirement = AGENT_MODEL_REQUIREMENTS[agentName] const requirement = AGENT_MODEL_REQUIREMENTS[agentName]
const isPrimaryAgent = isFactory(source) && source.mode === "primary"
const resolution = resolveModelWithFallback({ const resolution = resolveModelWithFallback({
uiSelectedModel, uiSelectedModel: isPrimaryAgent ? uiSelectedModel : undefined,
userModel: override?.model, userModel: override?.model,
fallbackChain: requirement?.fallbackChain, fallbackChain: requirement?.fallbackChain,
availableModels, availableModels,