fix(config): resolve category to model for Prometheus (Planner) agent (#652)

* fix(config): resolve category to model for Prometheus (Planner) agent

When Prometheus (Planner) was configured with only a category (e.g.,
"ultrabrain") and no explicit model, the category was ignored and the
agent fell back to the hardcoded default "anthropic/claude-opus-4-5".
Add resolveModelFromCategoryWithUserOverride() helper that checks user
categories first, then DEFAULT_CATEGORIES, to resolve category names
to their corresponding models. Apply this resolution when building
the Prometheus agent configuration.

Co-Authored-By: Sisyphus <sisyphus@mengmota.com>

* fix(test): use actual implementation instead of local duplicate

Co-Authored-By: Sisyphus <sisyphus@mengmota.com>

* fix(config): apply all category properties, not just model for Prometheus (Planner)

The resolveModelFromCategoryWithUserOverride() helper only extracted
the model field from CategoryConfig, ignoring critical properties like
temperature, top_p, tools, maxTokens, thinking, reasoningEffort, and
textVerbosity. This caused categories like "ultrabrain" (temperature:
0.1) to run with incorrect default temperatures.

Refactor resolveModelFromCategoryWithUserOverride() to
resolveCategoryConfig() that returns the full CategoryConfig. Update
Prometheus (Planner) configuration to apply all category properties
(temperature, top_p, tools, etc.) when a category is specified, matching
the pattern established in Sisyphus-Junior. Explicit overrides still
take precedence during merge.

Co-Authored-By: Sisyphus <sisyphus@mengmota.com>

---------

Co-authored-by: Sisyphus <sisyphus@mengmota.com>
This commit is contained in:
Ivan Marshall Widjaja 2026-01-12 14:04:55 +11:00 committed by GitHub
parent 91c490a358
commit f9dca8d877
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 150 additions and 2 deletions

View File

@ -0,0 +1,104 @@
import { describe, test, expect } from "bun:test"
import { resolveCategoryConfig } from "./config-handler"
import type { CategoryConfig } from "../config/schema"
describe("Prometheus category config resolution", () => {
test("resolves ultrabrain category config", () => {
// #given
const categoryName = "ultrabrain"
// #when
const config = resolveCategoryConfig(categoryName)
// #then
expect(config).toBeDefined()
expect(config?.model).toBe("openai/gpt-5.2")
expect(config?.temperature).toBe(0.1)
})
test("resolves visual-engineering category config", () => {
// #given
const categoryName = "visual-engineering"
// #when
const config = resolveCategoryConfig(categoryName)
// #then
expect(config).toBeDefined()
expect(config?.model).toBe("google/gemini-3-pro-preview")
expect(config?.temperature).toBe(0.7)
})
test("user categories override default categories", () => {
// #given
const categoryName = "ultrabrain"
const userCategories: Record<string, CategoryConfig> = {
ultrabrain: {
model: "google/antigravity-claude-opus-4-5-thinking",
temperature: 0.1,
},
}
// #when
const config = resolveCategoryConfig(categoryName, userCategories)
// #then
expect(config).toBeDefined()
expect(config?.model).toBe("google/antigravity-claude-opus-4-5-thinking")
expect(config?.temperature).toBe(0.1)
})
test("returns undefined for unknown category", () => {
// #given
const categoryName = "nonexistent-category"
// #when
const config = resolveCategoryConfig(categoryName)
// #then
expect(config).toBeUndefined()
})
test("falls back to default when user category has no entry", () => {
// #given
const categoryName = "ultrabrain"
const userCategories: Record<string, CategoryConfig> = {
"visual-engineering": {
model: "custom/visual-model",
},
}
// #when
const config = resolveCategoryConfig(categoryName, userCategories)
// #then
expect(config).toBeDefined()
expect(config?.model).toBe("openai/gpt-5.2")
expect(config?.temperature).toBe(0.1)
})
test("preserves all category properties (temperature, top_p, tools, etc.)", () => {
// #given
const categoryName = "custom-category"
const userCategories: Record<string, CategoryConfig> = {
"custom-category": {
model: "test/model",
temperature: 0.5,
top_p: 0.9,
maxTokens: 32000,
tools: { tool1: true, tool2: false },
},
}
// #when
const config = resolveCategoryConfig(categoryName, userCategories)
// #then
expect(config).toBeDefined()
expect(config?.model).toBe("test/model")
expect(config?.temperature).toBe(0.5)
expect(config?.top_p).toBe(0.9)
expect(config?.maxTokens).toBe(32000)
expect(config?.tools).toEqual({ tool1: true, tool2: false })
})
})

View File

@ -24,7 +24,9 @@ import type { OhMyOpenCodeConfig } from "../config";
import { log } from "../shared";
import { migrateAgentConfig } from "../shared/permission-compat";
import { PROMETHEUS_SYSTEM_PROMPT, PROMETHEUS_PERMISSION } from "../agents/prometheus-prompt";
import { DEFAULT_CATEGORIES } from "../tools/sisyphus-task/constants";
import type { ModelCacheState } from "../plugin-state";
import type { CategoryConfig } from "../config/schema";
export interface ConfigHandlerDeps {
ctx: { directory: string };
@ -32,6 +34,13 @@ export interface ConfigHandlerDeps {
modelCacheState: ModelCacheState;
}
export function resolveCategoryConfig(
categoryName: string,
userCategories?: Record<string, CategoryConfig>
): CategoryConfig | undefined {
return userCategories?.[categoryName] ?? DEFAULT_CATEGORIES[categoryName];
}
export function createConfigHandler(deps: ConfigHandlerDeps) {
const { ctx, pluginConfig, modelCacheState } = deps;
@ -173,15 +182,50 @@ export function createConfigHandler(deps: ConfigHandlerDeps) {
planConfigWithoutName as Record<string, unknown>
);
const prometheusOverride =
pluginConfig.agents?.["Prometheus (Planner)"];
pluginConfig.agents?.["Prometheus (Planner)"] as
| (Record<string, unknown> & { category?: string; model?: string })
| undefined;
const defaultModel = config.model as string | undefined;
// Resolve full category config (model, temperature, top_p, tools, etc.)
// Apply all category properties when category is specified, but explicit
// overrides (model, temperature, etc.) will take precedence during merge
const categoryConfig = prometheusOverride?.category
? resolveCategoryConfig(
prometheusOverride.category,
pluginConfig.categories
)
: undefined;
const prometheusBase = {
model: defaultModel ?? "anthropic/claude-opus-4-5",
model:
prometheusOverride?.model ??
categoryConfig?.model ??
defaultModel ??
"anthropic/claude-opus-4-5",
mode: "primary" as const,
prompt: PROMETHEUS_SYSTEM_PROMPT,
permission: PROMETHEUS_PERMISSION,
description: `${configAgent?.plan?.description ?? "Plan agent"} (Prometheus - OhMyOpenCode)`,
color: (configAgent?.plan?.color as string) ?? "#FF6347",
// Apply category properties (temperature, top_p, tools, etc.)
...(categoryConfig?.temperature !== undefined
? { temperature: categoryConfig.temperature }
: {}),
...(categoryConfig?.top_p !== undefined
? { top_p: categoryConfig.top_p }
: {}),
...(categoryConfig?.maxTokens !== undefined
? { maxTokens: categoryConfig.maxTokens }
: {}),
...(categoryConfig?.tools ? { tools: categoryConfig.tools } : {}),
...(categoryConfig?.thinking ? { thinking: categoryConfig.thinking } : {}),
...(categoryConfig?.reasoningEffort !== undefined
? { reasoningEffort: categoryConfig.reasoningEffort }
: {}),
...(categoryConfig?.textVerbosity !== undefined
? { textVerbosity: categoryConfig.textVerbosity }
: {}),
};
agentConfig["Prometheus (Planner)"] = prometheusOverride