Skip to content

Session Compaction

Session compaction is BMO’s foreground context-window recovery path. When a live session approaches the model’s context limit, BMO compacts the conversation so active work continues without losing bounded continuity state — and never trusts a summary it has not first proven safe.

This page is about the foreground session-recovery meaning of compaction. For idle-gated background upkeep, see Compaction (idle-gated automation). The two are distinct: session compaction runs inline against the run you are in right now, driven by prompt-budget pressure; the compaction swarm is idle-gated work that never competes with a foreground turn.

flowchart TD
    pressure["Prompt budget pressure"] --> mech["Phase 1 - mechanical summary (instant, no LLM)"]
    mech --> trusted["Trusted continuation source"]
    mech --> shadow["Derive retention shadow (load-bearing facts)"]
    mech -. optional .-> sem["Phase 2 - semantic summary (background LLM)"]
    sem --> gate{"Retention quality gate"}
    shadow --> gate
    gate -->|all probes survive| swap["Commit-safe hot-swap"]
    gate -->|drop / truncate / mismatch| reject["Reject - keep mechanical"]
    swap --> posture["Bounded posture and reason codes"]
    reject --> posture
    trusted --> posture
    posture --> readouts["session status, describe_context, app read model"]

Use session compaction when a long-running conversation is approaching the model’s context window and you want BMO to preserve continuity instead of failing on length alone. It is most valuable on:

  • long implementation sessions that accumulate many tool calls and file edits
  • sessions under provider pressure where the prompt budget is tightening
  • work where dropping a single load-bearing fact (an active goal, a permission boundary, an open file path) would silently corrupt the continuation

It does not have to be invoked manually; the auto-summarize trigger fires near the limit when enabled.

Session compaction is a two-phase model. The first phase is always trusted; the second must earn its place.

Phase 1 — mechanical (always the trusted continuation source)

Section titled “Phase 1 — mechanical (always the trusted continuation source)”

The mechanical phase is instant, deterministic, and uses no LLM. It extracts structured facts directly from the transcript — file paths, errors, user goals, instructions, provider pressure, decision state, validation results, and tool-call summaries — and keeps a verbatim window of the most recent turns. Because it is deterministic it is always available and always safe, so BMO treats it as the trusted continuation source and measures every later decision against it.

Phase 2 — semantic (optional, must earn trust)

Section titled “Phase 2 — semantic (optional, must earn trust)”

The semantic phase is an optional background LLM summary that runs after Phase 1, inline or on a background goroutine. Only one runs at a time: a newer compaction cancels an in-flight one, and stale results are rejected by a monotonic version guard. A fluent semantic summary is never trusted on appearance alone — it must pass the retention quality gate before it can replace the mechanical summary.

Phase 2 is validated against a bounded retention shadow of load-bearing facts derived from the mechanical summary. There are nine fact classes: active_goal, user_constraint, permission_boundary, active_path, tool_outcome, provider_pressure, decision_state, validation_result, and summary_link.

The policy is two-tier:

  • Majority classesactive_goal and active_path — require at least 50% coverage and are paraphrase-tolerant, so reworded but faithful summaries pass.
  • Fail-closed classes — every other class — must survive in full. A single dropped load-bearing fact rejects the semantic summary.

Other rejection triggers are equally strict: empty content, a missing or max_tokens stop reason (semantic_truncated), a turn-range mismatch, a zero token count, or a retention ratio greater than 1.0.

If the semantic summary is unsafe or unclassifiable, BMO keeps the mechanical summary. This is intended behavior: fluent semantic output can still be rejected. A summary that reads well but quietly dropped a permission boundary is more dangerous than a terse mechanical one, so the gate fails closed.

When a semantic summary does pass, the swap is peek → persist → commit. BMO peeks the ready semantic content and its version without consuming it, updates the linked summary message, and only commits the swap if the version still matches. A failed persist falls back to the mechanical summary rather than losing both. If the summary-message link is missing, the swap is deferred.

A burn-rate signal can trigger an early Phase-1 pass before the hard limit is reached. It fires at a usage-ratio threshold, is guarded against re-entry with a ~30-second cooldown, and is staged by turn-range so it compacts at a clean boundary rather than mid-turn.

Every read surface projects a bounded posture vocabulary and bounded reason codes. Raw transcript content and provider response bodies never leak through these surfaces — posture evidence is metadata-only. A broken summary link is surfaced before any streaming phase, so you learn about a continuity gap up front instead of mid-stream.

Posture is read through describe_context / the Context hub, bmo session status, and the app read model. All three share one vocabulary; see Session compaction parity for the full list of states and reason codes.

No single TUI screen renders the whole posture vocabulary at once — posture is a per-session projection, and any given session sits in exactly one state. The readout below is one real instance captured from the running CLI rather than a diagram of the taxonomy: bmo session status prints the bounded posture line for a live session (here the resting idle state, before any prompt-budget pressure). Every other state in the vocabulary prints on this same line; read it alongside the full list in Session compaction parity.

Live session compaction posture
Live, deterministic posture readout: bmo session status for a real BMO session prints the bounded compaction posture line — here the resting idle (no_active_compaction) state, before prompt-budget pressure triggers a compaction. Every other state in the vocabulary prints on this same line; see Session compaction parity for the full set. Captured from the running CLI, not drawn as a diagram.

The automatic trigger is controlled by:

  • options.enable_auto_summarize
  • options.summarization.enabled
  • options.summarization.context_window_buffer
  • options.summarization.context_window_ratio

Streaming and the two-phase path live under options.compaction.streaming:

  • latency_budget_seconds (default 5)
  • precompact_threshold (default 0.7)
  • precompact_window (default 20)
  • semantic_timeout_seconds (default 120)
  • semantic_enabled (default true; set false for mechanical-only compaction)

For exact fields and defaults, see Configuration.

SurfacePurpose
summarize_sessionManual session compaction on demand
bmo session statusSession budget and shared compaction posture
describe_context / Context hubSummary-link health, continuity state, and recent compaction evidence
Context hub posture surface
Live BMO Context hub (/context): the Budget section with current token usage and the inline pruning controls plus the Compaction and Memory deep-link rows. This is the operator's in-TUI context-posture surface; the same bounded session-compaction state and reason codes are also available through bmo session status and describe_context.

Session compaction is not:

  • the idle-gated builtin://compaction executor
  • scheduler or nap-controller background work
  • branch or PR promotion
  • the /compaction reaction-gate inspector by itself
  • a path that trusts semantic summaries by default — the mechanical summary is always the fallback