fix(agents): fall back to agents.defaults.model when agent has no model config#24210
Conversation
src/agents/agent-scope.ts
Outdated
| export function resolveAgentModelPrimary(cfg: OpenClawConfig, agentId: string): string | undefined { | ||
| const raw = resolveAgentConfig(cfg, agentId)?.model; | ||
| if (!raw) { | ||
| if (raw) { | ||
| if (typeof raw === "string") { | ||
| const trimmed = raw.trim(); | ||
| if (trimmed) { | ||
| return trimmed; | ||
| } | ||
| } else { | ||
| const primary = raw.primary?.trim(); | ||
| if (primary) { | ||
| return primary; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Fallback to agents.defaults.model when agent has no model configured | ||
| const defaultModel = cfg.agents?.defaults?.model; | ||
| if (!defaultModel) { | ||
| return undefined; | ||
| } | ||
| if (typeof raw === "string") { | ||
| return raw.trim() || undefined; | ||
| if (typeof defaultModel === "string") { | ||
| return defaultModel.trim() || undefined; | ||
| } | ||
| const primary = raw.primary?.trim(); | ||
| return primary || undefined; | ||
| return defaultModel.primary?.trim() || undefined; | ||
| } |
There was a problem hiding this comment.
Defaults fallback changes model source attribution in status command
The callers in src/commands/models/list.status-command.ts (line 345) and (line 407) use the return value of resolveAgentModelPrimary to determine whether the model source is "agent" or "defaults":
defaultSource: agentModelPrimary ? "agent" : "defaults",With this change, when an agent has no per-agent model but agents.defaults.model is set, the function now returns the defaults value instead of undefined. This causes the status command to incorrectly report the model source as "agent" when it actually came from "defaults".
Consider either:
- Updating the status command callers to distinguish between agent-level and defaults-level sources, or
- Keeping
resolveAgentModelPrimaryfocused on agent-specific config and adding a separateresolveAgentEffectiveModelPrimarythat includes the defaults fallback.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/agent-scope.ts
Line: 145-170
Comment:
**Defaults fallback changes model source attribution in status command**
The callers in `src/commands/models/list.status-command.ts` (line 345) and (line 407) use the return value of `resolveAgentModelPrimary` to determine whether the model source is `"agent"` or `"defaults"`:
```typescript
defaultSource: agentModelPrimary ? "agent" : "defaults",
```
With this change, when an agent has no per-agent model but `agents.defaults.model` is set, the function now returns the defaults value instead of `undefined`. This causes the status command to incorrectly report the model source as `"agent"` when it actually came from `"defaults"`.
Consider either:
1. Updating the status command callers to distinguish between agent-level and defaults-level sources, or
2. Keeping `resolveAgentModelPrimary` focused on agent-specific config and adding a separate `resolveAgentEffectiveModelPrimary` that includes the defaults fallback.
How can I resolve this? If you propose a fix, please make it concise.
src/agents/agent-scope.test.ts
Outdated
| const cfgWithStringDefault: OpenClawConfig = { | ||
| agents: { | ||
| defaults: { | ||
| model: "anthropic/claude-sonnet-4", | ||
| }, | ||
| list: [{ id: "main" }], | ||
| }, | ||
| }; |
There was a problem hiding this comment.
Test sets defaults.model to a string but the type only allows an object
AgentDefaultsConfig.model is typed as AgentModelListConfig ({ primary?: string; fallbacks?: string[] }), not as AgentModelConfig (string | { primary?: string; fallbacks?: string[] }). This test assigns a bare string to defaults.model, which should be a TypeScript type error.
While the runtime handling of a string here is fine as defensive coding, the test should match the declared types. Consider changing this to use the object format:
| const cfgWithStringDefault: OpenClawConfig = { | |
| agents: { | |
| defaults: { | |
| model: "anthropic/claude-sonnet-4", | |
| }, | |
| list: [{ id: "main" }], | |
| }, | |
| }; | |
| const cfgWithStringDefault: OpenClawConfig = { | |
| agents: { | |
| defaults: { | |
| model: { | |
| primary: "anthropic/claude-sonnet-4", | |
| }, | |
| }, | |
| list: [{ id: "main" }], | |
| }, | |
| }; |
If string-format defaults should be supported, the AgentDefaultsConfig.model type in src/config/types.agent-defaults.ts should be updated to AgentModelConfig instead.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/agent-scope.test.ts
Line: 63-70
Comment:
**Test sets `defaults.model` to a string but the type only allows an object**
`AgentDefaultsConfig.model` is typed as `AgentModelListConfig` (`{ primary?: string; fallbacks?: string[] }`), not as `AgentModelConfig` (`string | { primary?: string; fallbacks?: string[] }`). This test assigns a bare string to `defaults.model`, which should be a TypeScript type error.
While the runtime handling of a string here is fine as defensive coding, the test should match the declared types. Consider changing this to use the object format:
```suggestion
const cfgWithStringDefault: OpenClawConfig = {
agents: {
defaults: {
model: {
primary: "anthropic/claude-sonnet-4",
},
},
list: [{ id: "main" }],
},
};
```
If string-format defaults should be supported, the `AgentDefaultsConfig.model` type in `src/config/types.agent-defaults.ts` should be updated to `AgentModelConfig` instead.
How can I resolve this? If you propose a fix, please make it concise.7020f2e to
453011a
Compare
453011a to
bd0a47c
Compare
359af77 to
30939f9
Compare
…el config When an agent in agents.list lacks a model configuration, resolveAgentModelPrimary now correctly falls back to cfg.agents.defaults.model instead of returning undefined. This fixes embedded agents (like slug-generator) silently falling back to a hardcoded DEFAULT_MODEL when the main agent omits the model block but global defaults are configured. Fixes openclaw#24168
6fcd177 to
0f272b1
Compare
|
Merged via squash. Thanks @bianbiandashen! |
…el config (openclaw#24210) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 0f272b1 Co-authored-by: bianbiandashen <16240681+bianbiandashen@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
…el config (openclaw#24210) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 0f272b1 Co-authored-by: bianbiandashen <16240681+bianbiandashen@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
…el config (openclaw#24210) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 0f272b1 Co-authored-by: bianbiandashen <16240681+bianbiandashen@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
…el config (openclaw#24210) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 0f272b1 Co-authored-by: bianbiandashen <16240681+bianbiandashen@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
…el config (openclaw#24210) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 0f272b1 Co-authored-by: bianbiandashen <16240681+bianbiandashen@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
…el config (openclaw#24210) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 0f272b1 Co-authored-by: bianbiandashen <16240681+bianbiandashen@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
…el config (openclaw#24210) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 0f272b1 Co-authored-by: bianbiandashen <16240681+bianbiandashen@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
…el config (openclaw#24210) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 0f272b1 Co-authored-by: bianbiandashen <16240681+bianbiandashen@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
…el config (openclaw#24210) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 0f272b1 Co-authored-by: bianbiandashen <16240681+bianbiandashen@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
…el config (openclaw#24210) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 0f272b1 Co-authored-by: bianbiandashen <16240681+bianbiandashen@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
Summary
agents.defaults.modelwhen per-agent model is omittedresolveAgentModelPrimarynow correctly falls back to global defaultsRoot Cause
When configuring
openclaw.json, if a user specifies global default models inagents.defaults.modelbut omits the model block in a specific agent's entry,resolveAgentModelPrimaryreturnedundefinedinstead of checking the global defaults.This caused embedded background agents (like slug-generator) to fall back to a hardcoded
DEFAULT_MODEL = "claude-opus-4-6"instead of using the user's configured default.Solution
Modified
resolveAgentModelPrimaryto checkcfg.agents.defaults.modelwhen the agent-specific model config is not present. Supports both string and object formats for the default model.Test Plan
Fixes #24168
🤖 AI-assisted (Claude) - Lightly tested locally
Greptile Summary
This PR modifies
resolveAgentModelPrimaryto fall back tocfg.agents.defaults.modelwhen an agent has no per-agent model configured, fixing the issue where embedded agents would ignore user-configured global defaults and fall back to the hardcodedDEFAULT_MODEL.Key issues found:
src/commands/models/list.status-command.ts) uses the return value ofresolveAgentModelPrimaryto determine whether to display"agent"or"defaults"as the model source. After this change, agents inheriting from defaults will be incorrectly reported as"agent"source, since the function now returns a value instead ofundefinedeven when the model comes from defaults. This affects both the CLI display and JSON output.defaults.modelto a bare string ("anthropic/claude-sonnet-4"), butAgentDefaultsConfig.modelis typed asAgentModelListConfig(object only:{ primary?: string; fallbacks?: string[] }), not the union typeAgentModelConfigthat includes strings. Either the test or the type definition should be updated to match.Confidence Score: 2/5
resolveAgentModelPrimaryfrom "agent-specific model" to "effective model including defaults" without updating downstream callers that depend on the distinction. The status command will incorrectly label default-sourced models as agent-sourced. Additionally, the test has a type mismatch with the declared config types.src/agents/agent-scope.tsand its interaction withsrc/commands/models/list.status-command.ts(lines 345, 407) where model source attribution is determined.Last reviewed commit: 7020f2e