Files
Douwe MaanandGitHub 442e9ff493 Add experimental compaction menu under pydantic_ai_harness.experimental (#191)
Compaction capabilities (SlidingWindow, ClearToolResults, DeduplicateFileReads, SummarizingCompaction, TieredCompaction, LimitWarner) shipped as experimental, with a HarnessExperimentalWarning + single category-wide silence filter. 100% coverage; ruff + pyright strict.
2026-06-04 19:00:42 -05:00

64 lines
3.4 KiB
Markdown

# Compaction Capability — Implementation Plan
Closes #21
## Overview
This PR adds three compaction-related capabilities to `pydantic-harness`:
1. **`SlidingWindow`** — Zero-cost message trimming via a configurable sliding window.
2. **`LimitWarner`** — Injects warning messages when the agent approaches iteration, context-window, or total-token limits.
3. **`Compaction`** — LLM-powered summarization that replaces older messages with a compact summary.
All three are `AbstractCapability` subclasses that operate via the `before_model_request` hook, modifying `request_context.messages` before each model call.
## Design Decisions
### Tool-call / tool-return pair safety
The most critical invariant: trimming or compacting must **never** orphan a `ToolCallPart` without its corresponding `ToolReturnPart` (or vice versa). Doing so causes HTTP 400 errors from LLM providers.
The implementation uses a `_is_safe_cutoff()` function that searches around a proposed cutoff point for tool-call pairs that would be split. If a cutoff is unsafe, it walks backward to find a safe one. This approach is adapted from [vstorm-co/summarization-pydantic-ai](https://github.com/vstorm-co/summarization-pydantic-ai)'s `_cutoff.py`.
### Trigger and retention modes
Both `SlidingWindow` and `Compaction` support two trigger modes:
- `max_messages` — fire when message count exceeds threshold
- `max_tokens` — fire when estimated token count exceeds threshold
And two retention modes:
- `keep_messages` — retain N tail messages
- `keep_tokens` — retain messages fitting within a token budget
### Token estimation
A simple `estimate_token_count()` function approximates tokens at ~4 characters per token. This avoids requiring a tokenizer dependency while providing reasonable estimates for threshold detection.
### LimitWarner design
Warnings are injected as a trailing `ModelRequest` with a `UserPromptPart` (not a system message), because models tend to pay more attention to user messages. A `[LimitWarner]` marker enables stripping previous warnings before injecting new ones, preventing warning accumulation.
### Compaction summarization
The `Compaction` capability creates a temporary `pydantic_ai.Agent` with the configured summarization model. System prompts from the beginning of the conversation are preserved and prepended to the summary message.
## Dependencies
- Requires `pydantic-ai-slim` with the capabilities branch (not yet on PyPI).
- For local development, add a `[tool.uv.sources]` override pointing to the capabilities branch checkout.
## Files
- `src/pydantic_harness/compaction.py` — All three capabilities plus helpers
- `src/pydantic_harness/__init__.py` — Package exports
- `tests/test_compaction.py` — 81 tests covering all code paths
- `pyproject.toml` — Coverage threshold adjustment (98% due to branch coverage of elif chains)
## References
- [pydantic/pydantic-ai#4137](https://github.com/pydantic/pydantic-ai/issues/4137) — First-class Context Compaction API
- [pydantic/pydantic-ai#4267](https://github.com/pydantic/pydantic-ai/issues/4267) — Anthropic Compactions
- [pydantic/pydantic-ai#4013](https://github.com/pydantic/pydantic-ai/issues/4013) — OpenAI Compactions
- [pydantic/pydantic-harness#35](https://github.com/pydantic/pydantic-harness/issues/35) — Expose context window size on ModelProfile
- [vstorm-co/summarization-pydantic-ai](https://github.com/vstorm-co/summarization-pydantic-ai) — Prior art for cutoff logic