fix(delegate-task): category built-in model takes precedence over inherited model
Previously, when using categories like 'quick', the parent session's model (e.g., Opus 4.5) would override the category's built-in model (e.g., Haiku). Fixed priority: userConfig.model → category built-in → systemDefault The inherited model from parent session no longer affects category-based delegation - categories have their own explicit models.
This commit is contained in:
parent
3e5265700b
commit
3d3d3e493b
@ -122,10 +122,8 @@ describe("buildAgent with category and skills", () => {
|
|||||||
// #when
|
// #when
|
||||||
const agent = buildAgent(source["test-agent"], TEST_MODEL)
|
const agent = buildAgent(source["test-agent"], TEST_MODEL)
|
||||||
|
|
||||||
// #then - DEFAULT_CATEGORIES only has temperature, not model
|
// #then - category's built-in model is applied
|
||||||
// Model remains undefined since neither factory nor category provides it
|
expect(agent.model).toBe("google/gemini-3-pro-preview")
|
||||||
expect(agent.model).toBeUndefined()
|
|
||||||
expect(agent.temperature).toBe(0.7)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("agent with category and existing model keeps existing model", () => {
|
test("agent with category and existing model keeps existing model", () => {
|
||||||
@ -142,9 +140,8 @@ describe("buildAgent with category and skills", () => {
|
|||||||
// #when
|
// #when
|
||||||
const agent = buildAgent(source["test-agent"], TEST_MODEL)
|
const agent = buildAgent(source["test-agent"], TEST_MODEL)
|
||||||
|
|
||||||
// #then
|
// #then - explicit model takes precedence over category
|
||||||
expect(agent.model).toBe("custom/model")
|
expect(agent.model).toBe("custom/model")
|
||||||
expect(agent.temperature).toBe(0.7)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("agent with category inherits variant", () => {
|
test("agent with category inherits variant", () => {
|
||||||
@ -247,9 +244,9 @@ describe("buildAgent with category and skills", () => {
|
|||||||
// #when
|
// #when
|
||||||
const agent = buildAgent(source["test-agent"], TEST_MODEL)
|
const agent = buildAgent(source["test-agent"], TEST_MODEL)
|
||||||
|
|
||||||
// #then - DEFAULT_CATEGORIES["ultrabrain"] only has temperature, not model
|
// #then - category's built-in model and skills are applied
|
||||||
expect(agent.model).toBeUndefined()
|
expect(agent.model).toBe("openai/gpt-5.2-codex")
|
||||||
expect(agent.temperature).toBe(0.1)
|
expect(agent.variant).toBe("xhigh")
|
||||||
expect(agent.prompt).toContain("Role: Designer-Turned-Developer")
|
expect(agent.prompt).toContain("Role: Designer-Turned-Developer")
|
||||||
expect(agent.prompt).toContain("Task description")
|
expect(agent.prompt).toContain("Task description")
|
||||||
})
|
})
|
||||||
|
|||||||
@ -10,10 +10,10 @@ describe("Prometheus category config resolution", () => {
|
|||||||
// #when
|
// #when
|
||||||
const config = resolveCategoryConfig(categoryName)
|
const config = resolveCategoryConfig(categoryName)
|
||||||
|
|
||||||
// #then - DEFAULT_CATEGORIES only has temperature, not model
|
// #then
|
||||||
expect(config).toBeDefined()
|
expect(config).toBeDefined()
|
||||||
expect(config?.model).toBeUndefined()
|
expect(config?.model).toBe("openai/gpt-5.2-codex")
|
||||||
expect(config?.temperature).toBe(0.1)
|
expect(config?.variant).toBe("xhigh")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("resolves visual-engineering category config", () => {
|
test("resolves visual-engineering category config", () => {
|
||||||
@ -23,10 +23,9 @@ describe("Prometheus category config resolution", () => {
|
|||||||
// #when
|
// #when
|
||||||
const config = resolveCategoryConfig(categoryName)
|
const config = resolveCategoryConfig(categoryName)
|
||||||
|
|
||||||
// #then - DEFAULT_CATEGORIES only has temperature, not model
|
// #then
|
||||||
expect(config).toBeDefined()
|
expect(config).toBeDefined()
|
||||||
expect(config?.model).toBeUndefined()
|
expect(config?.model).toBe("google/gemini-3-pro-preview")
|
||||||
expect(config?.temperature).toBe(0.7)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("user categories override default categories", () => {
|
test("user categories override default categories", () => {
|
||||||
@ -71,10 +70,10 @@ describe("Prometheus category config resolution", () => {
|
|||||||
// #when
|
// #when
|
||||||
const config = resolveCategoryConfig(categoryName, userCategories)
|
const config = resolveCategoryConfig(categoryName, userCategories)
|
||||||
|
|
||||||
// #then - falls back to DEFAULT_CATEGORIES which has no model
|
// #then - falls back to DEFAULT_CATEGORIES
|
||||||
expect(config).toBeDefined()
|
expect(config).toBeDefined()
|
||||||
expect(config?.model).toBeUndefined()
|
expect(config?.model).toBe("openai/gpt-5.2-codex")
|
||||||
expect(config?.temperature).toBe(0.1)
|
expect(config?.variant).toBe("xhigh")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("preserves all category properties (temperature, top_p, tools, etc.)", () => {
|
test("preserves all category properties (temperature, top_p, tools, etc.)", () => {
|
||||||
|
|||||||
@ -226,21 +226,21 @@ describe("sisyphus-task", () => {
|
|||||||
expect(result!.config.temperature).toBe(0.3)
|
expect(result!.config.temperature).toBe(0.3)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("inheritedModel takes precedence over systemDefaultModel", () => {
|
test("category built-in model takes precedence over inheritedModel", () => {
|
||||||
// #given - builtin category, parent model provided
|
// #given - builtin category with its own model, parent model also provided
|
||||||
const categoryName = "visual-engineering"
|
const categoryName = "visual-engineering"
|
||||||
const inheritedModel = "cliproxy/claude-opus-4-5"
|
const inheritedModel = "cliproxy/claude-opus-4-5"
|
||||||
|
|
||||||
// #when
|
// #when
|
||||||
const result = resolveCategoryConfig(categoryName, { inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
const result = resolveCategoryConfig(categoryName, { inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
||||||
|
|
||||||
// #then - inheritedModel wins over systemDefaultModel
|
// #then - category's built-in model wins over inheritedModel
|
||||||
expect(result).not.toBeNull()
|
expect(result).not.toBeNull()
|
||||||
expect(result!.config.model).toBe("cliproxy/claude-opus-4-5")
|
expect(result!.config.model).toBe("google/gemini-3-pro-preview")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("inheritedModel is used as fallback when category has no user model", () => {
|
test("systemDefaultModel is used as fallback when custom category has no model", () => {
|
||||||
// #given - custom category with no model defined, only inheritedModel as fallback
|
// #given - custom category with no model defined
|
||||||
const categoryName = "my-custom-no-model"
|
const categoryName = "my-custom-no-model"
|
||||||
const userCategories = { "my-custom-no-model": { temperature: 0.5 } } as unknown as Record<string, CategoryConfig>
|
const userCategories = { "my-custom-no-model": { temperature: 0.5 } } as unknown as Record<string, CategoryConfig>
|
||||||
const inheritedModel = "cliproxy/claude-opus-4-5"
|
const inheritedModel = "cliproxy/claude-opus-4-5"
|
||||||
@ -248,9 +248,9 @@ describe("sisyphus-task", () => {
|
|||||||
// #when
|
// #when
|
||||||
const result = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
const result = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
||||||
|
|
||||||
// #then - parent model is used as fallback since custom category has no user model
|
// #then - systemDefaultModel is used since custom category has no built-in model
|
||||||
expect(result).not.toBeNull()
|
expect(result).not.toBeNull()
|
||||||
expect(result!.config.model).toBe("cliproxy/claude-opus-4-5")
|
expect(result!.config.model).toBe(SYSTEM_DEFAULT_MODEL)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("user model takes precedence over inheritedModel", () => {
|
test("user model takes precedence over inheritedModel", () => {
|
||||||
@ -918,18 +918,18 @@ describe("sisyphus-task", () => {
|
|||||||
expect(resolved!.config.model).toBe("anthropic/claude-sonnet-4-5")
|
expect(resolved!.config.model).toBe("anthropic/claude-sonnet-4-5")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("inheritedModel takes precedence over systemDefaultModel for builtin category", () => {
|
test("category built-in model takes precedence over inheritedModel for builtin category", () => {
|
||||||
// #given - builtin ultrabrain category, inherited model from parent
|
// #given - builtin ultrabrain category with its own model, inherited model also provided
|
||||||
const categoryName = "ultrabrain"
|
const categoryName = "ultrabrain"
|
||||||
const inheritedModel = "cliproxy/claude-opus-4-5"
|
const inheritedModel = "cliproxy/claude-opus-4-5"
|
||||||
|
|
||||||
// #when
|
// #when
|
||||||
const resolved = resolveCategoryConfig(categoryName, { inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
const resolved = resolveCategoryConfig(categoryName, { inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
||||||
|
|
||||||
// #then - inheritedModel wins over systemDefaultModel
|
// #then - category's built-in model wins (ultrabrain uses gpt-5.2-codex)
|
||||||
expect(resolved).not.toBeNull()
|
expect(resolved).not.toBeNull()
|
||||||
const actualModel = resolved!.config.model
|
const actualModel = resolved!.config.model
|
||||||
expect(actualModel).toBe("cliproxy/claude-opus-4-5")
|
expect(actualModel).toBe("openai/gpt-5.2-codex")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("when user defines model - modelInfo should report user-defined regardless of inheritedModel", () => {
|
test("when user defines model - modelInfo should report user-defined regardless of inheritedModel", () => {
|
||||||
@ -977,18 +977,18 @@ describe("sisyphus-task", () => {
|
|||||||
// ===== TESTS FOR resolveModel() INTEGRATION (TDD GREEN) =====
|
// ===== TESTS FOR resolveModel() INTEGRATION (TDD GREEN) =====
|
||||||
// These tests verify the NEW behavior where categories do NOT have default models
|
// These tests verify the NEW behavior where categories do NOT have default models
|
||||||
|
|
||||||
test("FIXED: inheritedModel takes precedence over systemDefaultModel", () => {
|
test("FIXED: category built-in model takes precedence over inheritedModel", () => {
|
||||||
// #given a builtin category, and an inherited model from parent
|
// #given a builtin category with its own model, and an inherited model from parent
|
||||||
// The NEW correct chain: userConfig?.model ?? inheritedModel ?? systemDefaultModel
|
// The CORRECT chain: userConfig?.model ?? categoryBuiltIn ?? systemDefaultModel
|
||||||
const categoryName = "ultrabrain"
|
const categoryName = "ultrabrain"
|
||||||
const inheritedModel = "anthropic/claude-opus-4-5" // inherited from parent session
|
const inheritedModel = "anthropic/claude-opus-4-5"
|
||||||
|
|
||||||
// #when userConfig.model is undefined and inheritedModel is set
|
// #when category has a built-in model (gpt-5.2-codex for ultrabrain)
|
||||||
const resolved = resolveCategoryConfig(categoryName, { inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
const resolved = resolveCategoryConfig(categoryName, { inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
||||||
|
|
||||||
// #then inheritedModel should be used, NOT systemDefaultModel
|
// #then category's built-in model should be used, NOT inheritedModel
|
||||||
expect(resolved).not.toBeNull()
|
expect(resolved).not.toBeNull()
|
||||||
expect(resolved!.model).toBe("anthropic/claude-opus-4-5")
|
expect(resolved!.model).toBe("openai/gpt-5.2-codex")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("FIXED: systemDefaultModel is used when no userConfig.model and no inheritedModel", () => {
|
test("FIXED: systemDefaultModel is used when no userConfig.model and no inheritedModel", () => {
|
||||||
@ -1027,8 +1027,8 @@ describe("sisyphus-task", () => {
|
|||||||
expect(resolved!.model).toBe("custom/user-model")
|
expect(resolved!.model).toBe("custom/user-model")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("FIXED: empty string in userConfig.model is treated as unset and falls back", () => {
|
test("FIXED: empty string in userConfig.model is treated as unset and falls back to systemDefault", () => {
|
||||||
// #given userConfig.model is empty string ""
|
// #given userConfig.model is empty string "" for a custom category (no built-in model)
|
||||||
const categoryName = "custom-empty-model"
|
const categoryName = "custom-empty-model"
|
||||||
const userCategories = { "custom-empty-model": { model: "", temperature: 0.3 } }
|
const userCategories = { "custom-empty-model": { model: "", temperature: 0.3 } }
|
||||||
const inheritedModel = "anthropic/claude-opus-4-5"
|
const inheritedModel = "anthropic/claude-opus-4-5"
|
||||||
@ -1036,13 +1036,13 @@ describe("sisyphus-task", () => {
|
|||||||
// #when resolveCategoryConfig is called
|
// #when resolveCategoryConfig is called
|
||||||
const resolved = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
const resolved = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
||||||
|
|
||||||
// #then should fall back to inheritedModel since "" is normalized to undefined
|
// #then should fall back to systemDefaultModel since custom category has no built-in model
|
||||||
expect(resolved).not.toBeNull()
|
expect(resolved).not.toBeNull()
|
||||||
expect(resolved!.model).toBe("anthropic/claude-opus-4-5")
|
expect(resolved!.model).toBe(SYSTEM_DEFAULT_MODEL)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("FIXED: undefined userConfig.model falls back to inheritedModel", () => {
|
test("FIXED: undefined userConfig.model falls back to category built-in model", () => {
|
||||||
// #given user explicitly sets a category but leaves model undefined
|
// #given user sets a builtin category but leaves model undefined
|
||||||
const categoryName = "visual-engineering"
|
const categoryName = "visual-engineering"
|
||||||
// Using type assertion since we're testing fallback behavior for categories without model
|
// Using type assertion since we're testing fallback behavior for categories without model
|
||||||
const userCategories = { "visual-engineering": { temperature: 0.2 } } as unknown as Record<string, CategoryConfig>
|
const userCategories = { "visual-engineering": { temperature: 0.2 } } as unknown as Record<string, CategoryConfig>
|
||||||
@ -1051,9 +1051,9 @@ describe("sisyphus-task", () => {
|
|||||||
// #when resolveCategoryConfig is called
|
// #when resolveCategoryConfig is called
|
||||||
const resolved = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
const resolved = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
||||||
|
|
||||||
// #then should use inheritedModel
|
// #then should use category's built-in model (gemini-3-pro-preview for visual-engineering)
|
||||||
expect(resolved).not.toBeNull()
|
expect(resolved).not.toBeNull()
|
||||||
expect(resolved!.model).toBe("anthropic/claude-opus-4-5")
|
expect(resolved!.model).toBe("google/gemini-3-pro-preview")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("systemDefaultModel is used when no other model is available", () => {
|
test("systemDefaultModel is used when no other model is available", () => {
|
||||||
|
|||||||
@ -124,11 +124,12 @@ export function resolveCategoryConfig(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Model priority: user override > inherited from parent > default config > system default
|
// Model priority for categories: user override > category default > system default
|
||||||
|
// Categories have explicit models - no inheritance from parent session
|
||||||
const model = resolveModel({
|
const model = resolveModel({
|
||||||
userModel: userConfig?.model,
|
userModel: userConfig?.model,
|
||||||
inheritedModel,
|
inheritedModel: defaultConfig?.model, // Category's built-in model takes precedence over system default
|
||||||
systemDefault: defaultConfig?.model ?? systemDefaultModel,
|
systemDefault: systemDefaultModel,
|
||||||
})
|
})
|
||||||
const config: CategoryConfig = {
|
const config: CategoryConfig = {
|
||||||
...defaultConfig,
|
...defaultConfig,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user