Skip to content

Commit f2f5ab3

Browse files
authored
🤖 feat: split Exec sub-agent AI defaults (#3215)
## Summary Adds a separate "Exec" sub-agent slot in `Settings > Tasks > Agent Defaults` so the model and reasoning level used when Exec runs as a sub-agent (delegated by Plan, Orchestrator, or others) can diverge from the model used when Exec is selected interactively in the UI. Sub-agent fields are stored sparsely under `subagentAiDefaults.exec` and inherit unset fields from the UI Exec entry. Also fixes a critical bug in the existing sub-agent dispatch path that made the new override (and `agentAiDefaults.exec`) ineffective: the `task` tool was forwarding the parent agent's `MUX_MODEL_STRING` and `MUX_THINKING_LEVEL` env vars into `taskService.create`, which took precedence over the configured defaults in `resolveTaskAISettings`. ## Background Until now, `agentAiDefaults.exec` controlled both the interactive UI Exec agent and Exec invoked as a sub-agent. Users (and orchestrators) often want a cheaper or faster model for delegated Exec runs while keeping a stronger model for the interactive Exec they drive themselves. There was no way to express that without flipping defaults back and forth, and the UI offered no obvious place to configure the delegated case. While dogfooding the new override, the user observed that setting "Exec sub-agent" to GPT-5.5 / high in Settings had no effect: spawned Exec sub-agents kept running with the parent's model (GPT-5.4 / xhigh). Tracing through `task` tool to `taskService.create` to `resolveTaskAISettings` revealed that the task tool was reading `MUX_MODEL_STRING`/`MUX_THINKING_LEVEL` from the parent's runtime env and passing them as the highest-precedence override, clobbering the configured sub-agent defaults. ## Implementation - New on-disk shape: `subagentAiDefaults: { exec?: { modelString?, thinkingLevel? } }` validated by the config schema. The entry is created lazily on first override and pruned whenever the last field is reset, so unconfigured installs keep a clean config file. - One-time migration `execSubagentDefaultsSplit` records that the split has been applied; existing `agentAiDefaults.exec` keeps its meaning for the UI Exec slot, no values are duplicated into the new shape on upgrade. - Resolution in `taskService.resolveTaskAISettings` walks `subagentAiDefaults.exec` first, falls back to `agentAiDefaults.exec`, then to the parent workspace's running model. With the task-tool fix below, that ordering is now actually reachable. - Settings UI renders a dedicated "Exec" row under `Sub-agents` (the parent section provides the disambiguating context) with `Inherit from UI Exec` reset affordances and live "Inherits from UI Exec: ..." hints. The row reuses `AiDefaultsControls` so the model selector and reasoning select stay consistent with other agent rows. - Helper text below the row explains that Enabled/Advisor stay shared with UI Exec; only model and reasoning split. - Shared the legacy "mirror agent defaults to subagents" helpers (`AGENT_DEFAULT_IDS_EXCLUDED_FROM_LEGACY_SUBAGENTS`, `shouldMirrorAgentDefaultToLegacySubagent`, `deriveLegacySubagentAiDefaultsFromAgentDefaults`) into `src/common/types/tasks.ts` so `node/config.ts` and `node/orpc/router.ts` no longer keep duplicate copies. - Removed a dead guard in `normalizeSubagentAiDefaults` (and its twin in `normalizeAgentAiDefaults`). - `task` tool no longer reads `MUX_MODEL_STRING`/`MUX_THINKING_LEVEL` from the parent runtime env and no longer forwards them to `taskService.create`. The resolution chain in `resolveTaskAISettings` already falls back to `parentAiSettings.model` as the lowest-priority default, so behavior with no configured defaults is unchanged. Added a regression test asserting `taskService.create` receives `modelString: undefined` and `thinkingLevel: undefined` even when those env vars are present. - Storybook play function now asserts the dual "Exec" rows (UI Exec + sub-agent Exec) and the new aria-label group, so the structural change is exercised. - Plan to Exec auto-handoff (`handleSuccessfulProposePlanAutoHandoff`) now routes through the same `resolveTaskAISettings` helper instead of reading `agentAiDefaults[targetAgentId]` directly, so a configured `subagentAiDefaults.exec` actually takes effect when Plan delegates to Exec via `propose_plan`. ## Validation - Hand-driven UAT against a sandboxed dev-server with an empty `MUX_ROOT`, covering: row presence, inheritance display, sparse persistence (model-only and reasoning-only overrides), entry pruning when the last field resets, inheritance text following live UI Exec changes, persistence across full reload, and full independence between the two slots after reload. All 10 cases plus the bonus independence case passed; on-disk JSON was inspected after each step to confirm sparse shape and pruning. - `make typecheck` and `make lint` pass. - `bun test` for: `TasksSection.ui.test.tsx`, `src/node/config.test.ts`, `src/common/types/tasks.test.ts`, `src/node/services/taskService.test.ts`, and `src/node/services/tools/task.test.ts` (including the new regression test for the env-var fix). ## Risks - Touches config load, schema, and migration paths. Mitigated by: schema test coverage in `config.test.ts`, sparse-write semantics (no mass rewrite of existing configs), and a one-shot migration marker so we never re-run the split. - The task-tool change alters one user-observable default: when a parent agent runs with model X and the user has UI Exec configured to model Y, sub-agent Exec invocations now use Y (the configured default) instead of X (whatever the parent happens to be running with). This was the historical leftover that prevented `agentAiDefaults.exec` from acting as a real default for sub-agents. The lowest-priority fallback in `resolveTaskAISettings` still inherits from the parent workspace when no defaults are configured. - Resolution precedence in `taskService.resolveTaskAISettings` is: explicit task arguments first, then `subagentAiDefaults`, then `agentAiDefaults`, then a `parentRuntimeAiSettings` hint (the parent agent's live runtime model/thinking, forwarded by the `task` tool from `MUX_MODEL_STRING` / `MUX_THINKING_LEVEL` as a low-priority fallback only), then persisted parent workspace settings, then global defaults. This keeps per-task overrides effective and configured defaults effective, while still letting unconfigured delegated runs inherit the parent's live model. Covered by new precedence, runtime-hint, and policy-clamp tests in `taskService.test.ts` and `tools/task.test.ts`. - Sub-agent resolution path runs on every delegated Exec call; covered by `taskService.test.ts` cases that exercise `subagentAiDefaults` overrides, `agentAiDefaults` fallbacks, and partial overrides combining both. - UI rename ("Exec as subagent" -> "Exec") relies on the parent "Sub-agents" section heading for context. Co-located UI tests look up the row by aria-label `"Exec defaults"` to stay unambiguous. --- _Generated with `mux` • Model: `anthropic:claude-opus-4-7` • Thinking: `xhigh` • Cost: `$10.05`_ <!-- mux-attribution: model=anthropic:claude-opus-4-7 thinking=xhigh costs=10.05 -->
1 parent d2e53f6 commit f2f5ab3

23 files changed

Lines changed: 2425 additions & 274 deletions

docs/agents/index.mdx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,17 @@ task({
185185

186186
Only agents with `subagent.runnable: true` can be used this way.
187187

188+
### Run-context AI defaults
189+
190+
The same agent identity can use different default model and thinking settings depending on how it runs:
191+
192+
- **UI defaults** (`agentAiDefaults`) apply when you select the agent directly in the UI, such as choosing Exec in the chat input.
193+
- **Subagent defaults** (`subagentAiDefaults`) apply when that agent is spawned through the `task` tool.
194+
195+
Subagent defaults inherit from UI defaults per field. If the subagent model is unset, Mux uses the matching UI agent model; if subagent thinking is unset, Mux uses the matching UI agent thinking level. You can override one subagent field and keep the other inherited.
196+
197+
Mux resolves the subagent model and thinking level when the `task` call creates the child workspace. Those resolved values are stored with that child workspace, so changing defaults later affects future subagent tasks only.
198+
188199
## Examples
189200

190201
### Security Audit Agent

0 commit comments

Comments
 (0)