Runs, Events & Streams
This page is the detailed reference for run and event records plus stream lineage and live delivery. Start with the Platform Execution Model for the complete relationship among events, handlers, runs, tasks, retries, workers, and streams.
Runs
A Run is one platform-invoked, top-level Hot function execution attempt, such as an API call, selected event handler, schedule, or task execution. Ordinary function calls inside it remain part of the same run's execution trace.
Run Lifecycle
| State | Description |
|---|---|
running | Worker is executing the function |
succeeded | Function completed successfully |
failed | Function threw an error or timed out |
cancelled | Run was cancelled before completion |
pending_retry | Function failed but will be retried automatically |
Runs with "retry" metadata that fail are temporarily set to pending_retry until the retry executes. See Retries for details.
Run Data
Every run captures:
{
"run_id": "run_abc123xyz",
"function": "::myapp::orders/process-order",
"status": "succeeded",
"input": {
"order_id": "ord_12345"
},
"result": {
"status": "processed",
"total": 99.99
},
"started_at": "2024-12-04T10:30:00Z",
"completed_at": "2024-12-04T10:30:02Z",
"duration_ms": 2150,
"trigger": {
"type": "event",
"event_id": "evt_xyz789"
}
}
Execution Trace
Hot captures a full execution trace for every run, showing:
- Each expression evaluated
- Intermediate values
- Function calls and returns
- Timing for each step
- Any errors with stack traces
Triggering Runs
Runs can be triggered in several ways:
1. API Call (via hot:call event)
curl -X POST https://api.hot.dev/v1/events \
-H "Authorization: Bearer $HOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"event_type": "hot:call", "event_data": {"fn": "::myapp::orders/process-order", "args": [{"order_id": "12345"}]}}'
2. Event Handler
on-order-created
meta {on-event: "order:created"}
fn (event) {
process-order(event.data.order_id)
}
3. Schedule (recurring)
daily-report
meta {schedule: "0 0 * * *"}
fn (event) {
generate-report()
}
4. Dynamic Schedule (one-time or created at runtime)
// Schedule a function to run in 10 minutes
send("hot:schedule:new", {
fn: "::myapp::tasks/process",
args: [{task_id: "123"}],
schedule: "in 10 minutes"
})
See Dynamic Schedules for more details.
5. Asynchronous Function Dispatch (from another run)
process-batch fn (orders) {
// Each send publishes an event that dispatches a separate run
map(orders, (order) {
send("hot:call", {
fn: "::myapp::orders/process-order",
args: [order]
})
})
}
Events
Events are messages that trigger asynchronous workflows. They decouple event producers from consumers, enabling scalable and maintainable systems.
Event Structure
An Event in Hot has two fields:
Event type {
type: Str,
data: Any
}
The send function has two arities:
// Pass event type and data directly
send("user:created", {id: "usr_12345", email: "alice@example.com"})
// Or pass an Event
send(Event({type: "user:created", data: {id: "usr_12345", email: "alice@example.com"}}))
Sending Events
From Hot Code:
// Send an event after user creation
create-user fn (data) {
user insert-user(data)
// Send event for other handlers (send is a core function)
send("user:created", {
id: user.id,
email: user.email,
name: user.name
})
user
}
From the API:
curl -X POST https://api.hot.dev/v1/events \
-H "Authorization: Bearer $HOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"event_type": "user:created",
"event_data": {"id": "usr_12345", "email": "alice@example.com"}
}'
From External Systems (Webhooks): Configure webhooks to forward events from services like Stripe, GitHub, or Slack directly to Hot.
Event Handlers
Define handlers using the on-event metadata:
::myapp::notifications ns
// Handle a specific event type
on-user-created meta {on-event: "user:created"}
fn (event) {
send-welcome-email(event.data.email)
}
An event may match zero, one, or multiple handlers. Each selected handler invocation is recorded as its own run in the event's stream.
Event Delivery
Hot guarantees at-least-once delivery for events:
- Events are persisted before acknowledgment
- Failed handlers can be retried automatically with configurable attempts and delay
- Retry status is visible in the Hot App UI
Delivery is optimized for durability and throughput, not strict global ordering. Concurrent workers may process different events from the same stream at the same time, and retries or infrastructure redelivery can arrive after newer events. Queue message fields are additive so rolling deploys can read older messages; workers hydrate the authoritative event payload from the database before routing.
Handlers should be idempotent when they perform external side effects, because the same event can be delivered more than once after a retry, worker crash, Redis pending-entry reclaim, or task reconciliation pass. This also applies to run timeouts: when a handler exceeds its run timeout it is recorded as a failure and retried according to its retry policy. The worker cancels the timed-out run cooperatively, but a handler stuck in non-cooperative work (a tight native loop or blocking syscall) can keep running in the background while its retry begins, so the two attempts may briefly overlap.
Streams
Streams are the correlation boundary for platform work. Related events,
runs, retries, and tasks share a stream ID, producing one end-to-end workflow
history. A new externally published event creates a stream unless its request
supplies an existing stream_id; events and tasks created inside running Hot
code inherit the current stream.
The same stream ID is also a live delivery channel. Clients can subscribe to
run lifecycle notifications and data emitted with ::hot::stream/data.
User-emitted stream data is ephemeral and is not persisted with the durable
event, run, and task records.
Use Cases
- Workflow lineage - Trace related events, runs, retries, and tasks
- AI/LLM Responses - Stream tokens as they're generated
- Live Updates - Push data to clients in real-time
- Long-Running Operations - Report progress incrementally
- Bidirectional Communication - WebSocket-style interactions
Server-Sent Events (SSE)
Stream data to clients in real-time using ::hot::stream/data.
Hot code — emit chunks as they arrive:
handle-chat
meta { on-event: "chat:message" }
fn (event) {
// Call a streaming AI API
response ::anthropic::messages/post-stream({
model: "claude-sonnet-4-20250514",
max_tokens: 4096,
messages: [{role: "user", content: event.data.message}]
})
// Process stream and emit chunks to the client
process-stream(response.body, "")
}
// Recursive stream processor
process-stream fn (iter, accumulated: Str): Str {
result next(iter)
cond {
result.done => { accumulated }
=> {
delta or(result.value.data.delta.text, "")
// Emit chunk to client in real-time
::hot::stream/data("ai:delta", { text: delta })
process-stream(iter, concat(accumulated, delta))
}
}
}
JavaScript client — publish an event, then subscribe to the stream:
// 1. Publish event to trigger the handler
const eventRes = await fetch('/v1/events', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
event_type: 'chat:message',
event_data: { message: 'Hello!' }
})
});
const { data: { stream_id } } = await eventRes.json();
// 2. Subscribe to stream for real-time updates
// GET (classic SSE) and POST (streamable HTTP style) are both supported.
const response = await fetch(`/v1/streams/${stream_id}/subscribe`, {
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Accept': 'text/event-stream'
}
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
// Parse SSE events (data: {...}\n\n format)
for (const line of text.split('\n')) {
if (line.startsWith('data: ')) {
const event = JSON.parse(line.slice(6));
if (event.type === 'stream:data') {
// Real-time chunk from ::hot::stream/data
appendToResponse(event.payload.text);
}
if (event.type === 'run:stop') {
// Run completed
console.log('Final result:', event.run.result);
}
}
}
}
Subscription States
The subscription lifecycle is distinct from the durable stream record. Closing a client connection does not delete the events, runs, or tasks correlated by the stream ID.
Viewing Streams
Active and completed streams are visible in the Hot App:
- Connection status and duration
- Messages sent/received
- Bandwidth usage
- Error details
Monitoring
All runs, events, tasks, and streams are visible in the Hot App with:
- Real-time updates as executions happen
- Filtering by status, function, event type
- Full-text search across payloads
- Detailed drill-down views