Skip to content

Event streams

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.

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.

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.