Skip to content

Expose a finite session event snapshot API for server-side projections #727

Description

@thomaskwon416

Problem

Eve hooks are documented as the extension point for persisting session/message events to an application database, but the current public APIs do not expose a finite, cursor-bearing server-side read of a session's durable event log.

In eve@0.22.5:

  • message.completed / session.waiting hooks receive the durable event and HookContext, but neither carries the event's remote streamIndex.
  • ClientSession.stream({ startIndex }) is a live async stream. It correctly remains open after session.waiting because the durable parent accepts future turns.
  • Therefore an awaited boundary hook cannot notify an application that then opens session.stream() and waits to project the suffix: the callback never returns, the hook never settles, and the next queued turn cannot start.
  • Browser onEvent / onFinish persistence is useful for UI state, but cannot be the correctness boundary for a server-owned product projection when the browser refreshes or disappears.

Observed local timeline:

  1. message.completed is visible to the browser.
  2. A downstream model/tool fetch times out and Eve correctly retries the durable step.
  3. Eve emits turn.completed and session.waiting on the retry.
  4. The application callback opens session.stream() from its stored cursor.
  5. The stream waits for future parent events, so the callback and session.waiting hook remain pending; the product turn stays running, and the queued follow-up remains unattached.

This is not a request to make live streams close at session.waiting; that would break continuation semantics.

Requested public capability

Please expose a finite snapshot/replay operation on ClientSession (name flexible), for example:

const { events, state } = await session.snapshot({ startIndex });
// events: durable events from startIndex through a tail fixed at invocation
// state.streamIndex: the exclusive cursor immediately after that captured tail

Required semantics:

  • Captures a finite tail at invocation and never waits for future events.
  • Returns ordered public HandleMessageStreamEvent[] plus the resulting SessionState/exclusive cursor.
  • Uses the existing Eve session authorization/continuation contract.
  • Works while the durable parent is running, waiting, completed, or failed.
  • Empty suffix is a successful idempotent result with an unchanged cursor.
  • Concurrent appends after the captured tail are excluded and available to the next snapshot.
  • Does not require consumers to call raw HTTP routes, parse NDJSON, count chunks, or mirror Eve internals.

An equivalent hook API that supplies the exact durable cursor after the current event could also solve incremental projection, but a finite snapshot is more generally useful for bounded recovery and idempotent server-side projection.

Suggested coverage

  • finite read of a waiting-but-continuable parent;
  • empty suffix / duplicate callback;
  • concurrent append around tail capture;
  • completed and failed sessions;
  • invalid/out-of-range cursor;
  • authorization parity with session.stream();
  • client types and server-projection documentation.

I can contribute implementation, tests, docs, and a changeset after maintainer direction on the preferred API shape.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions