Skip to content

Live Stream: preserve Worklog identity across active session reattach#6304

Open
franksong2702 wants to merge 5 commits into
nesquena:masterfrom
franksong2702:franksong2702/lifecycle-session-reattach-gate
Open

Live Stream: preserve Worklog identity across active session reattach#6304
franksong2702 wants to merge 5 commits into
nesquena:masterfrom
franksong2702:franksong2702/lifecycle-session-reattach-gate

Conversation

@franksong2702

@franksong2702 franksong2702 commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Closes #6303

Thinking Path

  • Live-to-Final requires every visible activity row in one assistant turn to keep one durable order
    and owner across live rendering, settlement, and reload.
  • Live Stream: add public conversation lifecycle browser gate #6251 establishes the public normal-path browser seam, but intentionally leaves active session
    switch/reattach as a separate proof-matrix row.
  • Reattach cannot rely on browser-only INFLIGHT or Anchor registries; the durable run journal must
    reconstruct the same scene and cursor.
  • Focused red tests showed that reasoning/prose/tool segment numbering and chronology could diverge
    between the live browser path and server replay, especially around no-prose tools and reasoning
    echoes.
  • This PR fixes those replay/reattach boundaries and extends the deterministic gate through session
    switch, post-return continuation, settlement, and hard reload.
  • Aftercare found a terminal navigation race: done could retain the complete stream-owned Anchor
    registry but skip persistence when _loadingSessionId already pointed at another session.

What Changed

  • Add a separate session-reattach lifecycle workflow scenario built on Live Stream: add public conversation lifecycle browser gate #6251's real
    server/Gateway/Chromium seam.
  • Create an idle session through the real composer, start an active run, switch away and back through
    the sidebar, and clear browser-only inflight/Anchor caches before returning so durable recovery is
    required.
  • Assert the complete live scene's row order, local identities, stream owner, timer/group identity,
    post-reattach reasoning continuation, settlement, and hard-reload parity.
  • Reconstruct run-journal reasoning as distinct ordered segments instead of one aggregate owner.
  • Use one monotonic live segment high-water across reasoning, process prose, and tool boundaries;
    preserve journal chronology for no-prose and completion-only tools, including after reasoning echo
    removal.
  • Keep restored aggregate reasoning as display history while starting a fresh live reasoning buffer
    for events received after reattach.
  • Treat structured reasoning as authoritative during hydration, suppress duplicate top-level mirrors,
    and drop stale live Thinking rows when their content no longer matches the settled transcript.
  • Persist the closure-owned completed Anchor scene during the session-switch ownership window without
    mutating the destination pane, using the completed payload's own message offset.
  • Make the reattach gate hold a real draft-save navigation await open across done, then return and
    prove settled/hard-reload parity; collect ownership/registry diagnostics on failure.
  • Ensure the reattach workflow step still runs when the normal gate fails, preserving both diagnostic
    results in the informational job.
  • Document both lifecycle scenarios and two controlled test mutations in TESTING.md.

Why It Matters

Users should be able to inspect another session and return to an active run without changing what the
assistant has already shown. The same reasoning, process prose, and tool activity should remain in the
same order, and new activity should append once to the same assistant turn.

This also turns that product invariant into a public browser gate, so future streaming/replay changes
can be checked without real credentials or private user sessions.

Behavioral Before / After

Before the fix, red regressions showed:

  • [thinking, prose, tool, thinking] after replay for a live chronology of
    [thinking, tool, prose, thinking];
  • segment groups [1, 1, 2, 1, 3] where the live browser owned
    [1, 1, 2, 2, 3];
  • a consumed reasoning local id being reused after echo removal;
  • post-reattach reasoning repeating restored aggregate history or updating an existing card;
  • a complete nine-row terminal Anchor registry producing zero persistence requests when done landed
    after navigation started, so returning showed final prose but no Worklog.

After the fix, the browser gate preserves the same semantic scene before switch, after reattach,
after new reasoning, through a terminal navigation window, after settlement, and after hard reload.

There are no CSS, layout, or control changes. Semantic browser snapshots and identity/order
assertions are the relevant visual-behavior proof; static screenshots cannot prove row ownership.

Contract Routing

Task type: runtime/replay correctness fix plus contract-semantic regression coverage.

Touched areas:

  • run-journal live snapshot reconstruction (api/routes.py)
  • live and reattached reasoning/tool ownership (static/messages.js)
  • deterministic browser lifecycle gate and informational workflow
  • focused Anchor persistence, ordering, echo, and reconciliation tests

Relevant public docs:

  • AGENTS.md
  • CONTRIBUTING.md
  • docs/CONTRACTS.md
  • docs/rfcs/live-to-final-assistant-replies.md
  • docs/rfcs/stable-assistant-turn-anchors.md
  • docs/rfcs/webui-run-state-consistency-contract.md
  • docs/rfcs/session-sse-contract-v1.md

