Skip to content

Expose a consistent index on hook and durable stream events #1193

Description

@vercel-gh-bot-3

Summary

Expose the durable stream position on every stream event and pass the same value to matching hooks.

Motivation

Hooks run after an event has been durably recorded, but hook handlers cannot identify that record's durable position. Consumers therefore cannot use one stable cursor to correlate hook work with durable-stream reads, resume after a failure, or deduplicate an event replay.

Proposed contract

Add a zero-based index to every session stream event.

  • index identifies the event's absolute position in that session's durable event stream
  • The value visible to a hook must equal the value returned for the same event by session.stream({ startIndex })
  • startIndex: n yields the event whose index is n as its first event
  • The index is scoped to one session. It is not a global ordering key across sessions, parent and child sessions, or other durable streams
  • Existing event fields such as turnId, sequence, and stepIndex retain their current grouping semantics. They do not replace index

Ordering and concurrency

The index must reflect durable write order, not hook completion order or wall-clock time.

  • Assign it exactly once when the event is committed to the session's durable stream
  • Preserve strict ordering within a session, including when multiple producers attempt writes concurrently
  • Do not promise a total order across sessions or parent and child streams. Consumers must correlate those records with fields such as turnId, callId, and childSessionId
  • Keep each emitted durable event addressable. Adjacent append events may be coalesced by the writer, so consumers must not infer indexes from token chunks or timestamps

Replay, retries, and idempotency

Durable streams resume from an absolute cursor after a transport interruption. A consumer can receive an event again if it completes side effects before persisting its own checkpoint.

  • Document (sessionId, index) as the stable idempotency key for hook and stream consumers
  • Replay must preserve an event's original index
  • Retrying a durable write must not create two indexes for one committed event
  • A failed or throwing hook must not change the committed event's index. Hook consumers should make side effects idempotent because the event is already durable when the hook runs
  • Readers that intentionally start at a later index may skip earlier events. Tail-relative reads do not establish an absolute resume cursor

Cancellation and lifecycle

Cancellation is confirmed by durable lifecycle events, not only by a cancel request's response. The resulting turn.cancelled and session.waiting events need indexes like every other event.

Acceptance criteria

  • Every event returned from a session's durable stream includes index
  • Every hook receives the identical index for the matching durable event
  • Documentation defines scope, zero-based numbering, ordering, replay, and the lack of cross-session ordering
  • Tests cover ordered writes, concurrent write attempts, reconnect and replay, a hook failure after persistence, cancellation events, and parent and child session correlation
  • Tests prove that a consumer can checkpoint (sessionId, index), reconnect, and process any replay idempotently

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions