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 { AgentPromptMetadata } from "./types"
import type { AgentMode, AgentPromptMetadata } from "./types"
const MODE: AgentMode = "primary"
import type { AvailableAgent, AvailableSkill, AvailableCategory } from "./dynamic-agent-prompt-builder"
import { buildCategorySkillsDelegationGuide } from "./dynamic-agent-prompt-builder"
import type { CategoryConfig } from "../config/schema"
@@ -530,7 +532,7 @@ export function createAtlasAgent(ctx: OrchestratorContext): AgentConfig {
return {
description:
"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 } : {}),
temperature: 0.1,
prompt: buildDynamicOrchestratorPrompt(ctx),
@@ -539,6 +541,7 @@ export function createAtlasAgent(ctx: OrchestratorContext): AgentConfig {
...restrictions,
} as AgentConfig
}
createAtlasAgent.mode = MODE
export const atlasPromptMetadata: AgentPromptMetadata = {
category: "advisor",

View File

@@ -1,7 +1,9 @@
import type { AgentConfig } from "@opencode-ai/sdk"
import type { AgentPromptMetadata } from "./types"
import type { AgentMode, AgentPromptMetadata } from "./types"
import { createAgentToolRestrictions } from "../shared/permission-compat"
const MODE: AgentMode = "subagent"
export const EXPLORE_PROMPT_METADATA: AgentPromptMetadata = {
category: "exploration",
cost: "FREE",
@@ -34,7 +36,7 @@ export function createExploreAgent(model: string): AgentConfig {
return {
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)',
mode: "subagent" as const,
mode: MODE,
model,
temperature: 0.1,
...restrictions,
@@ -119,4 +121,4 @@ Use the right tool for the job:
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 { AgentPromptMetadata } from "./types"
import type { AgentMode, AgentPromptMetadata } from "./types"
import { createAgentToolRestrictions } from "../shared/permission-compat"
const MODE: AgentMode = "subagent"
/**
* Metis - Plan Consultant Agent
*
@@ -311,7 +313,7 @@ export function createMetisAgent(model: string): AgentConfig {
return {
description:
"Pre-planning consultant that analyzes requests to identify hidden intentions, ambiguities, and AI failure points. (Metis - OhMyOpenCode)",
mode: "subagent" as const,
mode: MODE,
model,
temperature: 0.3,
...metisRestrictions,
@@ -319,7 +321,7 @@ export function createMetisAgent(model: string): AgentConfig {
thinking: { type: "enabled", budgetTokens: 32000 },
} as AgentConfig
}
createMetisAgent.mode = MODE
export const metisPromptMetadata: AgentPromptMetadata = {
category: "advisor",

View File

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

View File

@@ -1,7 +1,9 @@
import type { AgentConfig } from "@opencode-ai/sdk"
import type { AgentPromptMetadata } from "./types"
import type { AgentMode, AgentPromptMetadata } from "./types"
import { createAgentToolAllowlist } from "../shared/permission-compat"
const MODE: AgentMode = "subagent"
export const MULTIMODAL_LOOKER_PROMPT_METADATA: AgentPromptMetadata = {
category: "utility",
cost: "CHEAP",
@@ -15,7 +17,7 @@ export function createMultimodalLookerAgent(model: string): AgentConfig {
return {
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)",
mode: "subagent" as const,
mode: MODE,
model,
temperature: 0.1,
...restrictions,
@@ -53,4 +55,4 @@ Response rules:
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 { AgentMode } from "./types"
import { isGptModel } from "./types"
const MODE: AgentMode = "primary"
import type { AvailableAgent, AvailableTool, AvailableSkill, AvailableCategory } from "./dynamic-agent-prompt-builder"
import {
buildKeyTriggersSection,
@@ -434,7 +437,7 @@ export function createSisyphusAgent(
const base = {
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)",
mode: "primary" as const,
mode: MODE,
model,
maxTokens: 64000,
prompt,
@@ -448,3 +451,4 @@ export function createSisyphusAgent(
return { ...base, thinking: { type: "enabled", budgetTokens: 32000 } }
}
createSisyphusAgent.mode = MODE

View File

@@ -1,6 +1,20 @@
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

View File

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