Skip to content

Conversation

@hannesrudolph
Copy link
Collaborator

@hannesrudolph hannesrudolph commented Jan 21, 2026

Summary

This PR implements a "Fresh Start Model" for context condensation - a fundamental architectural change where summary messages transition from assistant role to user role. This simplifies the condensation architecture by eliminating several workarounds while introducing targeted mechanisms for command preservation and orphan cleanup.

What Changed

Fresh Start Model

The core change: summary messages now have role: "user" instead of role: "assistant".

Before:

  • Summary was an assistant message inserted before kept messages
  • Required synthetic reasoning blocks for DeepSeek-reasoner compatibility
  • Preserved tool_use blocks to maintain pairing with subsequent tool_results
  • Kept trailing N messages after summary

After:

  • Effective API history contains only the summary (true fresh start) after a condensing event
  • Summary is a user message placed at the end of message history
  • No reasoning blocks needed (user messages don't require them)
  • No tool_use preservation needed

Command Block Preservation

New extractCommandBlocks() function extracts <command> XML blocks from the original task and injects them into summaries wrapped in <system-reminder> tags. This ensures workflow directives (like /prr #123) survive multiple condensation cycles.

Orphan Tool Result Filtering

getEffectiveApiHistory() now filters orphan tool_result blocks that reference tool_use IDs from condensed-away messages. This prevents API validation errors that would occur when tool_result blocks have no matching tool_use.

Consecutive User Message Merging

New mergeConsecutiveApiMessages() utility non-destructively merges consecutive user messages for API requests only. This handles the case where a user summary followed by a user message would create invalid consecutive same-role messages.

Slash Command Help Separation

parseMentions() now returns slashCommandHelp separately from the main text. This allows selective extraction of command blocks during condensation while keeping the user's actual message content clean.

Improved Error Handling

  • Error details now include both message and details fields (JSON structure)
  • CondensationErrorRow uses shared ErrorRow component with copy functionality
  • New i18n string condense_api_failed for API error messages

Restructured CONDENSE Prompt

Completely rewritten with a highly structured XML format:

<summary>
1. Primary Request and Intent
2. Key Technical Concepts
3. Files and Code Sections (with snippets)
4. Errors and fixes (with user feedback)
5. Problem Solving
6. All user messages (non-tool-result)
7. Pending Tasks
8. Current Work (precise description)
9. Optional Next Step
</summary>

Breaking Changes

  • Summary messages now have role: "user" instead of role: "assistant"
  • Effective API history after condensation contains only the summary (no trailing messages)
  • N_MESSAGES_TO_KEEP constant removed
  • Summary timestamp now uses lastMsgTs + 1 instead of firstKeptTs - 1

Why This Approach

Problem Solution
DeepSeek reasoning requirement forced complex assistant summaries User role doesn't need reasoning blocks
Tool_use/tool_result pairing complex across condensations User summaries don't need tool_use; orphan cleanup handles residuals
Workflow directives lost after condensation extractCommandBlocks() preserves <command> blocks
Consecutive user messages cause API errors mergeConsecutiveApiMessages() for API requests only
Important information lost during summarization Highly structured prompt with explicit sections
Error details not copyable JSON structure with ErrorRow component integration

Dependency Graph

Fresh Start Model (summary = user role)
       │
       ├── No reasoning blocks needed
       ├── No tool_use preservation needed
       │
       ├── extractCommandBlocks() → Preserves workflow directives
       │
       ├── getEffectiveApiHistory() → Orphan tool_result filtering
       │
       ├── mergeConsecutiveApiMessages() → Handles consecutive user messages
       │
       ├── slashCommandHelp separation → Enables command extraction
       │
       ├── Structured CONDENSE prompt → Better information preservation
       │
       └── JSON error structure → ErrorRow component integration

Important

Transition summary messages from assistant to user role for context condensation, simplifying architecture and improving functionality.

  • Behavior:
    • Summary messages now have role: "user" instead of role: "assistant".
    • Effective API history contains only the summary after a condensing event.
    • New extractCommandBlocks() function to preserve <command> blocks in summaries.
    • getEffectiveApiHistory() filters orphan tool_result blocks.
    • mergeConsecutiveApiMessages() utility merges consecutive user messages for API requests.
  • Tests:
    • Updates to condense.spec.ts and index.spec.ts to reflect new behavior.
    • New tests for extractCommandBlocks() and mergeConsecutiveApiMessages().
  • Misc:
    • N_MESSAGES_TO_KEEP constant removed.
    • Summary timestamp now uses lastMsgTs + 1 instead of firstKeptTs - 1.

This description was created by Ellipsis for 7a361ae. You can customize this summary. It will automatically update as commits are pushed.

@roomote
Copy link
Contributor

roomote bot commented Jan 21, 2026

Oroocle Clock   Follow along on Roo Cloud

Re-review in progress for 7a361ae5b4e69a6a74f07489684d34e4a36e507d.

  • Fix CONDENSE support prompt section numbering: previously "6." appeared twice (All user messages vs Pending Tasks).
  • Condense: finalRequestMessage.content ignores customCondensingPrompt (custom prompt not actually sent as the summarization request content).
  • Condense: summarizeConversation() returns errorDetails, but Task.condenseContext() drops it from the UI error path (fix-id: ab3a636175).
Previous reviews

Mention @roomote in a comment to request specific changes to this pull request or fix all unresolved issues.

Copy link
Contributor

@roomote roomote bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

  • Condense is now non-destructive: older messages are tagged with condenseParent and filtered via getEffectiveApiHistory(), while rewind/edit semantics keep the original message boundaries.
  • New API-only shaping merges consecutive user turns via mergeConsecutiveApiMessages() to reduce token overhead without mutating stored history.
  • Slash-command mention parsing now returns slashCommandHelp separately and processUserContentMentions() appends it as a dedicated text block (instead of mixing it into the main user text).

Tested

  • cd src && npx vitest run core/task/tests/mergeConsecutiveApiMessages.spec.ts core/condense/tests/condense.spec.ts core/condense/tests/index.spec.ts core/condense/tests/rewind-after-condense.spec.ts core/mentions/tests/processUserContentMentions.spec.ts tests/command-mentions.spec.ts core/webview/tests/webviewMessageHandler.delete.spec.ts
  • cd apps/web-roo-code && npm run lint && npm run check-types
  • cd apps/web-evals && npm run lint && npm run check-types && npx vitest run

@hannesrudolph hannesrudolph force-pushed the fix/condense-rework branch 2 times, most recently from bb3fb35 to 9ea6b3f Compare January 22, 2026 01:02
@hannesrudolph hannesrudolph changed the base branch from main to fix/migrate-condensing-prompt January 22, 2026 01:02
@RooCodeInc RooCodeInc deleted a comment from github-actions bot Jan 22, 2026
Base automatically changed from fix/migrate-condensing-prompt to main January 22, 2026 02:33
@hannesrudolph hannesrudolph changed the base branch from main to fix/openai-native-double-emission January 22, 2026 05:21
Base automatically changed from fix/openai-native-double-emission to main January 22, 2026 06:22
@hannesrudolph hannesrudolph force-pushed the fix/condense-rework branch 2 times, most recently from f56bced to dca5f66 Compare January 22, 2026 20:45
…iveApiHistory

- Update comment to accurately describe variable content structure (optional command block)
- Remove redundant O(n*m) loop that duplicated the .has() check for condenseParent filtering
…clearer sections

- Add <analysis> step before summary for structured thinking
- Restructure sections for clarity and completeness
- Add new sections: 'Errors and fixes', 'All user messages'
- Separate 'Pending Tasks' and 'Optional Next Step' sections
- Add example output formatting with XML-style tags
- Include instructions for handling additional summarization instructions
…oduction

Replace task-specific summarization instruction with a more neutral AI
assistant prompt that describes the assistant's role for summarization.
- Fix orphan tool_result filtering after fresh start condensation
- Add CRITICAL instructions to SUMMARY_PROMPT and CONDENSE template to prevent tool calls during condensing
- Inject synthetic tool_results for orphan tool_calls before condensing
- Pass tools/metadata to condensation API call
- Use standard ErrorRow with Details button for condensation errors
- Remove stack trace from error details
- Separate summary content into multiple text blocks
- Update tests for fresh start model behavior
- Add missing await to this.say() in condenseContext() error path to
  prevent race conditions with persistence/UI updates
- Update comment at line 4075 to accurately reflect that
  mergeConsecutiveApiMessages() excludes summary messages
The mergeConsecutiveApiMessages function now allows merging a regular
user message that follows a summary message. Since this is API-only
shaping (storage is unaffected), rewind semantics remain intact.

- Remove !prev.isSummary guard to allow merge INTO summary
- Keep !msg.isSummary guard to prevent merging a summary INTO something
- Update tests to reflect new expected behavior
@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Jan 23, 2026
Remove unused functions and types that were part of the old condensation model:
- getKeepMessagesWithToolBlocks function
- KeepMessagesResult type
- N_MESSAGES_TO_KEEP constant
- hasToolResultBlocks helper
- getToolResultBlocks helper
- findToolUseBlockById helper
- getReasoningBlocks helper

These were relics of the old model that kept trailing N messages after the summary.
The fresh start model no longer needs them.
- Remove unnecessary JSON encoding in Task.ts when calling say() for
  condense_context_error - now passes error message directly
- Simplify CondensationErrorRow component: remove JSON parsing logic and
  ErrorRow dependency, just display errorText directly with inline styling
- Reduces code complexity while maintaining the same user-facing behavior
cost,
newContextTokens = 0,
error,
errorDetails,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Task.condenseContext(), summarizeConversation() returns errorDetails, but the error path drops it and only calls say("condense_context_error", error). This effectively hides the detailed API diagnostics (status/body/stack) that were intentionally captured, making condense failures much harder to debug.

Fix it with Roo Code or mention @roomote and request a fix.

When a user sets a custom condensing prompt, it should control what
instructions are sent to the model. Previously, the code used the
custom prompt only for the system prompt while the user message
content was hardcoded to supportPrompt.default.CONDENSE.

Now the custom prompt (or default CONDENSE) is used as the
finalRequestMessage content, ensuring user customization is respected.

Updated tests to verify the custom prompt appears in the user message.
@hannesrudolph
Copy link
Collaborator Author

Yes, this was an unintentional bug. I've pushed a fix (commit 7a361ae) that now properly uses the customCondensingPrompt (or defaults to supportPrompt.default.CONDENSE) for the user message content. This ensures user customization is respected when they set a custom condensing prompt.

Before: The custom prompt was only used for the system prompt, while the user message was hardcoded to supportPrompt.default.CONDENSE.

After: The custom prompt (or default) is now used as the finalRequestMessage.content, so users can fully customize the summarization instructions sent to the model.

cost,
newContextTokens = 0,
error,
errorDetails,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Task.condenseContext(), summarizeConversation() now returns errorDetails, but the error branch drops it and only displays error. This makes condense failures harder to debug; consider threading errorDetails into the UI payload (e.g., include it in the condense_context_error message so the webview can show/copy it).

Fix it with Roo Code or mention @roomote and request a fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement New feature or request lgtm This PR has been approved by a maintainer size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

Status: Triage

Development

Successfully merging this pull request may close these issues.

3 participants