Skip to content

fix(think,agents): keep a restart-interrupted regeneration on its own branch - #2417

Merged
threepointone merged 1 commit into
mainfrom
fix-regenerate-partial-recovery
Sep 30, 2026
Merged

threepointone merged 1 commit into
mainfrom
fix-regenerate-partial-recovery

Conversation

@threepointone

@threepointone threepointone commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #2413 (#2028). That PR noted one gap: a regeneration interrupted after partial output takes the "continue" recovery path. We had described that as continuing on the right branch instead of restarting. Writing a test for it showed a real bug instead.

Problem

When a restart (Durable Object eviction or a deploy) interrupts a regeneration that has already streamed part of its answer:

  1. Orphan recovery rebuilds the partial answer from the stored stream chunks. It appends it with no parent (persistReconstructedOrphan then appendMessage(message)), so it lands under the latest leaf, which is the response being replaced. The tree becomes user, old answer, new partial instead of the new partial sitting beside the old answer.
  2. The continuation extends that leaf, so the model prompt is user, assistant: <old answer>, assistant: <new partial>, followed by "continue". The replaced answer is back in the context, which is exactly what Think regeneration includes the previous response in the model prompt #2028 fixed for live turns.

The live interruption path (stall or transient error) already passes branchParentId into persistPartial (#2413). Only the restart paths went wrong: fiber recovery, and a client's resume ACK after hibernation. Neither has the turn's fiber snapshot where the orphan is persisted.

Fix

  • agents: ResumableStream.start() accepts an optional parentMessageId, stored in the stream row's chat metadata next to messageId and originMessageIds. getStreamParentMessageId(streamId) reads it back.
  • think: _streamResult records the turn's parentId (set for regenerations) when it opens the stream. _persistOrphanedStream appends the reconstructed message under that parent. Both orphan paths go through it. On an agents release without the getter it falls back to the current behavior.

Kept the continue behavior rather than restarting the answer: the user has already watched the partial answer stream, and every other interrupted turn keeps and continues its partial.

Tests

  • continues a regeneration interrupted after partial output on its new branch (#2028): a restored streaming row with a recorded parent. It asserts the partial lands beside a-previous, the old answer is untouched, and the continuation prompt is user then assistant: New partial, without the old answer. Mutation check: disabling the parent lookup fails it.
  • keeps a recovered partial with no recorded parent on the latest leaf: turns that do not branch are unchanged.
  • The existing regeneration test now also asserts that a live regeneration records its user message as the stream's parent, and a normal turn records none.
  • agents: the parent survives restore(), and it is null for a non-branching stream or a missing row.
  • Think's full Workers suite passes (1,222 tests), as does pnpm run check.

Devin Review

… branch

After a restart, orphan recovery appended a regeneration's partial answer
under the latest leaf, the response it was replacing, and the continuation
then extended it with that response still in the prompt. Record the turn's
parent message on the resumable stream and append the recovered partial under
it.

Co-authored-by: Cursor <cursoragent@cursor.com>
@changeset-bot

changeset-bot Bot commented Sep 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e382893

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@cloudflare/think Patch
agents Patch
@cloudflare/agent-think Patch

Not sure what this means? Click here to learn what changesets are.

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

@agent-think

agent-think Bot commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

✅ agents import sizes: no significant changes (c2a7bc54 → e3828933, workflow run)

@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: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@pkg-pr-new

pkg-pr-new Bot commented Sep 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

agents

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

@cloudflare/ai-chat

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

@cloudflare/codemode

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

hono-agents

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

@cloudflare/shell

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

@cloudflare/think

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

@cloudflare/voice

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

@cloudflare/worker-bundler

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

commit: e382893

@threepointone
threepointone merged commit 9906881 into main Sep 30, 2026
16 checks passed
@threepointone
threepointone deleted the fix-regenerate-partial-recovery branch September 30, 2026 09:22
@github-actions github-actions Bot mentioned this pull request Oct 2, 2026
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