Skip to content

Reduce xterm.js scrollback for background terminals to reclaim memory #3281

@gregpriday

Description

@gregpriday

Summary

Background terminals — agent panels in inactive worktrees or panels not currently visible — hold their full scrollback buffer in memory indefinitely, even though they are not being viewed. Automatically reducing the scrollback limit for terminals in lower renderer tiers would free substantial memory in proportion to how many panels are backgrounded.

Problem Statement

Canopy's TerminalRendererPolicy already classifies terminals into FOCUSED, VISIBLE, and BACKGROUND tiers with hysteresis-debounced transitions. However, tier changes currently only affect rendering behavior and PTY activity signaling — they do not adjust the xterm.js scrollback buffer.

A backgrounded terminal retains its full allocated buffer (up to 10,000 lines for agents) even though no user is viewing it. When a user has 20 agent panels across several worktrees, only a handful are ever visible at once, yet all 20 hold full-size buffers. The memory is entirely idle.

xterm.js supports dynamic scrollback adjustment at runtime: setting terminal.options.scrollback to a lower value immediately trims the oldest lines from the circular buffer, freeing memory synchronously. The reverse — increasing the limit — expands capacity for new incoming lines without restoring lost history.

The tier system in TerminalRendererPolicy.applyRendererPolicyImmediate is the natural integration point. It already distinguishes "active" vs "background" backend states and triggers wake/restore cycles on tier upgrade. Scrollback adjustment fits this pattern.

Desired Behavior

When a terminal transitions to the BACKGROUND tier and remains there past the existing downgrade hysteresis window, its xterm.js scrollback should shrink to a lower limit, freeing the buffer memory it no longer needs. When the terminal is brought back to an active tier, its scrollback limit should expand again to accommodate new output.

The transition must be safe:

  • If the user is currently scrolled up in the terminal's history, the reduction should be deferred or skipped to avoid a jarring scroll snap to the bottom
  • If there is an active text selection, the reduction should be deferred to prevent selection index corruption
  • The alternate buffer (vim, less, top) is unaffected — xterm.js enforces scrollback: 0 for alternate buffers regardless

The goal is transparent to the user: background terminals quietly use less memory; when the user switches back, the terminal continues streaming live output normally within the restored limit.

Context

Relevant infrastructure already in place:

  • TerminalRendererPolicy — tier management with debounced downgrade and onTierApplied callback hook
  • src/utils/scrollbackConfig.ts — type-specific policies with multipliers and limits; PERFORMANCE_MODE_SCROLLBACK = 100 shows the precedent for policy-driven scrollback reduction
  • src/store/slices/terminalRegistry/index.ts#L304-L322 — where the initial effective scrollback is computed from user settings and terminal type
  • The ManagedTerminal type holds the live xterm.js terminal instance, making it accessible to policy decisions

Acceptance Criteria

  • Terminals that have been in BACKGROUND tier past the hysteresis window have a reduced xterm.js scrollback compared to their configured limit
  • Terminals that return to an active tier have their scrollback limit restored to the configured value, so new output fills up to the full allowed buffer
  • A terminal whose user is actively scrolled up in history is not trimmed while the user is reading it
  • A terminal with an active text selection is not trimmed until the selection is cleared
  • The alternate buffer (vim, less, top, etc.) is unaffected
  • No visible jank, snap-to-bottom, or selection glitches during normal panel-switching workflows
  • Agent state detection continues to function correctly (the detection window is the last ~50 lines, well within any reasonable background minimum)

Edge Cases & Risks

  • Rapid worktree switching could cause many terminals to cycle through tiers quickly; the existing TIER_DOWNGRADE_HYSTERESIS_MS debounce already mitigates this but the scrollback changes should not add latency to tier upgrades
  • The background scrollback minimum must be high enough that new output from a still-running agent isn't immediately lost — the terminal keeps receiving data even while backgrounded
  • History visible to the user when they switch to a background terminal will only extend back to what fit in the reduced buffer; this is a known tradeoff of trimming-based approaches

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions