Skip to content

Context compaction

As a conversation grows it eventually approaches the model's context window. Rather than let a turn fail, Wallah compacts: it replaces the oldest turns with a summary while keeping a recent tail of the conversation live and verbatim. This runs automatically between turns, and you can force it at any time with /compact.

Automatic compaction follows a sawtooth: once enough new context has accrued since the last compaction (grow_by), the oldest turns are summarized down, leaving the keep_recent tail untouched. A hard ceiling (compact_at) forces a compaction regardless of the other gates, so a single very large turn cannot overflow the window. A minimum gain and a refractory period keep it from compacting too eagerly.

The summary has two parts: a deterministic reduction of the dropped turns, plus an optional model-written Observations summary. The latter maps over the dropped conversation in chunks and can run against a different (typically cheaper) model than the session itself.

Configure both from config.lua in a reconfigure handler, which fires at startup and on /reload:

agent.on('reconfigure', function()
  agent.configure_compaction {
    mode = 'auto',        -- 'auto' (default) compacts between turns; 'off'
                          -- leaves only the manual /compact command
    grow_by = '35%',      -- sawtooth cadence: compact once this much new
                          -- context has accrued since the last compaction
    compact_at = '85%',   -- hard ceiling: always compact once live context
                          -- reaches this, overriding the other gates
    keep_recent = 20000,  -- recent-conversation budget always kept verbatim
    min_gain = 10000,     -- skip an automatic compaction that would drop less
    min_turns = 5,        -- minimum turns between automatic compactions

    -- Summarizing behaviour:
    enable_llm_summary = true,   -- false yields a deterministic-only summary
                                 -- while leaving auto-compaction on
    summarizer_model = 'ollama/llama3.1:8b',  -- [provider/]model[@effort]; any
                                 -- omitted part inherits the session's own
    summarizer_chunk = '25%',    -- per-chunk input budget for the map step
    summarizer_concurrency = 2,  -- max concurrent map calls (default 1)
    summarizer_timeout = '2m',   -- wall-clock bound on the whole pass
  }
end)

agent.configure_compaction takes every field as optional; an omitted field takes its built-in default. Token amounts accept an absolute count (20000, "20k", "1.5M") or a fraction of the model's context window (0.5, "35%"). A fraction cannot be resolved for a provider whose window is unknown (for example a local Ollama endpoint); in that case the trigger it controls is simply disabled, so prefer absolute counts for those providers. Durations like summarizer_timeout accept "500ms", "30s", "2m", or "1h" (a bare number is read as seconds).