Skip to content

Fix/1984 synthetic compaction - #2036

Open
justanotherbyte wants to merge 2 commits into
cloudflare:mainfrom
justanotherbyte:fix/1984-synthetic-compaction
Open

Fix/1984 synthetic compaction#2036
justanotherbyte wants to merge 2 commits into
cloudflare:mainfrom
justanotherbyte:fix/1984-synthetic-compaction

Conversation

@justanotherbyte

@justanotherbyte justanotherbyte commented Aug 4, 2026

Copy link
Copy Markdown

What this fixes

Fixes #1984 where a Think session's compaction summary got persisted as a real message row and duplicated on every later read — sending the summary to the model twice, breaking keyed rendering on the client, and compounding with each further compaction.

How

Fixed at the shared chokepoint every provider and caller routes through, rather than patching Think's intake. Session._appendMessage now refuses to persist any message using the reserved compaction_ prefix, and applyCompactions in both the agent (SQLite) and Postgres providers filters compaction-prefixed rows out of the read projection so already-affected sessions self-heal on read with no migration — a no-op for healthy sessions.

Added testing coverage in the agents package (intake-persistence repro, pre-filed-row read-dedup repro, Session write-guard unit tests) plus a full Think test.

Why

getHistory() substitutes a synthetic compaction_<id> overlay for the compacted range on read — it has no row of its own. The browser transport echoes the full transcript back on the next turn, so that overlay arrived as an incoming message and got filed as a real assistant_messages row; from then on getHistory() returned the id twice (once as the substituted overlay, once as the stored row).


Open in Devin Review

@changeset-bot

changeset-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: ce4e39e

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

…loudflare#1984)

getHistory() synthesizes a compaction_<id> overlay on read; when a client echoed it back it was filed as a real row, duplicating the summary in every later read. Session now refuses to persist reserved compaction_ ids, and applyCompactions filters such rows so existing sessions self-heal on read.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 1 additional finding in Devin Review.

Open in Devin Review

Comment thread .changeset/reconnect-optimistic-send.md Outdated
Comment on lines +1 to +5
---
"agents": patch
---

Fix `useAgentChat` dropping an in-flight optimistic send on reconnect. When a message was sent while the socket was down, PartySocket buffers the frame for delivery, but the idle-connect transcript the server replays on reconnect (`cf_agent_chat_messages`) doesn't include it yet — so the whole-array `setMessages` replace erased the just-sent message from the UI until the turn completed and the server rebroadcast. The reconnect replay now preserves a trailing local-only `user` message that was buffered while disconnected, while still letting the server snapshot win for a delivered send it deliberately rolls back (e.g. `messageConcurrency: "drop"`) and for a regenerate's assistant replacement. Also fixes `@cloudflare/ai-chat` and `@cloudflare/think`, which re-export the hook unchanged.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Compaction duplication fix ships without a release note, so users never get it

The bug fix that stops compaction summaries being stored and duplicated is added to a published package without an accompanying changeset entry (only .changeset/reconnect-optimistic-send.md exists, which describes an unrelated reconnect fix), so the fix is not released or documented.
Impact: The fix may not be published in a version bump and won't appear in the changelog users read.

Repository rule: changesets required for package bug fixes

AGENTS.md ("Contributing → Changesets") states: "Changes to packages/ that affect the public API or fix bugs need a changeset". Commit ffaafdc4 changes packages/agents/src/experimental/memory/session/session.ts:624-635, packages/agents/src/experimental/memory/session/providers/agent.ts:535-541 and packages/agents/src/experimental/memory/session/providers/postgres.ts:269-272 — a user-visible bug fix (#1984) — but adds no .changeset/*.md. The only changeset in the PR (.changeset/reconnect-optimistic-send.md:1-5) documents the useAgentChat reconnect fix from the other commit.

Prompt for agents
The PR contains two independent fixes but only one changeset. Add a second changeset markdown file under .changeset/ (patch bump for the "agents" package) describing the #1984 fix: Session no longer persists messages using the reserved compaction_ id prefix, and both the SQLite (AgentSessionProvider) and Postgres session providers filter compaction-prefixed rows out of the read projection so previously corrupted sessions self-heal on read.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you remove the changeset? It’s important to include one otherwise the change won’t be released when we merge.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh wait I now see that was an unrelated changeset. Can you add a new one for this change @justanotherbyte ?

@justanotherbyte
justanotherbyte force-pushed the fix/1984-synthetic-compaction branch from ffaafdc to 33b7b89 Compare August 4, 2026 15:45
@pkg-pr-new

pkg-pr-new Bot commented Aug 4, 2026

Copy link
Copy Markdown

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@2036

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@2036

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@2036

create-think

npm i https://pkg.pr.new/create-think@2036

hono-agents

npm i https://pkg.pr.new/hono-agents@2036

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@2036

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@2036

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@2036

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@2036

commit: ce4e39e

@cjol cjol left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall shape is good, thank you! Definitely need a changeset, a few nits otherwise in comments.

Comment on lines +630 to +635
if (isCompactionMessage(message)) {
console.warn(
`[Session] Refusing to persist a message with the reserved compaction id "${message.id}" — synthetic compaction overlays are computed on read and are never stored (#1984).`
);
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary? Receiving a compaction message from the browser is expected, so we’re logging a warning under normal, happy-path conditions (potentially even on every single turn after a compaction?)

expect(compactions).toHaveLength(1);
});

it("does not persist synthetic compaction overlays echoed back by a client (#1984)", async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For completeness, it might be good to add a test for the postgres provider as well

// Simulate an already-corrupted session: a synthetic overlay was filed as
// a real row on a prior turn (before the intake guard existed), parented
// into the live chain via the raw insert seam.
await agent.rawInsertChildForTest("m5", overlay!.id);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For completeness, I might test that a child of the overlay message is restored and re-parented correctly

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.

bug: synthetic compaction messages are persisted as real rows, duplicating the summary in every later read

2 participants