Skip to content

feat(events): reconnect with last_event_id — replay missed events instead of losing them silently #14818

Closed

Description

@mrveiss

Part of #14815.

Problem

Events published while a client is disconnected are lost silently. There is no gap detection
anywhere, so the failure is invisible to both the user and the logs.

Current state:

  • autobot-frontend/src/services/LiveEventService.ts:218 reconnects with exponential backoff and
    correctly re-subscribes its channels on reopen (:148).
  • autobot-frontend/src/services/GlobalWebSocketService.ts:447 reconnects with backoff plus a
    /api/health polling wait mode.
  • Neither sends a last-seen event id, and api/live_events.py has nowhere to receive one.
  • LiveEventService.ts:409 receives event_id and only logs it — no high-water mark, no gap check.

This is the pattern AHP specifies as reconnect { clientId, lastSeenServerSeq, subscriptions[] },
answered with either replayed actions or a fresh snapshot when the gap exceeds the buffer
(AHP docs/specification/lifecycle.md).

Scope

Backend — autobot-backend/api/live_events.py:_handle_message

  • Accept an optional last_event_id on the subscribe action.
  • If the id is within the retained stream, replay the missed events via xrange before resuming
    live delivery.
  • If the id is older than maxlen (or unknown), respond with a resync signal instead of a partial
    replay — never a silent partial.
  • Mirror AHP's missing[]: name any requested channel that cannot be resumed.

Frontend — autobot-frontend/src/services/LiveEventService.ts

  • Track the highest event_id seen per channel.
  • Send it on re-subscribe after a reconnect.
  • On a resync signal, clear local state for that channel and refetch rather than appending onto a
    stale view.

Depends on #14816 and #14817 — replay needs a durable stream and a durable id.

Acceptance criteria

  • Events published while a client is disconnected are delivered on reconnect.
  • A gap larger than the retention window produces an explicit resync, never a silent hole.
  • Test: publish N events with the socket closed, reconnect, assert all N arrive in order.
  • Test: publish more than maxlen events, reconnect, assert a resync signal rather than a
    partial replay.

Sources

Parent: #14815 · Full analysis: docs/research/agent-host-and-agent-client-protocols.md

Activity

  1. mrveiss commented on Aug 22, 2026

    @mrveiss
    OwnerAuthor

    Audit trail and full analysis: #14826 — docs/research/agent-host-and-agent-client-protocols.md.

    Sources for this finding:

  2. mrveiss commented on Aug 23, 2026

    @mrveiss
    OwnerAuthor

    Delivered and merged.

    api/live_events.py accepts last_event_id on subscribe and replays from a per-channel Redis Stream; when it cannot produce a complete history it emits an explicit resync frame rather than a partial one. LiveEventService.ts tracks a high-water mark per channel, never lowers it, and clears it on resync. A lost durable write is recorded so an affected client is resynced instead of silently short-changed.
    Tests: 13 backend + 19 frontend, covering gap-beyond-retention, Redis unavailable, corrupt entry, by-design gaps, and marker-never-goes-backwards.

    Evidence

    • PR feat(events): multi-client session synchronization, replay, and an ACP agent surface #14826, squash-merged as ded8d07e7 into Dev_new_gui
    • CI on the merged head 889e3ac4a: 57 passed / 0 failed / 0 cancelled / 0 pending
    • Codecov: "All modified and coverable lines are covered by tests" — 100% patch coverage
    • Independent code-reviewer pass run before merge; all 3 findings fixed in-PR with regression tests
    • Content verified present in origin/Dev_new_gui after merge

    Not verified on a host. The tests above exercise the seams, but no running instance has confirmed the end-to-end behaviour (a second tab receiving events, a reconnect replaying the gap). If that matters for this issue, reopen against a deployed check.

    Closing by hand: Closes #N does not fire here, since the PR targeted Dev_new_gui rather than the default branch.

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions