Skip to content

Conversation

@ThomasK33
Copy link
Member

@ThomasK33 ThomasK33 commented Dec 10, 2025

Summary

Optimizes context usage in plan mode by:

  1. Removing redundant planContent from propose_plan tool results (plan is already visible via file_edit_* diffs)
  2. Including the full plan in the mode transition message when switching plan → exec

Changes

  • propose_plan tool: No longer returns planContent in the result, saving context during iterative planning sessions
  • Mode transition (plan → exec): Now includes the full plan with soft framing: "evaluate whether it's relevant to the user's request"
  • UI: ProposePlanToolCall fetches content on-demand for the latest plan; shows path info for historical plans without embedded content
  • Backwards compatibility: Old chat history with planContent in results still renders correctly

Context Flow

Phase Before After
During planning Plan in file_edit_* diffs + full plan in propose_plan result Plan in file_edit_* diffs only
On mode switch Generic "mode switched" message Full plan with "evaluate relevance" framing

Testing

  • Added 4 new tests for plan content in mode transitions
  • All existing tests pass

Plan

Plan: Context-Efficient Plan Mode

Summary

Improve the plan mode → exec mode transition to include the approved plan content in the model's context only when relevant, avoiding redundant context when the plan is already visible in conversation history or no plan was created.

Current Behavior

How Plan Mode Works Now

  1. System Prompt: When in plan mode, getPlanModeInstruction() adds instructions telling the model to write its plan to ~/.mux/plans/{workspaceId}.md

  2. propose_plan Tool: When called, reads the plan file from disk and returns:

    { success: true, planPath, planContent, message: "Plan proposed. Waiting for user approval." }

    This content is stored in the tool result in chat history — this is redundant since the plan was already written via file_edit_* calls.

  3. Mode Transition: When switching from plan → exec, injectModeTransition() inserts a synthetic user message:

    [Mode switched from plan to exec. Follow exec mode instructions. Available tools: file_read, bash, ...]
    

Key Observation

The plan content is duplicated in multiple places:

  1. file_edit_* tool calls - The actual writes/edits to the plan file (as diffs)
  2. propose_plan tool result - The full plan content (redundant!)
  3. Not included - The mode transition message when switching to exec

For iterative planning sessions, this means the plan content appears multiple times, wasting context:

  • Each plan revision has file_edit_* diffs ✓ (necessary, minimal)
  • Each propose_plan call duplicates the full content ✗ (redundant)
  • Final plan isn't surfaced when switching to exec ✗ (missing)

Problem Statement

When the user switches from plan mode to exec mode (implicitly by changing the mode selector), the model:

  1. Doesn't receive explicit confirmation that the plan was approved
  2. May not realize the plan from earlier in the conversation is what it should execute
  3. Has to infer relevance from context rather than being told explicitly

Proposed Solution

Enhance the mode transition injection to optionally include the approved plan content when switching from plan → exec mode.

Design Principles

  1. Only include plan when relevant - The model should determine if the plan applies to the current user message
  2. Avoid redundancy - Don't include plan if it's already visible in recent context
  3. Implicit approval - Mux's mode switching is implicit (no explicit approve/reject), so we frame it as "plan available for reference"
  4. Graceful fallback - If no plan file exists, proceed without it

Implementation

1. Modify injectModeTransition() to accept plan content (~20 LoC)

File: src/browser/utils/messages/modelMessageTransform.ts

Add an optional planContent parameter that gets included when transitioning plan → exec:

export function injectModeTransition(
  messages: MuxMessage[],
  currentMode?: string,
  toolNames?: string[],
  planContent?: string  // NEW: optional plan content for plan→exec transition
): MuxMessage[] {
  // ... existing logic ...

  // If transitioning from plan to exec AND plan content provided
  if (lastMode === "plan" && currentMode === "exec" && planContent) {
    transitionText += `

The following plan was developed in plan mode. Evaluate whether it's relevant to the user's request. If relevant, use it to guide your implementation:

<approved-plan>
${planContent}
</approved-plan>`;
  }

  // ... rest of function ...
}

2. Read plan content during stream preparation (~15 LoC)

File: src/node/services/aiService.ts

Before calling injectModeTransition, check if we're transitioning plan→exec and read the plan file:

// In prepareStream(), around line 959:
let planContentForTransition: string | undefined;
if (mode === "exec") {
  // Check if last assistant message was in plan mode
  const lastAssistantMessage = [...filteredMessages].reverse().find(m => m.role === "assistant");
  if (lastAssistantMessage?.metadata?.mode === "plan") {
    // Read plan file for transition context
    const planFilePath = getPlanFilePath(workspaceId);
    try {
      planContentForTransition = await readFileString(runtime, planFilePath);
    } catch {
      // No plan file, proceed without
    }
  }
}

const messagesWithModeContext = injectModeTransition(
  messagesWithSentinel,
  mode,
  toolNamesForSentinel,
  planContentForTransition  // NEW parameter
);

3. Update test coverage (~40 LoC)

File: src/browser/utils/messages/modelMessageTransform.test.ts

Add tests for:

  • Plan content included when transitioning plan→exec with plan content
  • Plan content NOT included when transitioning exec→plan
  • Plan content NOT included when no plan content provided
  • Plan content NOT included when staying in same mode

Alternative Considered: Include in System Prompt

We could add the plan content to the system prompt during exec mode. However:

  • This would bust the system message cache on every plan change
  • Less contextually appropriate (plans are conversation-specific, not workspace-wide)
  • Mode transition injection is already the established pattern for mode-switching context

Edge Cases

  1. No plan file exists: Skip including plan content, use existing transition message
  2. Empty plan file: Treat same as no plan
  3. Plan from previous conversation: If user switches modes without a plan in current conversation, no plan content is included
  4. Very long plans: Consider truncating after N characters with "... (plan truncated, see ~/.mux/plans/...)"

4. Remove planContent from propose_plan tool result (~5 LoC)

File: src/node/services/tools/propose_plan.ts

The tool currently returns the full plan content in the result. Since:

  • The plan is already in history via file_edit_* tool calls (as diffs)
  • The plan will be included in the mode transition message when switching to exec

We can exclude it from the tool result to save context during iterative planning:

// Before:
return {
  success: true as const,
  planPath,
  planContent,  // REMOVE THIS
  message: "Plan proposed. Waiting for user approval.",
};

// After:
return {
  success: true as const,
  planPath,
  message: "Plan proposed. Waiting for user approval.",
};

5. Update ProposePlanToolCall UI to fetch content on demand (~10 LoC)

File: src/browser/components/tools/ProposePlanToolCall.tsx

The UI component already has logic to fetch fresh content via getPlanContent for the latest plan. We need to ensure it falls back to this API call when planContent is not in the result:

// The component already handles this case - when isLatest is true AND freshContent is fetched
// For historical plans (not latest), we can either:
// 1. Fetch on demand when expanded (lazy load)
// 2. Show "Plan content not available" with option to fetch

Since the UI already prioritizes freshContent from disk for the latest plan, this mostly works. For historical plans, we should show a minimal message indicating the plan exists at the path.

Estimated LoC Changes

File Change LoC
modelMessageTransform.ts Add planContent parameter +20
aiService.ts Read plan on transition +15
propose_plan.ts Remove planContent from result -3
ProposePlanToolCall.tsx Handle missing planContent +10
modelMessageTransform.test.ts New test cases +40
Total ~82

Design Decisions

  1. No truncation - Include the full plan content. Can revisit if context limits become an issue.
  2. No "outdated" marking - The existing file change notification system already handles external edits with diffs.
  3. Soft framing - Use "developed in plan mode, evaluate relevance" rather than "approved" since Mux's approval is implicit.

Generated with mux

@ThomasK33 ThomasK33 force-pushed the context-efficient-plan-mode branch 3 times, most recently from ed7a889 to 11b52a4 Compare December 10, 2025 11:12
- Remove planContent from propose_plan tool result to avoid redundant
  context (plan is already visible via file_edit_* diffs in history)
- Include full plan content in mode transition message when switching
  from plan → exec mode, with soft framing asking model to evaluate
  relevance
- Update ProposePlanToolCall UI to handle missing planContent with
  backwards compatibility for old chat history
- Add tests for plan content in mode transitions

Change-Id: I3d8ecdbc01805c229a73c1bf0afa9bc9fb93a5f9
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33 ThomasK33 force-pushed the context-efficient-plan-mode branch from 11b52a4 to 2da9ed1 Compare December 10, 2025 12:28
@ThomasK33 ThomasK33 added this pull request to the merge queue Dec 10, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Dec 10, 2025
@ThomasK33 ThomasK33 added this pull request to the merge queue Dec 10, 2025
Merged via the queue into main with commit 385830d Dec 10, 2025
20 checks passed
@ThomasK33 ThomasK33 deleted the context-efficient-plan-mode branch December 10, 2025 15:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant