Events (kletso.events/v1)
The envelope every server message uses, the complete list of event types with their payload fields, and the app.notify payload.
Envelope
{ "id": "evt_01J8EVENT00042", "seq": 42, "type": "message.delta",
"ts": "2026-09-28T12:00:00.123Z", "conversationId": "conv_01J8DEMO000001",
"turnId": "trn_01J8TURN000007",
"data": { "messageId": "msg_01J8MSG0000013", "text": "Hel" } }
| Field | Notes |
|---|---|
id | evt_…, unique; dedupe key |
seq | integer ≥ 1, contiguous per conversation; replay cursor |
type | dotted lowercase name |
ts | ISO-8601 UTC |
conversationId | conv_… |
turnId | trn_…, groups the events of one assistant turn (optional) |
data | type-specific payload |
Event types
| Type | Payload | Emitted when |
|---|---|---|
conversation.created | conversation {id, agentId, title, status, createdAt, updatedAt, lastSeq} | A conversation starts |
conversation.updated | same | Title, status (open, handoff, closed) changes |
message.created | messageId, role user·assistant·system·tool, text?, value?, clientId? | A message starts (user echo or assistant) |
message.delta | messageId, text (append) | Streaming text |
message.completed | messageId, text, usage {in, out, cachedIn, reasoning}, costMicros, latencyMs, finishReason stop·length·tool_calls·error·cancelled | The turn’s message is final |
agent.typing | {} | The turn started; clients show dots with their own TTL |
tool.started | toolCallId, name, args | A tool call begins |
tool.completed | toolCallId, name, durationMs, result? | Success |
tool.failed | toolCallId, name, durationMs, error {code, message} | Failure (including invalid_args) |
tool.confirmation_required | toolCallId, name, args, surface | The turn paused for approval; the surface holds the confirm block |
ui.pending | toolCallId, surfaceId?, components [{id, type}] | A render_ui call is streaming: what it will contain so far, repeated as more components appear (show loaders) |
ui.render | surface, toolCallId? | A surface to display; replaces the ui.pending with the same toolCallId |
ui.patch | surfaceId, data?, components? | Change to an existing surface (applied by the SDK; not emitted by the runtime yet) |
ui.action | surfaceId, componentId, actionId, value? | Echo of a user interaction to all clients |
app.command | name, args, closeChat | The agent asks the app to run a local action |
app.notify | see below | A notification for the user |
trigger.fired | triggerId, kind manual·event·screen·time, name | A rule matched |
handoff.started | target, agentName? | The assistant handed off |
handoff.completed | same | Reserved |
workflow.started / .completed / .failed | runId, output?, error? | Reserved for live workflow dispatch |
error | code, message, retryable | A turn-level error |
Events marked reserved are in the schema and handled by the SDK but not produced by the current runtime.
User-level events (conversation.created, conversation.updated, trigger.fired, app.notify, app.command) are also delivered to sockets that are attached to the user but not to a specific conversation, so a launcher-only connection still receives notifications.
app.notify payload
{ "notificationId": "ntf_01J8NTF00001",
"title": "Order ORD-2201 shipped",
"body": "Arrives tomorrow. Track it live or ask me anything.",
"channel": "system",
"openChat": true,
"ttlSeconds": 30,
"action": { "id": "tap", "kind": "local", "name": "open_orders", "args": {}, "label": "View" },
"surface": { "schema": "kletso.ui/v1", "surfaceId": "sfc_…", "root": "…", "components": {}, "fallbackText": "…" },
"imageUrl": "https://cdn.acme.com/ship.png",
"data": { "orderId": "ORD-2201" } }
| Field | Notes |
|---|---|
notificationId | required; clients dedupe on it |
title | required, 1–120 chars |
body | ≤ 1000 chars |
channel | banner (default), toast, alert, system, silent |
openChat | open the chat on tap; immediately for silent |
action | a local, url or agent action run on tap |
surface | inline surface shown in banners and alerts |
ttlSeconds | 1–86400; auto-dismiss for banner and toast |
imageUrl, data | optional decoration and opaque host data |