Tasks
Tasks are long-running, asynchronous processes on the Hot Platform. They extend the platform run model with a durable resource, background execution, and task-specific lifecycle controls. See the Platform Execution Model for their place in event and stream lineage.
Ordinary Runs vs Tasks
| Runs | Tasks | |
|---|---|---|
| Duration | Short-lived, synchronous | Long-running, asynchronous |
| Trigger | HTTP requests, events, schedules | Started from runs or other tasks |
| Return | Waits for completion, returns result | Returns immediately with TaskInfo |
| Execution record | The run is the execution attempt | The task resource links to a task-type run |
| Use case | Request-response, event handlers | Background jobs, containers, long-lived processes |
Runs
Runs are short-lived, synchronous top-level function execution attempts. Nested Hot function calls remain inside the current run's trace. Runs are triggered by:
- HTTP requests (API calls, webhooks)
- Events (
send,hot:call) - Schedules (cron, dynamic schedules)
Runs block until the function completes. See Runs, Events & Streams for details.
Tasks
Tasks are long-running, asynchronous resources. When you start a task, the
current run returns immediately with a TaskInfo containing the task ID and
stream ID. The task inherits that stream, records the originating run, and
executes in the background on a task worker. Its execution is recorded as a
task-type run.
There are two types of tasks:
- Code Tasks — Hot code with messaging (
::hot::task/start,::hot::task/send,::hot::task/receive) and WebSocket support (::hot::ws) - Container Tasks — Docker/OCI containers via
::hot::box/start
When to Use Each
| Scenario | Use |
|---|---|
| Request-response, event handlers, scheduled jobs | Runs |
| Long-running Hot code with send/receive messaging | Code Tasks |
| Arbitrary languages, CLI tools, system binaries | Container Tasks |
Task Lifecycle
Tasks move through these states:
| State | Description |
|---|---|
queued | Task is waiting for a worker |
running | Task is executing |
completed | Task finished successfully |
failed | Task exited with an error |
timed_out | Task exceeded its timeout |
cancelled | Task was cancelled before completion |
Starting Tasks
Code Tasks
Use ::hot::task/start to start a Hot function as a long-running task:
::task ::hot::task
// Start a task with no arguments
info ::task/start(::myapp/background-sync)
// Start a task with arguments
info ::task/start(::myapp/process-data, {url: "https://example.com"})
// Start with options (timeout, retry)
info ::task/start(::myapp/long-job, {input: data}, {
timeout: 3600000,
retry: {attempts: 3, delay: 5000, backoff: "exponential"}
})
Container Tasks
Use ::hot::box/start to run Docker/OCI containers:
::box ::hot::box
task ::box/start(BoxConf({
image: "python:3.13-alpine",
cmd: ["python", "-c", "print('Hello')"],
size: "nano"
}))
See Containers for full container documentation.
TaskInfo
Both ::hot::task/start and ::hot::box/start return a TaskInfo with:
| Field | Type | Description |
|---|---|---|
id | Str | Unique task identifier (UUID) |
stream-id | Str | Stream this task belongs to |
For code tasks, TaskInfo also includes stream (the full stream object) and origin-run (the run that spawned the task).
Waiting for Completion
Choose where to wait based on who needs the result:
- If later Hot code in the same execution depends on the result, call
::hot::task/await(info.id). - If a client needs the result, return
info.idfrom the run and use the official SDK task waiter. This lets the originating run finish while the task continues asynchronously.
All SDK waiters subscribe to /v1/tasks/{task_id}/subscribe. The first
task:update is always the latest persisted state, so the client cannot miss a
task that completed before it subscribed. The waiter reconnects when needed,
returns the completed task record, and raises a structured task error for
failed, cancelled, or timed_out.
The task's existing stream also emits durable task:update snapshots. Subscribe
to that stream when one client is coordinating several tasks; use the
task-specific waiter when it only needs one task's terminal result.
| Language | Wait method |
|---|---|
| JavaScript / TypeScript | await hot.tasks.wait(taskId) |
| Python | hot.tasks.wait(task_id) or await async_hot.tasks.wait(task_id) |
| Go | client.Tasks.Wait(ctx, taskID, nil) |
| Rust | client.tasks().wait(task_id, TaskWaitOptions::default()).await |
| Java | client.tasks().waitFor(taskId) |
See SDKs for timeout examples and language-specific failure types.
Cancellation
Cancel a queued or running task with ::hot::task/cancel:
::task ::hot::task
info ::task/start(::myapp/long-job, data)
// Later, cancel the task
cancelled ::task/cancel(info.id)
Returns true if the task was cancelled, false if it was already in a terminal state.
For running tasks, a cancellation message is delivered to the task's receive channel (as {$cancel: true}) so it can exit cooperatively.
Messaging (Code Tasks Only)
Code tasks can receive messages from other runs or tasks using ::hot::task/send and ::hot::task/receive:
::task ::hot::task
// From a run: start a task and send it data
info ::task/start(::myapp/worker, null)
::task/send(info.id, {command: "process", payload: data})
::task/send(info.id, "shutdown")
// Inside the task function: receive messages
my-task fn (initial-args: Any): Any {
msg ::task/receive()
cond {
eq(msg, "shutdown") => { "done" }
=> { process(msg) }
}
}
receive blocks until a message arrives. Returns null when the task's inbox closes.
Checkpoint & Restore (Code Tasks)
Long-running code tasks can save application state that persists across restarts. If a task is interrupted (worker crash, deploy) and retried, the new instance can call restore() to pick up where it left off.
::task ::hot::task
my-etl fn (config: Map): Any {
// Restore previous state, or start fresh
state or(::task/restore(), {offset: 0, processed: 0})
// ... process batch starting from state.offset ...
// Save progress
::task/checkpoint({offset: add(state.offset, batch-size), processed: add(state.processed, batch-size)})
}
checkpoint accepts any serializable value and returns true on success. restore returns the last checkpointed value, or null if no checkpoint exists. Both are only callable from inside a task.
You can also inspect a different task's checkpoint by passing a task ID: ::task/restore(task-id).
WebSocket Support (Code Tasks)
Code tasks can maintain long-lived WebSocket connections using ::hot::ws:
::ws ::hot::ws
// Inside a task
conn ::ws/connect("wss://echo.websocket.org", {headers: {}})
::ws/send(conn, {type: "hello", text: "world"})
msg ::ws/receive(conn)
::ws/close(conn)
WebSocket connections outlive a single run, making them ideal for real-time sessions inside tasks.