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
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
indexto every session stream event.indexidentifies the event's absolute position in that session's durable event streamsession.stream({ startIndex })startIndex: nyields the event whoseindexisnas its first eventturnId,sequence, andstepIndexretain their current grouping semantics. They do not replaceindexOrdering and concurrency
The index must reflect durable write order, not hook completion order or wall-clock time.
turnId,callId, andchildSessionIdReplay, 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.
(sessionId, index)as the stable idempotency key for hook and stream consumersCancellation and lifecycle
Cancellation is confirmed by durable lifecycle events, not only by a cancel request's response. The resulting
turn.cancelledandsession.waitingevents need indexes like every other event.Acceptance criteria
indexindexfor the matching durable event(sessionId, index), reconnect, and process any replay idempotently