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 gRPCTaskInput (api/grpc/worker/worker.proto):
A minimal example in JSON form (field names follow the
json tags of the Go type commontypes.TaskInput):
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 withCALL_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 enteroutputs; they only hand their result back to the parent call. The nesting depth limit isMaxSubcallDepth = 8.
Outputs
When the Calls phase ends, the return values of all successful root calls are aggregated intooutputs. 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
ReturnValueHandlerputs the wholeoutputsarray intopluginResults[i].result; table-writing plugins return only statistics such aswritten_rows. - Failures are expressed with
failureCodeandretryable.retryable=truemeans 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 theWatchTasksstream, or polls withGetTaskResult.
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
RequestTaskSlotandSubmitTask. - 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_idisRUNNINGor its result is still within the retention window, a repeatedSubmitTaskreturns 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 unregisteredresultHandler.type, and so on are all returned as a gRPC status; once a task entersRUNNING, every later failure converges to theFAILEDterminal 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;
outputsis a flat collection of return values. - Externally there are only four states,
ALLOCATED / RUNNING / SUCCEEDED / FAILED; failures are expressed withfailureCode+retryable. - The framework neither retries nor cancels tasks; the idempotency key is
task_id.
- Task lifecycle: the end-to-end sequence, the slot state machine, and a quick reference for time semantics.
- Call Builder and Writer Plugin: the config shapes of
functionCallConfigandresultHandler. - Function code: what the code a call runs looks like.
- Submit a task: examples: submit a task with blockx-py or by calling gRPC directly.