#20954: feat: per-agent model allowlist
docs
channel: bluebubbles
channel: telegram
gateway
agents
size: XL
Cluster:
Model Management Enhancements
## Summary
- Adds `models` field to `agents.list[]` entries that, when present, **replaces** the global `agents.defaults.models` allowlist for that agent
- Agents without per-agent `models` continue using the global catalog (no breaking change)
- Scopes `/model` switching, `/models` listing, Telegram model picker buttons, and session override validation to the agent's allowlist
- Fixes global defaults/fallbacks leaking into agent-scoped model lists
## Changes
**Type + schema:**
- `AgentConfig.models` type in `types.agents.ts`
- Zod validation in `zod-schema.agent-runtime.ts`
**Core threading:**
- `agentModels` param added to `buildAllowedModelSet()`, `buildModelAliasIndex()`, `buildConfiguredAllowlistKeys()`, `getModelRefStatus()`, `resolveAllowedModelRef()`
- `resolveAgentConfig()` passes through `entry.models`
- `createModelSelectionState()` considers agent-level models for allowlist detection
**Call sites:**
- `getReplyFromConfig()` resolves agent models and passes through the full reply pipeline
- `resolveReplyDirectives()` uses agent models for alias resolution
- `buildModelsProviderData()` skips global default/fallback injection when agent allowlist is active
- Telegram callback handler resolves agent route before building model picker data
**Docs:**
- `models.md`: new "Per-agent model allowlist" subsection
- `multi-agent.md`: new section with config example
- `configuration-reference.md`: `models` field reference
## Config example
```json
{
"id": "karl-brett",
"model": { "primary": "openrouter/minimax/minimax-m2.5" },
"models": {
"openrouter/minimax/minimax-m2.5": { "alias": "MiniMax M2.5" },
"openrouter/moonshotai/kimi-k2.5": { "alias": "Kimi K2.5" },
"openrouter/z-ai/glm-5": { "alias": "GLM 5" }
}
}
```
## Test plan
- [ ] `npx tsc --noEmit` passes (only pre-existing test file errors)
- [ ] Agent with per-agent `models` only sees those models in `/models` and Telegram picker
- [ ] Agent without per-agent `models` sees full global catalog
- [ ] Global defaults/fallbacks do not leak into agent-scoped model lists
- [ ] `/model <ref>` rejects models not in the agent's allowlist
- [ ] Session model override resets correctly when model is not in agent allowlist
✨ Per aspera ad astra — Nebulatio
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Adds per-agent model allowlists via `agents.list[].models`, scoping `/model`, `/models`, and Telegram pickers to each agent's catalog.
**Key changes:**
- New `models` field in `AgentConfig` type and Zod schema
- Core model selection functions updated to accept optional `agentModels` parameter
- Agent-scoped allowlists replace global defaults (no merge)
- Model validation, alias resolution, and command handlers thread `agentModels` through the reply pipeline
- Telegram callback handler resolves agent route before building picker data
- Global defaults/fallbacks correctly excluded when agent allowlist is active
**Issues found:**
- `openclaw agent` CLI command (line 394 in `src/commands/agent.ts`) checks global `agentCfg?.models` instead of resolving per-agent models for `sessionAgentId`, causing agent allowlists to be ignored
- `sessions_model` tool (`src/agents/tools/session-status-tool.ts:141-151`) doesn't pass agent models to validation, allowing agents to bypass their allowlists via tool calls
**Unrelated changes bundled in this PR:**
- BlueBubbles extension: surfaces inbound message IDs via system events
- Three new skills added: findmy, group-manager, sonos-cloud
<h3>Confidence Score: 3/5</h3>
- This PR has critical logic gaps that allow per-agent model allowlists to be bypassed
- The core implementation is solid: types, schema validation, reply pipeline threading, and Telegram integration all correctly handle per-agent models. However, two critical code paths bypass the feature: the `openclaw agent` CLI command and the `sessions_model` tool both fail to resolve and pass agent models, allowing users to select disallowed models. These gaps undermine the security/policy enforcement aspect of per-agent allowlists. The PR also bundles unrelated changes (BlueBubbles, skills) which should be separate commits.
- Pay close attention to `src/commands/agent.ts` and `src/agents/tools/session-status-tool.ts` - both have logic bugs that bypass per-agent model allowlists
<sub>Last reviewed commit: 8c9c9b3</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#9583: fix(models): allow models in agents.defaults.models even if not in ...
by hotzen100 · 2026-02-05
82.3%
#14640: feat(agents): support per-agent temperature and maxTokens in agents...
by lailoo · 2026-02-12
80.8%
#13376: fix: pass model directly to agent for sub-agent runs
by jrbobbyhansen-pixel · 2026-02-10
80.0%
#21088: fix: sessions_sspawn model override ignored for sub-agents
by Slats24 · 2026-02-19
79.3%
#11562: Fix #10883: Enforce subagent model configuration
by divol89 · 2026-02-08
78.9%
#21882: feat(gateway): filter models.list by agents.defaults.models allowlist
by kckylechen1 · 2026-02-20
78.4%
#17021: fix(agents): read models from gateway config first
by Limitless2023 · 2026-02-15
78.1%
#21556: fix(agents): graceful fallback when spawned model is not in allowlist
by irchelper · 2026-02-20
76.8%
#6673: fix: preserve allowAny flag in createModelSelectionState for custom...
by tenor0 · 2026-02-01
76.5%
#16838: fix: include configured fallbacks in model allowlist
by taw0002 · 2026-02-15
76.3%