Repository navigation
Closed
Description
Goal
Expose replayable aggregate event streams without changing the event envelope based on where it was subscribed from.
opencode.sessions.events({ sessionID, after? })Decision
Use one minimal, orthogonal payload shape everywhere:
type Payload = {
id: Event.ID
type: string
location?: Location.Ref
subject?: string
durable?: {
version: number
seq: number
}
data: unknown
}Each field answers one question:
id: Which event? Independently generated for every event; no DB required for ephemeral fast paths.type+data: What happened?location: Where did it happen?subject: Which resource does it concern? For Session events, this is the Session ID. Subject membership does not imply durability.durable: Can this exact event be replayed?version: persisted schema version.seq: ordered position within the subject stream.
Subscription changes selection, never payload shape. EventV2.subscribe, EventV2.all, and sessions.events return the same envelope.
Stream semantics
sessions.events({ sessionID, after })is a typed subject-specific stream.afteris an exclusive durable sequence: replay events withdurable.seq > after, then tail live events.- During catch-up, emit durable history only. Do not buffer ephemeral deltas.
- After catch-up, emit both durable and ephemeral events live.
- Full-value terminal events (
text.ended, reasoning/tool-input equivalents) are durable, occur after all corresponding deltas, and are never followed by another delta for that identity. - A reconnecting client may miss partial deltas and wait for the durable terminal value. This is intentional.
sequenceDiagram
participant C as Client
participant E as Event stream
participant DB as Durable log
C->>E: events({ subject, after: 12 })
E->>DB: read seq > 12
DB-->>E: text.ended (seq 13, full value)
E-->>C: durable event (seq 13)
Note over E,C: catch-up complete; ephemeral events now enabled
E-->>C: text.started (durable seq 14)
E-->>C: text.delta (ephemeral)
E-->>C: text.delta (ephemeral)
E-->>C: text.ended (durable seq 15, full value)
Consumer strategies
- GUI: on reconnect, discard local state, refetch canonical state, then restart live observation.
- Durable integration: persist the latest
durable.seq, resume withafter, and make external side effects idempotent. OpenCord uses this for restart-safe Discord publication. - Control-plane replication: may share the underlying log and sequence semantics, but should use a stricter protocol for ownership, fencing, and replay validation.
Participant perspectives
- Dax (
@thdxr): prioritize a consistent envelope; keepversionandseqtogether; keep random event IDs; model subject independently of durability; retain location even when redundant in a Session subscription. - James (
@jlongster): prioritize one ergonomic stream; terminal full values should make simple reducers converge; skip ephemeral buffering during catch-up. - Kit (
@kitlangton): preserve durable replication for integrations such as OpenCord while also exposing useful live deltas; keep the public model minimal and orthogonal.
Implementation work
- Restructure
EventV2.Payloadto the agreed envelope. - Separate subject routing from durable persistence in event definitions.
- Generalize aggregate replay/tail into a subject-scoped primitive; retain
sessions.eventsas the typed facade. - Add ordering and reconnect tests, especially proving no delta appears after its terminal full-value event.
- Update OpenCord to read
durable.seqrather than the current cursor wrapper.
Metadata
Metadata
Assignees
Labels
No labels
Activity