🤖 refactor: clean up Mux agent system (switch_agent, ui.requires, redundant schema)#3408
Conversation
Accurate map of the Agent system: frontmatter fields, base resolution, tool-policy layering, runtime restrictions. Fixes the wrong agent picker keybind and documents previously-undocumented fields (top-level disabled, ui.routable, ui.requires, subagent.append_prompt, tools.require, regex anchoring, switch_agent literal opt-in, plan-mode file-edit restriction).
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f0b01572a8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Removed three sources of duplication in the Agent definition system: - `ui.selectable` (legacy opt-in) had been kept solely as a fallback alongside `ui.hidden` (modern opt-out). No built-in used it, no docs referenced it. The five inline resolver ladders that supported both forms collapse into one helper. - `ui.disabled` was functionally identical to top-level `disabled`, kept only because the same-name precedence rule existed. Standardize on top-level `disabled` (it gates `task()` and `switch_agent`, not just UI) and drop the dead second field. - `AgentDiscoveryEntry.disabled` was an internal flag the resolver code computed from `ui.disabled` for "debugging/logging only" — no reader in the codebase. Goes away with `ui.disabled`. Extract `resolveAgentVisibility(ui)` so the four call sites that had re-implemented the `hidden → selectable → routable` ladder inline (agentDefinitionsService, router, streamContextBuilder, agentSession, acp/configOptions) all share one definition. No user-visible behavior change: built-ins use neither `ui.selectable` nor `ui.disabled`, and top-level `disabled` semantics are preserved.
The switch_agent tool was originally introduced for the Auto agent (#2717) and let it dynamically hand off to other agents — most notably the hidden `mux` agent for global config edits. Auto mode was removed in #3087 and the Chat-with-Mux flow in #3123, leaving switch_agent as opt-in machinery used by exactly one built-in (desktop, as a routable target) and no built-in producer. The desktop agent is already documented to be invoked via `task` (exec.md system prompt says "delegate to the `desktop` agent via task"), and the task tool filters by `subagent.runnable`, not `ui.routable`. So removing switch_agent does not compromise the desktop workflow. Removed: - `switch_agent` tool: schema, factory, handler, tests, UI component + story, icon mapping, tool-component registry entry, oRPC schema field `ui.routable` + descriptor `uiRoutable`. - `forceToolChoice` plumbing in streamManager + aiService (the only required-tool that needed forced toolChoice was switch_agent). - `dispatchAgentSwitch` and its 9 helper methods in agentSession.ts (~400 lines), including the consecutive-switch ping-pong guard, fallback target resolver, and stream-end dispatch block. - switch_agent literal opt-in machinery in `resolveToolPolicy` (the `isExplicitSwitchAgentEnablePattern` / `matchesSwitchAgentPattern` helpers and runtime require/disable rules). - switch_agent entry in `SUBAGENT_HARD_DENIED_TOOLS` and `EXCLUDED_TOOLS` (ToolBridge). - `routable: true` on `desktop.md` and its assertion in `builtInAgentDefinitions.test.ts`. Kept: - `ui.requires` (`"plan" | "desktop"`). This gates picker visibility and — more importantly — `task`-tool subagent discovery. Without `requires: [desktop]`, the LLM could try to spawn a desktop subagent on a workspace with no desktop capability. Net: +49 / -916 lines across 32 files. All 4,090 non-integration tests pass.
ui.requires had two values: - requires: ["plan"] — used by the (now-deleted) switch_agent plan-gated check and by a UI hook that re-ran agents.list after propose_plan succeeded. Zero built-ins used it, no shipped docs surface featured it as a stable user-facing knob. - requires: ["desktop"] — used by the desktop built-in. After audit, the only remaining live job was hiding desktop from the task tool's subagent menu on workspaces without desktop capability. Since desktop is already hidden: true in the picker and never selectable that way, the agents.list gate was decorative. Replaced the generic field with a hardcoded check in the one place that still mattered: discoverAvailableSubagentsForToolContext now filters when `descriptor.id === "desktop"`. The router.ts agents.list machinery (plan-ready probe, desktop-capability probe, requires gating) is gone. The propose_plan → agents-refresh path in the chat aggregator is gone (kept the AGENTS_REFRESH_REQUESTED event; it is still dispatched from Settings when users toggle agent enablement manually). Net: +29 / -244 lines. 4,460 / 4,460 non-integration tests pass.
|
@codex review The previous comment referenced documentation for |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aa5dbfe0b1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
A user-defined .mux/agents/desktop.md (or global equivalent) replaces the built-in semantics entirely. Without the scope check, the runtime gate silently hid such overrides whenever the workspace lacked desktop capability, even when the override no longer depended on it. Tighten the check to descriptor.scope === "built-in" so only the shipped built-in is gated; same-name overrides resolve via the agent discovery override chain as usual. Addresses Codex review on PR #3408.
|
@codex review Addressed your desktop capability gate finding in 8b3827: the runtime check now requires |
|
Codex Review: Didn't find any major issues. Already looking forward to the next diff. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
Four-commit cleanup of Mux's Agent system, all internal:
docs/agents/index.mdxbrought in line with schema + runtime.ui.selectable,ui.disabled, internalAgentDiscoveryEntry.disabled; extractedresolveAgentVisibility.switch_agentremoved — the tool, theui.routablefield, thedispatchAgentSwitchmachinery, theforceToolChoiceplumbing.ui.requiresremoved — replaced with a one-liner that names thedesktopagent directly.No user-visible change in the docs or schema-cleanup commits. The two removal commits are behavioural changes (see Risks).
Background
While auditing every frontmatter field for necessity, two pieces fell out:
Schema redundancy —
ui.selectablewas a dead legacy fallback toui.hidden.ui.disabledduplicated top-leveldisabled. Five files re-implemented the same visibility ladder inline.Dead subsystem —
switch_agentwas introduced for the Auto agent (#2717) and the hiddenmuxagent (Chat-with-Mux). Both were removed (#3087, #3123). After the audit, switch_agent had zero built-in producers; the only built-in markedroutable: truewasdesktop, which is already invoked viatask(andexec.mdexplicitly says so). Decorative routing.ui.requiresredundancy —requires: ["plan"]was used only by the (now-gone) switch_agent plan check and a UI agents-refresh hook.requires: ["desktop"]was used by the desktop built-in to filter itself from the task tool's subagent menu, but the agents.list capability probe was always dead weight (desktop ishidden: true). The one remaining live job (hide desktop from the LLM's task menu on capability-less workspaces) is now a one-line check namingdescriptor.id === "desktop"directly.Commit-by-commit
Commit 1: docs rewrite
Bugs fixed: wrong picker keybind (was
Cmd+Shift+M, actuallyCmd+Shift+A); wrong subagent hard-deny list; wrong "no recursive task" claim. Previously-undocumented fields documented: top-leveldisabled,subagent.append_prompt,tools.require, regex anchoring,baseresolution rules, the inheritance cap, runtime restriction table.Commit 2: schema cleanup
ui.selectable,ui.disabledfrom the Zod schema.AgentDiscoveryEntryand its internal debug-onlydisabledflag.src/node/services/agentDefinitions/agentVisibility.tshousesresolveAgentVisibility(ui).Commit 3: remove switch_agent
Deleted: tool schema/factory/handler/tests, UI component/story, oRPC field
ui.routable+uiRoutable, the entiredispatchAgentSwitchmachinery inagentSession.ts(~400 lines + 9 helper methods), theforceToolChoiceparameter chain instreamManager+aiService,isExplicitSwitchAgentEnablePattern/matchesSwitchAgentPatternhelpers, theroutable: trueondesktop.md.Commit 4: drop ui.requires
ui.requiresandAgentDefinitionUiRequirementSchemadeleted.router.ts agents.list: droppedplanReadyprobe,desktopCapabilityAvailableprobe,requiresPlan/requiresDesktopgating.uiSelectablecollapses toentry.uiSelectableBase.streamContextBuilder.discoverAvailableSubagentsForToolContext: replaced therequires?.includes("desktop")check withdescriptor.id === "desktop". Same observable behaviour, no generic field needed.applyWorkspaceChatEventToAggregator.ts: removed the propose_plan →AGENTS_REFRESH_REQUESTEDdispatch and its helpers. (The event itself is kept; Settings still dispatches it when users toggle agent enablement.)desktop.mdno longer declaresrequires: ["desktop"].Validation
make static-check(lint, typecheck, fmt-check, eager-imports, shellcheck, hadolint, code-docs-links, mintlify broken-links): passed.bun test src/node/ src/browser/utils/messages/ tests/).Net diff
Risks
.mdfiles withui.selectable:/ui.disabled:silently drop those keys (Zod.strip()). No built-in used them; never documented externally.tools.require: ["switch_agent"]ortools.add: ["switch_agent"]silently drop the rule. No built-in used switch_agent; never documented as a stable primitive.ui.requires:silently drop the rule. No built-in (other than desktop, which is updated here) used it.agents.listno longer togglesuiSelectablefor capability-gated agents. The only built-in that was capability-gated (desktop) ishidden: true, souiSelectableis alreadyfalseregardless. Future user-authored desktop-capability-gated visible agents would need to handle their own gating.Generated with
mux• Model:anthropic:claude-opus-4-7• Thinking:xhigh• Cost:$45.14