feat!: scope chat model override settings to organizations (#28442)

Moves chat model override settings from deployment scope to organization
scope, following the org-scoping of chat model configs in #28440.

Depends on #28440 (based on
`ethan/chat-models-availability-consolidation`).

Remote dogfood UAT passed on this change, including multi-organization
scenarios on a licensed deployment with real models.

## Problem

Chat model configs are now owned by organizations, but the override
settings that reference them stayed deployment-wide: admin
subagent/title/compaction overrides in `site_configs`, personal
overrides in `user_configs`, and the advisor model embedded in the
advisor runtime JSON. A single deployment-level override can only point
at one organization's model config, so overrides effectively worked for
the default organization only, and title generation had to soften its
hard-failure contract to tolerate unusable overrides.

## Fix

Admin overrides (general, explore, title generation, compaction,
advisor) and personal overrides (root, general, explore) become
organization-scoped rows in two new typed tables,
`chat_organization_model_overrides` and `chat_user_model_overrides`.
Composite foreign keys on `(organization_id, model_config_id)` make
cross-organization model references unrepresentable. The migration
deletes the legacy serialized `site_configs`/`user_configs` override
keys instead of migrating them; admins and users re-select models in the
new organization-scoped settings.

New endpoints live under
`/api/experimental/organizations/{organization}/chats/model-overrides`
(admin defaults) and
`/api/experimental/organizations/{organization}/members/{user}/chats/model-overrides`
(personal overrides); the legacy deployment-level routes are removed.
chatd resolves overrides via the chat's organization, restoring the
hard-failure contract for configured-but-unusable title generation
overrides. The deployment-level `AllowUsers` switch and advisor runtime
limits stay deployment-scoped.

The Coder Agents settings UI gains a per-organization "Defaults &
overrides" page under AI Settings > Models, and the personal overrides
page gains an organization selector for users in multiple organizations.
Advisor telemetry now reports per-organization override rows.

**BREAKING CHANGE**: Existing deployment-level overrides are dropped,
not migrated. Every organization starts with no overrides after upgrade,
and model selections must be re-applied in the new settings. The legacy
deployment-level override endpoints are removed.

Closes https://linear.app/coder/issue/CODAGT-872

> [!NOTE]
> Xum (AI agent) authored this PR on behalf of @ibetitsmike.

<!-- xum-attribution: model=claude-opus-4-6 thinking=high -->

---------

