Skip to content

fix(frontend): rewind forks into a new session instead of resurrecting - #5860

Open
mmabrouk wants to merge 1 commit into
release/v0.112.0from
fix/rewind-session-hydration
Open

fix(frontend): rewind forks into a new session instead of resurrecting#5860
mmabrouk wants to merge 1 commit into
release/v0.112.0from
fix/rewind-session-hydration

Conversation

@mmabrouk

@mmabrouk mmabrouk commented Aug 9, 2026

Copy link
Copy Markdown
Member

Mahmoud's bug: rewind to the first message, resend it, and moments later the entire "rewound" conversation reappears — hydration refetches the append-only server records under the same session id and merges them back.

Why FORK (a new session id), not supersede or anchor-respecting hydration

The rewind was broken in TWO layers, and only a new id fixes both:

  1. The backend cannot represent a truncation. Records are append-only (query/get/ingest only), with no parent/branch pointer — any local truncation loses to the next hydration.
  2. The runner remembers anyway. A warm session (or a cold one reloading via ACP session/load) answers from its own native transcript; the wire protocol has no "continue from turn N". Hiding records client-side would have made the UI show a truncated chat while the model answers as if nothing was cut — the same bug, made silent.

The fork mechanics

Rewind mints a new session id, keeps the sliced prefix locally, and the fork's FIRST request carries the transcript explicitly (replayHistory on buildAgentRequest) because the prefix was logged under the old id; the flag clears at the first onFinish, after which the runner's own continuity carries the conversation. The original session stays whole under its own id, still in History. No hydration change needed — shouldAdoptServerTranscript's existing count floor already rejects the fork's subset transcript (pinned by a named test). An info toast says "Rewound into a new chat — the original is in History."

Also recorded for later

  • A true in-place rewind is closer than expected on the backend (records already soft-delete-capable) but needs runner-side warm-pool + continuity invalidation — documented in the report, out of scope.
  • agenta-chat/src/hooks/useAgentConversation.ts:520,546 carries the same un-forked rewind, unconsumed today — needs the same treatment before anything ships it.

Verification

158 (AgentChatSlice) + 216 (playground) + 955 (entities) tests pass, including new ones for the fork atom, the replayHistory request shape, and the subset-rejection floor. tsc + eslint clean. Live repro for Mahmoud: converse, rewind to message one, resend — the old conversation must NOT come back; the original must still sit complete in History.

@vercel

vercel Bot commented Aug 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Error Error Aug 10, 2026 10:15pm

Request Review

@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. bug report Something isn't working frontend labels Aug 9, 2026
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added conversation rewind with forked sessions, preserving the original conversation in History.
    • Rewound conversations can restore a draft or automatically rerun an assistant response.
    • Ensured forked sessions replay the complete conversation history when needed.
    • Added notifications for rewind and rerun actions.
  • Bug Fixes

    • Prevented longer local conversation history from being overwritten by incomplete server transcripts.
  • Tests

    • Added coverage for rewind, draft restoration, reruns, history replay, and transcript preservation.

Walkthrough

Rewind now creates a new session from a retained message prefix. User rewinds restore an editable draft. Assistant rewinds schedule a rerun. Unlogged fork history is replayed in full requests and cleared after successful completion.

Changes

Rewind fork replay

Layer / File(s) Summary
Fork state and session ephemera
web/oss/src/components/AgentChatSlice/state/rewindFork.ts, web/oss/src/components/AgentChatSlice/state/sessionEphemera.ts, web/oss/src/components/AgentChatSlice/state/rewindFork.test.ts
Fork creation preserves the original session, seeds retained messages, tracks unlogged history, stores drafts, and schedules assistant reruns.
Transcript replay requests
web/packages/agenta-playground/src/state/execution/agentRequest.ts, web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts, web/packages/agenta-entities/tests/unit/session-transcript-adoption.test.ts, web/packages/agenta-playground/tests/unit/agentRequest.test.ts
Unlogged fork history enables full transcript requests. Successful completion clears the marker. Tests cover request history and transcript adoption.
Chat rewind integration
web/oss/src/components/AgentChatSlice/AgentConversation.tsx
The conversation UI creates rewind forks, consumes pending rerun requests, regenerates eligible turns, and notifies that the original remains in History.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AgentConversation
  participant rewindForkAtomFamily
  participant useAgentChatSession
  participant buildAgentRequest
  AgentConversation->>rewindForkAtomFamily: Create fork from retained messages
  rewindForkAtomFamily-->>AgentConversation: Return fork session ID
  AgentConversation->>useAgentChatSession: Regenerate trailing user turn
  useAgentChatSession->>buildAgentRequest: Build request with replayHistory
  buildAgentRequest-->>useAgentChatSession: Send full conversation history
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: rewinding now creates a new forked session instead of reusing the original session.
Description check ✅ Passed The description directly explains the rewind fork fix, replay history behavior, preserved original session, user notification, and related tests.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 60.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/rewind-session-hydration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Preview URL https://gateway-pr-5860.up.railway.app/w
Project agenta-oss-clone-spike
Image tag pr-5860-81f4a32
Status Deployed
Railway logs Open logs
Workflow logs View workflow run
Updated at 2026-08-10T22:26:00.240Z

Mahmoud's bug: rewind to the first message, resend, and moments later the
whole 'rewound' conversation returns - hydration re-adopts the server
records under the same session id. Chose the FORK model (new session id at
the rewind point) because the truncation was broken in TWO layers:
records are append-only with no branch pointer (a local truncation always
loses to the next hydration), and the runner's continuity would keep
answering from its own native transcript even if the UI hid the records -
a silent version of the same bug.

The fork: rewindFork mints the new session id, keeps the sliced prefix,
and the fork's FIRST request carries the transcript (replayHistory on
buildAgentRequest - the prefix was logged under the OLD id), cleared at
first onFinish, after which runner continuity owns it. The original
session stays whole under its own id in History. No hydration change
needed: shouldAdoptServerTranscript's count floor already rejects the
fork's subset transcript (pinned by a named test). One info toast tells
the user the tab became a new chat.

158 + 216 + 955 tests pass; tsc + eslint clean.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
web/packages/agenta-playground/tests/unit/agentRequest.test.ts (1)

253-262: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace any in the replay request test.

Line 260 uses any. Workspace packages must avoid any. Use a narrow structural assertion for requestBody.data.

Proposed fix
-            expect((req!.requestBody.data as any).inputs.messages).toEqual([u1, a1, u2])
+            const data = req!.requestBody.data as {inputs: {messages: unknown[]}}
+            expect(data.inputs.messages).toEqual([u1, a1, u2])

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 479ed2ba-5480-4f1d-be45-44b6254c94d0

📥 Commits

Reviewing files that changed from the base of the PR and between 4af1551 and 14b8837.

📒 Files selected for processing (8)
  • web/oss/src/components/AgentChatSlice/AgentConversation.tsx
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts
  • web/oss/src/components/AgentChatSlice/state/rewindFork.test.ts
  • web/oss/src/components/AgentChatSlice/state/rewindFork.ts
  • web/oss/src/components/AgentChatSlice/state/sessionEphemera.ts
  • web/packages/agenta-entities/tests/unit/session-transcript-adoption.test.ts
  • web/packages/agenta-playground/src/state/execution/agentRequest.ts
  • web/packages/agenta-playground/tests/unit/agentRequest.test.ts

Comment on lines +528 to +535
rewindFork({
fromSessionId: sessionId,
messages: msgs.slice(0, idx),
// A user turn re-opens for editing; an assistant turn re-runs the turn as-is
// (its user message, attachments included, is the prefix's last message).
draft: isUser ? messageText(message) : undefined,
rerun: !isUser,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Preserve attachments during a user-side rewind.

When the selected user message has file parts, Line 530 removes that message from the fork prefix and Line 533 retains only its text. The fork then cannot re-send the original attachments.

Store a typed draft payload that includes text and file parts, and initialize both composer states for the fork. Add a user-rewind test with an attachment-only message.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug report Something isn't working frontend size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant