Skip to content

feat(sessions): let the sender read from the shared path and detach the run from its request - #6531

Merged
mmabrouk merged 25 commits into
feat/session-live-eventsfrom
feat/session-shared-sender
Sep 5, 2026
Merged

feat(sessions): let the sender read from the shared path and detach the run from its request#6531
mmabrouk merged 25 commits into
feat/session-live-eventsfrom
feat/session-shared-sender

Conversation

@mmabrouk

@mmabrouk mmabrouk commented Sep 4, 2026

Copy link
Copy Markdown
Member

Context

The browser that starts a turn still renders from its own invoke stream, and closing or refreshing that browser can end the run. This is increment 5 of the session-control design on PR #6495: the sender joins the shared read model from PR #6522 and PR #6524, and the execution's lifetime no longer depends on the request that started it.

Changes

  • Behind AGENTA_SESSIONS_SHARED_READER plus a capability the desktop reads from the snapshot: the sender still submits through invoke, but renders from the shared events route like any other reader; the invoke stream is consumed only for acceptance and errors. Off means today's behavior.
  • With the switch on, a dropped invoke connection no longer cancels the turn: the runner keeps running and reports through records and frames, and the API ends its response without cancelling. The Stop path is untouched.
  • On refresh, the sender loads the snapshot, sees the running execution, follows the events route after latest_sequence, and the live text continues, with "This turn is still running" copy.
  • Mobile follows through the shared hook.

Tests

  • See the status notes on PR docs(sessions): collect overnight reviews, spikes, and test evidence #6505 for the exact counts; the branch ran the API sessions suite, the runner suite where touched, and the web package suites.
  • Whole-PR review by an Opus agent: the switch-on path is coherent and tested (no double render, invoke used only for acceptance and errors, a detached run survives a dropped invoke). One conflict with the reconnect base was decided by the design rule that the switch off changes nothing: the runner's disconnect gate keeps session-owned runs alive as before AND detached runs in addition. Round 2 in progress: that rule with tests for all three cases, the rebase onto feat(sessions): durable reconnect with a per-session sequence, snapshot, and replay #6524 at 0045c04573, two flag-off leaks closed (no snapshot request and no "still running" strip when the capability is not advertised), an end-to-end error-survival test, and the contract note for detached and session-accepted. The live proof (closing or refreshing the sender does not stop the run) follows the reconnect proof on the relay stack.

Agent-generated, low weight. Not merged.

https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV

Round 2 (2026-09-04 afternoon)

Head b78b12a. Fixes the round 1 findings F1, F2, F4, F5, F6, and F8 with regression tests; F3 is declined with the rationale in the branch notes. Rebased onto the #6524 head 664d1b7; the ready handler now completes the durable replay with the watermark before it reports readiness. Suites after the rebase: chat 664 passed, type check clean; before the rebase the job reported runner 2,690, playground 267, OSS 442, mobile 144, API unit 545 and integration 16. A browser pass on a live stack is still owed before this PR is done.

Browser pass and the strip fix (2026-09-04 evening)

The browser pass on the proof stack proved scenarios 1 to 4: the sender renders once from the session events, a reload mid-turn re-attaches with the running strip and live text, a normal turn stays warm and the codeword comes back, and Stop settles the turn with no output after it. It found one bug: on a reloaded tab the "still running" strip stayed visible across later completed turns. Head 05f517f fixes it: the snapshot-derived running state clears when the live state reports the turn ended; a regression test keeps the reload-time strip and asserts it hides on running=false (chat 665 passed, type check clean). Still owed: a direct read of the x-ag-session-response header and the mobile check (a fresh-profile re-run is in progress), and the old-server fallback check on a stack without shared_reader.

Fix of 2026-09-04, evening: a failed-turn card that survived a reload

The browser pass opened two tabs on one session, approved and sent from the second tab while the first was in the background, then reloaded the first tab. It showed the continued turn as "The agent run failed: Could not reach Agenta" although the turn had completed server-side. Cause: when the sender's invoke stream died, the transport error was stamped on the acceptance carrier row and persisted to local storage; that extra row made the transcript adoption floor rule refuse the server transcript. Fix (33c3cde082): on the shared-sender path only, a control-only row with a transport error is dropped from the seed, the persisted copy, and the adoption count when the server had accepted the turn; a message that never got an acceptance keeps its card, on desktop and on mobile. Adoption also clears the stale error the chat hook holds, so the session dot no longer stays red beside a completed turn. 10 new tests; chat 675 pass, desktop chat slice 286 pass. Browser re-check on the proof stack follows.

Browser re-check of 2026-09-04, evening, on the proof stack at merged head 2bf1944425

Fresh browser profile and account, local storage cleared, two tabs on one session throughout. Evidence: 14 screenshots under ~/agenta-qa-evidence/2026-09-04-inc5-browser/recheck/.

  • Tab A sends a turn that stops at an approval; tab B mirrors the pending approval live without sending anything. Pass (01, 02).
  • Tab B approves and sends one more message; tab A reloads: both turns complete, no failure card, no "Could not reach Agenta", session dot not red. Pass (03 to 05c).
  • Tab A brought to the foreground without a reload: it picks up its own turn and clears the "running elsewhere" strip live. Pass (07).
  • Negative control, a server-reported failure (an invalid provider key): the failure card stays after a reload. Pass (08, 09).
  • Negative control, a send that never reached the API (client connection exhaustion with two open streams): "Could not reach Agenta" stays after a reload. Pass (06, 06b).
  • Strip fix: tab A shows the strip while tab B runs a turn; after that turn ends and tab A reloads, the strip is gone. Pass (10, 11).
  • Mobile hello on /m: pass (12).

@vercel

vercel Bot commented Sep 4, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
agenta-documentation Ready Ready Preview Sep 5, 2026 10:33am UTC

Request Review

@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (1)
  • release/.*

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

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

Review profile: CHILL

Plan: Team

Run ID: 860d90d0-ccdc-42fc-838b-1117116e7f97

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@mmabrouk mmabrouk left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agent-generated review, low weight. A human must confirm each point before acting.

Reading guide:

  1. Start at services/runner/src/server.ts:453 — the detach gate is the load-bearing change and the main risk.
  2. Then web/packages/agenta-chat/src/hooks/useSessionLivePreview.ts — the sender now always subscribes; check the off path.
  3. web/packages/agenta-chat/src/transport/AgentChatTransport.ts and src/model/livePreview.ts hold the invoke-as-acceptance filter.
  4. web/packages/agenta-playground/src/state/execution/agentRequest.ts sets flags.detached and the header only after the event route is ready.
  5. Tests: server.test.ts (detach on and off), livePreview.test.ts (reducer + snapshot running), agentRequest.test.ts (request on and off).

Verdict: the switch-on path is coherent and well tested. The main items are two off-path leaks (an unconditional snapshot fetch and a running strip that shows with the flag off) and one base-rebase conflict on the runner disconnect gate. The Stop path is untouched, the copy exists, and mobile shares the hook. The PR already reports mergeStateStatus DIRTY, which is the rebase conflict below.

Comment thread services/runner/src/server.ts Outdated
Comment thread services/runner/src/server.ts Outdated
Comment thread services/runner/src/server.ts
Comment thread web/packages/agenta-chat/src/hooks/useSessionLivePreview.ts Outdated
Comment thread web/packages/agenta-chat/src/hooks/useSessionLivePreview.ts Outdated
Comment thread web/packages/agenta-chat/src/hooks/useSessionLivePreview.ts Outdated
Comment thread web/packages/agenta-playground/src/state/execution/agentRequest.ts
Comment thread web/packages/agenta-chat/src/transport/AgentChatTransport.ts
Comment thread services/runner/src/server.ts
Comment thread services/runner/src/server.ts
Comment thread web/packages/agenta-playground/src/state/execution/agentRequest.ts
@mmabrouk

mmabrouk commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

Round 2 review — verdict: SHIP after the owed browser QA

