Events Guide
Koor’s pub/sub event bus with SQLite-backed history and real-time WebSocket streaming.
Concepts
Events are messages published to a topic and stored in an ordered history. Subscribers receive events in real-time via WebSocket. The history is also queryable via REST for agents that weren’t connected when an event was published.
Topics
Topics are dot-separated strings. Examples:
api.change.contractbuild.startedtest.failed.frontenddeploy.production
There is no topic registration — any string works. Convention is category.action.detail.
Event Structure
Every event has:
| Field | Type | Description |
|---|---|---|
id |
integer | Auto-incrementing ID (unique, ordered) |
topic |
string | Dot-separated topic |
data |
JSON | Any JSON value (object, array, string, number, null) |
source |
string | Source identifier (currently empty, reserved for future use) |
created_at |
datetime | When the event was published |
Publishing Events
Via REST
curl -X POST http://localhost:9800/api/events/publish \
-H "Content-Type: application/json" \
-d '{"topic":"api.change.contract","data":{"version":"2.0","breaking":true}}'
Via CLI
koor-cli events publish api.change.contract --data '{"version":"2.0","breaking":true}'
Programmatically
Any HTTP client can POST to /api/events/publish:
{
"topic": "build.completed",
"data": {
"project": "frontend",
"duration_ms": 3200,
"tests_passed": 47
}
}
Subscribing to Events
WebSocket (Real-time)
Connect a WebSocket client to receive events as they are published:
websocat ws://localhost:9800/api/events/subscribe?pattern=api.*
Each event arrives as a JSON text frame:
{"id":42,"topic":"api.change.contract","data":{"version":"2.0"},"source":"","created_at":"2026-02-09T14:30:00Z"}
The connection stays open until the client disconnects or the server shuts down.
CLI Polling Fallback
The CLI doesn’t include a WebSocket library (to stay dependency-free), so it falls back to polling the history endpoint every 2 seconds:
koor-cli events subscribe "api.*"
New events are printed as JSON lines to stdout. Previously-seen event IDs are tracked to avoid duplicates.
Subscriber Buffer
Each WebSocket subscriber has a 64-event channel buffer. If a subscriber is slow and the buffer fills, events are dropped for that subscriber. This prevents one slow subscriber from blocking others.
Event History
Query Recent Events
# Last 50 events (default)
curl http://localhost:9800/api/events/history
# Last 10 events
curl "http://localhost:9800/api/events/history?last=10"
# Filter by topic pattern
curl "http://localhost:9800/api/events/history?last=100&topic=api.*"
Via CLI
koor-cli events history
koor-cli events history --last 10
koor-cli events history --topic "api.*"
koor-cli events history --last 100 --topic "build.*"
Topic Pattern Matching
Patterns use Go’s path.Match glob syntax:
| Pattern | Matches | Does Not Match |
|---|---|---|
* |
Everything | — |
api.* |
api.change, api.deploy |
api.change.contract (only one level) |
api.change.* |
api.change.contract, api.change.schema |
api.deploy |
build.* |
build.started, build.completed |
test.passed |
Patterns match against the full topic string. A * in a pattern matches any non-dot characters within a single segment.
Event Pruning
Events are automatically pruned to the most recent 1000. A background goroutine runs every 60 seconds and removes events beyond this limit. This keeps the database from growing indefinitely.
Pruning is based on event ID order — the oldest events are removed first.
Use Cases
API Contract Changes
When one agent updates a shared API contract, publish an event so other agents can react:
# Agent A updates the contract
curl -X PUT http://localhost:9800/api/state/api-contract -d '{"version":"2.0"}'
# Agent A publishes a change event
curl -X POST http://localhost:9800/api/events/publish \
-d '{"topic":"api.change.contract","data":{"version":"2.0","breaking":true}}'
Agent B, subscribed to api.change.*, receives the event and re-reads the contract.
Build/Test Notifications
# CI publishes build result
curl -X POST http://localhost:9800/api/events/publish \
-d '{"topic":"build.completed","data":{"status":"success","tests":52}}'
Agent Lifecycle
# Agent announces it's starting work
curl -X POST http://localhost:9800/api/events/publish \
-d '{"topic":"agent.started","data":{"name":"claude-frontend","task":"dark mode"}}'
# Agent announces it's done
curl -X POST http://localhost:9800/api/events/publish \
-d '{"topic":"agent.completed","data":{"name":"claude-frontend","task":"dark mode"}}'