# Pydantic AI SummarizingCompaction(keep_user_messages=True) drops recent messages

*Disclosure: I work on MemorySync (I'm Mohammed, the founder). I drafted this page with AI assistance and checked it against the source issue and the official docs listed under Sources on Oct 29, 2026. I read the issue, the pydantic-ai-harness compaction README and the Pydantic AI message history docs; I didn't run a reproducer for this page.*

## The question

In [pydantic/pydantic-ai#11804](https://github.com/pydantic/pydantic-ai/issues/11804) (opened Oct 29, 2026), a user found that `SummarizingCompaction(keep_user_messages=True)` in message-count mode silently loses messages. With this config on an 11-turn history:

```python
SummarizingCompaction(
    model=..., max_messages=1, keep_messages=4,
    keep_user_messages=True, preserve_first_user_message=False,
)
```

the summarizer saw `U1`-`U9`, the result held the summary, retained copies of `U6`-`U9`, and `U11`, and this was the check output:

```text
A9 in summary input: False | in result: False
U10 in summary input: False | in result: False
A10 in summary input: False | in result: False
```

So `A9`, `U10` and `A10` end up in neither the summary nor the kept history. Versions in the report: pydantic-ai 2.63.0, pydantic-ai-harness 0.63.0 (the latest harness release on PyPI as of Nov 7, 2026 is 0.66.0, and its `compact()` still has this path).

**Status (checked Nov 7, 2026):** open, labelled `needs-maintainer-discussion`, no fix merged. One commenter reproduced it and traced it to `compact()`. A maintainer opened [PR #12652](https://github.com/pydantic/pydantic-ai/pull/12652) on Nov 6, 2026 to summarize the displaced tail messages instead of dropping them; it isn't merged yet, and the latest harness release (0.66.0) still has the old code path. An earlier fix, #9715, only covered the case where the current request itself was dropped (#9713).

> **Skip the memory plumbing around this fix.** Once the fix below is in, the [MemorySync Pydantic AI guide](https://docs.memorysync.io/guides/pydantic-ai?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_pydantic-ai-summarizing-compaction-keep-user-messages-drops-messages) shows how to add per-user memory with MemorySync. MemorySync doesn't fix this error itself. When you're ready, [see MemorySync](https://memorysync.io/?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_pydantic-ai-summarizing-compaction-keep-user-messages-drops-messages). Founding offer: use code FOUNDING30 for 30% off Starter for your first 3 months (Settings → Billing).

## Why it happens

From the issue's walk-through of `_summarizing_compaction.py`:

1. `compact()` picks `cutoff` from `keep_messages`, sets `to_summarize = split[:cutoff]` and `preserved = messages[cutoff:]`.
2. With `keep_user_messages=True`, it copies newer user turns out of the summarized prefix, then trims `preserved` from the front to make room for them.
3. The summarizer still gets the original `to_summarize`.

The messages trimmed in step 2 come after `cutoff`, so they never reach the summarizer, and they're gone from the result too. Each retained user copy costs one recent message.

The [compaction README](https://github.com/pydantic/pydantic-ai/blob/main/src/pydantic_ai_harness/pydantic_ai_harness/compaction/README.md) does say retained user turns "consume the existing `keep_messages` tail budget" in message-count mode. What it doesn't say is that the trimmed tail skips the summary.

## Fix 1: turn off `keep_user_messages` for now

The reporter notes that with `keep_user_messages=False` the same messages "survive in the tail". If full coverage matters more to you than verbatim user turns, drop the flag until a fix lands:

```python
SummarizingCompaction(max_tokens=120_000, keep_messages=20)
```

## Fix 2: use token mode

The README describes a different rule when `keep_tokens` is positive: "the original suffix takes precedence: `keep_messages` caps only the additional older user turns, which do not reduce the retained tail." The bug report and the code path it points to are for message-count mode (`keep_tokens` is `None`). If you want to keep `keep_user_messages=True`, a positive `keep_tokens` avoids the shrinking tail per the README. Test it on your own history, since the issue doesn't include a token-mode repro.

## Fix 3: keep the full transcript outside the compacted history

Compaction changes what the model sees. It doesn't have to be your only copy. The [Pydantic AI message history docs](https://ai.pydantic.dev/message-history/) show saving `result.all_messages()` to JSON and loading it back with `ModelMessagesTypeAdapter`:

```python
from pydantic_ai import ModelMessagesTypeAdapter
from pydantic_core import to_jsonable_python

history = result.all_messages()
as_json = to_jsonable_python(history)   # store this in your own DB
restored = ModelMessagesTypeAdapter.validate_python(as_json)
```

If you write that after each run, messages the compaction step drops are still in your store, and you can re-run compaction once a fixed release is out.

## Checklist

1. Using `SummarizingCompaction(keep_user_messages=True)` with `keep_messages` and no `keep_tokens`? You're on the affected path.
2. Short term: set `keep_user_messages=False`, or move to a positive `keep_tokens`.
3. Save `result.all_messages()` somewhere you control so nothing is lost for good.
4. Watch #11804 and PR #12652, and check the harness changelog before turning the flag back on.

## Common questions

**Is the current user request lost?**
No. That case was #9713, fixed by #9715, which reserves one tail slot for the request being answered.

**How many messages go missing?**
Per the issue, one recent message per retained user copy in message-count mode. With `keep_messages=20` on the same history, `A1` was lost the same way.

**Does a bigger `keep_messages` fix it?**
Not on its own. The issue shows the loss still happens; it just moves.

## Skip the plumbing with MemorySync

Disclosure: I build MemorySync.

It doesn't fix this bug, and it isn't a transcript backup. The [MemorySync Pydantic AI guide](https://docs.memorysync.io/guides/pydantic-ai?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_pydantic-ai-summarizing-compaction-keep-user-messages-drops-messages) documents `MemorySyncCapability`: after each successful run it sends the user's prompt to fact extraction and stores the durable facts (not the prompt text, and not model replies), and it adds recall through the instructions channel rather than as message parts. So user facts from a turn that compaction later drops can still come back as that user's memories in later runs. For the full message list, use Fix 3. Read the [guide](https://docs.memorysync.io/guides/pydantic-ai?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_pydantic-ai-summarizing-compaction-keep-user-messages-drops-messages) first. For an overview of MemorySync, see [the MemorySync homepage](https://memorysync.io/?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_pydantic-ai-summarizing-compaction-keep-user-messages-drops-messages).

From the guide:

**Unverified (not run):**

```bash
pip install pydantic-ai-memorysync
```

```python
from pydantic_ai import Agent
from pydantic_ai_memorysync import MemorySyncCapability

agent = Agent(
    "openai:your-model",
    instructions="You are a helpful assistant.",
    capabilities=[MemorySyncCapability(user_id="customer-7")],
)

result = await agent.run("What should I cook tonight?")
# The model saw:
#   You are a helpful assistant.
#
#   Relevant memories about this user from previous conversations:
#   - Is vegetarian
#   - Flies aisle
#
# ...and the durable facts in this prompt are remembered for
# every future run.
```

*Snippet adapted from the [MemorySync Pydantic AI guide](https://docs.memorysync.io/guides/pydantic-ai?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_pydantic-ai-summarizing-compaction-keep-user-messages-drops-messages); I haven't run it for this page.*

**Try it:** MemorySync is optional here: the steps above work without it. If you want the memory layer from the snippet, [see MemorySync](https://memorysync.io/?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_pydantic-ai-summarizing-compaction-keep-user-messages-drops-messages), create an API key in the dashboard and drop the snippet into your agent.

## Sources

- github.com/pydantic/pydantic-ai/issues/11804 (opened 2026-10-29, open, 2 comments; read 2026-11-07)
- github.com/pydantic/pydantic-ai/pull/12652 (open, unmerged; read 2026-11-07)
- pydantic-ai-harness compaction README, "Keeping user messages (`keep_user_messages`)" (main branch; read 2026-10-29)
- ai.pydantic.dev/message-history/ (`all_messages()`, `ModelMessagesTypeAdapter`; read 2026-10-29)
- MemorySync docs: docs.memorysync.io/guides/pydantic-ai

**Next step:** read the [MemorySync Pydantic AI guide](https://docs.memorysync.io/guides/pydantic-ai?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_pydantic-ai-summarizing-compaction-keep-user-messages-drops-messages), then [see MemorySync](https://memorysync.io/?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_pydantic-ai-summarizing-compaction-keep-user-messages-drops-messages). Founding offer: use code FOUNDING30 for 30% off Starter for your first 3 months (Settings → Billing).