Read the full diff against feat/session-durable-reconnect (37 files, SDK + runner + web) and ran every suite. The switch-on path is coherent: invoke is consumed only for acceptance and errors, live text and tools render from the events route, the acceptance carrier is filtered from the transcript and from persistence, and the capability gate keeps the flag-off path byte-identical to today. No blockers.

Round 1 findings

  • F1 (rebase conflict + narrowing) — resolved. The gate is if (!sessionOwned && !detached), so every session-owned run survives disconnect as the reconnect base intended, plus detached. Three-case regression test added. (!detached is redundant — see inline nit.)
  • F2 (aborted client-gone legacy session parked warm) — resolved. Session-owned runs take the else branch that sets clientDisconnected=true; abort() is wired only for genuinely non-session runs, which have no warm park. shouldPark destroys a client-gone session at turn end.
  • F3 (executionId: turnId) — declined, and the decline is well-founded. The client never reads the acceptance payload's ids; it correlates through the events route and the durable snapshot on turnId. The event is transient and unpersisted, so it cannot break correlation or pollute cold replay. See inline note.
  • F4 (snapshot probe when off) — resolved. The effect early-returns on !sharedReaderAdvertised before any fetch. Test added.
  • F5 ("still running" strip leaks when off) — resolved. The returned runningFromSnapshot is gated sharedReaderAdvertised && ….
  • F6 (teardown/reopen every turn) — resolved. subscribed for a sender no longer depends on runningElsewhere/busy; the effect deps changed. Test proves the connection survives a remote-running toggle.
  • F7 (contract docs) — land-together. The contract note exists on the RFC branch, not this diff. Acceptable under this design's doc split; confirm both land together. See inline note.
  • F8 (implicit error-survival invariant) — resolved. Two tests lock it (transport passes error through; the hook renders a shared-carrier error).

