fix(team-mode): preserve per-category model for kind:"category" members
The shared resolveCategoryExecution path treats `agents.sisyphus-junior.model` (plugin config) as a higher-precedence override than the category default. That ranking is correct for plain `task(category=...)` delegations - it lets users pin sisyphus-junior to their preferred general model - but in team-mode it collapses every kind:"category" member onto the same model. Hyperplan was the visible victim: skeptic/validator/researcher/architect/creative are routed through sisyphus-junior with five distinct categories (unspecified-low / unspecified-high / deep / ultrabrain / artistry). With any sisyphus-junior model configured, all five resolved to that single override model instead of their category defaults, defeating the multi-model adversarial debate. Strip the override at the team-mode boundary in resolveMember rather than changing resolveCategoryExecution itself, so: - delegate-task callers keep the existing override semantics (3 regression tests at tools.test.ts:2832, 2958, 3020 stay green) - per-category user overrides (`categories[X].model`) and explicit fallback chains continue to apply - only the `kind:"category"` team-mode path opts out of the global override Lock the contract with a regression test in resolve-member.test.ts. Generated with assistance of [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode).
This commit is contained in:
@@ -75,6 +75,36 @@ describe("resolveMember", () => {
|
||||
expect(result.systemContent).toBe("resolved-system-content")
|
||||
})
|
||||
|
||||
test("strips sisyphusJuniorModel before resolving category members so each declared category keeps its own model", async () => {
|
||||
// given
|
||||
const member = {
|
||||
backendType: "in-process",
|
||||
isActive: true,
|
||||
kind: "category",
|
||||
name: "architect",
|
||||
category: "ultrabrain",
|
||||
prompt: "design X",
|
||||
} satisfies Member
|
||||
const ctxWithJuniorOverride: ExecutorContext = {
|
||||
...createExecutorContext(),
|
||||
sisyphusJuniorModel: "anthropic/claude-sonnet-4-6",
|
||||
}
|
||||
resolveCategoryExecutionMock.mockResolvedValue({
|
||||
agentToUse: "sisyphus-junior",
|
||||
categoryModel: { providerID: "openai", modelID: "gpt-5.5", variant: "xhigh" },
|
||||
categoryPromptAppend: "appendix",
|
||||
maxPromptTokens: 256,
|
||||
fallbackChain: [],
|
||||
})
|
||||
|
||||
// when
|
||||
await resolveMember(member, ctxWithJuniorOverride, "ultrabrain, deep")
|
||||
|
||||
// then
|
||||
const [, executorCtxArg] = resolveCategoryExecutionMock.mock.calls[0]
|
||||
expect(executorCtxArg.sisyphusJuniorModel).toBeUndefined()
|
||||
})
|
||||
|
||||
test("routes subagent members through resolveSubagentExecution", async () => {
|
||||
// given
|
||||
const member = {
|
||||
|
||||
@@ -51,6 +51,14 @@ function resolveSystemContent(input: {
|
||||
}) ?? ""
|
||||
}
|
||||
|
||||
// Strip global `agents.sisyphus-junior.model` override at the team-mode boundary —
|
||||
// `resolveCategoryExecution` ranks it above category defaults (correct for plain
|
||||
// `task(category=…)`, wrong here) and would collapse every team member to the same model.
|
||||
function withoutSisyphusJuniorOverride(ctx: ExecutorContext): ExecutorContext {
|
||||
if (ctx.sisyphusJuniorModel === undefined) return ctx
|
||||
return { ...ctx, sisyphusJuniorModel: undefined }
|
||||
}
|
||||
|
||||
export async function resolveMember(
|
||||
member: Member,
|
||||
ctx: ExecutorContext,
|
||||
@@ -65,7 +73,7 @@ export async function resolveMember(
|
||||
category: member.category,
|
||||
subagent_type: "sisyphus-junior",
|
||||
},
|
||||
ctx,
|
||||
withoutSisyphusJuniorOverride(ctx),
|
||||
undefined,
|
||||
undefined,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user