feat: add Explore mode as subagent-only modality (#24448)

> This PR was authored by Mux on behalf of Mike.

Introduce Explore mode, a read-only subagent modality for delegated
discovery and code investigation.

## What

Adds a `spawn_explore_agent` tool that creates child chats restricted to
read-only operations. An admin can optionally configure a
deployment-wide
model override so Explore subagents use a model optimized for large
context
or reasoning without changing the root chat's model.

### Backend

- New `ChatModeExplore` enum value (migration 000471).
- `spawn_explore_agent` tool definition with read-only allowlist:
`read_file`, `execute`, `process_output`, `read_skill`,
`read_skill_file`.
  Write tools, file editors, and nested subagent spawning are blocked.
- Deployment config storage for the Explore model override
  (`agents_chat_explore_model_override` in `site_configs`).
- Model resolution hierarchy: configured override, then current turn
model,
then global default. Silent fallback with warning log when the override
  becomes unavailable.
- RBAC: `AsChatd` for daemon reads, `ActionRead` and `ActionUpdate` on
  `ResourceDeploymentConfig` for admin API calls.
- Plan mode root chats can use `spawn_explore_agent` for read-only
research,
  matching the planning prompt guidance.
- The Explore override config API now reports malformed saved overrides
as
  "treated as unset" so admins can clear them explicitly.

### Frontend

- `ExploreModelOverrideSettings` component in admin agent behavior
settings.
  Uses `ModelSelector`, handles unavailable model warnings, and supports
  explicit Save and Clear actions.
- Malformed saved overrides show a warning and require an explicit Save
to
  clear, instead of Clear auto-submitting behind the scenes.

### Tests

- Integration: `TestExploreSubagentIsReadOnly` (full spawn flow, tool
  verification, prompt overlay, DB state).
- Unit: tool allowlist tests for explore, plan, and default modes.
- Internal: model override resolution with valid, invalid UUID,
disabled, and
  unconfigured override scenarios.
- RBAC: `dbauthz_test.go` for `GetChatExploreModelOverride` and
  `UpsertChatExploreModelOverride`.
- API: admin set and clear, malformed stored override reporting,
disabled
  model rejection, non-admin denial.
This commit is contained in:
Michael Suchacz
2026-04-17 13:40:17 +02:00
committed by GitHub
parent 890c610e08
commit 73b5058923
42 changed files with 1852 additions and 157 deletions
+18
View File
@@ -3276,6 +3276,24 @@ class ExperimentalApiMethods {
);
};
getChatExploreModelOverride =
async (): Promise<TypesGen.ChatExploreModelOverrideResponse> => {
const response =
await this.axios.get<TypesGen.ChatExploreModelOverrideResponse>(
"/api/experimental/chats/config/explore-model-override",
);
return response.data;
};
updateChatExploreModelOverride = async (
req: TypesGen.UpdateChatExploreModelOverrideRequest,
): Promise<void> => {
await this.axios.put(
"/api/experimental/chats/config/explore-model-override",
req,
);
};
getChatDesktopEnabled =
async (): Promise<TypesGen.ChatDesktopEnabledResponse> => {
const response =
+17
View File
@@ -922,6 +922,23 @@ export const updateChatPlanModeInstructions = (queryClient: QueryClient) => ({
},
});
const chatExploreModelOverrideKey = ["chat-explore-model-override"] as const;
export const chatExploreModelOverride = () => ({
queryKey: chatExploreModelOverrideKey,
queryFn: () => API.experimental.getChatExploreModelOverride(),
});
export const updateChatExploreModelOverride = (queryClient: QueryClient) => ({
mutationFn: (req: TypesGen.UpdateChatExploreModelOverrideRequest) =>
API.experimental.updateChatExploreModelOverride(req),
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: chatExploreModelOverrideKey,
});
},
});
const chatDesktopEnabledKey = ["chat-desktop-enabled"] as const;
export const chatDesktopEnabled = () => ({
+23
View File
@@ -1582,6 +1582,20 @@ export interface ChatDiffStatus {
readonly stale_at?: string;
}
// From codersdk/chats.go
/**
* ChatExploreModelOverrideResponse is the response body for the Explore
* subagent model override configuration endpoint.
*/
export interface ChatExploreModelOverrideResponse {
readonly model_config_id?: string;
/**
* HasMalformedOverride reports whether the saved override is malformed and
* is currently being treated as unset.
*/
readonly has_malformed_override: boolean;
}
// From codersdk/chats.go
/**
* ChatFileMetadata contains lightweight metadata about a file
@@ -7581,6 +7595,15 @@ export interface UpdateChatDesktopEnabledRequest {
readonly enable_desktop: boolean;
}
// From codersdk/chats.go
/**
* UpdateChatExploreModelOverrideRequest is the request body for updating the
* Explore subagent model override configuration endpoint.
*/
export interface UpdateChatExploreModelOverrideRequest {
readonly model_config_id?: string;
}
// From codersdk/chats.go
/**
* UpdateChatModelConfigRequest updates a chat model config.