Co-authored-by: Ethan Dickson <ethan@coder.com>
This commit is contained in:
Michael Suchacz
2026-08-24 11:20:26 +01:00
committed by GitHub
co-authored by Ethan Dickson
parent 3809314b31
commit 0ef331fcd0
75 changed files with 4800 additions and 4952 deletions
+37 -22
View File
@@ -298,37 +298,50 @@ contact an administrator.
## Model overrides
Beyond the chat-level model picker, Coder Agents supports two override
layers:
layers. Both are stored per organization and resolve from the chat's
organization:
- **Subagent overrides** (admin, deployment-wide): Pin specific subagent
contexts to a particular model. Configure them at **Agents** >
**Settings** > **Manage Agents** > **Agents**.
- **Personal overrides** (per user, opt-in by admin): Let users override
the model for their own root chats and delegated subagents. Admins
enable the toggle on the same admin page; once on, each user sees an
**Agents** tab in their personal **Agents** > **Settings**.
- **Admin overrides** (per organization): Pin specific contexts to a
particular model. Configure them on the **Defaults & overrides** tab
under **AI Settings** > **Models** for the selected organization.
- **Personal overrides** (per user and organization, opt-in by admin):
Let users override the model for their own root chats and delegated
subagents. Admins enable the deployment-wide toggle under
**AI Settings** > **Coder Agents**; once on, each user sees an
**Agents** tab in their personal **Agents** > **Settings**. Users in
more than one organization pick which organization to configure.
> [!IMPORTANT]
> When a deployment upgrades from the older deployment-wide override
> storage, existing admin and personal overrides are deleted rather than
> migrated. Admins and users must re-select their override models in
> settings after the upgrade.
The configurable contexts:
| Context | Layer | Applies to |
|----------------------|--------------|--------------------------------------------------------------------------------|
| **General** | Admin + user | Write-capable subagents (`spawn_agent` with `type=general` or `computer_use`). |
| **Explore** | Admin + user | Read-only subagents (`spawn_agent` with `type=explore`). |
| **Title generation** | Admin only | Automatic title generation for new chats. |
| **Root** | User only | The user's own root chats. |
| Context | Layer | Applies to |
|----------------------|--------------|----------------------------------------------------------------------------------------|
| **General** | Admin + user | Write-capable subagents (`spawn_agent` with `type=general` or `computer_use`). |
| **Explore** | Admin + user | Read-only subagents (`spawn_agent` with `type=explore`). |
| **Title generation** | Admin only | Automatic title generation for new chats. |
| **Compaction** | Admin only | Conversation summarization near the context limit. |
| **Advisor** | Admin only | The [advisor](./platform-controls/advisor.md). Requires the `chat-advisor` experiment. |
| **Root** | User only | The user's own root chats. |
Resolution order, evaluated per chat or subagent:
Resolution order, evaluated per chat or subagent from the chat's
organization:
1. Explicit `model_config_id` on the `spawn_agent` tool call (general and
explore subagents only).
1. Personal override (when the admin gate is on and a model is set).
1. Admin subagent override.
1. The chat's selected model (or the deployment default for new chats).
1. Admin override.
1. The chat's selected model (or the organization default for new chats).
If a referenced model is later disabled or deleted, that layer is skipped
and resolution falls through to the next. Explicit `spawn_agent` selection
is different: an unusable `model_config_id` fails the tool call instead of
falling through. Agents discover selectable models (and their reasoning
and resolution falls through to the next, with two exceptions: an unusable
explicit `spawn_agent` `model_config_id` fails the tool call, and an
unusable title generation override skips title generation instead of
falling back. Agents discover selectable models (and their reasoning
effort ranges) with the `list_subagent_models` tool, which only returns
enabled models usable with the chat owner's credentials. Computer-use
subagents always run on the administrator-configured computer-use model and
@@ -336,8 +349,10 @@ reject explicit model selection.
> [!NOTE]
> Both override layers may change between releases.
> The same values are available through the chat
> configuration API under `/api/v2/chats/config/`.
> Admin overrides are available through the API at
> `/api/experimental/organizations/{organization}/chats/model-overrides`
> and personal overrides at
> `/api/experimental/organizations/{organization}/members/{user}/chats/model-overrides`.
## User API keys (BYOK)
@@ -29,20 +29,25 @@ after repeated failures, or risk reduction before a destructive operation.
## Configuration
Once the experiment is enabled, configure the advisor under **AI Settings** >
**Coder Agents** > **Advisor**.
Once the experiment is enabled, configure the advisor's runtime limits
under **AI Settings** > **Coder Agents** > **Advisor**. These limits apply
deployment-wide.
| Field | Default | Notes |
|-------------------|----------------------|-------------------------------------------------------------------------------------------------------------------------|
| Max uses per turn | `0` (unlimited) | Caps how many times the root agent can call the advisor in a single chat turn. Must be a non-negative integer. |
| Max output tokens | `0` (server default) | Caps the advisor model's response length. `0` uses the server default of 16,384 tokens. Must be a non-negative integer. |
| Advisor model | Use chat model | Optional dedicated chat model config for the advisor. When unset, the advisor reuses the root agent's model. |
| Reasoning effort | Model default | Overrides the selected advisor model's reasoning effort. Available only when the model supports selectable effort. |
The advisor model and its reasoning effort are organization-scoped
[model overrides](../models.md#model-overrides). Configure them on the
**Defaults & overrides** tab under **AI Settings** > **Models** for the
selected organization. When no override is set, the advisor reuses the
root agent's model.
The advisor is not available in plan mode or to subagents.
Failed advisor invocations refund the per-turn budget.
The same configuration is available at:
- `GET /api/v2/chats/config/advisor`
- `PUT /api/v2/chats/config/advisor`
The same configuration is available through the API: runtime limits at
`GET`/`PUT` `/api/experimental/chats/config/advisor`, and the advisor
model override at
`PUT /api/experimental/organizations/{organization}/chats/model-overrides/advisor`.
+211
View File
@@ -4087,6 +4087,58 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
| `reasoning` | [codersdk.ChatModelReasoningOptions](#codersdkchatmodelreasoningoptions) | false | | |
| `user` | string | false | | |
## codersdk.ChatModelOverrideContext
```json
"general"
```
### Properties
#### Enumerated Values
| Value(s) |
|-------------------------------------------------------------------|
| `advisor`, `compaction`, `explore`, `general`, `title_generation` |
## codersdk.ChatModelOverrideResponse
```json
{
"context": "general",
"model_config_id": "string",
"reasoning_effort": "string"
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|--------------------|------------------------------------------------------------------------|----------|--------------|-------------|
| `context` | [codersdk.ChatModelOverrideContext](#codersdkchatmodeloverridecontext) | false | | |
| `model_config_id` | string | false | | |
| `reasoning_effort` | string | false | | |
## codersdk.ChatModelOverridesResponse
```json
{
"overrides": [
{
"context": "general",
"model_config_id": "string",
"reasoning_effort": "string"
}
]
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|-------------|-----------------------------------------------------------------------------------|----------|--------------|-------------|
| `overrides` | array of [codersdk.ChatModelOverrideResponse](#codersdkchatmodeloverrideresponse) | false | | |
## codersdk.ChatModelProviderDescriptor
```json
@@ -4390,6 +4442,80 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
| `top_logprobs` | integer | false | | |
| `user` | string | false | | |
## codersdk.ChatPersonalModelOverride
```json
{
"context": "root",
"is_set": true,
"mode": "deployment_default",
"model_config_id": "string",
"reasoning_effort": "string"
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|--------------------|----------------------------------------------------------------------------------------|----------|--------------|-------------|
| `context` | [codersdk.ChatPersonalModelOverrideContext](#codersdkchatpersonalmodeloverridecontext) | false | | |
| `is_set` | boolean | false | | |
| `mode` | [codersdk.ChatPersonalModelOverrideMode](#codersdkchatpersonalmodeloverridemode) | false | | |
| `model_config_id` | string | false | | |
| `reasoning_effort` | string | false | | |
## codersdk.ChatPersonalModelOverrideContext
```json
"root"
```
### Properties
#### Enumerated Values
| Value(s) |
|------------------------------|
| `explore`, `general`, `root` |
## codersdk.ChatPersonalModelOverrideDeploymentDefaults
```json
{
"explore": {
"context": "general",
"model_config_id": "string",
"reasoning_effort": "string"
},
"general": {
"context": "general",
"model_config_id": "string",
"reasoning_effort": "string"
}
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|-----------|--------------------------------------------------------------------------|----------|--------------|-------------|
| `explore` | [codersdk.ChatModelOverrideResponse](#codersdkchatmodeloverrideresponse) | false | | |
| `general` | [codersdk.ChatModelOverrideResponse](#codersdkchatmodeloverrideresponse) | false | | |
## codersdk.ChatPersonalModelOverrideMode
```json
"deployment_default"
```
### Properties
#### Enumerated Values
| Value(s) |
|-----------------------------------------------|
| `chat_default`, `deployment_default`, `model` |
## codersdk.ChatPlanMode
```json
@@ -14982,6 +15108,22 @@ Restarts will only happen on weekdays in this list on weeks which line up with W
| `user_roles` | object | false | | |
| » `[any property]` | [codersdk.ChatRole](#codersdkchatrole) | false | | |
## codersdk.UpdateChatModelOverrideRequest
```json
{
"model_config_id": "string",
"reasoning_effort": "string"
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|--------------------|--------|----------|--------------|-------------|
| `model_config_id` | string | false | | |
| `reasoning_effort` | string | false | | |
## codersdk.UpdateChatModelRequest
```json
@@ -15485,6 +15627,24 @@ Restarts will only happen on weekdays in this list on weeks which line up with W
| `theme_light` | `dark`, `dark-protan-deuter`, `dark-tritan`, `light`, `light-protan-deuter`, `light-tritan` |
| `theme_mode` | `single`, `sync` |
## codersdk.UpdateUserChatPersonalModelOverrideRequest
```json
{
"mode": "deployment_default",
"model_config_id": "string",
"reasoning_effort": "string"
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|--------------------|----------------------------------------------------------------------------------|----------|--------------|-------------|
| `mode` | [codersdk.ChatPersonalModelOverrideMode](#codersdkchatpersonalmodeloverridemode) | false | | |
| `model_config_id` | string | false | | |
| `reasoning_effort` | string | false | | |
## codersdk.UpdateUserNotificationPreferences
```json
@@ -16111,6 +16271,57 @@ If the schedule is empty, the user will be updated to use the default schedule.|
| `theme_mode` | [codersdk.ThemeMode](#codersdkthememode) | false | | |
| `theme_preference` | string | false | | Theme preference is the legacy single-field appearance setting. In "single" mode it mirrors the active theme. In "sync" mode modern clients normally mirror the active OS slot, but older clients can update only this field, so it may diverge from ThemeLight or ThemeDark until a modern client saves the full appearance state again. |
## codersdk.UserChatPersonalModelOverridesResponse
```json
{
"deployment_defaults": {
"explore": {
"context": "general",
"model_config_id": "string",
"reasoning_effort": "string"
},
"general": {
"context": "general",
"model_config_id": "string",
"reasoning_effort": "string"
}
},
"enabled": true,
"explore": {
"context": "root",
"is_set": true,
"mode": "deployment_default",
"model_config_id": "string",
"reasoning_effort": "string"
},
"general": {
"context": "root",
"is_set": true,
"mode": "deployment_default",
"model_config_id": "string",
"reasoning_effort": "string"
},
"root": {
"context": "root",
"is_set": true,
"mode": "deployment_default",
"model_config_id": "string",
"reasoning_effort": "string"
}
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|-----------------------|--------------------------------------------------------------------------------------------------------------|----------|--------------|-------------|
| `deployment_defaults` | [codersdk.ChatPersonalModelOverrideDeploymentDefaults](#codersdkchatpersonalmodeloverridedeploymentdefaults) | false | | |
| `enabled` | boolean | false | | |
| `explore` | [codersdk.ChatPersonalModelOverride](#codersdkchatpersonalmodeloverride) | false | | |
| `general` | [codersdk.ChatPersonalModelOverride](#codersdkchatpersonalmodeloverride) | false | | |
| `root` | [codersdk.ChatPersonalModelOverride](#codersdkchatpersonalmodeloverride) | false | | |
## codersdk.UserLatency
```json