Skip to content

fix(frontend): keep agent chat mounted across revision switches#4997

Merged
ardaerzin merged 3 commits into
big-agentsfrom
fe-fix/agent-chat-revision-remount-decouple
Jul 1, 2026
Merged

fix(frontend): keep agent chat mounted across revision switches#4997
ardaerzin merged 3 commits into
big-agentsfrom
fe-fix/agent-chat-revision-remount-decouple

Conversation

@ardaerzin

@ardaerzin ardaerzin commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Problem

In the agent playground, switching the displayed revision — the agent self-committing a new revision, or the user picking one in the config header — caused two symptoms at once:

  • the chat transcript for the in-flight turn disappeared, and
  • the chat showed "connection lost".

Both are one root cause. The conversation lives inside a generation subtree keyed by the revision id (<ExecutionItems key={variantId}> in MainLayoutAgentChatPaneluseChat). A revision switch changes that key and remounts the whole conversation:

  • on unmount, the D9 teardown aborts the in-flight stream → "connection lost";
  • on remount, useChat re-seeds messages from localStorage, which omits the still-streaming turn (persistence is deliberately skipped mid-stream) → the transcript vanishes.

A one-line "defer the switch" isn't enough: the manual picker remounts the same way, and even keeping ExecutionItems mounted isn't sufficient because ExecutionItems itself drops to its loading skeleton / swaps render modes during the fresh-revision load gap.

Fix

