Skip to main content
A task is a piece of work the Client submits to a Worker: a task_id, a “what to compute” configuration, an optional “how to handle the result” configuration, and an optional timeout. The Worker expands it into a number of calls, and each call is one invocation of “one function + one set of positional arguments”. A task runs on only one Worker, and its calls run fully in parallel in that Worker’s Executor pool.

Task input

The structure submitted externally is the gRPC TaskInput (api/grpc/worker/worker.proto): A minimal example in JSON form (field names follow the json tags of the Go type commontypes.TaskInput):
The top level of a task carries no chain or block information. Block context is carried by the config of the specific Builder and Writer Plugin, for example BlockTableCallConfig.block and BlockTableWriteHandler.block.

Call

Where a call comes from is decided by the Call Builder: with InputsCallConfig, each row of callList is one call; scanning Builders generate one call for each row of the trigger table, and place the row itself into args according to the params template. Properties of a call:
  • Calls are independent of each other and fully parallel, sharing the same read-only world state at a single point in time. Return order is completion order and is unrelated to the order of the call list.
  • Within one task, calls with the same function and the same arguments execute only once (task-level result cache and singleflight).
  • A single call allows bounded retries (DefaultMaxAttempts = 3); once any root call fails terminally, the whole task fails with CALL_FAILED (fastFail).
  • A single call’s execution budget is CallDeadlineMs = 5000, counting only CPU and IO backend time, not queueing.
  • Sub-function calls inside a user function (function.call) are calls too, but they do not enter outputs; they only hand their result back to the parent call. The nesting depth limit is MaxSubcallDepth = 8.

Outputs

When the Calls phase ends, the return values of all successful root calls are aggregated into outputs. Each element is a piece of JSON kept as is: an object represents one row, an array of objects represents multiple rows, and null means this call produced nothing. The Writer Plugin sees only outputs; it does not see which call maps to which row. When the accumulated byte count exceeds MaxCollectedOutputBytes, the task stops dispatching and fails with OUTPUT_BYTES_EXCEEDED.

External states

Result

TaskResult expresses task-level results only:
  • It does not contain the return value of each call. Only ReturnValueHandler puts the whole outputs array into pluginResults[i].result; table-writing plugins return only statistics such as written_rows.
  • Failures are expressed with failureCode and retryable. retryable=true means the Client can resubmit the same task; the framework itself does no task-level retry.
  • Terminal results are kept on the Worker for ResultRetentionMs (default 300000) and then cleared. The Client subscribes to terminal states with the WatchTasks stream, or polls with GetTaskResult.
Common failureCode values:

Invariants

  • Task-centric: read caches, state sharing, and timeouts are all anchored to the task and released when the task ends.
  • Single-Worker execution: a task runs on only one Worker; the Worker is unaware of the Coordinator and exposes only RequestTaskSlot and SubmitTask.
  • Function versions are fixed: a code snapshot (taskCodeEpoch) is pinned when the task is activated, and all subsequent calls and sub-calls use that snapshot; hot code updates only affect new tasks.
  • Idempotent: while the same task_id is RUNNING or its result is still within the retention window, a repeated SubmitTask returns the current state and does not execute again.
  • No task-level automatic retry, no cancellation: the framework only provides timeouts; whether to resubmit is up to the Client, based on retryable.
  • Admission errors do not enter TaskResult: an invalid slot, no free slot, invalid arguments, an unregistered resultHandler.type, and so on are all returned as a gRPC status; once a task enters RUNNING, every later failure converges to the FAILED terminal state.

Summary

  • task = task_id + Call Builder config + optional Writer Plugin config + optional timeout; call = function + positional arguments.
  • Calls are fully parallel, unordered, and deduplicable; outputs is a flat collection of return values.
  • Externally there are only four states, ALLOCATED / RUNNING / SUCCEEDED / FAILED; failures are expressed with failureCode + retryable.
  • The framework neither retries nor cancels tasks; the idempotency key is task_id.
Continue reading: