codeagent keeps long conversations within the model’s context window using an automatic, Claude Code-aligned compaction strategy.
Dynamic context window
The context window is resolved from the model rather than hard-coded. Resolution order (highest priority first):
-
CODEAGENT_MAX_CONTEXT_TOKENSenvironment variable. - A
[1m]suffix in the model name (forces a 1,000,000-token window). - A built-in capability table (claude / gpt / gemini / deepseek / …), or a provider-reported window when available.
- A 200,000-token default.
# The effective window reserves output tokens; the auto-compaction threshold
# sits one buffer below that (e.g. ~167K for a 200K Claude model).When compaction triggers
Compaction runs when the estimated token usage crosses the
auto-compaction threshold
(effective window - output reserve - buffer). Token usage
prefers the real usage from the last API exchange
(Chat$get_tokens()), falling back to a character
estimate.
Two-level flow
- Snip old tool results (cheap pre-step).
- Session-memory compaction — an incremental summary of the earliest turns.
- Full compaction — a single 9-section structured summary (verbatim Claude Code prompt) replacing the conversation, used when session-memory can’t run.
A circuit breaker stops auto-compaction after repeated failures, and a 413 / prompt-too-long error triggers a reactive fallback that drops the oldest turns (parsing the real context limit from the error when present).
Compaction flow
token count = Chat$get_tokens() (real usage) else char/3.5 estimate
TIER 1 -- turn boundary, before each chat$chat() [maybe_compact()]
tokens >= model_limit - 33K margin ?
| yes (circuit breaker: stop after 3 consecutive failures)
v compact_now()
snip_old_tools() clear old tool results (cheap, no LLM)
v
session_memory_compact() L1: incremental summary of the earliest turns
| did not run (too few turns)
v
full_compact() L2: one 9-section structured summary replaces history
TIER 2 -- between tool rounds, on ellmer on_tool_result [.midloop_compact_step()]
settings$midloop_compact (default ON) AND tokens >= midloop_trigger ?
+- default: snip_old_tools(target_tokens) budget-aware micro-snip (no LLM)
+- opt-in (settings$midloop_full_compact): compact_now() (same L1 -> L2)
REACTIVE -- on send error 413 / prompt_too_long [ptl_fallback()]
drop oldest turns until under the parsed real limit, then retry chat$chat() once
The mid-loop step currently rides ellmer’s
on_tool_result (fires between tool rounds); the cleaner
upstream target is on_turn_start (ellmer PR #1052).
Context-left indicator
Both the REPL banner and the Shiny status bar show “N% context left”,
turning yellow near the warning line and red near the error/blocking
line (calculate_token_warning_state()).
Controls
Sys.setenv(CODEAGENT_DISABLE_COMPACT = "1") # disable auto-compaction
Sys.setenv(CODEAGENT_MAX_CONTEXT_TOKENS = "500000") # override the window
# Manual compaction from the REPL / Shiny:
# /compact