Skip to content

Commit 3f80680

Browse files
committed
🤖 refactor: remove plan subagent auto-handoff
Remove plan-mode subagent auto-handoff after propose_plan, drop the executor-routing settings and backend router, reject plan-like task creation, and update related docs/stories/tests. --- _Generated with `mux` • Model: `openai:gpt-5.4` • Thinking: `high` • Cost: `$2.78`_ <!-- mux-attribution: model=openai:gpt-5.4 thinking=high costs=2.78 -->
1 parent 9d16d04 commit 3f80680

18 files changed

Lines changed: 127 additions & 1451 deletions

File tree

docs/AGENTS.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ description: Agent instructions for AI assistants working on the Mux codebase
5959
Use `agent-browser` for web automation. Run `agent-browser --help` for all commands.
6060

6161
Core workflow:
62+
6263
1. `agent-browser open <url>` - Navigate to page
6364
2. `agent-browser snapshot -i` - Get interactive elements with refs (@e1, @e2)
6465
3. `agent-browser click @e1` / `fill @e2 "text"` - Interact using refs
@@ -68,8 +69,8 @@ Core workflow:
6869

6970
- If a PR has Codex review comments, address + resolve them, then re-request review by commenting `@codex review` on the PR.
7071
- Prefer `gh` CLI for GitHub interactions over manual web/curl flows.
71-
- In Orchestrator mode, delegate implementation/verification commands to `exec` or `explore` sub-agents and integrate their patches; do not bypass delegation with direct local edits.
72-
- In Orchestrator mode, route higher-complexity implementation tasks to `plan` sub-agents so they can research and produce a precise plan before auto-handoff to implementation.
72+
- When delegation is required by the active mode, use `exec` or `explore` sub-agents as directed and integrate their patches; do not bypass delegation with direct local edits.
73+
- Keep implementation tasks on `exec` sub-agents; use a top-level plan workspace when you need a separate planning phase before delegation.
7374

7475
- User preference: when work is already on an open PR, push branch updates at the end of each completed change set so the PR stays current.
7576
- **PR creation gate:** Do **not** open/create a pull request unless the user explicitly asks (e.g., "open a PR", "create PR", "submit this"). By default, complete local validation, commit/push branch updates as requested, and let the user review before deciding whether to open a PR.
@@ -81,11 +82,11 @@ Core workflow:
8182
When a PR exists, you MUST remain in this loop until the PR is fully ready:
8283

8384
1. Push your latest fixes.
84-
2. Run local validation (`make static-check` and targeted tests as needed); in Orchestrator mode, delegate command execution to sub-agents.
85+
2. Run local validation (`make static-check` and targeted tests as needed); delegate command execution to sub-agents when the active mode requires it.
8586
3. Request review with `@codex review`.
8687
4. Run `./scripts/wait_pr_ready.sh <pr_number>` (which must execute `./scripts/wait_pr_checks.sh <pr_number> --once` while checks are pending).
87-
5. If Codex leaves comments, address them (delegate fixes in Orchestrator mode), resolve threads with `./scripts/resolve_pr_comment.sh <thread_id>`, push, and repeat.
88-
6. If checks/mergeability fail, fix issues locally (delegate fixes in Orchestrator mode), push, and repeat.
88+
5. If Codex leaves comments, address them (delegating fixes when required by the active mode), resolve threads with `./scripts/resolve_pr_comment.sh <thread_id>`, push, and repeat.
89+
6. If checks/mergeability fail, fix issues locally (delegating fixes when required by the active mode), push, and repeat.
8990

9091
The only early-stop exception is when the reviewer is clearly misunderstanding the intended change and further churn would be counterproductive. In that case, leave a clarifying PR comment and pause for human direction.
9192

docs/agents/index.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -526,9 +526,9 @@ Example dependency chain (schema download → generation):
526526
Patch integration loop (default):
527527

528528
1. Identify a batch of independent subtasks.
529-
2. Spawn one implementation sub-agent task per subtask with `run_in_background: true` (`exec` for low complexity, `plan` for higher complexity).
529+
2. Spawn one `exec` implementation sub-agent task per subtask with `run_in_background: true`.
530530
3. Await the batch via `task_await`.
531-
4. For each successful implementation task (`exec` directly, or `plan` after auto-handoff to implementation), integrate patches one at a time:
531+
4. For each successful implementation task, integrate patches one at a time:
532532
- Treat every successful child task with a `taskId` as pending patch integration, whether the completion arrived inline from `task` or later from `task_await`.
533533
- Complete each dry-run + real-apply pair before starting the next patch. Applying one patch changes `HEAD`, which can invalidate later dry-run results.
534534
- Dry-run apply: `task_apply_git_patch` with `dry_run: true`.
@@ -579,7 +579,7 @@ description: Create a plan before coding
579579
ui:
580580
color: var(--color-plan-mode)
581581
subagent:
582-
runnable: true
582+
runnable: false
583583
tools:
584584
add:
585585
# Allow all tools by default (includes MCP tools which have dynamic names)

src/browser/components/icons/EmojiIcon/EmojiIcon.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ const EMOJI_TO_ICON: Record<string, LucideIcon> = {
4848
"🔗": Link,
4949
"🔄": RefreshCw,
5050
"🧪": Beaker,
51-
// Used by auto-handoff routing status while selecting the executor.
5251
"🤔": CircleHelp,
5352

5453
// Directions

src/browser/features/Settings/Sections/TasksSection.tsx

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,7 @@ import {
3434
import {
3535
DEFAULT_TASK_SETTINGS,
3636
TASK_SETTINGS_LIMITS,
37-
isPlanSubagentExecutorRouting,
3837
normalizeTaskSettings,
39-
type PlanSubagentExecutorRouting,
4038
type TaskSettings,
4139
} from "@/common/types/tasks";
4240
import { getThinkingOptionLabel, type ThinkingLevel } from "@/common/types/thinking";
@@ -173,8 +171,6 @@ function areTaskSettingsEqual(a: TaskSettings, b: TaskSettings): boolean {
173171
a.maxParallelAgentTasks === b.maxParallelAgentTasks &&
174172
a.maxTaskNestingDepth === b.maxTaskNestingDepth &&
175173
a.proposePlanImplementReplacesChatHistory === b.proposePlanImplementReplacesChatHistory &&
176-
a.planSubagentExecutorRouting === b.planSubagentExecutorRouting &&
177-
a.planSubagentDefaultsToOrchestrator === b.planSubagentDefaultsToOrchestrator &&
178174
a.bashOutputCompactionMinLines === b.bashOutputCompactionMinLines &&
179175
a.bashOutputCompactionMinTotalBytes === b.bashOutputCompactionMinTotalBytes &&
180176
a.bashOutputCompactionMaxKeptLines === b.bashOutputCompactionMaxKeptLines &&
@@ -499,25 +495,10 @@ export function TasksSection() {
499495
);
500496
};
501497

502-
const setPlanSubagentExecutorRouting = (value: string) => {
503-
if (!isPlanSubagentExecutorRouting(value)) {
504-
return;
505-
}
506-
507-
setTaskSettings((prev) =>
508-
normalizeTaskSettings({
509-
...prev,
510-
planSubagentExecutorRouting: value,
511-
})
512-
);
513-
};
514498
const setNewWorkspaceDefaultAgentId = (agentId: string) => {
515499
setGlobalDefaultAgentIdRaw(coerceAgentId(agentId));
516500
};
517501

518-
const planSubagentExecutorRouting: PlanSubagentExecutorRouting =
519-
taskSettings.planSubagentExecutorRouting ?? "exec";
520-
521502
const setAgentModel = (agentId: string, value: string) => {
522503
setAgentAiDefaults((prev) =>
523504
updateAgentDefaultEntry(prev, agentId, (updated) => {
@@ -917,28 +898,6 @@ export function TasksSection() {
917898
aria-label="Toggle plan Implement replaces conversation with plan"
918899
/>
919900
</div>
920-
921-
<div className="flex items-center justify-between gap-4">
922-
<div className="flex-1">
923-
<div className="text-foreground text-sm">Plan sub-agents: executor routing</div>
924-
<div className="text-muted text-xs">
925-
Choose how plan sub-agent tasks route after propose_plan.
926-
</div>
927-
</div>
928-
<Select
929-
value={planSubagentExecutorRouting}
930-
onValueChange={setPlanSubagentExecutorRouting}
931-
>
932-
<SelectTrigger className="border-border-medium bg-background-secondary h-9 w-44">
933-
<SelectValue />
934-
</SelectTrigger>
935-
<SelectContent>
936-
<SelectItem value="exec">Exec</SelectItem>
937-
<SelectItem value="orchestrator">Orchestrator</SelectItem>
938-
<SelectItem value="auto">Auto (Agent chooses)</SelectItem>
939-
</SelectContent>
940-
</Select>
941-
</div>
942901
</div>
943902

944903
{saveError ? <div className="text-danger-light mt-4 text-xs">{saveError}</div> : null}

src/browser/features/Tools/ProposePlan/ProposePlanToolCall.stories.tsx

Lines changed: 0 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,7 @@ import {
66
createUserMessage,
77
createAssistantMessage,
88
createProposePlanTool,
9-
createStatusTool,
109
} from "@/browser/stories/mockFactory";
11-
import {
12-
PLAN_AUTO_ROUTING_STATUS_EMOJI,
13-
PLAN_AUTO_ROUTING_STATUS_MESSAGE,
14-
} from "@/common/constants/planAutoRoutingStatus";
1510

1611
const meta = { ...appMeta, title: "App/Chat/Tools/ProposePlan" };
1712
export default meta;
@@ -167,84 +162,6 @@ graph TD
167162
},
168163
};
169164

170-
/**
171-
* Captures the handoff pause after a plan is presented and before the executor stream starts.
172-
*
173-
* This reproduces the visual state where the sidebar shows "Deciding execution strategy…"
174-
* while the proposed plan remains visible in the conversation.
175-
*/
176-
export const ProposePlanAutoRoutingDecisionGap: AppStory = {
177-
render: () => (
178-
<AppWithMocks
179-
setup={() =>
180-
setupSimpleChatStory({
181-
workspaceId: "ws-plan-auto-routing-gap",
182-
workspaceName: "feature/plan-auto-routing",
183-
messages: [
184-
createUserMessage(
185-
"msg-1",
186-
"Plan and implement a safe migration rollout for auth tokens.",
187-
{
188-
historySequence: 1,
189-
timestamp: STABLE_TIMESTAMP - 240000,
190-
}
191-
),
192-
createAssistantMessage("msg-2", "Here is the implementation plan.", {
193-
historySequence: 2,
194-
timestamp: STABLE_TIMESTAMP - 230000,
195-
toolCalls: [
196-
createProposePlanTool(
197-
"call-plan-1",
198-
`# Auth Token Migration Rollout
199-
200-
## Goals
201-
202-
- Migrate token validation to the new signing service.
203-
- Maintain compatibility during rollout.
204-
- Keep rollback simple and low risk.
205-
206-
## Steps
207-
208-
1. Add dual-read token validation behind a feature flag.
209-
2. Ship telemetry for token verification outcomes.
210-
3. Enable new validator for 10% of traffic.
211-
4. Ramp to 100% after stability checks.
212-
5. Remove legacy validator once metrics stay healthy.
213-
214-
## Rollback
215-
216-
- Disable the rollout flag to return to legacy validation immediately.
217-
- Keep telemetry running to confirm recovery.`
218-
),
219-
],
220-
}),
221-
createAssistantMessage("msg-3", "Selecting the right executor for this plan.", {
222-
historySequence: 3,
223-
timestamp: STABLE_TIMESTAMP - 220000,
224-
toolCalls: [
225-
createStatusTool(
226-
"call-status-1",
227-
PLAN_AUTO_ROUTING_STATUS_EMOJI,
228-
PLAN_AUTO_ROUTING_STATUS_MESSAGE
229-
),
230-
],
231-
}),
232-
],
233-
})
234-
}
235-
/>
236-
),
237-
parameters: {
238-
docs: {
239-
description: {
240-
story:
241-
"Chromatic regression story for the plan auto-routing gap: after `propose_plan` succeeds, " +
242-
"the sidebar stays in a working state with a 'Deciding execution strategy…' status before executor kickoff.",
243-
},
244-
},
245-
},
246-
};
247-
248165
/**
249166
* Mobile viewport version of ProposePlan.
250167
*

src/common/config/schemas/appConfigOnDisk.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import { TaskSettingsSchema } from "./taskSettings";
88

99
export { RuntimeEnablementOverridesSchema } from "../../schemas/runtimeEnablement";
1010
export type { RuntimeEnablementOverrides } from "../../schemas/runtimeEnablement";
11-
export { PlanSubagentExecutorRoutingSchema, TaskSettingsSchema } from "./taskSettings";
12-
export type { PlanSubagentExecutorRouting, TaskSettings } from "./taskSettings";
11+
export { TaskSettingsSchema } from "./taskSettings";
12+
export type { TaskSettings } from "./taskSettings";
1313

1414
export const AgentAiDefaultsEntrySchema = z.object({
1515
modelString: z.string().optional(),

src/common/config/schemas/taskSettings.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ export const SYSTEM1_BASH_OUTPUT_COMPACTION_LIMITS = {
1212
bashOutputCompactionTimeoutMs: { min: 1_000, max: 120_000, default: 5_000 },
1313
} as const;
1414

15-
export const PlanSubagentExecutorRoutingSchema = z.enum(["exec", "orchestrator", "auto"]);
16-
17-
export type PlanSubagentExecutorRouting = z.infer<typeof PlanSubagentExecutorRoutingSchema>;
18-
1915
export const TaskSettingsSchema = z.object({
2016
maxParallelAgentTasks: z
2117
.number()
@@ -30,8 +26,6 @@ export const TaskSettingsSchema = z.object({
3026
.max(TASK_SETTINGS_LIMITS.maxTaskNestingDepth.max)
3127
.optional(),
3228
proposePlanImplementReplacesChatHistory: z.boolean().optional(),
33-
planSubagentExecutorRouting: PlanSubagentExecutorRoutingSchema.optional(),
34-
planSubagentDefaultsToOrchestrator: z.boolean().optional(),
3529
bashOutputCompactionMinLines: z
3630
.number()
3731
.int()

src/common/constants/planAutoRoutingStatus.ts

Lines changed: 0 additions & 4 deletions
This file was deleted.

src/common/types/tasks.test.ts

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -53,36 +53,12 @@ describe("normalizeTaskSettings", () => {
5353
expect(normalized).toEqual(DEFAULT_TASK_SETTINGS);
5454
});
5555

56-
test("preserves explicit planSubagentExecutorRouting values", () => {
56+
test("ignores removed plan subagent handoff settings", () => {
5757
const normalized = normalizeTaskSettings({
58-
planSubagentExecutorRouting: "auto",
59-
});
60-
61-
expect(normalized.planSubagentExecutorRouting).toBe("auto");
62-
expect(normalized.planSubagentDefaultsToOrchestrator).toBe(false);
63-
});
64-
65-
test("migrates deprecated planSubagentDefaultsToOrchestrator when routing is unset", () => {
66-
expect(
67-
normalizeTaskSettings({
68-
planSubagentDefaultsToOrchestrator: true,
69-
}).planSubagentExecutorRouting
70-
).toBe("orchestrator");
71-
72-
expect(
73-
normalizeTaskSettings({
74-
planSubagentDefaultsToOrchestrator: false,
75-
}).planSubagentExecutorRouting
76-
).toBe("exec");
77-
});
78-
79-
test("prefers planSubagentExecutorRouting when both new and deprecated fields are set", () => {
80-
const normalized = normalizeTaskSettings({
81-
planSubagentExecutorRouting: "exec",
58+
planSubagentExecutorRouting: "orchestrator",
8259
planSubagentDefaultsToOrchestrator: true,
8360
});
8461

85-
expect(normalized.planSubagentExecutorRouting).toBe("exec");
86-
expect(normalized.planSubagentDefaultsToOrchestrator).toBe(false);
62+
expect(normalized).toEqual(DEFAULT_TASK_SETTINGS);
8763
});
8864
});

0 commit comments

Comments
 (0)