Skip to Content

Context rot

An agent whose context window fills up, or whose history is silently dropped, keeps running and keeps answering. Nothing errors, so nothing tells you the answers got worse. Context rot is the pre-defined alert that finds these runs: it reads the token counts, finish reasons, and compaction markers your agents already send, and raises a finding when the context becomes a problem.

Every workspace with alerting has a Context rot alert, enabled by default. Like the other pre-defined alerts, you tune its thresholds on its details page and attach channels to it.

What it finds

Context rot looks at the LLM calls of each run and reports four findings.

FindingFires whenSeverity
Context too fullA call uses at least 70% of its model’s context windowWarning. Critical at 90%
One tool takes overOne tool’s results make up at least 30% of a call’s input and stay that largeWarning
Lost history or compactionA call’s input is at least 50% smaller than the previous call’s in the same conversationWarning when nothing explains it. Info when the drop was a marked compaction
Output cut offA call stopped at its output token limitWarning. Critical when the cut-off output was a tool call

Findings are built from finished calls, so they are never retracted. They carry counts and names, never message content.

Context too full

Models get less reliable well before their window is full, and agents that compact on their own usually start only when the window is nearly full, after the damage. This finding fires on the first call that crosses the threshold and reports the fullest call of the run, with its model and percentage.

Usage counts all input tokens: fresh input plus cache reads and cache writes. A cached prompt counts in full.

One tool takes over

Fires when the results of a single tool fill a large share of a call’s input and keep doing so. The share has to stay at least half the threshold (15% by default) for the next two calls of the same conversation, so a single large result that gets trimmed away on the next call never fires.

Problems this is built to catch:

  • A search tool that returns whole pages, so every later call carries them.
  • A file reader that dumps a large log into the conversation and leaves it there.

Lost history or compaction

Fires when a call’s input falls sharply from the previous call of the same conversation. The finding says how many tokens were lost and which parts of the context lost the most, such as earlier messages or one tool’s results.

What the finding means depends on whether the producer said the drop was on purpose:

  • Lost (warning): the history shrank and nothing reported a compaction. Something dropped it without telling you.
  • Compacted (info): a compaction span, a rius.compaction event, or the gen_ai.conversation.compacted flag explains the drop. The compaction was deliberate, but it is still a moment where the agent can forget something it needed.

In both cases the finding is a warning if the system prompt shrank by more than 10% or vanished. An agent that lost its instructions behaves differently afterwards, whoever caused it.

Output cut off

Fires when a call stopped because it hit its output token limit. It matches the finish reasons length, max_tokens, and max_output_tokens, ignoring case. A cut-off answer is truncated mid-thought. A cut-off tool call is worse, because the tool receives broken arguments, so that case is critical.

See what was lost

Open a run with a lost history or compaction finding and look at the context growth strip. A marker (▼) sits at each drop. Click it to open What was lost:

  • One line with the tokens lost and the parts that lost the most.
  • Dropped messages: what was removed.
  • What replaced it: the compaction summary, when the producer sent one.
  • System prompt: before and after.

The text appears only when the workspace captures content. With capture_content=False you still see the sizes and the parts, but not the words. Claude Code never records its system prompt, so for it the system prompt row says so.

The view is approximate for very long histories. The SDKs keep per-message detail for the last 50 input messages and fold older ones into a single block, so a drop inside the folded part is reported as one block.

Tune the thresholds

Open the Context rot alert from the Alerts page to change its knobs.

KnobDefaultMeaning
Time window10 minutesThe rolling window finished runs are evaluated over
Context usage70%Share of the context window that counts as too full
Critical context usage90%Share of the context window at which the finding is critical. At least the context usage
Tool share30%Share of a call’s input taken by one tool’s results
Sustained calls2Calls after the first that must keep the tool share up
Minimum drop50%Fall in input size between calls that counts as lost history
Detect lost historyonTurn the lost history and compaction finding off
Detect cut-offonTurn the output cut-off finding off

No single fill level is right for every model and task, so these are knobs and not fixed rules. Lower the context usage if your agents degrade earlier.

What to send

Context rot can only judge what your agents report. Each finding needs something specific:

To getYour spans need
Context too fullInput token counts on LLM calls, and a model with a known context window
One tool takes overThe per-part context breakdown that the SDKs and the plugin send, with tool names
Lost history or compactionA session id, so calls are compared within one conversation
Output cut offThe finish reasons of each LLM call
A drop marked as a compactionA compaction marker, described next

A quiet alert can mean healthy agents or missing inputs, and the console doesn’t say which, so compare the table above with what your spans carry.

The Rius SDKs and the Claude Code plugin send all of these inputs for you. If you send OpenTelemetry spans yourself, the compaction attributes are listed in the table below.

Report compactions

Without a marker, an agent that summarizes its own history looks the same as one that lost it, and you get a warning for a decision your code made on purpose. Compactions are reported on a shared convention, in two shapes:

  • A compaction span, with gen_ai.operation.name set to compaction, when the compaction is a step of its own, such as a summarization call.
  • A rius.compaction span event on the LLM span, when the compaction happened inside the request, such as a provider that clears old tool results as part of the call.

Either one carries these attributes, which rius-semconv defines from version 1.8.0 . The registry has the full definitions:

AttributeMeaning
rius.compaction.kindsummarize, clear_tool_results, clear_thinking, or truncate. Required on a compaction span or event
rius.compaction.executorclient when your code or agent did it, provider when the model provider did. Required on a compaction span or event
rius.compaction.triggerauto when the context neared its limit, manual when a person or your code asked
rius.compaction.tokens.beforeContext size before, in tokens
rius.compaction.tokens.afterContext size after, in tokens
rius.compaction.tokens.droppedTokens removed
rius.compaction.context_windowThe limit the context was measured against
rius.compaction.messages.compactedMessages summarized, cut, or emptied
rius.compaction.messages.keptRecent messages the next call still sees unchanged
rius.compaction.tool_results.clearedTool results emptied, for clear_tool_results
rius.compaction.idThe provider’s or agent’s own id for the compaction, when it has one
rius.compaction.summary.bytesSize of the summary, always sent when known
rius.compaction.summary.textThe summary itself. Content, so it follows your privacy settings

The first LLM call on the compacted context also gets gen_ai.conversation.compacted set to true, which is how a drop is told apart from lost history. The convention allows it on later calls too, but the SDKs and the plugin set it once. Producers set it only when true and never write false.

From the SDKs

The Python and TypeScript SDKs report the compactions that Anthropic and OpenAI return in their responses, with no code change: Anthropic context editing and server-side compaction, and OpenAI Responses compaction items. Some calls report nothing:

  • OpenAI truncation: "auto", because the response does not say what was cut.
  • In the TypeScript SDK, streamed Anthropic and chat completions responses, and OpenAI’s standalone /responses/compact endpoint. Mark those yourself.

When your own code trims or summarizes the history before calling the model, mark it:

import rius history = summarize(history) rius.mark_compaction( kind="summarize", trigger="auto", tokens_before=168_000, tokens_after=31_000, summary=history[0], )

The call records a compaction span under the current span, and the next LLM call in the same context is flagged as running on compacted history, once. Every argument is optional, and kind defaults to summarize. An unknown kind or trigger raises an error. The summary text is recorded only when content capture is on, and its size is always recorded.

Mark the compaction in the flow that makes the next model call. A mark made inside a child task or thread is not seen by the code that started it. See the Python and TypeScript references.

From Claude Code

The Rius plugin for Claude Code turns every compaction, automatic or from /compact, into a compaction span with the trigger, the tokens before and after, and how many messages were kept. The first call after each compaction is flagged as compacted, and later calls are not. With content capture on, the compaction summary appears under What replaced it.

Next steps

Last updated on