Decouple the conversation's mount identity from the revision id at the two layers that tear it down:

  1. MainLayout — give the single-agent generation panel a stable key instead of variantId, so a switch flows through as an entityId prop update, not a remount. (Agents are single-entity; they're excluded from comparison.)
  2. ExecutionItemslatch the agent surface so it keeps rendering across the fresh-revision load gap (where flags load a beat late: isAgent flips false and the query goes pending) instead of unmounting the chat to show the skeleton.

Both use a flip-proof latch keyed on the workflow query's pending state, released only once the entity has loaded as a definitively non-agent workflow (isPending === false ⟺ flags loaded ⟺ agent-ness is accurate).

Follow-up: keep new turns on the live revision

Removing the remount exposed a second issue in the request chain. useChat pins its Chat (and the transport it holds) for the life of the session id and does not recreate it when entityId changes. The transport's prepareSendMessagesRequest captured entityId by value, and buildAgentRequest reads the config/references from whatever id it's given — so without a remount, every subsequent turn would have gone out with the config of the revision displayed when the session first mounted. Fixed by reading entityId through a ref in the request builder, so each send resolves the currently displayed revision (restoring the pre-fix "next turn uses the new revision" behavior, without the stream-resetting remount).

Net effect: sessionId never changes across a revision switch, so useChat keeps its live stream and messages — no abort, no re-seed — and new turns still run against the live revision's config.

Scope / blast radius

  • FE-only. The conversation is already app/session-scoped in localStorage; no backend/SDK/service change and no move toward server-side sessions (not used in the first release).
  • Non-agent chat / completion / comparison and the create/edit drawer fall through to the identical original code paths (both keying changes are gated on agent-ness).

Testing

Verified live against a running big-agents EE-dev stack:

  • ✅ Manual picker switch while streaming → stream completes, transcript intact, no error.
  • ✅ Manual picker switch idle → transcript persists, no reconnect.
  • ✅ UI Commit (creates + switches revision) with a settled transcript → transcript persists.
  • Agent self-commit mid-stream (the original repro) → streaming turn survives, config refreshes to the new revision.

Also: tsc clean on all touched files, ESLint + prettier clean.

Recommended extra QA for the follow-up: after switching revisions mid-conversation, send a new message and confirm the outgoing request body carries the newly selected revision's config/references (inspect the network request) — the visual checks above don't cover which revision the next turn runs against.

The agent playground conversation lives inside a generation subtree keyed by
the revision id (`ExecutionItems key={variantId}` in MainLayout), so every
revision switch — the agent self-committing a new revision, or the user picking
one in the config header — remounted the whole conversation. The remount aborted
the in-flight stream (surfaced as "connection lost") and re-seeded messages from
localStorage, which omits the still-streaming turn (persistence is skipped
mid-stream), so the transcript for that turn vanished.

Decouple the conversation's mount identity from the revision id at the two layers
that tear it down:

- MainLayout: give the single-agent generation panel a stable key instead of the
  revision id, so a switch flows through as an `entityId` prop update, not a
  remount. Agents are single-entity (excluded from comparison).
- ExecutionItems: latch the agent surface so it keeps rendering across the
  fresh-revision load gap (where flags load a beat late, isAgent flips false and
  the query goes pending) instead of dropping to the skeleton and unmounting the
  chat.

Both use a flip-proof latch keyed on query pending state, released only once the
entity has loaded as a definitively non-agent workflow. Non-agent chat/completion/
comparison and the create/edit drawer are untouched. FE-only; the conversation is
already app/session-scoped in localStorage, so no backend change is needed.
@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Jul 1, 2026
@vercel

vercel Bot commented Jul 1, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview, Comment Jul 1, 2026 3:12pm

Request Review

@dosubot dosubot Bot added bug Something isn't working Frontend labels Jul 1, 2026
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4508a460-fbee-4a5c-9ff5-19653e743017

📥 Commits

Reviewing files that changed from the base of the PR and between b66e3fb and 0e6eb2f.

📒 Files selected for processing (3)
  • web/oss/src/components/AgentChatSlice/AgentChatPanel.tsx
  • web/oss/src/components/Playground/Components/MainLayout/index.tsx
  • web/packages/agenta-playground-ui/src/components/ExecutionItems/index.tsx

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.


📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved stability when switching entities or revisions in the playground and chat view.
    • The active agent/generation panel now stays visible during brief loading gaps instead of flashing away or resetting.
    • Messages are now sent with the latest selected entity, reducing the chance of content being routed to the wrong context.

Walkthrough

The changes keep agent chat requests tied to the latest entityId without recreating transport on entity switches, and they latch agent-generation rendering in Playground so the agent UI persists across entity/revision transitions and pending states.

Changes

Agent UI/transport stability across entity switches

Layer / File(s) Summary
Agent chat transport uses ref for latest entityId
web/oss/src/components/AgentChatSlice/AgentChatPanel.tsx
prepareSendMessagesRequest reads entityId from a ref updated each render, and the transport useMemo depends only on sessionId so entity switches do not recreate the transport.
MainLayout latches agent generation host across switches
web/oss/src/components/Playground/Components/MainLayout/index.tsx
Adds useMemo, computes single-entity agent-mode and workflow pending state, and uses agentHostRef to keep ExecutionItems mounted with a constant key while the agent state is latched.
ExecutionItems latches AgentGenerationPanel through pending states
web/packages/agenta-playground-ui/src/components/ExecutionItems/index.tsx
Adds useRef and an agentSurfaceRef latch that returns AgentGenerationPanel early during pending transitions, bypassing the loading skeleton and normal branch selection.

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

Possibly related PRs

  • Agenta-AI/agenta#4904: Touches the same buildAgentRequest / prepareSendMessagesRequest path used by the agent chat transport change.
  • Agenta-AI/agenta#4936: Also affects entity/revision switching behavior that drives the chat and playground entity updates.
  • Agenta-AI/agenta#4978: Modifies related AgentChatPanel run/send behavior in the same playground flow.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main frontend fix: keeping agent chat mounted across revision switches.
Description check ✅ Passed The description accurately explains the chat remount issue and the frontend fix in the touched components.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fe-fix/agent-chat-revision-remount-decouple

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

…switch

Follow-up to the mount-decoupling fix. `useChat` pins its `Chat` (and the
transport it holds) for the life of the session `id`; it is not recreated when
`entityId` changes. The transport's `prepareSendMessagesRequest` captured
`entityId` by value, and `buildAgentRequest` reads the config/references from
whatever id it's given — so once the conversation no longer remounts on a
revision switch, every subsequent turn would have gone out with the config of
the revision that was displayed when the session first mounted.

Read `entityId` through a ref inside the request builder so each send resolves
the currently displayed revision. This restores the pre-fix behavior (a switch
or a self-commit makes the next turn run against the new revision) without the
remount that used to reset the stream.
@ardaerzin

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 10d6c19c-8d2f-40a4-9382-8d85f1ef58a8

📥 Commits

Reviewing files that changed from the base of the PR and between b66e3fb and 04e7309.

📒 Files selected for processing (3)
  • web/oss/src/components/AgentChatSlice/AgentChatPanel.tsx
  • web/oss/src/components/Playground/Components/MainLayout/index.tsx
  • web/packages/agenta-playground-ui/src/components/ExecutionItems/index.tsx

Comment thread web/packages/agenta-playground-ui/src/components/ExecutionItems/index.tsx Outdated
Addresses a review note. The latched agent branch rendered ExecutionHeader,
which self-nulls for agents (`if (isAgent) return null`). But during the
revision-switch load gap the new revision's flags aren't loaded yet, so isAgent
is momentarily false — ExecutionHeader then flashed the non-agent header (Clear /
Run all) and registered the run-all shortcut over the agent chat until the
revision resolved. Agents own their composer and never show this header, so the
latched branch skips it. Mount stability is unchanged (the panel keeps its
position across the gap).
@ardaerzin ardaerzin merged commit 1bdd3d1 into big-agents Jul 1, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working Frontend size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant