Skip to content

refactor: promote history compaction to a first-class pydantic-ai capability - #829

Open
mpfaffenberger wants to merge 2 commits into
mainfrom
feature/history-compaction-capability
Open

refactor: promote history compaction to a first-class pydantic-ai capability#829
mpfaffenberger wants to merge 2 commits into
mainfrom
feature/history-compaction-capability

Conversation

@mpfaffenberger

Copy link
Copy Markdown
Owner

What

The in-run compaction pipeline was an async closure (make_history_processor) wrapped in the generic ProcessHistory adapter at two call sites (main builder + sub-agent invocation). This promotes it to HistoryCompaction, a proper AbstractCapability subclass living in _compaction.py — the same house-style upgrade #828 gave the steering injector.

Why

  • The builder already composes capabilities=[...]; compaction was the biggest feature still hiding behind a closure-in-an-adapter.
  • DRY: HistoryCompaction(agent) replaces closure construction + ProcessHistory wrapping at both call sites.
  • The capability owns its docs: the 7-step pipeline contract and the agent attribute contract now live on the class that implements them.

Design notes

  • Byte-identical seam semantics. before_model_request REPLACES request_context.messages with the processed durable history — exactly what ProcessHistory does internally — so registration order against steer injection and the response clamp is preserved (compaction still fires first; a fresh steer can't be compacted away).
  • get_serialization_name() -> None — not spec-serializable, it holds a live agent reference (same rationale as ProcessHistory itself).
  • Feature parity checklist: start/end history-processor hooks hash-dedup merge with newest-message collision protection forced mid-run /compact via pause controller dropped-hash bookkeeping empty-ThinkingPart stripping trailing-ModelResponse trim (Anthropic prefill) tool_call_id sanitization spinner context badge updates

Tests

  • Ported the closure suite to drive the real before_model_request seam.
  • New: seam-parity test (messages replaced, not appended) + a TestModel-backed Agent.run() proving dispatch through the actual capability chain lands the user prompt in the durable history.
  • Removed two stale make_history_processor mock patches — constructing the dataclass is side-effect-free.
  • Full suite: 7580 passed; the one failure (test_render_version_check_current) is pre-existing on main.

Heads-up

Touches the same capabilities=[...] block as #828 (steering capability). Whichever lands second gets a trivial rebase.

Do not merge — awaiting review.

…ability

The in-run compaction pipeline was an async closure (make_history_processor)
jammed into the generic ProcessHistory adapter at two call sites (main
builder + sub-agent invocation). Promote it to HistoryCompaction, a proper
AbstractCapability subclass in _compaction.py, matching harness house style.

- before_model_request override with byte-identical ProcessHistory
  semantics: request_context.messages is REPLACED with the processed
  durable history, so registration order against steer injection and the
  response clamp is preserved exactly
- get_serialization_name() -> None (holds a live agent reference)
- Both call sites now wire HistoryCompaction(agent) directly - one class,
  zero closure indirection, DRY across main + sub-agent paths
- Feature parity: start/end hooks, hash-dedup merge with newest-message
  collision protection, forced /compact via pause controller, dropped-hash
  bookkeeping, empty-ThinkingPart stripping, trailing-ModelResponse trim,
  tool_call_id sanitization
- Tests exercise the real before_model_request seam plus a TestModel-backed
  Agent.run() proving dispatch through the actual capability chain; stale
  make_history_processor mock patches removed (constructing the dataclass
  is side-effect-free)
… proof, stale docstring

- The replace-not-append test now asserts list IDENTITY: the processed
  durable history object itself becomes the outbound list
- The capability-chain dispatch test captures the messages the model
  actually received via FunctionModel and asserts they are exactly the
  processed durable history (the docstring no longer overclaims)
- Refresh the stale ProcessHistory(compaction) wiring prose in
  test_runtime_pause_leakage.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant