Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2206e6f
Add switch_agent tool definition and registration
ThomasK33 Feb 13, 2026
6435154
🤖 feat: add auto built-in routing agent definition
ThomasK33 Feb 13, 2026
3d824b0
feat: gate switch_agent policy with auto-session metadata flag
ThomasK33 Feb 13, 2026
4a95d62
Handle switch_agent stop and follow-up restart flow
ThomasK33 Feb 13, 2026
7bcf758
Validate switch_agent targets and add flow tests
ThomasK33 Feb 13, 2026
e562f93
fix: lint errors in switch_agent (async, nullish coalescing)
ThomasK33 Feb 13, 2026
0f30952
chore: regenerate docs for switch_agent tool and auto agent
ThomasK33 Feb 13, 2026
258dcf6
fix: enforce ui.requires gating + handle empty followUp in switch_agent
ThomasK33 Feb 13, 2026
81e48f8
fix: always disable switch_agent by default in runtime policy
ThomasK33 Feb 13, 2026
8e8e391
fix: remove hidden explore from Auto defaults + strip edit options fr…
ThomasK33 Feb 13, 2026
1b029b3
fix: preserve disableWorkspaceAgents in switch follow-up options
ThomasK33 Feb 13, 2026
e6b15e3
fix: preserve toolPolicy in switch follow-up options
ThomasK33 Feb 13, 2026
ce7ce4b
fix: preserve additionalSystemInstructions + scope-aware validation f…
ThomasK33 Feb 13, 2026
a9f72cd
chore: regenerate docs after auto.md update
ThomasK33 Feb 13, 2026
c86d97b
fix: make auto strict switch-only router
ThomasK33 Feb 19, 2026
8665bba
fix: gate agent-switch metadata on resolved auto agent
ThomasK33 Feb 19, 2026
54ff496
fix: await switch metadata sync before auto follow-up
ThomasK33 Feb 19, 2026
2ec3146
fix: register auto built-in agent definition
ThomasK33 Feb 19, 2026
e086c45
fix: fallback when switch_agent target is unavailable
ThomasK33 Feb 19, 2026
ca66e5d
fix: degrade auto switch requirement in subagent policy
ThomasK33 Feb 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions docs/agents/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,47 @@ Your job is to answer the user's question by delegating research to sub-agents (

</Accordion>

### Auto

**Automatically selects the best agent for your task**

<Accordion title="View auto.md">

```md
---
name: Auto
description: Automatically selects the best agent for your task
base: exec
ui:
color: var(--color-auto-mode)
subagent:
runnable: false
tools:
remove:
# Strict router mode: strip all inherited exec tools.
# `switch_agent` is re-enabled at runtime for Auto-started sessions.
- .*
---

You are **Auto**, a routing agent.

- Analyze the user's request and pick the best agent to handle it.
- Immediately call `switch_agent` with the chosen `agentId`.
- Include an optional follow-up message when it helps hand off context.
- Do not do the work yourself; your sole job is routing.
- Do not emit a normal assistant answer before calling `switch_agent`.

Use these defaults:

- Implementation tasks → `exec`
- Planning/design tasks → `plan`
- Conversational Q&A, explanations, or investigation → `ask`

Only switch to agents visible in the UI (e.g. `exec`, `plan`, `ask`). Do not target hidden agents like `explore`, `compact`, or `system1_bash`.
```

</Accordion>

### Exec

**Implement changes in the repository**
Expand Down
11 changes: 11 additions & 0 deletions docs/hooks/tools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,17 @@ If a value is too large for the environment, it may be omitted (not set). Mux al

</details>

<details>
<summary>switch_agent (3)</summary>

| Env var | JSON path | Type | Description |
| -------------------------- | ---------- | ------ | ----------- |
| `MUX_TOOL_INPUT_AGENT_ID` | `agentId` | string | — |
| `MUX_TOOL_INPUT_FOLLOW_UP` | `followUp` | string | — |
| `MUX_TOOL_INPUT_REASON` | `reason` | string | — |

</details>

<details>
<summary>system1_keep_ranges (4)</summary>

Expand Down
2 changes: 2 additions & 0 deletions src/browser/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
--color-ask-mode-light: hsl(246 100% 85%);
--color-ask-mode-alpha: hsla(246 100% 68% / 0.1);

--color-auto-mode: hsl(168 72% 43%);

