Agents: fix subagent model precedence

This commit is contained in:
Neerav Makwana
2026-03-30 20:15:49 -04:00
committed by Ayaan Zaidi
parent d4cccda570
commit e394262bd8
5 changed files with 77 additions and 5 deletions

View File

@@ -12,6 +12,7 @@ import {
modelKey,
resolveAllowedModelRef,
resolveConfiguredModelRef,
resolveSubagentConfiguredModelSelection,
resolveThinkingDefault,
resolveModelRefFromString,
} from "./model-selection.js";
@@ -857,3 +858,48 @@ describe("normalizeModelSelection", () => {
expect(normalizeModelSelection(42)).toBeUndefined();
});
});
describe("resolveSubagentConfiguredModelSelection", () => {
it("prefers the agent primary model over agents.defaults.subagents.model", () => {
const cfg = {
agents: {
defaults: {
model: { primary: "anthropic/claude-sonnet-4-6" },
subagents: { model: "openai/gpt-5.4" },
},
list: [
{
id: "research",
model: { primary: "anthropic/claude-opus-4-6" },
},
],
},
} as OpenClawConfig;
expect(resolveSubagentConfiguredModelSelection({ cfg, agentId: "research" })).toBe(
"anthropic/claude-opus-4-6",
);
});
it("still prefers agent subagents.model over the agent primary model", () => {
const cfg = {
agents: {
defaults: {
model: { primary: "anthropic/claude-sonnet-4-6" },
subagents: { model: "openai/gpt-5.4" },
},
list: [
{
id: "research",
model: { primary: "anthropic/claude-opus-4-6" },
subagents: { model: "google/gemini-2.5-pro" },
},
],
},
} as OpenClawConfig;
expect(resolveSubagentConfiguredModelSelection({ cfg, agentId: "research" })).toBe(
"google/gemini-2.5-pro",
);
});
});

View File

@@ -440,8 +440,8 @@ export function resolveSubagentConfiguredModelSelection(params: {
const agentConfig = resolveAgentConfig(params.cfg, params.agentId);
return (
normalizeModelSelection(agentConfig?.subagents?.model) ??
normalizeModelSelection(params.cfg.agents?.defaults?.subagents?.model) ??
normalizeModelSelection(agentConfig?.model)
normalizeModelSelection(agentConfig?.model) ??
normalizeModelSelection(params.cfg.agents?.defaults?.subagents?.model)
);
}

View File

@@ -95,6 +95,7 @@ async function runSubagentModelCase(params: {
home: string;
cfgOverrides?: Partial<OpenClawConfig>;
jobModelOverride?: string;
agentId?: string;
}) {
const storePath = await writeSessionStore(params.home);
mockEmbeddedAgent();
@@ -102,6 +103,9 @@ async function runSubagentModelCase(params: {
if (params.jobModelOverride) {
job.payload = { kind: "agentTurn", message: "do work", model: params.jobModelOverride };
}
if (params.agentId) {
job.agentId = params.agentId;
}
await runCronIsolatedAgentTurn({
cfg: makeCfg(params.home, storePath, params.cfgOverrides),
@@ -192,4 +196,25 @@ describe("runCronIsolatedAgentTurn: subagent model resolution (#11461)", () => {
expect(call?.model).toBe("gpt-4o");
});
});
it("prefers the agent model over agents.defaults.subagents.model", async () => {
await withTempHome(async (home) => {
const call = await runSubagentModelCase({
home,
agentId: "research",
cfgOverrides: {
agents: {
defaults: {
model: "anthropic/claude-sonnet-4-5",
workspace: path.join(home, "openclaw"),
subagents: { model: "ollama/llama3.2:3b" },
},
list: [{ id: "research", model: { primary: "anthropic/claude-opus-4-6" } }],
},
},
});
expect(call?.provider).toBe("anthropic");
expect(call?.model).toBe("claude-opus-4-6");
});
});
});

View File

@@ -19,6 +19,7 @@ export type ResolveCronModelSelectionParams = {
cfg: OpenClawConfig;
cfgWithAgentDefaults: OpenClawConfig;
agentConfigOverride?: {
model?: unknown;
subagents?: {
model?: unknown;
};
@@ -61,6 +62,7 @@ export async function resolveCronModelSelection(
const subagentModelRaw =
normalizeModelSelection(params.agentConfigOverride?.subagents?.model) ??
normalizeModelSelection(params.agentConfigOverride?.model) ??
normalizeModelSelection(params.cfg.agents?.defaults?.subagents?.model);
if (subagentModelRaw) {
const resolvedSubagent = resolveAllowedModelRef({

View File

@@ -65,7 +65,7 @@ async function applySubagentModelPatch(cfg: OpenClawConfig) {
}
function makeKimiSubagentCfg(params: {
agentPrimaryModel: string;
agentPrimaryModel?: string;
agentSubagentModel?: string;
defaultsSubagentModel?: string;
}): OpenClawConfig {
@@ -83,7 +83,7 @@ function makeKimiSubagentCfg(params: {
list: [
{
id: "kimi",
model: { primary: params.agentPrimaryModel },
model: params.agentPrimaryModel ? { primary: params.agentPrimaryModel } : undefined,
subagents: params.agentSubagentModel ? { model: params.agentSubagentModel } : undefined,
},
],
@@ -400,7 +400,6 @@ describe("gateway sessions patch", () => {
test("allows global defaults.subagents.model for subagent session even when missing from global allowlist", async () => {
const cfg = makeKimiSubagentCfg({
agentPrimaryModel: "anthropic/claude-sonnet-4-6",
defaultsSubagentModel: SUBAGENT_MODEL,
});