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.
| Finding | Fires when | Severity |
|---|---|---|
| Context too full | A call uses at least 70% of its model’s context window | Warning. Critical at 90% |
| One tool takes over | One tool’s results make up at least 30% of a call’s input and stay that large | Warning |
| Lost history or compaction | A call’s input is at least 50% smaller than the previous call’s in the same conversation | Warning when nothing explains it. Info when the drop was a marked compaction |
| Output cut off | A call stopped at its output token limit | Warning. 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.compactionevent, or thegen_ai.conversation.compactedflag 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.
| Knob | Default | Meaning |
|---|---|---|
| Time window | 10 minutes | The rolling window finished runs are evaluated over |
| Context usage | 70% | Share of the context window that counts as too full |
| Critical context usage | 90% | Share of the context window at which the finding is critical. At least the context usage |
| Tool share | 30% | Share of a call’s input taken by one tool’s results |
| Sustained calls | 2 | Calls after the first that must keep the tool share up |
| Minimum drop | 50% | Fall in input size between calls that counts as lost history |
| Detect lost history | on | Turn the lost history and compaction finding off |
| Detect cut-off | on | Turn 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 get | Your spans need |
|---|---|
| Context too full | Input token counts on LLM calls, and a model with a known context window |
| One tool takes over | The per-part context breakdown that the SDKs and the plugin send, with tool names |
| Lost history or compaction | A session id, so calls are compared within one conversation |
| Output cut off | The finish reasons of each LLM call |
| A drop marked as a compaction | A 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.nameset tocompaction, when the compaction is a step of its own, such as a summarization call. - A
rius.compactionspan 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:
| Attribute | Meaning |
|---|---|
rius.compaction.kind | summarize, clear_tool_results, clear_thinking, or truncate. Required on a compaction span or event |
rius.compaction.executor | client when your code or agent did it, provider when the model provider did. Required on a compaction span or event |
rius.compaction.trigger | auto when the context neared its limit, manual when a person or your code asked |
rius.compaction.tokens.before | Context size before, in tokens |
rius.compaction.tokens.after | Context size after, in tokens |
rius.compaction.tokens.dropped | Tokens removed |
rius.compaction.context_window | The limit the context was measured against |
rius.compaction.messages.compacted | Messages summarized, cut, or emptied |
rius.compaction.messages.kept | Recent messages the next call still sees unchanged |
rius.compaction.tool_results.cleared | Tool results emptied, for clear_tool_results |
rius.compaction.id | The provider’s or agent’s own id for the compaction, when it has one |
rius.compaction.summary.bytes | Size of the summary, always sent when known |
rius.compaction.summary.text | The 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/compactendpoint. Mark those yourself.
When your own code trims or summarizes the history before calling the model, mark it:
Python
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
- Pre-defined alerts: the other detectors and how knobs and channels work.
- Distribution channels: route findings to webhook, email, or Slack.
- Privacy controls: decide what content leaves your process.