context-compactor
Automatic context window management for AI agent SDKs.
Why?
Long-running agent conversations exceed model context windows, causing:
- API errors when context is too large
- Lost context when manually truncating
- Expensive token usage on repeated information
This library provides automatic, intelligent context compaction with:
- Generic & Type-Safe — Works with native SDK message types
- Multiple strategies — From simple truncation to LLM summarization
- SDK adapters — pydantic-ai, openai-agents, claude-agent-sdk
- Lifecycle hooks — UI feedback during compaction (spinners, progress)
Installation
# Core (no dependencies)
pip install context-compactor
# With pydantic-ai support
pip install context-compactor[pydantic-ai]
# With all SDKs
pip install context-compactor[all-sdks]
Quick Start
pydantic-ai
from pydantic_ai import Agent
from context_compactor import ContextCompactor, LoggingHook
from context_compactor.adapters.pydantic_ai import pydantic_ai_adapter
from context_compactor.strategies import KeepRecentMessages
from context_compactor.tokenizers.pydantic_ai import PydanticAITokenCounter
compactor = ContextCompactor(
max_context_tokens=128_000,
strategy=KeepRecentMessages(keep_count=20),
token_counter=PydanticAITokenCounter(),
hooks=[LoggingHook()], # Optional: log compaction events
)
agent = Agent(
'openai:gpt-4o',
history_processors=[pydantic_ai_adapter(compactor)],
)
# Compaction happens automatically when context approaches limit
result = await agent.run("Continue...", message_history=long_history)
openai-agents
from agents import Agent, Runner
from context_compactor import ContextCompactor
from context_compactor.adapters.openai_agents import openai_agents_adapter
from context_compactor.strategies import SlidingWindow
from context_compactor.tokenizers.openai_agents import OpenAIAgentsTokenCounter
compactor = ContextCompactor(
max_context_tokens=128_000,
strategy=SlidingWindow(),
token_counter=OpenAIAgentsTokenCounter(),
)
result = await Runner.run(
agent,
input="Hello",
hooks=openai_agents_adapter(compactor),
)
claude-agent-sdk
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
from context_compactor import ContextCompactor
from context_compactor.adapters.claude_agent import claude_agent_adapter
from context_compactor.strategies import KeepFirstLast
from context_compactor.tokenizers.claude_agent import ClaudeAgentTokenCounter
compactor = ContextCompactor(
max_context_tokens=200_000,
strategy=KeepFirstLast(keep_first=2, keep_last=10),
token_counter=ClaudeAgentTokenCounter(),
)
hook_event, hook_matchers = claude_agent_adapter(compactor)
options = ClaudeAgentOptions(hooks={hook_event: hook_matchers})
async with ClaudeSDKClient(options=options) as client:
await client.query("Help me with this large codebase...")
Lifecycle Hooks
Hooks let you react to compaction events—perfect for showing UI feedback like Cursor's "Summarizing conversation..." spinner.
Built-in Hooks
from context_compactor import LoggingHook, CallbackHook
# LoggingHook - prints to stdout
compactor = ContextCompactor(
...,
hooks=[LoggingHook(prefix="[MyApp]")],
)
# CallbackHook - call your own async functions
async def on_start():
await show_spinner("Summarizing context...")
async def on_end(result):
await hide_spinner()
print(f"Saved {result.tokens_saved} tokens")
compactor = ContextCompactor(
...,
hooks=[CallbackHook(on_start_callback=on_start, on_end_callback=on_end)],
)
Custom Hooks
Implement the CompactionHook protocol:
from dataclasses import dataclass
from context_compactor import CompactionResult
@dataclass
class WebhookHook:
"""Send compaction events to your backend."""
webhook_url: str
async def on_start(self) -> None:
async with httpx.AsyncClient() as client:
await client.post(self.webhook_url, json={
"type": "compaction_started",
"message": "Summarizing context..."
})
async def on_end(self, result: CompactionResult) -> None:
async with httpx.AsyncClient() as client:
await client.post(self.webhook_url, json={
"type": "compaction_completed",
"tokens_saved": result.tokens_saved,
})
# Use multiple hooks
compactor = ContextCompactor(
max_context_tokens=128_000,
strategy=KeepRecentMessages(keep_count=20),
token_counter=PydanticAITokenCounter(),
hooks=[
WebhookHook(webhook_url="https://your-app.com/events"),
LoggingHook(),
],
)
Event Order
Hooks fire synchronously before/after compaction, ensuring correct ordering with streaming:
1. User sends message
2. history_processor runs
→ hook.on_start() fires → UI shows spinner
→ compaction happens
→ hook.on_end() fires → UI hides spinner
3. Stream begins → tokens flow to UI
Compaction Strategies
| Strategy | Description | Best For |
|---|---|---|
KeepRecentMessages |
Keep last N messages | Simple truncation |
KeepFirstLast |
Keep first N + last M, drop middle | Preserve initial context |
SlidingWindow |
Fit as many recent as token budget allows | Token-efficient |
DropOldestUntilFits |
Remove oldest until under budget | Minimal dropping |
SummarizeMiddle |
Keep first/last, LLM-summarize middle | Best preservation |
Custom Strategies
Write type-safe strategies that work with native SDK message types:
from pydantic_ai.messages import ModelRequest, ModelResponse, TextPart, ToolCallPart
class KeepToolCalls:
"""Keep all tool interactions, drop regular text."""
async def compact(
self,
messages: list[ModelRequest | ModelResponse],
target_tokens: int,
token_counter,
) -> list[ModelRequest | ModelResponse]:
result = []
for msg in messages:
if isinstance(msg, ModelResponse):
tool_parts = [p for p in msg.parts if isinstance(p, ToolCallPart)]
if tool_parts:
result.append(ModelResponse(parts=tool_parts))
return result
Examples
See the examples/ directory for complete working examples:
| SDK | Examples |
|---|---|
| pydantic-ai | keep_recent, keep_first_last, sliding_window, summarize_middle, streaming_hooks |
| openai-agents | keep_recent, sliding_window |
| claude-agent-sdk | keep_recent |
API Reference
ContextCompactor
ContextCompactor(
max_context_tokens: int, # Model's context window
strategy: CompactionStrategy, # How to compact
token_counter: TokenCounter, # How to count tokens
trigger_at_percent: float = 0.8, # Compact at 80% full
verbose: bool = False, # Print debug info
hooks: list[CompactionHook] = [], # Lifecycle hooks
)
Methods
await compactor.maybe_compact(messages)— Compact if over thresholdcompactor.get_stats()— Get compaction statisticscompactor.reset_stats()— Reset statistics
CompactionResult
Passed to hook.on_end():
@dataclass
class CompactionResult:
original_tokens: int # Tokens before compaction
compacted_tokens: int # Tokens after compaction
tokens_saved: int # original - compacted
original_message_count: int
compacted_message_count: int
strategy_name: str # e.g., "KeepRecentMessages"
Development
# Clone and setup
git clone ...
cd context-compactor
pip install -e ".[dev]"
# Run tests
pytest tests/ -v
# Lint
ruff check .
# Type check
ty check context_compactor/
License
MIT
Release files for agentic-context-compaction 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 | |
|---|---|---|---|
| agentic_context_compaction-0.1.0.tar.gz | 31.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| agentic_context_compaction-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 57.3 kB
Release files / agentic_context_compaction-0.1.0.tar.gz
| Download URL | agentic_context_compaction-0.1.0.tar.gz |
|---|---|
| Size | 31.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e83e7ac5cc72ce8c7776feee9caa2dacec1ae0a4fd43ce532d67e80b643cd579
|
|
BLAKE2b-256 checksum How to use checksums |
24e6ee412792b41fea961f42d9339f20af5d3ecaeef8005cedc3debdecc95f0d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 8, 2026.
Transparency logRelease files / agentic_context_compaction-0.1.0-py3-none-any.whl
| Download URL | agentic_context_compaction-0.1.0-py3-none-any.whl |
|---|---|
| Size | 25.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cf4b1b97753a122cad16437eb4989fc25edb68e024b4ca4b7b76b193b6ec3557
|
|
BLAKE2b-256 checksum How to use checksums |
f458e1a9861c0751077e6a4ae251a19ebd4191e35e49a9459193e99373b98844
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 8, 2026.
Transparency log