From d8f89b6965d1672d1fa9a864440706e38e21563e Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Thu, 30 Apr 2026 15:58:31 +0900 Subject: [PATCH] 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). --- .../team-runtime/resolve-member.test.ts | 30 +++++++++++++++++++ .../team-mode/team-runtime/resolve-member.ts | 10 ++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/features/team-mode/team-runtime/resolve-member.test.ts b/src/features/team-mode/team-runtime/resolve-member.test.ts index 09daeda61..f991d9435 100644 --- a/src/features/team-mode/team-runtime/resolve-member.test.ts +++ b/src/features/team-mode/team-runtime/resolve-member.test.ts @@ -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 = { diff --git a/src/features/team-mode/team-runtime/resolve-member.ts b/src/features/team-mode/team-runtime/resolve-member.ts index dde6e1a2c..5df673618 100644 --- a/src/features/team-mode/team-runtime/resolve-member.ts +++ b/src/features/team-mode/team-runtime/resolve-member.ts @@ -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, )