State ownership:

  • the deterministic Gateway owns only controlled event input;
  • the run journal and /api/session snapshot own durable reattach data;
  • INFLIGHT and the Anchor registry own browser live projection only;
  • a stream closure may persist its own completed Anchor scene during navigation but cannot mutate the
    destination session's visible pane;
  • the transcript-backed activity_scene_v1 owns settled/reload presentation;
  • display-only recovery data remains excluded from model-facing context.

Contract effect: no intentional contract change. This PR enforces the existing one-turn, one-ordered-
activity-model invariant across an additional lifecycle transition.

Verification

Red-before-green evidence:

  • no-prose tool chronology initially replayed [thinking, prose, tool, thinking] instead of
    [thinking, tool, prose, thinking];
  • the adjacent global segment regression initially produced [1, 1, 2, 1, 3] instead of
    [1, 1, 2, 2, 3];
  • the identity mutation replaces one durable reasoning id and fails at the reattach checkpoint;
  • the persistence mutation silently drops settled Anchor persistence and fails after hard reload.

Final verification after base-refresh head bf718ee50:

  • GitHub readback on bf718ee50: browser-smoke, docs-ci, lint, informational live-to-final, Greptile, and all Python 3.11/3.12/3.13 test shards pass
  • ./scripts/test.sh tests/test_anchor_scene_persistence.py tests/test_run_journal_routes.py tests/test_live_anchor_progress_echo.py tests/test_pure_prose_turn_not_worklog.py::test_gates_call_worklog_worthy_predicate tests/test_stable_assistant_turn_anchor_registry.py::test_slice6_live_shadow_feed_wires_anchor_scene_for_visible_order_handoff tests/test_issue3820_chat_activity_display_mode.py::test_chat_activity_display_mode_settled_hide_all_scene_persists_without_worklog tests/test_5749_anchor_scene_client_prefix_dedupe.py::test_settled_scene_keys_live_token_prefix_dedupe_to_final_answer_identity tests/test_live_to_final_anchor_visible_order.py::test_done_persists_owned_anchor_scene_during_session_switch_window tests/test_live_to_final_anchor_visible_order.py::test_settled_anchor_scene_suppresses_final_suffix_live_accumulator tests/test_regressions.py::test_messages_js_supports_live_reasoning_and_tool_completion -q -> 74 passed
  • LIFECYCLE_ARTIFACT_DIR=/tmp/hermes-pr6304-lifecycle-session-reattach-refresh LIFECYCLE_SCENARIO=session-reattach .venv/bin/python tests/browser_conversation_lifecycle.py -> pass
  • git diff --check origin/master...HEAD
  • .venv/bin/ruff check --diff origin/master -> no new violations
  • related Python/ownership suite -> 333 passed, 1 skipped (the skip requires a local
    hermes-agent executable)
  • normal lifecycle browser gate -> pass
  • enhanced session-reattach gate with terminal navigation ownership window -> 10/10
  • LIFECYCLE_TEST_BITE=drop-anchor-persistence -> expected nonzero failure after live + settled
  • LIFECYCLE_SCENARIO=session-reattach LIFECYCLE_TEST_BITE=replace-runtime-reasoning-id ->
    expected nonzero identity failure after switching back
  • Greptile's prior workflow P2 was addressed with an unconditional reattach-step execution guard;
    all latest-head checks pass and the refreshed Greptile review reports no blocker

Risks / Follow-ups

  • Live Stream: add public conversation lifecycle browser gate #6251 is merged; this branch is based directly on its merge commit and extends that public
    browser seam rather than replacing it.
  • The workflow remains informational while runtime and stability are observed.
  • Terminal error, cancel, compression, and Pending Intent are separate proof-matrix rows and remain
    out of scope.
  • The deterministic Gateway proves WebUI behavior against controlled protocol input; it does not
    claim provider-specific event behavior.

Release Note

Active sessions now preserve the same ordered Compact Worklog when users switch to another session
and return, including new reasoning that arrives after reattach, terminal completion during the
navigation window, and parity after settlement/reload.

Model Used

  • Provider: OpenAI
  • Model: GPT-5 via Codex Desktop
  • Notable tools: isolated git worktree, deterministic Playwright browser gate, controlled mutation
    testing, and an independent Codex sub-agent review

@greptile-apps

greptile-apps Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes Worklog identity preservation across session reattach — when a user leaves an active stream and returns, the same reasoning segments, tools, and ordered rows must resume without duplication or loss. It reconstructs reasoning as distinct ordered segments in the run-journal replay path, fixes anchor persistence during the session-switch ownership window, and gates the full lifecycle through a new Playwright scenario.

  • api/routes.py: replaces the single aggregate reasoning owner with a per-segment list; adds monotonic live-segment high-water tracking shared across reasoning, prose, and tool boundaries; and applies a journal-order sort so tool_complete-only events preserve correct chronology on replay.
  • static/messages.js: seeds liveReasoningText with an empty string on reconnect (not the aggregate), seals the reasoning segment before a tool event when no prose has streamed, persists the completed Anchor scene during the session-switch ownership window, and treats persisted reasoning segments as authoritative over the transcript's aggregate mirror during hydration.
  • Tests/workflow: adds the session-reattach Playwright scenario with full pre/post-switch assertions, parametrizes the stale-thinking drop test to cover completed as well as running status, and guards the reattach workflow step with if: always() so it runs even when the normal gate fails.

Confidence Score: 5/5

Safe to merge — changes are confined to live replay reconstruction and the done-event persistence path, both guarded by extensive unit tests and a new deterministic Playwright gate.

The segment tracking, journal ordering, and session-switch persistence logic write only to in-memory data structures and the existing anchor-scene endpoint. The Playwright gate runs both the normal and reattach scenarios unconditionally, and the unit suite covers completion-only tools, cross-segment echo removal, authoritative-thinking hydration, and the terminal-navigation ownership window. No schema migrations, no new API surface, no control-flow changes outside the streaming closure.

No files require special attention. The most complex changes in api/routes.py and static/messages.js both have dedicated unit and browser-level test coverage.

Important Files Changed

Filename Overview
api/routes.py Refactors run-journal snapshot to track per-segment reasoning (not one aggregate), adds segment high-water, journal-order sort for chronological row ordering, and strips internal fields from the public response. Logic is correct and thoroughly tested.
static/messages.js Key browser-side fixes: empty live-reasoning buffer on reattach, seal-segment-before-tool helper, session-switch anchor persistence in the done handler, authoritative-thinking preference over transcript aggregate in settlement. All paths guarded correctly.
tests/browser_conversation_lifecycle.py Adds session-reattach scenario with sidebar switch, inflight/registry cache clearing, runtime-scene wait, semantic identity assertions, continuation reasoning check, and terminal navigation ownership window.
.github/workflows/conversation-lifecycle.yml Adds reattach step with if: always() guard so it executes even when the normal gate fails; job-level continue-on-error: true keeps the workflow informational.
tests/test_anchor_scene_persistence.py Adds tests for multi-segment reasoning identities, completion-only tool boundary, no-prose global order, timestamp preservation, hydration preference for persisted segments, and cross-segment echo stripping.
tests/test_live_to_final_anchor_visible_order.py Adds structural tests for the session-switch done handler path, duplicate-thinking suppression, and final-suffix live-accumulator deduplication.

Reviews (3): Last reviewed commit: "Merge remote-tracking branch 'origin/mas..." | Re-trigger Greptile

base_url: str,
session_id: str,
expected_reasoning_ids: list[str],
timeout: float = 10.0,

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.

P2 Hard-coded 10 s timeout is inconsistent with established CI constant

_wait_for_runtime_scene polls until the server's runtime journal scene exposes the expected reasoning IDs. The rest of this file uses ANCHOR_SCENE_PERSIST_TIMEOUT = 60.0 for analogous waits; the new function uses a hardcoded 10.0. On a loaded CI runner the journal rebuild can be delayed, causing a false-negative assertion failure where the established constant would have waited long enough.

Suggested change
timeout: float = 10.0,
timeout: float = ANCHOR_SCENE_PERSIST_TIMEOUT,

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@nesquena-hermes nesquena-hermes added the size:L Large PR (>10 files or >250 LOC) label Jul 19, 2026
@franksong2702
franksong2702 force-pushed the franksong2702/lifecycle-session-reattach-gate branch from 943cfef to f89f7af Compare July 19, 2026 05:33
@franksong2702

franksong2702 commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

Aftercare readback at ec596989c:

  • The Linux lifecycle run caught an aggregate-final edge where a live final suffix could remain inside the Worklog; the latest head now drops that suffix while keeping the final answer outside the Worklog.
  • The terminal navigation test holds a real draft-save navigation await open across done and proves that the original stream closure persists its completed Anchor scene without mutating the destination pane.
  • The prior Greptile workflow P2 is addressed: the reattach diagnostic step now runs with always() even if the normal scenario fails.
  • Local latest-head verification: six focused regressions pass, and the real server/Gateway/Chromium session-reattach scenario passes all six lifecycle checkpoints.
  • GitHub browser-smoke, docs-ci, lint, informational live-to-final, and all 15 Python matrix shards pass. Refreshed Greptile review completed on ec596989c with 5/5 Safe to merge and no blocker.

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

Labels

size:L Large PR (>10 files or >250 LOC)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Live Stream: preserve Worklog identity across active session reattach

2 participants