Skip to content

PRD: Context Trimming Commands (/clear, /compact, /new) #82

@harrryyd

Description

@harrryyd

Problem Statement

Agents accumulate conversation history over many turns. This history consumes the provider context window (Codex has a finite context budget) and eventually causes the agent to lose track of early turns or degrade response quality. Users need a way to prune conversation context without losing their worktree, git history, or the ability to undo changes.

Currently, there is no mechanism to trim context. Users are stuck with ever-growing threads until they manually create a new thread, losing the worktree association and git checkpoint history.

Solution

Three slash commands that give users explicit control over conversation context pruning:

  • /clear [N]: Prune all messages before the last N turns, insert a trim point divider. The surviving last N turns are used to bootstrap a restarted provider session.
  • /compact: Ask the provider to compact (summarize) the full conversation history, then insert a summary banner and trim point divider. The compacted summary bootstraps a restarted provider session.
  • /new: Archive the current thread and create a new one that inherits the worktree, branch, and project — a clean slate with no conversation history.

All three commands restart the provider session rather than pruning messages in-place (per ADR 0003), because provider SDKs maintain their own internal conversation state. Git checkpoints are preserved so users can still revert to filesystem states from pruned turns.

User Stories

  1. As a developer in a long agent session, I want to type /clear 3 to keep only my last 3 turns, so that the agent stops being confused by irrelevant early conversation without losing my worktree state.
  2. As a developer whose agent session is hitting context limits, I want to type /clear (no argument) to wipe all messages and start fresh in the same thread, so that I can continue working on the same branch without creating a new thread.
  3. As a developer who wants to preserve the gist of a long conversation, I want to type /compact to have the agent summarize everything before trimming, so that the bootstrapped session still knows the high-level context of what we were working on.
  4. As a developer who wants a true clean slate, I want to type /new to archive my current thread and create a new one inheriting my worktree, so that I can start fresh without losing my filesystem state.
  5. As a developer reviewing a trimmed thread, I want to see a visual divider (Context Trim Point) marking where the conversation was pruned, so that I know which messages are still in the agent context.
  6. As a developer reading a compacted thread, I want to see the compacted summary above the trim point divider, so that I can quickly understand what was covered before the trim.
  7. As a developer, I want to expand/collapse messages before a trim point divider, so that I can still read the full history when needed but it stays out of the way by default.
  8. As a developer who trimmed context, I want git checkpoints from pruned turns to still be available for thread.checkpoint.revert, so that I can undo to any previous filesystem state even if the messages are hidden.
  9. As a developer, I want to see /clear, /compact, and /new in the slash command autocomplete menu, so that I can discover and use them easily.
  10. As a developer, I want these commands to be blocked while an agent turn is active, so that I don not accidentally disrupt an in-progress generation.
  11. As a developer who accidentally typed /clear, I want the command to not execute if there is an active turn, with a clear indication of why it was blocked.
  12. As a developer using /clear 5, I want only the last 5 turns to be preserved in the bootstrapped session, so that the agent has exactly the context I specified.
  13. As a developer, I want the trim point divider in the UI to show how many messages were pruned, so that I can understand the scope of what was removed from context.
  14. As a developer working across multiple threads, I want compacted threads to show a summary banner that is visually distinct from normal messages, so that I can immediately tell this thread has been compacted.
  15. As a developer, I want multiple trim points to be supported (e.g., clear, work more, then compact), so that the UI handles repeated trimming operations on the same thread.
  16. As a developer archiving a thread with /new, I want the archived thread to remain accessible in the thread list, so that I can still reference its full history if needed.

Implementation Decisions

Command types and payloads

Three new orchestration commands:

thread.context.trim — User-initiated context pruning. Dispatched by both /clear and /compact (after the provider compact call for the latter).

  • keepLastNTurns?: number — when present, preserve the last N turns; when absent, trim everything before the current message
  • summary?: string — compaction summary text (only set when dispatched from /compact)

thread.context.summarize — Records a compaction summary. This is a separate command because /compact is a two-step process: first ask the provider to compact (which updates provider-internal state), then trim the H-code database and insert the summary.

thread.archive-and-new — Archives the current thread and creates a new one inheriting the worktree/branch/project. Dispatched by /new.

Domain events

thread.trim-point-created — Fired when a trim point is applied to a thread:

  • threadId, trimPointId, createdAt
  • beforeEntryId — the ID of the last message/entry before the trim boundary
  • prunedMessageCount — how many messages fall before this trim point
  • prunedTurnIds — the turn IDs whose messages are excluded from context

thread.context-summarized — Fired when a summary is associated with a trim point:

  • threadId, trimPointId, summary, compactDurationMs

thread.archived-and-new-created — Fired when /new is dispatched:

  • archivedThreadId, newThreadId
  • The new thread inherits worktreeId, branchName, projectId from the archived thread

Read model changes