Rebase seam (#6524 round 4)

Clean. onReady completes the durable replay with the watermark, then notifies readiness. The new running-state updates sit after the shouldRefetchSessionTranscript gate, but execution.* events are all typed, so they always pass the gate and the updates fire; only a duplicate-sequence event is skipped, correctly.

Test counts (all run locally in the worktree)

Suite Result
@agenta/chat 664 passed
runner test:unit 2690 passed, 0 failed
API unit/sessions 505 passed, 41 skipped (Postgres-gated DAO)
SDK agents (composition seam + wire contract) 83 passed
@agenta/playground 267 passed
@agenta/mobile 144 passed
@agenta/oss 442 passed, 1 skipped

Note: the runner suite reported 0 failures, not the 4 pre-existing failures in gateway-run-turn-composition.test.ts the brief warned about. Either the base or this stack cleared them.

Browser QA still owed (the PR agrees)

The live UI is not done until a real browser pass proves it:

  1. Sender renders from events, not invoke — start a turn with the flag on; text, reasoning, and tool progress appear, and invoke shows no duplicate rendering.
  2. Reload mid-turn re-attaches — refresh the sender during a turn; the run continues, the "This turn is still running" strip shows, and live text resumes from the events route.
  3. Warm-park behavior — a normal detached turn leaves the session warm for the next turn; a mid-turn refresh continues the run to completion but the next turn is cold (matches reconnect policy — confirm this is the intended trade).
  4. Stop during a shared-path turn — Stop cancels the run and the UI settles to cancelled.
  5. Old-server fallback — with shared_reader not advertised, the sender renders from invoke exactly as before: no snapshot request, no strip, no x-ag-session-response header, no flags.detached.

Agent-generated, low weight.

@mmabrouk

mmabrouk commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

Agent-generated, low weight.

Browser QA on increment 5 found a failed-turn card that survived a reload. Fixed in 33c3cde.

Symptom. Two tabs on one session. The second tab approves and sends while the first is backgrounded. The first tab goes stale, and after a manual reload it shows that turn as "The agent run failed: Could not reach Agenta. Check your connection and retry." The turn had completed server-side. Evidence: ~/agenta-qa-evidence/2026-09-04-inc5-browser/rerun/.

Cause. Under the shared sender the invoke stream carries acceptance and errors only, so it can die while the runner finishes the turn and writes it to the durable log. Two lines then treat that dead stream as a verdict on the turn:

  • web/packages/agenta-chat/src/hooks/useAgentConversation.ts:661 and the desktop copy at web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts:379 stamp the parsed stream error onto the last assistant row, and the persist effect writes it to localStorage, so a reload paints the card before any fetch.
  • That row is one message the server transcript does not have, so the floor rule at web/packages/agenta-entities/src/session/core/transcriptAdoption.ts:65 refuses the durable transcript while the log has the user turn but not yet the answer. The card then stays until something else revalidates.

Classification: exposed by this PR, on top of a habit main already had. On main the invoke stream IS the answer, so the stamp lands on a message the server also has and the next adoption replaces it.

One measured detail decided the shape of the fix. The runner marks the acceptance frame transient (services/runner/src/server.ts:484), and the AI SDK hands a transient data part to onData without putting it in parts. So the row a dropped stream leaves has sharedSender, a transport runError, and parts: [step-start]. It is neither empty nor holding an acceptance part.

Fix. The hook records whether the runner accepted the turn in flight, since onData is the only place that answer exists. The error effect writes turnAccepted beside the runError. withoutDeadSenderAcceptance drops a row only when all three hold: the server accepted the turn, the error is transport class, and the row carries no answer of its own. Both hosts apply it when seeding from the cache, when persisting on settle, and for the count the adoption guard compares with the log. Adoption also clears the error useChat holds, so the session dot no longer stays red beside a completed turn.

Everything else keeps its card: a send that never got an acceptance may never have started, the legacy path emits no acceptance at all, a row with content is a turn, and a server verdict such as a 422 has no transport flag. The rendered transcript is not filtered, so the tab that failed still shows the card and its retry button, live.

Not fixed, and stated here on purpose: dropping the control row makes the persisted tail a user message, so the stranded-tail check can stamp "This message never reached the agent" in a narrow window where the whole record log is still empty and liveness reports not running. That false card heals at the next adoption. Closing it properly needs durable per-turn acceptance state, which is a larger change than the bug.

Tests. 10 new tests in @agenta/chat: the accepted control row is dropped, a failure with no acceptance is kept, a turn with content is kept whole, a server verdict is kept, and two integration tests drive the shared invoke stream, one accepting the turn and then dropping the connection, one rejecting the request outright. Neutralising the guard's first condition fails 3 of them.

  • pnpm --filter @agenta/chat test: 63 files, 675 tests pass
  • vitest run src/components/AgentChatSlice in web/oss: 28 files, 286 pass, 1 skipped
  • types:check, lint, tsc --noEmit --incremental false in web/oss, and prettier --check on all 9 files: clean

What a browser re-check must do. Clear localStorage first. Open one session in two tabs, send a turn from tab 1 that stops at an approval, background tab 1, then approve and send from tab 2 and let the turn finish. Reload tab 1: it must show the completed turn, with no red card and no red session dot. Repeat and foreground tab 1 without reloading: it must converge on its own. Two negative controls must still show a card that survives a reload: a send refused for a real reason such as a missing model key, and a send made with the API down. Repeat the second control on /m, which has no stranded-tail recovery.

https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV

@mmabrouk
mmabrouk marked this pull request as ready for review September 4, 2026 16:36
@mmabrouk

mmabrouk commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

Review finding: emit acceptance only after preflight succeeds

At current head 33c3cde0827e, the runner emits session-accepted before validating the attachment limit and before initializing session persistence: services/runner/src/server.ts:475-495.

The client treats that event as proof that the server owns the turn. If the invoke connection then drops, it deliberately removes the transport-error carrier from persisted browser state because the durable server transcript is expected to replace it. That assumption is false for an over-limit attachment request. The runner emits acceptance, rejects the request, and returns before writing any session record.

I confirmed the sequence with a temporary regression probe. Adding detached: true to the existing over-limit test kept runCalls === 0 and session API calls empty, but changed the response from one rejection record to two records: acceptance followed by rejection. If the connection drops between those records, the specific rejection can disappear on reload because the client saw acceptance while the server persisted nothing.

Suggested fix: emit session-accepted only after preflight validation and session persistence setup complete, immediately before starting the run. Add a detached over-limit regression test that asserts no acceptance event is emitted for a rejected turn.

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Status Destroyed (PR closed)

Updated at 2026-09-05T10:50:52.340Z

@mmabrouk mmabrouk left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

🤖 The AI agent says:

This PR detaches an accepted run from the sender request and moves the sender onto the shared live path.

Focused simplification review using the new simplify skill: one non-blocking suggestion below. This is a static design/code review, not a rerun of the live release gate or a replacement for existing correctness reviews.

Comment thread web/packages/agenta-chat/src/model/error.ts Outdated
@mmabrouk

mmabrouk commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

🤖 The AI agent says:

Correctness follow-up at 33c3cde0827e7e6d60e9e471a1bbb5d7e4efcb29: reviewed detached-request lifetime, shared-response filtering, and subscription/reconnect lifecycle, with the simplify principles in mind. I found no additional actionable correctness issue beyond the existing review threads. This was a focused static pass; I did not rerun the frontend, runner, or live release-gate suites. Existing unresolved findings remain for their owners to address.

@mmabrouk mmabrouk left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agent-generated Codex review, low weight.

CHANGES NEEDED

The detached SDK/runner seam, capability-off path, acceptance-row cleanup, two-tab reload adoption, and terminal snapshot clearing are coherent in the lane delta. Four ownership/classification gaps remain in the shared sender handoff; details are inline.

Focused hook verification passed 11/11. Commit 6633830 only reformats tests, changes one unit fixture hostname, and narrowly allowlists the ApiKey test fixture fingerprint; it removes no assertion and does not hide the currently red services acceptance check (three evaluator-trace lookup timeouts).

Comment thread web/packages/agenta-chat/src/model/error.ts
Comment thread web/packages/agenta-chat/src/hooks/useAgentConversation.ts Outdated
Comment thread web/packages/agenta-chat/src/transport/AgentChatTransport.ts
Comment thread web/packages/agenta-chat/src/hooks/useAgentConversation.ts Outdated

@mmabrouk mmabrouk left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agent-generated Codex review, low weight.

SHIP

The four requested fixes survive the rebase onto d0c98b1:

  • Accepted runs remain pending after an invoke disconnect, gating queue release, busy state, rewind, regenerate, and resend until the shared reader observes stopped, failed, or lost. A relay-only outage closes or errors the stream and drives snapshot-first retries after 5s, 10s, 20s, then at most 30s; a terminal snapshot calls onExecutionSettled, so the bound is the current retry delay plus snapshot request latency once terminal state is observable. If the snapshot endpoint itself never responds, the client has no authoritative terminal state and deliberately remains pending.
  • prepareRequest latches shared versus legacy before the asynchronous request build in both engines, and legacy-owned turns suppress shared preview rendering.
  • data-agent-error parts preserve server provenance, so browser-like text such as Failed to fetch is not reclassified as a client transport failure.
  • Both engines reset turnAcceptedRef before invoking the request builder, preserving correct two-turn rejection behavior.

The autosquash folded 1af3f53 into the rewritten response-source fix at 59e873c. The final remote head is 1e7f250, still based on feat/session-durable-reconnect.

Validation: chat 64 files / 683 tests; OSS 50 files / 442 passed / 1 skipped; mobile 19 files / 144 tests. types:check and lint pass for chat, OSS, mobile, and playground; the mobile lint run reports four existing hook-dependency warnings and zero errors. No new correctness findings.

With two tabs on one session, the backgrounded tab showed the turn as
"The agent run failed: Could not reach Agenta" after a reload, even though
the turn had completed server-side.

Under the shared sender the invoke stream carries acceptance and errors
only, so it can die while the runner finishes the turn and writes it to
the durable log. The client stamped that dead stream onto the last
assistant row and persisted it to localStorage, so the reload painted the
card before any fetch. That row is also one message the server transcript
does not have, so the adoption guard's floor rule refused the durable
transcript and kept the card on screen.

The hook now records whether the runner accepted the turn in flight. The
acceptance frame is transient, so onData is the only place that answer
exists. The error effect writes it beside the runError, and the durable
filter drops a row only when the server accepted the turn, the error is
transport class, and the row carries no answer of its own. Both hosts use
that filter when they seed from the cache, when they persist on settle,
and for the count the adoption guard compares with the log. Adoption also
clears the error useChat holds, so the session dot no longer stays red
beside a completed turn.

Everything else keeps its card. A send that never got an acceptance may
never have started, the legacy path emits no acceptance at all, a row with
content is a turn, and a verdict the server issued is never touched. The
rendered transcript is not filtered, so the tab that failed still shows
the card and its retry button.

Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
Classify a sender transport loss after acceptance as an ephemeral warning shared by desktop and mobile. Keep unaccepted sends and server verdicts on the durable failure-card path, and leave only the normal SDK control carrier filter in transcript handling.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Describe sequence writes as its own rollout gate now that the referenced consolidation PR is closed.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Format the chat regressions with the repository-wide Prettier rules and suppress the known false-positive OTLP fixture fingerprint while making the live fixture less secret-like.\n\nClaude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Keep the service backend fixture and SDK request-flag assertion aligned with detached session creation, and give the sequence cursor table the lifecycle columns required by the API schema convention.\n\nClaude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Retain shared-path ownership after the invoke transport drops so queued and manual follow-up actions cannot race the accepted execution. Release that ownership only when the durable reader reports a terminal execution state.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Choose the legacy or shared delivery source synchronously when a request starts and retain that choice through the turn. Suppress shared preview frames for legacy-source turns so a reader becoming ready mid-run cannot render a second assistant response.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Keep data-agent-error in the accepted shared control stream and use it when classifying the paired error chunk. A runner or provider verdict now remains a failure card even when its text matches a browser transport phrase.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Clear the previous turn's acceptance synchronously when prepareRequest starts in both chat engines. Immediate builder or fetch failures can no longer inherit an accepted-disconnect classification from the prior turn.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Mock the snapshot-bounded transcript reads introduced by the durable reconnect base so the shared sender tests stay isolated. Construct the disconnecting stream at fetch time so hydration cannot exhaust it before the turn starts.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Expose live-reader readiness to both chat surfaces and reserve the running-elsewhere strip for disconnected or feature-off fallbacks. Preserve continuation ownership while using the normal turn activity treatment for connected readers.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Project interaction records onto the durable session relay so shared readers refresh the bounded transcript and interaction lifecycle as gates appear and settle.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Abort shared-sender invocations that neither return nor acknowledge the turn within fifteen seconds. Pre-acceptance hangs now enter the existing retryable failure-card path while accepted disconnects and legacy sends keep their prior behavior.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Coalesce shared live-frame envelopes by time, count, and serialized size while preserving frame order. Drain the independent live publisher at turn completion and keep the flag-off path inert.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Accept ordered live-frame batches while retaining the single durable-record contract. Exempt only platform-runner record ingest from the organisation plan bucket and keep browser and other runner routes throttled.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Refresh session-stream liveness from record notifications for legacy observers without raising the idle polling cadence. Keep flag-off presentation independent of the shared-reader snapshot and preserve connected-reader activity on desktop and mobile.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Allow the already-known loopback OTLP fixture under its rebased commit fingerprint. The old fingerprint remains for any history that still references the pre-rebase commit.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
State the effective age cap separately from count capacity, and document the split between per-event deduplication order and the reconnect watermark.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
@mmabrouk
mmabrouk changed the base branch from feat/session-durable-reconnect to feat/session-live-events September 5, 2026 10:45
@mmabrouk
mmabrouk merged commit c8721c2 into feat/session-live-events Sep 5, 2026
74 checks passed
@mmabrouk mmabrouk added the lgtm This PR has been approved by a maintainer label Sep 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm This PR has been approved by a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant