Files
kandev/docs/plans/inactive-session-transcript-reconciliation/plan.md
T

7.5 KiB

created, status, requirements, system_design, legacy_specs
created status requirements system_design legacy_specs
2026-08-30 complete
REQ-UI-TASK-PROMPT-TRANSCRIPT-VISIBILITY-001
../../specs/ui/system-design/task-prompt-transcript-visibility.md

Implementation Plan: Reconcile Inactive Session Transcript Windows

Overview

Revisiting a previously opened agent session will restore one contiguous newest message window. Older persisted rows will remain reachable through the existing upward pagination path.

This fixes the observed case where one sibling agent sent a durable prompt to another session, the receiver processed it while inactive, and the prompt later could not be reached in the UI.

Scope

In scope

  • Reconcile a bounded latest-message response with cached session rows as one contiguous window.
  • Preserve cached older pages only when they overlap the fetched suffix.
  • Preserve live rows that arrive while the latest fetch is in flight.
  • Derive oldestCursor from the true contiguous boundary.
  • Add a pure frontend regression test for overlapping, disjoint, and in-flight message cases.
  • Add desktop browser evidence for a same-task sibling session that receives an attributed prompt while inactive and accumulates more than 100 later rows.

Out of scope

  • Message persistence, MCP delivery, sender attribution, or backend pagination queries.
  • Global or task-wide delivery of session-detail WebSocket traffic.
  • Subscribing to inactive sibling sessions.
  • Eagerly loading a complete transcript when a task or session opens.
  • Changes to transcript layout, grouping, navigation, copy, or touch behavior.

Confirmed root cause

The receiver prompt was persisted and dispatched to the intended Luna session. The receiver was inactive, so the scoped BroadcastToSession correctly had no browser recipient. Luna still received the prompt at the agent runtime and acted on it.

When the browser revisited Luna 34 minutes later, 156 rows had been persisted after the prompt. The bounded latest fetch returned 100 rows whose oldest row was newer than the prompt. fetchAndStoreMessages retained the much older cached rows even though the two sets did not overlap, sorted both sets, and set oldestCursor from the oldest cached row. The local array therefore contained an unloaded middle gap while pagination started before that gap. The durable peer prompt became unreachable from the transcript.

Technical approach

Contiguous-window reconciliation

  • Extract a pure latest-window reconciliation helper beside use-session-messages.ts.
  • Capture the cached message IDs before issuing message.list.
  • After the response, detect whether the fetched suffix overlaps the current cached window.
  • For an overlapping window, deduplicate and sort the complete joined interval.
  • For a disjoint window, use the fetched suffix as the new base and retain only rows that were not present before the request and arrived through live delivery while it was in flight.
  • Return both the reconciled messages and the correct oldest cursor. A disjoint prefix must not influence that cursor.
  • Keep the existing monotonic fetch guard and Zustand identity reconciliation.

Browser regression

  • Launch one task with two normal agent sessions and open the receiver first so its initial rows enter the browser cache.
  • Switch to the sibling so the receiver becomes inactive.
  • Persist an attributed user prompt on the receiver, followed by enough agent or tool rows to push that prompt outside the newest 100-row fetch.
  • Restore the receiver's earlier cache snapshot after test-only live broadcasts settle, then trigger the normal foreground-recovery fetch.
  • Reopen the receiver, navigate upward, and assert the peer prompt and sender badge become visible exactly once.

Test strategy

Acceptance criterion Test evidence
AC-UI-TASK-PROMPT-TRANSCRIPT-VISIBILITY-001.4 The browser flow reaches the attributed prompt through the existing upward pagination action.
AC-UI-TASK-PROMPT-TRANSCRIPT-VISIBILITY-001.6 Collapsed activity pagination continues until the peer prompt becomes a standalone visible entry.
AC-UI-TASK-PROMPT-TRANSCRIPT-VISIBILITY-001.10 Pure reconciliation tests and the same-task sibling browser scenario prove that a stale cache cannot skip the persisted middle range.

Mobile design contract

  • Desktop outcome: switching back to a receiver session exposes one contiguous transcript window and makes the peer prompt reachable.
  • Mobile outcome: the same shared session-message hook and native transcript preserve the identical durable-history result.
  • Nearest mobile exemplar: apps/web/components/task/task-layout.tsx and the existing mobile message-pagination scenarios.
  • This fix changes state normalization only. It adds no mobile interaction, layout, navigation, scroll-owner, or touch-target behavior.
  • Focused helper coverage plus the desktop end-to-end regression owns the new behavior. Existing mobile-message-pagination.spec.ts remains the responsive proof for the shared cursor and upward-pagination path; no new viewport-specific scenario is required.

Work orders

Dependency order

Task 01 is one vertical repair. Its unit regression must fail before the helper is implemented, and its browser regression must fail before the production reconciliation is changed.

Verification strategy

Run the focused unit test first, then the production-build Chromium scenario. Finish with frontend type, lint, and localization ratchet checks. The backend is unchanged. Use a subshell for each command so the documented paths are safe to copy as one shell block.

Results

  • Added a pure reconciliation helper that keeps overlapping history, replaces disjoint stale prefixes, and retains rows received during an in-flight fetch.
  • Wired the helper's contiguous boundary into the existing latest-message fetch without changing backend, WebSocket, or store contracts.
  • Added unit coverage for all reconciliation branches and a two-session browser regression that reproduces the previously unreachable attributed prompt.
  • The PR fixup stores the cache baseline on the deduplicated request, compares RFC3339 timestamps at the backend's normalized-microsecond precision, and excludes out-of-order older live rows from a disjoint window until pagination reaches them.
  • Verified the focused unit suite (27 tests), desktop Chromium regression, existing mobile pagination suite (5 tests), frontend typecheck and lint, i18n ratchet, and specification lint.

Risks

  • Retaining every cached extra recreates the gap; discarding every extra can erase a live row that arrived while the fetch was pending.
  • An overlapping cache can contain legitimately paginated older rows and must keep them.
  • The cursor must follow the reconciled interval boundary, not simply the first row after sorting unrelated sets.
  • The test must seed deterministic message order and confirm that the peer prompt is outside the newest response page.

Rejected alternatives

  • Broadcasting session-detail messages globally or subscribing to every sibling session conflicts with the bounded session-stream architecture.
  • Automatically loading every missing page on revisit increases transcript traffic and conflicts with the bounded-opening contract.
  • Keeping a disjoint prefix and introducing multiple cursors would add a new store model and pagination protocol for a repair that one contiguous window can solve.