Add to OrchestrationThread:

  • contextTrimPoints: Array<ContextTrimPoint> where ContextTrimPoint has: id, createdAt, beforeEntryId, prunedMessageCount, prunedTurnIds, summary?
  • Trim points are an ordered array (oldest first). The "active context" starts after the latest trim point

No changes needed to OrchestrationMessage — trimming is a read-model concern. Messages before the latest trim point are excluded from deriveTimelineEntries (or included with a trimmed: true flag for the expandable section).

Provider session restart

Per ADR 0003, trim operations restart the provider session:

  1. Client dispatches thread.context.trim
  2. Decider emits thread.trim-point-created
  3. A reactor (or the decider case itself via thread.session.stop) stops the current provider session
  4. A new provider session is started with a resume cursor pointing past the trim boundary
  5. For /compact: the provider compaction is requested first (via thread/compact/start or similar JSON-RPC call), and the resulting summary is recorded via thread.context.summarize

The session restart flow reuses the existing thread.session.stopstartSession pipeline in ProviderService, but needs a new bootstrap mechanism: for /clear N, the bootstrap messages are the last N turns; for /compact, the bootstrap is the summary text; for /new, there is no bootstrap.

Slash command registration

Add to builtInSlashCommandItems in ChatComposer.tsx:

  • /clear [N] — dispatches thread.context.trim with keepLastNTurns if N is provided
  • /compact — dispatches thread.context.trim (with two-phase compaction first)
  • /new — dispatches thread.archive-and-new

Add /clear, /compact, /new to parseStandaloneComposerSlashCommand() regex.

These commands are standalone-only (no combined message). If there is trailing text after the command, the entire input is the command. These are blocked during active turns.

UI components

ContextTrimPointDivider: A horizontal divider bar with:

  • "Earlier messages" label
  • Hidden message count (e.g., "47 messages pruned")
  • Expand/collapse chevron

ContextSummaryBanner: A callout card above the trim divider showing:

  • "Context compacted" header
  • The summary text

Collapsed message section: Messages and activities before the oldest non-expanded trim point are hidden. Expanding shows them in a visually distinct (dimmed, collapsed-by-default) section.

Session bootstrap after trim

When restarting the session after a trim:

  • The resume cursor targets the first message after the latest trim point
  • For /clear N: the last N turns are sent as the initial conversation context
  • For /compact: the summary text is sent as a system message or initial context
  • For /new: a fresh session with no prior context

Testing Decisions

What makes a good test

  • Test external behavior through the CQRS command pipeline, not internal implementation
  • Verify domain events are produced with correct payloads
  • Verify the read model reflects trim points correctly
  • Verify timeline derivation produces the right entries with trim points
  • Verify session stop events fire when trim commands are dispatched

Modules to test

  1. Command dispatch pipeline — dispatch thread.context.trim, verify thread.trim-point-created event with correct payloads. Verify decider rejects trim when thread does not exist or turn is active.
  2. Projector — apply thread.trim-point-created to a thread with known messages, verify contextTrimPoints array is populated correctly and timeline derivation excludes trimmed messages.
  3. Slash command parsing — verify /clear, /clear 3, /compact, /new parse correctly and dispatch the right orchestration commands.
  4. Session lifecycle — verify that thread.context.trim causes a thread.session-stop-requested event to fire (per ADR 0003).
  5. Timeline rendering — given a thread with trim points from orchestration.subscribeThread, verify deriveTimelineEntries() produces trim entries and the UI shows the divider.
  6. Checkpoint preservation — after a trim, verify that thread.checkpoint.revert can still restore to a pruned turn checkpoint.

Prior art for tests

  • Existing CQRS tests in the codebase test command → event flows through the decider and projector
  • ChatView.test.tsx tests the composer and slash command handling
  • session-logic.test.ts tests timeline derivation

Out of Scope

  • Automatic/background compaction (provider-triggered thread.state.changed with state: "compacted" is already observed but only as an activity — it does not create trim points)
  • Combined command + message syntax (e.g., /clear 3 fix the login page) — all three commands are standalone-only
  • Custom compaction prompts or summary style configuration
  • Trim point cleanup of git checkpoints (checkpoints are explicitly preserved)
  • Undo of trim operations (revert already works via thread.checkpoint.revert which prunes trim points beyond the revert boundary)
  • Trim operations during an active turn (explicitly blocked)

Further Notes

  • The term "Context Trim Point" and "Context Summary" are defined in CONTEXT.md as canonical domain terms
  • ADR 0003 (docs/adr/0003-context-trim-restarts-session.md) establishes the architectural decision that trim operations restart the provider session
  • Multiple trim points per thread are supported — each /clear or /compact creates a new trim point after the previous one
  • The existing context-compaction activity (from provider auto-compaction) is informational only and does not interact with user-initiated trims
  • The RuntimeThreadState "compacted" value is set by the provider and is orthogonal to H-code trim points
  • Thread ID is preserved through /clear and /compact so worktree/branch bindings survive
  • /new creates a new thread ID but inherits worktree/branch/project so the user continues working on the same files

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions