Skip to content

Commit 6e2c2b6

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 6e2c2b6

18 files changed

Lines changed: 116 additions & 1439 deletions

File tree

docs/AGENTS.md

Lines changed: 2 additions & 1 deletion
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
@@ -69,7 +70,7 @@ Core workflow:
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.
7172
- 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.
73+
- In Orchestrator mode, 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.

docs/agents/index.mdx

Lines changed: 2 additions & 2 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`.

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
});

src/common/types/tasks.ts

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
import type {
2-
PlanSubagentExecutorRouting,
3-
TaskSettings as TaskSettingsOnDisk,
4-
} from "@/common/config/schemas/taskSettings";
1+
import type { TaskSettings as TaskSettingsOnDisk } from "@/common/config/schemas/taskSettings";
52
import {
63
SYSTEM1_BASH_OUTPUT_COMPACTION_LIMITS,
74
TASK_SETTINGS_LIMITS,
@@ -13,7 +10,7 @@ import type {
1310
import assert from "@/common/utils/assert";
1411
import { coerceThinkingLevel, type ThinkingLevel } from "./thinking";
1512

16-
export type { PlanSubagentExecutorRouting, SubagentAiDefaults, SubagentAiDefaultsEntry };
13+
export type { SubagentAiDefaults, SubagentAiDefaultsEntry };
1714
export {
1815
SYSTEM1_BASH_OUTPUT_COMPACTION_LIMITS,
1916
TASK_SETTINGS_LIMITS,
@@ -29,8 +26,6 @@ export const DEFAULT_TASK_SETTINGS: TaskSettings = {
2926
maxParallelAgentTasks: TASK_SETTINGS_LIMITS.maxParallelAgentTasks.default,
3027
maxTaskNestingDepth: TASK_SETTINGS_LIMITS.maxTaskNestingDepth.default,
3128
proposePlanImplementReplacesChatHistory: false,
32-
planSubagentExecutorRouting: "auto",
33-
planSubagentDefaultsToOrchestrator: false,
3429

3530
bashOutputCompactionMinLines:
3631
SYSTEM1_BASH_OUTPUT_COMPACTION_LIMITS.bashOutputCompactionMinLines.default,
@@ -84,12 +79,6 @@ function clampInt(value: unknown, fallback: number, min: number, max: number): n
8479
return rounded;
8580
}
8681

87-
export function isPlanSubagentExecutorRouting(
88-
value: unknown
89-
): value is PlanSubagentExecutorRouting {
90-
return value === "exec" || value === "orchestrator" || value === "auto";
91-
}
92-
9382
export function normalizeTaskSettings(raw: unknown): TaskSettings {
9483
const record = raw && typeof raw === "object" ? (raw as Record<string, unknown>) : ({} as const);
9584

@@ -111,28 +100,6 @@ export function normalizeTaskSettings(raw: unknown): TaskSettings {
111100
? record.proposePlanImplementReplacesChatHistory
112101
: (DEFAULT_TASK_SETTINGS.proposePlanImplementReplacesChatHistory ?? false);
113102

114-
const normalizedPlanSubagentExecutorRouting = isPlanSubagentExecutorRouting(
115-
record.planSubagentExecutorRouting
116-
)
117-
? record.planSubagentExecutorRouting
118-
: undefined;
119-
120-
const migratedPlanSubagentExecutorRouting =
121-
normalizedPlanSubagentExecutorRouting ??
122-
(typeof record.planSubagentDefaultsToOrchestrator === "boolean"
123-
? record.planSubagentDefaultsToOrchestrator
124-
? "orchestrator"
125-
: "exec"
126-
: undefined);
127-
128-
const planSubagentExecutorRouting =
129-
migratedPlanSubagentExecutorRouting ??
130-
DEFAULT_TASK_SETTINGS.planSubagentExecutorRouting ??
131-
"exec";
132-
133-
// Keep the deprecated boolean in sync for downgrade compatibility.
134-
const planSubagentDefaultsToOrchestrator = planSubagentExecutorRouting === "orchestrator";
135-
136103
const bashOutputCompactionMinLines = clampInt(
137104
record.bashOutputCompactionMinLines,
138105
SYSTEM1_BASH_OUTPUT_COMPACTION_LIMITS.bashOutputCompactionMinLines.default,
@@ -168,8 +135,6 @@ export function normalizeTaskSettings(raw: unknown): TaskSettings {
168135
maxParallelAgentTasks,
169136
maxTaskNestingDepth,
170137
proposePlanImplementReplacesChatHistory,
171-
planSubagentExecutorRouting,
172-
planSubagentDefaultsToOrchestrator,
173138
bashOutputCompactionMinLines,
174139
bashOutputCompactionMinTotalBytes,
175140
bashOutputCompactionMaxKeptLines,
@@ -191,16 +156,6 @@ export function normalizeTaskSettings(raw: unknown): TaskSettings {
191156
"normalizeTaskSettings: proposePlanImplementReplacesChatHistory must be a boolean"
192157
);
193158

194-
assert(
195-
isPlanSubagentExecutorRouting(planSubagentExecutorRouting),
196-
"normalizeTaskSettings: planSubagentExecutorRouting must be exec, orchestrator, or auto"
197-
);
198-
199-
assert(
200-
typeof planSubagentDefaultsToOrchestrator === "boolean",
201-
"normalizeTaskSettings: planSubagentDefaultsToOrchestrator must be a boolean"
202-
);
203-
204159
assert(
205160
Number.isInteger(bashOutputCompactionMinLines),
206161
"normalizeTaskSettings: bashOutputCompactionMinLines must be an integer"

0 commit comments

Comments
 (0)