Skip to content

fix(frontend): Keep the agent session streaming when you navigate away - #5862

Open
ashrafchowdury wants to merge 8 commits into
release/v0.112.2from
fix/sessions-continuesion-after-page-swtich
Open

fix(frontend): Keep the agent session streaming when you navigate away#5862
ashrafchowdury wants to merge 8 commits into
release/v0.112.2from
fix/sessions-continuesion-after-page-swtich

Conversation

@ashrafchowdury

Copy link
Copy Markdown
Contributor

Context

Start an agent run in the playground, switch to another page while it is still producing, and come back: the answer had stopped streaming. The turn was still alive on the runner, but the browser was no longer following it. The tab fell back to the 15s durable-log catch-up, so the rest of the answer arrived in jumps instead of live.

The cause was ownership. useChat created its Chat inside the conversation component, so the SSE read lived and died with the mount, and the D9 teardown effect called stop() on every unmount. A route change is an unmount, so it looked exactly like closing the tab. Fixes #5724.

Changes

The Chat instance now lives in a small module-scoped registry keyed by session id (state/chatRegistry.ts), and the component borrows it instead of owning it.

Before: unmount always aborted the stream.

useEffect(() => () => stop(), [sessionId, stop])

After: unmount asks whether the session itself is gone. A route change leaves the tab open, so the chat stays and the stream keeps running; re-entering the route re-binds to the same instance mid-turn. Closing, deleting, or archiving the session removes it from the open-tab set first, so that path still stops the stream and drops the instance.

const stillOpen = store.get(openSessionIdsAtomFamily(scopeKey)).has(sessionId)
releaseSessionChat(sessionId, {stillOpen})

Because the chat now outlives the mount, its callbacks (prepareRequest, sendAutomaticallyWhen, onFinish) are rebound on every acquire. That is what keeps a long-lived chat from running stale closures, and it is why a run still follows a revision switch or a self-commit rather than sticking to the revision the session first mounted on.

One subtlety is load-bearing and worth knowing while reading the diff: the registry must never hand useChat a fresh instance under a session id it already rendered. useChat swaps its internal ref on identity change but keys its message subscription on the chat id, which does not change, so it would keep listening to the dropped instance and the transcript would freeze. Keeping the entry alive for as long as the tab is open is what guarantees that. The trade-off is one idle Chat per open tab until that tab is closed or the page reloads.

Tests

  • chatRegistry.test.ts covers the acquire/release policy in 6 cases: re-bind on remount, preserve a streaming and a submitted chat across a navigation, keep an idle chat while its tab is open, tear down when the session is no longer open, and forward a settled turn to the current mount's onFinish.
  • Full slice suite green (vitest run src/components/AgentChatSlice, 18 files, 132 tests). tsc --noEmit and eslint clean on @agenta/oss.
  • Worth a reviewer's eye: the teardown reads "is this session still open?" from the open-tab set at cleanup time. That is correct because every close, delete, archive, and reset writer in state/sessions.ts removes the id from openIdsByAppAtom before React runs the cleanup. If a new teardown path is ever added, it has to follow the same order.
  • Known gap, not fixed here: a session archived from another device unmounts its pane while its id is still in the open list, so its chat lingers until reload. Closing that means letting an archive stop a running turn, which is a product call rather than a bug fix.

What to QA

  • Start a run in the agent playground. While it is streaming, go to Observability, then come back. The same turn is still streaming into the same bubble, no reload needed, no gap in the text.
  • Do the same but wait on the other page until the run finishes. Coming back shows the completed turn, and it survives a reload.
  • Start a run, then close that session tab while it is streaming. The run stops, as before.
  • Open a session and send a message as your very first action. The reply streams in progressively and your own message stays on screen. This is the regression the registry change is most likely to have broken, and it only shows up in dev.
  • Regression: reopen a closed session from the history picker. Its transcript rehydrates from the record log as before.
  • Regression: switch revisions (or let the agent commit a new revision of itself) in a session with history, then send another message. The turn runs on the new revision.

…c to maintain chat instances across navigation
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Aug 10, 2026
@vercel

vercel Bot commented Aug 10, 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 Aug 19, 2026 11:29am

Request Review

@dosubot dosubot Bot added bug report Something isn't working frontend labels Aug 10, 2026
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 4a3e7168-ca9f-4f03-abf5-09a94b86518c

📥 Commits

Reviewing files that changed from the base of the PR and between 96651c2 and 3fc2ec6.

📒 Files selected for processing (2)
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts
  • web/oss/src/components/pages/sessions/assets/sessionRouteScope.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts

Included review availability: Your plan includes up to 8 reviews per rolling hour; 7 remain after this review.


📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Preserved active chat sessions when navigating away and returning, including in-progress responses.
    • Prevented unnecessary stream interruptions when a session remains open.
    • Improved cleanup of chats for closed, deleted, archived, or reset sessions.
    • Ensured completed responses continue to trigger the appropriate completion handling after remounts.
    • Improved chat status handling so sessions remain accurately marked while activity continues.

Walkthrough

The PR adds a session-scoped chat registry. useAgentChatSession reuses shared Chat instances across remounts and releases them based on session state. Session cleanup paths remove closed chats. Tests cover reuse, preservation, disposal, callback binding, and teardown.

Changes

Session chat persistence

Layer / File(s) Summary
Session chat registry lifecycle
web/oss/src/components/AgentChatSlice/state/chatRegistry.ts
Adds registry-owned Chat instances, hook rebinding, busy-state detection, error handling, and release behavior for closed sessions.
Session hook integration
web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts, web/oss/src/components/AgentChatSlice/AgentConversation.tsx, web/oss/src/components/pages/sessions/assets/sessionRouteScope.ts
Uses the shared chat for request preparation, approval resumption, completion callbacks, busy-state checks, status handling, and unmount cleanup.
Session cleanup and registry validation
web/oss/src/components/AgentChatSlice/state/sessions.ts, web/oss/src/components/AgentChatSlice/state/chatRegistry.test.ts, web/oss/src/components/AgentChatSlice/state/sessions.teardown.test.ts
Drops chat state when sessions close, delete, archive, reconcile as archived, disappear remotely, or reset. Tests verify reuse, open-session preservation, callback binding, disposal, and teardown behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: ⚪ Minimal · up to 3fc2e

The change keeps active agent sessions streaming across navigation while preserving shutdown when a session is closed, deleted, or archived; no actionable merge-blocking risk remains beyond normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant AgentChatSession
  participant SessionChatRegistry
  participant SessionState
  participant Chat
  AgentChatSession->>SessionChatRegistry: acquireSessionChat(sessionId, hooks)
  SessionChatRegistry->>Chat: create or reuse session chat
  Chat-->>AgentChatSession: provide shared chat instance
  AgentChatSession->>SessionChatRegistry: releaseSessionChat(sessionId, stillOpen)
  SessionState->>SessionChatRegistry: dropSessionChat(sessionId) when session closes
  SessionChatRegistry->>Chat: stop and remove closed chat
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary fix: preserving agent session streaming when users navigate away.
Description check ✅ Passed The description explains the streaming issue, registry-based fix, lifecycle behavior, tests, and QA scenarios.
Linked Issues check ✅ Passed The registry preserves active agent runs across navigation and stops them when sessions close, satisfying issue #5724.
Out of Scope Changes check ✅ Passed The changes support session chat persistence and lifecycle cleanup; the route-formatting change is behavior-neutral and minor.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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 fix/sessions-continuesion-after-page-swtich

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.

@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: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: dda9c5a8-2ea1-43be-ba15-ba583abd5e42

📥 Commits

Reviewing files that changed from the base of the PR and between adec2aa and cd5e882.

📒 Files selected for processing (3)
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts
  • web/oss/src/components/AgentChatSlice/state/chatRegistry.test.ts
  • web/oss/src/components/AgentChatSlice/state/chatRegistry.ts

Comment thread web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts Outdated
Comment thread web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts Outdated
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Preview URL https://gateway-pr-5862.up.railway.app/w
Project agenta-oss-clone-spike
Image tag pr-5862-e55b759
Status Deployed
Railway logs Open logs
Workflow logs View workflow run
Updated at 2026-08-19T11:39:53.397Z

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/oss/src/components/AgentChatSlice/state/chatRegistry.ts (1)

47-65: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Do not publish a new registry entry during render.

useAgentChatSession calls acquireSessionChat during render (the supplied web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts snippet, Lines 65-224). Lines 49-65 create and register a Chat before any commit. If React abandons that first render, no effect cleanup runs. A later committed mount reuses the entry at Line 47, ignores its own initialMessages, and can retain the abandoned chat indefinitely.

Make registry ownership commit-aware. Keep a new entry provisional until a committed mount claims it, or discard uncommitted entries with a tokenized protocol. Add a regression test for an abandoned first acquisition with different initialMessages.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 23cb2404-9619-4daa-89e6-4d3d055919e5

📥 Commits

Reviewing files that changed from the base of the PR and between cd5e882 and 72b6837.

📒 Files selected for processing (4)
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts
  • web/oss/src/components/AgentChatSlice/state/chatRegistry.test.ts
  • web/oss/src/components/AgentChatSlice/state/chatRegistry.ts
  • web/oss/src/components/AgentChatSlice/state/sessions.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts

Comment thread web/oss/src/components/AgentChatSlice/state/chatRegistry.ts
Comment thread web/oss/src/components/AgentChatSlice/state/sessions.ts Outdated
@bekossy
bekossy changed the base branch from main to release/v0.112.2 August 18, 2026 14:34
@bekossy

bekossy commented Aug 18, 2026

Copy link
Copy Markdown
Member

Thanks for the PR @ashrafchowdury. Please resolve the merge conflicts here

Resolves three conflicts, keeping both sides' behaviour:

- useAgentChatSession: this branch moved sendAutomaticallyWhen/onFinish/onError
  out of `useChat` onto the registry-owned chat's per-mount hooks; release grew
  those same callbacks in place. onFinish now carries both the preserved-run dot
  retirement and release's session-liveness + session-list invalidation (#5844).
- chatRegistry: onError joins the SessionChatHooks contract (gated on isLive like
  onFinish) so release's "clear the pending gate marker on a failed stream" fix
  has somewhere to land; the F-033 console.warn stays unconditional.
- AgentConversation: comment-only. Dropped release's now-stale "reset to idle on
  unmount" sentence (that reset is conditional on hasSessionChat here) and kept
  its hitlPending paragraph.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@ashrafchowdury

Copy link
Copy Markdown
Contributor Author

Fixed @bekossy

One conflict in useAgentChatSession.ts. Release added the bounded-request
feature (#6042): buildRequestWithinDeadline retries while the invocation URL
is still loading and rejects if the build hangs, so a stalled send surfaces
as an error bubble instead of an eternal spinner. Release wired it into the
old useMemo AgentChatTransport; this branch moved request-building into the
registry-owned chat's hooks.prepareRequest, so the bounded build now lives
there (reading entityId/captureTurnRequest directly, not via refs) and the
helper owns the not-ready / timed-out errors. Dropped the stale
AgentChatTransport import (the registry owns the transport here).

Also normalized formatting on sessionRouteScope.ts (pre-commit prettier).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@bekossy

bekossy commented Aug 19, 2026

Copy link
Copy Markdown
Member

@coderabbitai please resolve merge conflict here

@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Merge conflicts resolved successfully!

Resolved 1 conflict file(s). Commit: 4c2ed3a45d8fb909a62e26d0126b6f5c9be41f26 pushed to fix/sessions-continuesion-after-page-swtich.

The resolved commit is ready for your repository's normal checks and review.

6 file operation(s)
  • web/oss/src/components/AgentChatSlice/AgentConversation.tsx (update)
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts (update)
  • web/oss/src/components/AgentChatSlice/state/chatRegistry.test.ts (update)
  • web/oss/src/components/AgentChatSlice/state/chatRegistry.ts (update)
  • web/oss/src/components/AgentChatSlice/state/sessions.teardown.test.ts (update)
  • web/oss/src/components/AgentChatSlice/state/sessions.ts (update)
View agent analysis

Resolved conflicts in:
- web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts (content)

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

@bekossy bekossy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the work here. The registry direction makes sense for preserving a live stream across route changes, but I don’t think this is ready yet.

Two things still need addressing:

  1. acquireSessionChat() still creates and stores a new Chat in the module registry during render. Moving hook rebinding to an effect helps with stale callbacks, but it doesn’t cover the first-acquire case: if React abandons that first render, the registry can retain a chat seeded from an uncommitted render, and the next committed mount will reuse it with the wrong initialMessages. Can we make the first registry claim commit-aware too, and add a regression test for an abandoned first acquisition with different initialMessages?

  2. Remote archive reconciliation drops the chat and marks the session archived, but it doesn’t remove that id from the open-tab list or update the active-session atom. Since some child hooks read the raw active atom, the visible fallback tab may not be treated as active for pending runs, shortcuts, or record watches. Can we mirror the local archive behavior here: remove the archived id from openIdsByAppAtom, repoint activeByAppAtom to the next available open tab, and add coverage for that case?

Once those are fixed, I think the approach should be in good shape.

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.

[bug] Agent run stops when leaving playground page

2 participants