Event streams
Sequenced events
Section titled “Sequenced events”An event.batch carries one stream and a dense ordered list of catalog-valid envelopes:
type EventBatch = { stream: string; batchId: string; reason: "replay" | "live" | "snapshot_delta"; events: Array<{ id: string; seq: number; type: string; ts: string; data: unknown; }>; firstSeq: number | null; lastSeq: number | null;};Every adjacent event satisfies next.seq === previous.seq + 1. Empty batches use null bounds. Only catalog events with delivery: "sequenced" may appear.
Implemented stream routing is deterministic:
| Stream | Owner and contents |
|---|---|
workspace |
Workbench workspace facts such as conversation lifecycle, settings, agents, projects, and plans |
conv/<conversationId> |
Conversation, run, turn, live-message, and tool-call events carrying that conversation ID |
One WebSocket can carry several subscribed streams. Opening a conversation changes the exact subscription set; it does not open another socket.
A client skips exact duplicates, rejects gaps, applies each event, and advances that stream’s cursor only after the reducer succeeds. A reducer invariant violation triggers snapshot recovery rather than cursor advancement.
Notifications
Section titled “Notifications”Catalog events with delivery: "ephemeral" use event.notify:
type NotifyEvent = { id: string; type: string; ts: string; data: unknown };Notifications are never persisted in stream logs, never replayed, and never consume sequence numbers. Catalog-approved latest_by_scope or concat_delta notifications may coalesce while queued. Examples include task output, usage updates, and streaming progress whose authoritative state is available from a snapshot or query.
Persistence and retention
Section titled “Persistence and retention”Workbench logs are per stream and maintain dense local high-water metadata. Supersedable deltas use a 25 ms/64-event group-commit window; lifecycle events force an immediate flush and fsync. On restart, the next sequence is max(meta.lastSeq, logTailSeq) + 1.
Retention truncates whole prefixes, normally keeping the latest 5,000 events and at most 8 MiB per workbench stream. Truncation never renumbers retained events. A cursor below earliestAvailableSeq - 1 therefore requires a repository-derived snapshot followed by a new subscription.