Events — io.best.agents.events
Events ride the same CloudEvents 1.0 envelope as commands (cloudEvent.json) — see SPEC.md — Wire Format for the field table and an example.
Events are immutable facts published as the result of processing: none is changed once told. Schema: events.json
| Method | Path | Description |
|---|---|---|
| GET | /events |
Historical query — paginated, filterable log of past events |
| GET | /events/stream |
Live stream — SSE; delivers events produced after the connection opens |
| GET | /events/{schema}/{version} |
JSON Schema document for one event type/version |
GET /events and GET /events/stream are complementary: load history first, then open the stream.
Typed vs untyped events: an event with dataschema is typed — consumers can fetch the schema and validate. Without it, the event is untyped and the consumer interprets data; the envelope (type, source, id, correlationid, time) still supports routing and correlation. Both patterns can coexist in one service.
No replay guarantee: GET /events returns whatever the server currently exposes — a full log, a recent window, or a mapped view of domain records. Clients cannot assume completeness, ordering, or replay fidelity. For reliable point-in-time delivery, use a push channel.
Query Parameters (GET /events)
| Parameter | Description |
|---|---|
type |
Filter by envelope type (PascalCase) |
correlationId |
Only events whose correlationid envelope attribute matches (the correlationId echoed by POST /commands) |
source |
Filter by publishing service |
from / to |
Time-range bounds (inclusive), as RFC 3339 timestamps, the form of the envelope's time; any other form is a 400 naming the parameter |
limit |
Max results; servers may apply a lower ceiling |
after |
Opaque pagination cursor from a previous response's nextCursor |
Responses are an eventList: { "events": [...], "nextCursor": "..." } — nextCursor present only when more pages exist.
SSE Stream (GET /events/stream)
Request with Accept: text/event-stream plus credentials; optional filters correlationId, type, source. Each event arrives as an SSE data field with the envelope JSON; the envelope id is echoed as the SSE event id. On reconnect, clients send Last-Event-ID and the server replays anything produced after it. A Last-Event-ID the server never told, or no longer holds, is no position in its log: the server should then deliver from now on, as for a new connection, and the client reads what it missed from GET /events. Servers should send : keepalive comments and may close after inactivity or a terminal event; clients must handle reconnection.
Event Types
There is no listing of events: GET /events is the log, an eventList and nothing else. A typed event's schema document is at GET /events/{schema}/{version}, where the event's own dataschema points; a command's schema document names in produces the event types it can raise, which is how a consumer learns them before any has been told. Untyped events rely on the description of the command that produces them.
Choosing a Delivery Channel
| Caller | Channel |
|---|---|
| Browser app, CLI, local agent | SSE |
| LLM client with an active MCP session | MCP push ("push": true on the mcp block) |
| Anything else | Polling GET /events — always available |
The events capability declares supported channels in its push block; check it before choosing.
Reading the log and the stream
When paging through GET /events with after, keep all other parameters as they were. The stream is not lossless, which is why clients handle reconnection.
Untyped events suit dynamic payloads: sensor readings, log streams, forwarded third-party events.
SSE needs no public endpoint on the caller's side. MCP push is server-to-client notifications, matched by correlation identifier.
Mapping domain records to BEST events
Implementations without a native event store may map domain records (audit entries, trade history, sensor readings) to the BEST event shape at query time. The protocol only requires that responses conform to the events schema — it never prescribes internal storage.