Script Sinks
Script sinks run an existing scriptling script on the knot server when a matching event fires. The script accesses the event payload and metadata through the knot.event module.
Configuration
| Field | Description |
|---|---|
| Script | Search and select an existing script from your Scripts library. Only active scripts of type script are shown. |
| Timeout | Reuses the server’s MCPToolTimeout config. |
| User context | The sink owner — knot.space.*, knot.user.* etc. act as this user. |
Scripts are authored on the existing Scripts page and referenced from the sink by name. This keeps them reusable — one script can serve many sinks.
knot.event Module
The knot.event module is context-sensitive. In sink scripts, only the server-side accessors are available (no emit — this prevents sink → event → sink recursion).
Payload Accessors
Read values from the event’s payload data using typed accessors. These mirror the mcp.tool.get_* pattern.
| Function | Description |
|---|---|
knot.event.get_string(name, default="") |
Get a payload field as string |
knot.event.get_int(name, default=0) |
Get a payload field as integer |
knot.event.get_bool(name, default=False) |
Get a payload field as boolean |
knot.event.get_list(name, default=[]) |
Get a payload field as list |
knot.event.get_dict(name, default={}) |
Get a payload field as dict |
If the field is missing or the wrong type, the default value is returned. The accessors read only from the payload — emitter keys can never collide with system metadata fields.
Metadata Functions
| Function | Returns | Description |
|---|---|---|
knot.event.type() |
str |
Event type string (e.g. "space.started") |
knot.event.id() |
str |
UUIDv7 event identifier |
knot.event.ts() |
str |
HLC timestamp string |
knot.event.space() |
dict |
Source space dict (id, name, urls) |
knot.event.space_urls() |
dict |
Source space URLs (web, vscode, terminal) |
knot.event.actor() |
dict |
Actor dict (id, username, kind) |
knot.event.custom() |
dict |
Custom fields from the source space |
.custom — Space Custom Fields
knot.event.custom() returns a dict of custom fields from the source space’s template. Each key is the field name defined on the template, and the value is what the user entered at space-creation time:
import knot.event
custom = knot.event.custom()
repo = custom.get("GITHUB_REPO", "")
env = custom.get("DEPLOY_ENV", "production")Fields that were defined on the template but not set on the space are absent from the dict.
Example: Trigger a Deployment
import knot.event
event_type = knot.event.type()
version = knot.event.get_string("version", "unknown")
commit = knot.event.get_string("commit", "")
space = knot.event.space()
if event_type == "custom.myapp.deployed":
# Call an external API or perform server-side work
print(f"Deploying {version} ({commit}) from {space['name']}")Example: React to Space Lifecycle
import knot.event
event_type = knot.event.type()
if event_type == "space.stopped":
# Clean up resources when a space stops
node = knot.event.get_string("node_id", "")
print(f"Space stopped on node {node}")
elif event_type == "space.unhealthy":
failures = knot.event.get_int("consecutive_failures", 0)
print(f"Space unhealthy after {failures} consecutive failures")System Event Payloads
For system events, the payload fields available via get_*. Space lifecycle events include space_name and space_id in the payload; space.created and space.started also include space_urls:
| Event | Payload fields |
|---|---|
space.created |
space_name, space_id, space_urls, template_id, startup_script_id |
space.started |
space_name, space_id, space_urls, node_id, started_at |
space.stopped |
space_name, space_id, stopped_at |
space.deleted |
space_name, space_id, deleted_at |
space.healthy |
space_name, space_id, previous, current, checked_at |
space.unhealthy |
space_name, space_id, previous, current, consecutive_failures, checked_at |
Idempotency
Delivery is at-least-once. On leader failover mid-execution, the same event may be delivered twice. Sink scripts must be idempotent — use knot.event.id() to deduplicate if needed:
import knot.event
event_id = knot.event.id()
# Check if we've already processed this event_id...
# e.g. write to a file, set a flag, etc.Available Libraries
Sink scripts run on the knot server with the full knot.* library set (except knot.event.emit, which is not registered). See the Scripting documentation for available libraries.