ContextWatch 🔍
Chrome DevTools' memory profiler, but for your LLM agent's context window.
Every long-running agent eventually hits the same wall: the context window fills up with stale tool results, the framework silently summarizes or drops messages, recall quality degrades, and your input-token bill climbs — and you have no visibility into any of it.
ContextWatch intercepts your agent's LLM calls and produces a per-turn token ledger of exactly what's inside the context window: what entered, what got evicted, what got compacted, what's stale, and what it all costs. It then renders everything into a self-contained HTML report you can open in any browser.
pip install contextwatch
The questions it answers
| Question | Where you see it |
|---|---|
| What fraction of my context is system prompt vs. tool results vs. conversation? | Composition stream chart |
| Which tool result from turn 3 is still burning 8K tokens at turn 20? | Stale burn analysis |
| Did my framework silently drop or summarize messages? When? | Eviction / compaction events |
| After compaction, can the summary still answer what the original could? | Compaction quality scoring |
| Is my client-side token estimate diverging from what the provider bills? | Client vs. provider divergence chart |
| Which source category drives my input-token bill? | Cost attribution |
What makes it different
ContextWatch detects every kind of context mutation, including ones most tools can't see:
| Event | What happened | Detected via |
|---|---|---|
entered |
New blocks joined the context | content-hash diff |
evicted |
Blocks silently dropped (e.g. trim_messages) |
content-hash diff |
compaction |
Blocks replaced by a summary (lossy) | hash diff + summary classification |
server_edit |
Server-side context editing (Anthropic clear_tool_uses) |
applied_edits in the API response |
server_compaction |
Server-side compaction (Anthropic compact) |
stop_reason / usage drop |
reversible_evict |
Reversible compression (Headroom) — content compressed but retrievable, not lost | exact tokens_before/tokens_after from the compressor |
Server-side mechanisms never touch your client-side messages list — hash
diffing alone sees nothing. ContextWatch reads the API response metadata
instead, so nothing escapes the ledger.
Quickstart
Raw Anthropic SDK — one line
from anthropic import Anthropic
from contextwatch import ContextProfiler, wrap_anthropic
profiler = ContextProfiler("run.jsonl", label="text2sql")
client = wrap_anthropic(Anthropic(), profiler)
# ... run your agent exactly as before ...
Using Anthropic's server-side context management (context editing /
compaction)? Use wrap_anthropic_beta instead — it additionally captures
server_edit and server_compaction events from the response metadata.
LangChain / LangGraph
from contextwatch import ContextProfiler
from contextwatch.integrations.langchain_handler import ContextWatchCallbackHandler
profiler = ContextProfiler("run.jsonl", label="text2sql")
handler = ContextWatchCallbackHandler(profiler)
graph.invoke(state, config={"callbacks": [handler]})
Bonus: langgraph_node metadata gives you per-node context attribution —
see exactly which subagent is bloating the context.
Headroom (reversible compression)
from headroom import compress
from contextwatch import ContextProfiler
from contextwatch.integrations.headroom_adapter import wrap_headroom_compress
profiler = ContextProfiler("run.jsonl", label="my-agent")
headroom_compress = wrap_headroom_compress(compress, profiler)
result = headroom_compress(raw_tool_results, model="claude-sonnet-4-6")
# every compression that saves tokens is recorded as a `reversible_evict`
# event with exact (not estimated) token counts
Framework-free — bring your own loop
profiler = ContextProfiler("run.jsonl")
profiler.record_turn(messages, system=system_prompt, model=model_name,
usage={"input_tokens": ..., "output_tokens": ...})
The profiler only ever sees [{role, content}] — it works with any provider
and any framework.
The report
contextwatch report run.jsonl -o report.html
One self-contained HTML file (no server, no dependencies, works offline) with:
- Composition stream — stacked per-turn token bands by source (system / user / assistant / tool results / compaction summaries), with event markers: rose ◆ for lossy compaction, teal ↺ for reversible compression
- Client vs. provider divergence — your token estimate against what the API actually reported, per turn
- Turn inspector — click any turn to see every block, its age, size, and a content preview
- Stale burn — blocks that entered long ago and are still paying rent
CLI
contextwatch report run.jsonl -o report.html # render standalone HTML
contextwatch stats run.jsonl # summary stats as JSON
contextwatch quality run.jsonl --model claude-haiku-4-5 # score compactions
quality replays probe questions against pre- and post-compaction context to
score how much recall the compaction actually destroyed.
Try the demo (no API key needed)
python examples/demo_agent.py
open examples/demo-report.html
Simulates a 24-turn Text-to-SQL session: growing context, large tool results, staleness, a mid-session compaction, and post-compaction growth.
Validated against real mechanisms
The examples/validation/ directory contains seven
runnable scripts, one per real-world context-mutation mechanism:
| # | Mechanism | Expected events |
|---|---|---|
| 1 | LangChain SummarizationMiddleware |
evicted + compaction |
| 2 | langmem SummarizationNode |
evicted + compaction |
| 3 | Manual RemoveMessage pattern |
compaction (with classifier hook) |
| 4 | trim_messages (negative control) |
evicted, no compaction |
| 5 | Anthropic clear_tool_uses (server-side) |
server_edit |
| 6 | Anthropic compact (server-side) |
server_compaction |
| 7 | Headroom reversible compression | reversible_evict |
Install
pip install contextwatch # core — stdlib only, zero dependencies
pip install "contextwatch[anthropic]" # + Anthropic SDK wrapper
pip install "contextwatch[langchain]" # + LangChain/LangGraph callback
pip install "contextwatch[tiktoken]" # + exact tokenizer for OpenAI models
Design principles
- Zero-rewrite adoption. One wrapper line around an existing client/graph.
- Zero core dependencies. The base install is pure stdlib.
- Plain-JSONL ledger. Any tool — pandas, jq, your dashboard — can consume it.
- Model-agnostic core. The profiler only ever sees
[{role, content}]. - Exact where possible, honest where not. Provider-reported
usagefor turn totals; per-block counts come from a pluggable tokenizer and are labeled as estimates. - Never breaks the agent. All recording is wrapped in try/except
(
strict=Falseby default); a profiler crash never crashes your agent.
Development
git clone https://github.com/sackri10/contextwatch.git
cd contextwatch
pip install -e ".[dev]"
pytest
See docs/architecture.md for the full design spec —
data model, event detection, integration internals, and build order.
License
MIT — see LICENSE.
Metadata
Release files for contextwatch 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| contextwatch-0.1.0.tar.gz | 26.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| contextwatch-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 51.9 kB
Release files / contextwatch-0.1.0.tar.gz
| Download URL | contextwatch-0.1.0.tar.gz |
|---|---|
| Size | 26.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
aee898584704d9483f6252d4a4daa30fd4319ad4495b9ba7a16b8a7052b540f6
|
|
BLAKE2b-256 checksum How to use checksums |
b4ba75574269d0a331a7b31acf98f11bf3c6ee422684d5c49837225269e8f2ce
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.8
|
Release files / contextwatch-0.1.0-py3-none-any.whl
| Download URL | contextwatch-0.1.0-py3-none-any.whl |
|---|---|
| Size | 25.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3fcce1cb39a1f7ffe8bdf7ee3b5ebf97bd0eb2abe92a5d471f08a61660891810
|
|
BLAKE2b-256 checksum How to use checksums |
761a88939db44294e08302b6b6ef0e442706d4fe353fb7f063a014f2be433861
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.8
|