Events
The events system lets you react to space lifecycle changes and custom events raised from inside spaces or through the API. Events are routed to event sinks — listeners that POST to a webhook URL, run a scriptling script, or deliver to a running JSON-RPC method.
System Events
Knot automatically fires system events when spaces change lifecycle state:
| Event type | When | Payload data |
|---|---|---|
space.created |
Space record created | space_name, space_id, space_urls, template_id, startup_script_id |
space.started |
Space reached deployed state | space_name, space_id, space_urls, node_id, started_at |
space.stopped |
Space reached stopped state | space_name, space_id, stopped_at |
space.deleted |
Space marked as deleted | space_name, space_id, deleted_at |
space.healthy |
Health check transitioned to healthy | space_name, space_id, previous, current, checked_at |
space.unhealthy |
Health check transitioned to unhealthy | space_name, space_id, previous, current, consecutive_failures, checked_at |
Custom Events
Anything running inside a space can raise custom events. Three equivalent paths:
Agent CLI
knot event myapp.deployed '{"version": "1.0"}'Or pipe payload via stdin:
echo '{"version": "1.0"}' | knot event myapp.deployedAgent HTTP API
curl -X POST http://127.0.0.1:12201/event \
-H "Content-Type: application/json" \
-d '{"type": "myapp.deployed", "payload": {"version": "1.0"}}'Scriptling
import knot.event
knot.event.emit("myapp.deployed", {"version": "1.0"})Server API
Outside a space, a custom event can be raised through the server’s API with POST /api/events/emit, using an API token. This is also what knot.event.emit() uses in an MCP tool, which runs on the server rather than in a space:
curl -X POST https://knot.example.com/api/events/emit \
-H "Authorization: Bearer $KNOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"type": "myapp.deployed", "payload": {"version": "1.0"}}'The event has no space: its space id is the nil UUID (00000000-0000-0000-0000-000000000000) and it belongs to the calling user, so it reaches that user’s own sinks and global sinks. The user needs the Use Spaces or Manage Spaces permission.
Every path prepends custom. to the event type automatically. This means knot event space.created produces custom.space.created, not the system event.
Event Sinks
Sinks are user-owned listeners. Each sink has:
- Name and description
- Event patterns — comma-separated glob patterns (
space.*,custom.myapp.*,*, exact match) - Sink type — webhook, script, or json-rpc
- Owner — user-owned (fires only for that user’s spaces) or global (fires for all spaces)
See the dedicated pages for each sink type:
- Webhook Sinks — POST events to an HTTP endpoint with a templated body
- Script Sinks — Run a scriptling script server-side with event accessors
- JSON-RPC Sinks — Named formatters that deliver events to running script methods
Event Selection Patterns
| Pattern | Matches |
|---|---|
* |
Every event |
space.* |
All system space events |
custom.myapp.* |
Any custom.myapp.* event (greedy prefix match) |
space.created |
Exact match only |
Whitespace around entries is trimmed. No leading or middle wildcards in v1.
Delivery Semantics
- Events are zone-local — they stay in the zone the space runs in.
- At-least-once delivery — consumers should dedup via the event UUID (
X-Knot-Event-Idheader for webhooks,knot.event.id()for scripts). - Per-sink delivery is in-order, one at a time.
- Each sink has a bounded queue (100 events). When full, new matching events are dropped for that sink.
- Dropped events are logged and an audit entry is written.
- Three delivery attempts with 5s / 15s backoff on failure.
Permissions
| Permission | Description |
|---|---|
| Manage Own Event Sinks | Create and manage user-owned sinks (fire for own spaces only) |
| Manage Global Event Sinks | Additionally manage global sinks (fire for all spaces) |