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

RunsTasks
DurationShort-lived, synchronousLong-running, asynchronous
TriggerHTTP requests, events, schedulesStarted from runs or other tasks
ReturnWaits for completion, returns resultReturns immediately with TaskInfo
Execution recordThe run is the execution attemptThe task resource links to a task-type run
Use caseRequest-response, event handlersBackground 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:

  1. Code Tasks — Hot code with messaging (::hot::task/start, ::hot::task/send, ::hot::task/receive) and WebSocket support (::hot::ws)
  2. Container Tasks — Docker/OCI containers via ::hot::box/start

When to Use Each

ScenarioUse
Request-response, event handlers, scheduled jobsRuns
Long-running Hot code with send/receive messagingCode Tasks
Arbitrary languages, CLI tools, system binariesContainer Tasks

Task Lifecycle

Tasks move through these states:

Task lifecycle states A queued task becomes running, then finishes as completed, failed, timed out, or cancelled. queued waiting for a worker claimed running task worker executing completed finished successfully failed exited with an error timed_out exceeded its timeout cancelled stopped cooperatively
StateDescription
queuedTask is waiting for a worker
runningTask is executing
completedTask finished successfully
failedTask exited with an error
timed_outTask exceeded its timeout
cancelledTask 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:

FieldTypeDescription
idStrUnique task identifier (UUID)
stream-idStrStream 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.id from 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.

LanguageWait method
JavaScript / TypeScriptawait hot.tasks.wait(taskId)
Pythonhot.tasks.wait(task_id) or await async_hot.tasks.wait(task_id)
Goclient.Tasks.Wait(ctx, taskID, nil)
Rustclient.tasks().wait(task_id, TaskWaitOptions::default()).await
Javaclient.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.