Skip to content

fix(agents): fall back to agents.defaults.model when agent has no model config#24210

Merged
gumadeiras merged 9 commits intoopenclaw:mainfrom
bianbiandashen:fix/agent-model-defaults-fallback
Feb 23, 2026
Merged

fix(agents): fall back to agents.defaults.model when agent has no model config#24210
gumadeiras merged 9 commits intoopenclaw:mainfrom
bianbiandashen:fix/agent-model-defaults-fallback

Conversation

@bianbiandashen
Copy link
Contributor

@bianbiandashen bianbiandashen commented Feb 23, 2026

Summary

  • Fix embedded agents ignoring agents.defaults.model when per-agent model is omitted
  • resolveAgentModelPrimary now correctly falls back to global defaults
  • Add test coverage for defaults fallback behavior

Root Cause

When configuring openclaw.json, if a user specifies global default models in agents.defaults.model but omits the model block in a specific agent's entry, resolveAgentModelPrimary returned undefined instead 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 resolveAgentModelPrimary to check cfg.agents.defaults.model when the agent-specific model config is not present. Supports both string and object formats for the default model.

Test Plan

  • Added unit tests for defaults fallback (string format)
  • Added unit tests for defaults fallback (object format with primary)
  • Verified existing per-agent model override behavior preserved
  • All existing tests pass

Fixes #24168


🤖 AI-assisted (Claude) - Lightly tested locally

Greptile Summary

This PR modifies resolveAgentModelPrimary to fall back to cfg.agents.defaults.model when 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 hardcoded DEFAULT_MODEL.

Key issues found:

  • Model source attribution regression: The status command (src/commands/models/list.status-command.ts) uses the return value of resolveAgentModelPrimary to 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 of undefined even when the model comes from defaults. This affects both the CLI display and JSON output.
  • Type mismatch in test: The test sets defaults.model to a bare string ("anthropic/claude-sonnet-4"), but AgentDefaultsConfig.model is typed as AgentModelListConfig (object only: { primary?: string; fallbacks?: string[] }), not the union type AgentModelConfig that includes strings. Either the test or the type definition should be updated to match.

Confidence Score: 2/5

  • This PR introduces a side effect that misattributes model source in the status command; should be addressed before merging.
  • The core logic change is sound in isolation, but it changes the semantics of resolveAgentModelPrimary from "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.
  • Pay close attention to src/agents/agent-scope.ts and its interaction with src/commands/models/list.status-command.ts (lines 345, 407) where model source attribution is determined.

Last reviewed commit: 7020f2e

@openclaw-barnacle openclaw-barnacle bot added agents Agent runtime and tooling size: S labels Feb 23, 2026
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 145 to 170
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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  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.
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.

Comment on lines +63 to +70
const cfgWithStringDefault: OpenClawConfig = {
agents: {
defaults: {
model: "anthropic/claude-sonnet-4",
},
list: [{ id: "main" }],
},
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Suggested change
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.

@gumadeiras gumadeiras self-assigned this Feb 23, 2026
@gumadeiras gumadeiras force-pushed the fix/agent-model-defaults-fallback branch from 7020f2e to 453011a Compare February 23, 2026 07:19
@openclaw-barnacle openclaw-barnacle bot added the commands Command implementations label Feb 23, 2026
@gumadeiras gumadeiras force-pushed the fix/agent-model-defaults-fallback branch from 453011a to bd0a47c Compare February 23, 2026 07:19
@openclaw-barnacle openclaw-barnacle bot added channel: tlon Channel integration: tlon extensions: llm-task Extension: llm-task size: M and removed size: S labels Feb 23, 2026
@gumadeiras gumadeiras force-pushed the fix/agent-model-defaults-fallback branch from 359af77 to 30939f9 Compare February 23, 2026 07:37
@openclaw-barnacle openclaw-barnacle bot added size: L docs Improvements or additions to documentation gateway Gateway runtime and removed size: M labels Feb 23, 2026
gumadeiras added a commit to bianbiandashen/openclaw that referenced this pull request Feb 23, 2026
Billion Bian and others added 9 commits February 23, 2026 03:18
…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
@gumadeiras gumadeiras force-pushed the fix/agent-model-defaults-fallback branch from 6fcd177 to 0f272b1 Compare February 23, 2026 08:18
@gumadeiras gumadeiras merged commit a4c3739 into openclaw:main Feb 23, 2026
13 checks passed
@gumadeiras
Copy link
Member

Merged via squash.

Thanks @bianbiandashen!

obviyus pushed a commit to jd316/openclaw that referenced this pull request Feb 23, 2026
…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
jaydiamond42 pushed a commit to jaydiamond42/bloomtbot that referenced this pull request Feb 23, 2026
…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
carlosrivera pushed a commit to myascendai/meshiclaw that referenced this pull request Feb 23, 2026
…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
gabrielkoo pushed a commit to gabrielkoo/openclaw that referenced this pull request Feb 23, 2026
…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
mreedr pushed a commit to mreedr/openclaw-custom that referenced this pull request Feb 24, 2026
…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
margulans pushed a commit to margulans/Neiron-AI-assistant that referenced this pull request Feb 25, 2026
…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
brianleach pushed a commit to brianleach/openclaw that referenced this pull request Feb 26, 2026
…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
mylukin pushed a commit to mylukin/openclaw that referenced this pull request Feb 26, 2026
…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
r4jiv007 pushed a commit to r4jiv007/openclaw that referenced this pull request Feb 28, 2026
…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
zooqueen pushed a commit to hanzoai/bot that referenced this pull request Mar 6, 2026
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling channel: tlon Channel integration: tlon commands Command implementations docs Improvements or additions to documentation extensions: llm-task Extension: llm-task gateway Gateway runtime size: L

Projects

None yet

2 participants