--color-exec-mode: hsl(268.56 94.04% 55.19%);
--color-exec-mode-hover: hsl(268.56 94.04% 67%);
--color-exec-mode-light: hsl(268.56 94.04% 78%);
Expand Down
4 changes: 4 additions & 0 deletions src/common/orpc/schemas/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ export const WorkspaceConfigSchema = z.object({
description:
'If set, selects an agent definition for this workspace (e.g., "explore" or "exec").',
}),
agentSwitchingEnabled: z.boolean().optional().meta({
description:
"When true, switch_agent tool is enabled for this workspace (set when session starts from Auto agent).",
}),
taskStatus: z.enum(["queued", "running", "awaiting_report", "reported"]).optional().meta({
description:
"Agent task lifecycle status for child workspaces (queued|running|awaiting_report|reported).",
Expand Down
4 changes: 4 additions & 0 deletions src/common/orpc/schemas/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ export const WorkspaceMetadataSchema = z.object({
description:
"ISO 8601 timestamp when workspace was last unarchived. Used for recency calculation to bump restored workspaces to top.",
}),
agentSwitchingEnabled: z.boolean().optional().meta({
description:
"When true, switch_agent tool is enabled for this workspace (set when session starts from Auto agent).",
}),
sectionId: z.string().optional().meta({
description: "ID of the section this workspace belongs to (optional, unsectioned if absent)",
}),
Expand Down
20 changes: 20 additions & 0 deletions src/common/utils/tools/toolDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,18 @@ export const AgentReportToolArgsSchema = z
})
.strict();

// -----------------------------------------------------------------------------
// switch_agent (agent switching for Auto agent)
// -----------------------------------------------------------------------------

export const SwitchAgentToolArgsSchema = z
.object({
agentId: AgentIdSchema,
reason: z.string().max(512).nullish(),
followUp: z.string().max(2000).nullish(),
})
.strict();

export const AgentReportToolResultSchema = z.object({ success: z.literal(true) }).strict();
const FILE_TOOL_PATH = z
.string()
Expand Down Expand Up @@ -886,6 +898,13 @@ export const TOOL_DEFINITIONS = {
"Call this exactly once when you have a final answer (after any spawned sub-tasks complete).",
schema: AgentReportToolArgsSchema,
},
switch_agent: {
description:
"Switch to a different agent and restart the stream. " +
"Only UI-selectable agents can be targeted. " +
"The current stream will end and a new stream will start with the selected agent.",
schema: SwitchAgentToolArgsSchema,
},
system1_keep_ranges: {
description:
"Internal tool used by mux to record which line ranges to keep when filtering large bash output.",
Expand Down Expand Up @@ -1432,6 +1451,7 @@ export function getAvailableTools(
"task_terminate",
"task_list",
...(enableAgentReport ? ["agent_report"] : []),
"switch_agent",
"system1_keep_ranges",
"todo_write",
"todo_read",
Expand Down
2 changes: 2 additions & 0 deletions src/common/utils/tools/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { createAgentSkillReadFileTool } from "@/node/services/tools/agent_skill_
import { createMuxGlobalAgentsReadTool } from "@/node/services/tools/mux_global_agents_read";
import { createMuxGlobalAgentsWriteTool } from "@/node/services/tools/mux_global_agents_write";
import { createAgentReportTool } from "@/node/services/tools/agent_report";
import { createSwitchAgentTool } from "@/node/services/tools/switch_agent";
import { createSystem1KeepRangesTool } from "@/node/services/tools/system1_keep_ranges";
import { wrapWithInitWait } from "@/node/services/tools/wrapWithInitWait";
import { withHooks, type HookConfig } from "@/node/services/tools/withHooks";
Expand Down Expand Up @@ -320,6 +321,7 @@ export async function getToolsForModel(
ask_user_question: createAskUserQuestionTool(config),
propose_plan: createProposePlanTool(config),
...(config.enableAgentReport ? { agent_report: createAgentReportTool(config) } : {}),
switch_agent: createSwitchAgentTool(config),
system1_keep_ranges: createSystem1KeepRangesTool(config),
todo_write: createTodoWriteTool(config),
todo_read: createTodoReadTool(config),
Expand Down
30 changes: 30 additions & 0 deletions src/node/builtinAgents/auto.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
name: Auto
description: Automatically selects the best agent for your task
base: exec
ui:
color: var(--color-auto-mode)
subagent:
runnable: false
tools:
remove:
# Strict router mode: strip all inherited exec tools.
# `switch_agent` is re-enabled at runtime for Auto-started sessions.
- .*
---

You are **Auto**, a routing agent.

- Analyze the user's request and pick the best agent to handle it.
- Immediately call `switch_agent` with the chosen `agentId`.
- Include an optional follow-up message when it helps hand off context.
- Do not do the work yourself; your sole job is routing.
- Do not emit a normal assistant answer before calling `switch_agent`.

Use these defaults:

- Implementation tasks → `exec`
- Planning/design tasks → `plan`
- Conversational Q&A, explanations, or investigation → `ask`

Only switch to agents visible in the UI (e.g. `exec`, `plan`, `ask`). Do not target hidden agents like `explore`, `compact`, or `system1_bash`.
10 changes: 9 additions & 1 deletion src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,7 @@ export class Config {
: undefined),
parentWorkspaceId: workspace.parentWorkspaceId,
agentType: workspace.agentType,
agentSwitchingEnabled: workspace.agentSwitchingEnabled,
taskStatus: workspace.taskStatus,
reportedAt: workspace.reportedAt,
taskModelString: workspace.taskModelString,
Expand Down Expand Up @@ -798,6 +799,7 @@ export class Config {
// Preserve tree/task metadata when present in config (metadata.json won't have it)
metadata.parentWorkspaceId ??= workspace.parentWorkspaceId;
metadata.agentType ??= workspace.agentType;
metadata.agentSwitchingEnabled ??= workspace.agentSwitchingEnabled;
metadata.taskStatus ??= workspace.taskStatus;
metadata.reportedAt ??= workspace.reportedAt;
metadata.taskModelString ??= workspace.taskModelString;
Expand Down Expand Up @@ -848,6 +850,7 @@ export class Config {
: undefined),
parentWorkspaceId: workspace.parentWorkspaceId,
agentType: workspace.agentType,
agentSwitchingEnabled: workspace.agentSwitchingEnabled,
taskStatus: workspace.taskStatus,
reportedAt: workspace.reportedAt,
taskModelString: workspace.taskModelString,
Expand Down Expand Up @@ -892,6 +895,7 @@ export class Config {
: undefined),
parentWorkspaceId: workspace.parentWorkspaceId,
agentType: workspace.agentType,
agentSwitchingEnabled: workspace.agentSwitchingEnabled,
taskStatus: workspace.taskStatus,
reportedAt: workspace.reportedAt,
taskModelString: workspace.taskModelString,
Expand Down Expand Up @@ -952,6 +956,7 @@ export class Config {
parentWorkspaceId: metadata.parentWorkspaceId,
agentType: metadata.agentType,
agentId: metadata.agentId,
agentSwitchingEnabled: metadata.agentSwitchingEnabled,
taskStatus: metadata.taskStatus,
reportedAt: metadata.reportedAt,
taskModelString: metadata.taskModelString,
Expand Down Expand Up @@ -1007,14 +1012,17 @@ export class Config {
*/
async updateWorkspaceMetadata(
workspaceId: string,
updates: Partial<Pick<WorkspaceMetadata, "name" | "runtimeConfig">>
updates: Partial<Pick<WorkspaceMetadata, "name" | "runtimeConfig" | "agentSwitchingEnabled">>
): Promise<void> {
await this.editConfig((config) => {
for (const [_projectPath, projectConfig] of config.projects) {
const workspace = projectConfig.workspaces.find((w) => w.id === workspaceId);
if (workspace) {
if (updates.name !== undefined) workspace.name = updates.name;
if (updates.runtimeConfig !== undefined) workspace.runtimeConfig = updates.runtimeConfig;
if (updates.agentSwitchingEnabled !== undefined) {
workspace.agentSwitchingEnabled = updates.agentSwitchingEnabled;
}
return config;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

export const BUILTIN_AGENT_CONTENT = {
"ask": "---\nname: Ask\ndescription: Delegate questions to Explore sub-agents and synthesize an answer.\nbase: exec\nui:\n color: var(--color-ask-mode)\nsubagent:\n runnable: false\ntools:\n # Inherits all tools from exec, then removes editing tools\n remove:\n # Read-only: no file modifications\n - file_edit_.*\n---\n\nYou are **Ask**.\n\nYour job is to answer the user's question by delegating research to sub-agents (typically **Explore**), then synthesizing a concise, actionable response.\n\n## When to delegate\n\n- Delegate when the question requires repository exploration, multiple viewpoints, or verification.\n- If the answer is obvious and does not require looking anything up, answer directly.\n\n## Delegation workflow\n\n1. Break the question into **1–3** focused research threads.\n2. Spawn Explore sub-agents in parallel using the `task` tool:\n - `agentId: \"explore\"` (or `subagent_type: \"explore\"`)\n - Use clear titles like `\"Ask: find callsites\"`, `\"Ask: summarize behavior\"`, etc.\n - Ask for concrete outputs: file paths, symbols, commands to reproduce, and short excerpts.\n3. Wait for results (use `task_await` if you launched tasks in the background).\n4. Synthesize:\n - Provide the final answer first.\n - Then include supporting details (paths, commands, edge cases).\n - Trust Explore sub-agent reports as authoritative for repo facts (paths/symbols/callsites). Do not redo the same investigation yourself; only re-check if the report is ambiguous or contradicts other evidence.\n\n## Safety rules\n\n- Do **not** modify repository files.\n- Prefer `agentId: \"explore\"`. Only use `\"exec\"` if the user explicitly asks to implement changes.\n",
"auto": "---\nname: Auto\ndescription: Automatically selects the best agent for your task\nbase: exec\nui:\n color: var(--color-auto-mode)\nsubagent:\n runnable: false\ntools:\n remove:\n # Strict router mode: strip all inherited exec tools.\n # `switch_agent` is re-enabled at runtime for Auto-started sessions.\n - .*\n---\n\nYou are **Auto**, a routing agent.\n\n- Analyze the user's request and pick the best agent to handle it.\n- Immediately call `switch_agent` with the chosen `agentId`.\n- Include an optional follow-up message when it helps hand off context.\n- Do not do the work yourself; your sole job is routing.\n- Do not emit a normal assistant answer before calling `switch_agent`.\n\nUse these defaults:\n\n- Implementation tasks → `exec`\n- Planning/design tasks → `plan`\n- Conversational Q&A, explanations, or investigation → `ask`\n\nOnly switch to agents visible in the UI (e.g. `exec`, `plan`, `ask`). Do not target hidden agents like `explore`, `compact`, or `system1_bash`.\n",
"compact": "---\nname: Compact\ndescription: History compaction (internal)\nui:\n hidden: true\nsubagent:\n runnable: false\n---\n\nYou are running a compaction/summarization pass. Your task is to write a concise summary of the conversation so far.\n\nIMPORTANT:\n\n- You have NO tools available. Do not attempt to call any tools or output JSON.\n- Simply write the summary as plain text prose.\n- Follow the user's instructions for what to include in the summary.\n",
"exec": "---\nname: Exec\ndescription: Implement changes in the repository\nui:\n color: var(--color-exec-mode)\nsubagent:\n runnable: true\n append_prompt: |\n You are running as a sub-agent in a child workspace.\n\n - Take a single narrowly scoped task and complete it end-to-end. Do not expand scope.\n - Preserve your context window: treat `explore` tasks as a context-saving repo scout for discovery (file locations, callsites, tests, config points, high-level flows).\n If you need repo context, spawn 1–N `explore` tasks (read-only) to scan the codebase and return paths + symbols + minimal excerpts.\n Then open/read only the returned files; avoid broad manual file-reading, and write a short internal \"mini-plan\" before editing.\n If the task brief already includes clear starting points + acceptance criteria, skip the initial explore pass and only explore when blocked.\n Prefer 1–3 narrow `explore` tasks (possibly in parallel).\n - If the task brief is missing critical information (scope, acceptance, or starting points) and you cannot infer it safely after a quick `explore`, do not guess.\n Stop and call `agent_report` once with 1–3 concrete questions/unknowns for the parent agent, and do not create commits.\n - Run targeted verification and create one or more git commits.\n - **Before your stream ends, you MUST call `agent_report` exactly once with:**\n - What changed (paths / key details)\n - What you ran (tests, typecheck, lint)\n - Any follow-ups / risks\n (If you forget, the parent will inject a follow-up message and you'll waste tokens.)\n - You may call task/task_await/task_list/task_terminate to delegate further when available.\n Delegation is limited by Max Task Nesting Depth (Settings → Agents → Task Settings).\n - Do not call propose_plan.\ntools:\n add:\n # Allow all tools by default (includes MCP tools which have dynamic names)\n # Use tools.remove in child agents to restrict specific tools\n - .*\n remove:\n # Exec mode doesn't use planning tools\n - propose_plan\n - ask_user_question\n # Internal-only tools\n - system1_keep_ranges\n---\n\nYou are in Exec mode.\n\n- If a `<plan>` block was provided (plan → exec handoff) and the user accepted it, treat it as the source of truth and implement it directly.\n Only do extra exploration if the plan is missing critical repo facts or you hit contradictions.\n- Use `explore` sub-agents just-in-time for missing repo context (paths/symbols/tests); don't spawn them by default.\n- Trust Explore sub-agent reports as authoritative for repo facts (paths/symbols/callsites). Do not redo the same investigation yourself; only re-check if the report is ambiguous or contradicts other evidence.\n- For correctness claims, an Explore sub-agent report counts as having read the referenced files.\n- Make minimal, correct, reviewable changes that match existing codebase patterns.\n- Prefer targeted commands and checks (typecheck/tests) when feasible.\n- Treat as a standing order: keep running checks and addressing failures until they pass or a blocker outside your control arises.\n",
"explore": "---\nname: Explore\ndescription: Read-only exploration of repository, environment, web, etc. Useful for investigation before making changes.\nbase: exec\nui:\n hidden: true\nsubagent:\n runnable: true\n skip_init_hook: true\n append_prompt: |\n You are an Explore sub-agent running inside a child workspace.\n\n - Explore the repository to answer the prompt using read-only investigation.\n - Return concise, actionable findings (paths, symbols, callsites, and facts).\n - When you have a final answer, call agent_report exactly once.\n - Do not call agent_report until you have completed the assigned task.\ntools:\n # Remove editing and task tools from exec base (read-only agent)\n remove:\n - file_edit_.*\n - task\n - task_apply_git_patch\n - task_.*\n - agent_skill_read\n - agent_skill_read_file\n---\n\nYou are in Explore mode (read-only).\n\n=== CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS ===\n\n- You MUST NOT manually create, edit, delete, move, copy, or rename tracked files.\n- You MUST NOT stage/commit or otherwise modify git state.\n- You MUST NOT use redirect operators (>, >>) or heredocs to write to files.\n - Pipes are allowed for processing, but MUST NOT be used to write to files (for example via `tee`).\n- You MUST NOT run commands that are explicitly about modifying the filesystem or repo state (rm, mv, cp, mkdir, touch, git add/commit, installs, etc.).\n- You MAY run verification commands (fmt-check/lint/typecheck/test) even if they create build artifacts/caches, but they MUST NOT modify tracked files.\n - After running verification, check `git status --porcelain` and report if it is non-empty.\n- Prefer `file_read` for reading file contents (supports offset/limit paging).\n- Use bash for read-only operations (rg, ls, git diff/show/log, etc.) and verification commands.\n",
Expand Down
10 changes: 10 additions & 0 deletions src/node/services/agentDefinitions/builtInAgentDefinitions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ describe("built-in agent definitions", () => {
expect(orchestrator?.body).toContain("counts as having read the referenced files");
});

test("includes auto router built-in", () => {
const pkgs = getBuiltInAgentDefinitions();
const byId = new Map(pkgs.map((pkg) => [pkg.id, pkg] as const));

const auto = byId.get("auto");
expect(auto).toBeTruthy();
expect(auto?.frontmatter.tools?.remove ?? []).toContain(".*");
expect(auto?.body).toContain("Immediately call `switch_agent`");
});

test("orchestrator includes an exec task brief template", () => {
const pkgs = getBuiltInAgentDefinitions();
const byId = new Map(pkgs.map((pkg) => [pkg.id, pkg] as const));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const BUILT_IN_SOURCES: BuiltInSource[] = [
{ id: "exec", content: BUILTIN_AGENT_CONTENT.exec },
{ id: "plan", content: BUILTIN_AGENT_CONTENT.plan },
{ id: "ask", content: BUILTIN_AGENT_CONTENT.ask },
{ id: "auto", content: BUILTIN_AGENT_CONTENT.auto },
{ id: "compact", content: BUILTIN_AGENT_CONTENT.compact },
{ id: "explore", content: BUILTIN_AGENT_CONTENT.explore },
{ id: "system1_bash", content: BUILTIN_AGENT_CONTENT.system1_bash },
Expand Down
Loading
Loading