> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blockx.chaintable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Task and call

> A task is the smallest scheduling unit in BlockX, and a call is the smallest execution unit within a task: what each one is, the task's input fields, its external states, the result model, and the invariants.

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`):

| Field                  | Type             | Description                                                                                                                                                                 |
| ---------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `task_id`              | `string`         | A stable unique ID generated by the Client (blockx-py uses uuid4). The same `task_id` is the idempotency key on the Worker                                                  |
| `function_call_config` | `{type, config}` | `type` selects the Call Builder; `config` is JSON that only that Builder parses                                                                                             |
| `result_handler`       | `{type, config}` | Optional. `type` selects the Writer Plugin. At most one per task                                                                                                            |
| `task_timeout_ms`      | `int64`          | Optional. `0` uses the Worker default (`TASK_DEADLINE_MS`, default 300000); negative values are rejected; values above `MAX_TASK_TIMEOUT_MS` (default 8 hours) are rejected |

A minimal example in JSON form (field names follow the `json` tags of the Go type `commontypes.TaskInput`):

```json theme={null}
{
  "task_id": "3f2b1c9e8a4d4c1e9b0a6f7e5d4c3b2a",
  "functionCallConfig": {
    "type": "InputsCallConfig",
    "config": {
      "function": "",
      "code": { "sourceCode": "def _(name):\n    return {\"message\": f\"hello, {name}\"}\n" },
      "callList": [["ct2"], ["blockx"]]
    }
  },
  "resultHandler": { "type": "ReturnValueHandler", "config": {} },
  "taskTimeoutMs": 30000
}
```

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

| Field        | Meaning                                                                                                                                                                  |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `functionId` | Function identity. A registered function uses its registration ID; for inline source, the Worker generates `inline-<first 8 chars of the digest>` from the source digest |
| `args`       | Array of positional arguments, JSON encoded; the user function receives them by position                                                                                 |
| `callId`     | Unique within the Worker. `InputsCallConfig` generates `{taskId}-{i}`; dbScan generates `{taskId}-{table}-{height}-{i}`                                                  |

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

| State       | When it is entered                                                   | Description                                                                              |
| ----------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `ALLOCATED` | `RequestTaskSlot` succeeds                                           | A placeholder only, not yet submitted; reclaimed automatically when the slot TTL expires |
| `RUNNING`   | `SubmitTask` passes validation                                       | The three internal phases Builder, Calls, and Plugin are all folded into this state      |
| `SUCCEEDED` | The Plugin phase succeeds                                            | Terminal state                                                                           |
| `FAILED`    | Any phase fails, the task times out, or the Watch stream disconnects | Terminal state; `failureCode` explains the reason                                        |

```mermaid theme={null}
stateDiagram-v2
    [*] --> ALLOCATED: RequestTaskSlot
    ALLOCATED --> [*]: reclaimed on TTL expiry
    ALLOCATED --> RUNNING: SubmitTask
    RUNNING --> SUCCEEDED: Plugin phase succeeds
    RUNNING --> FAILED: any phase fails / times out
    SUCCEEDED --> [*]: result retention window expires
    FAILED --> [*]: result retention window expires
```

## Result

`TaskResult` expresses task-level results only:

```json theme={null}
{
  "success": true,
  "executeResult": {
    "retryable": false,
    "pluginResults": [
      {
        "pluginName": "ReturnValueHandler",
        "success": true,
        "result": [{ "message": "hello, ct2" }, { "message": "hello, blockx" }]
      }
    ]
  }
}
```

* 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:

| `failureCode`                           | Meaning                                                                                                                                      | `retryable`                                  |
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
| `BUILDER_NOT_FOUND` / `BUILDER_FAILED`  | `functionCallConfig.type` is not registered, or the Builder failed to expand (table does not exist, invalid `operator`, BlockDB read failed) | Depends on the IO error classification       |
| `CALL_FAILED`                           | A root call failed terminally, typically because the user function raised an exception or the audit rejected it                              | `false`                                      |
| `OUTPUT_BYTES_EXCEEDED`                 | `outputs` exceeded `MaxCollectedOutputBytes`                                                                                                 | `false`                                      |
| `PLUGIN_NOT_FOUND` / `PLUGIN_FAILED`    | The Writer Plugin is not registered, or the write failed (config shape mismatch, target table has no columns, downstream write failed)       | Decided by the plugin's error classification |
| `TIMED_OUT`                             | The task timeout was exceeded                                                                                                                | `true`                                       |
| `ACTIVATION_FAILED` / `IO_SCOPE_FAILED` | Pinning the code snapshot or creating the IO scope failed during activation                                                                  | Depends on the error                         |
| `CANCELLED`                             | The task context was cancelled while waiting for phase admission                                                                             | —                                            |
| `WATCH_DISCONNECTED`                    | The last Watch stream disconnected and the grace period elapsed                                                                              | `false`                                      |

## 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:

* [Task lifecycle](/en/architecture/task-lifecycle): the end-to-end sequence, the slot state machine, and a quick reference for time semantics.
* [Call Builder and Writer Plugin](/en/concepts/builders-and-handlers): the config shapes of `functionCallConfig` and `resultHandler`.
* [Function code](/en/concepts/function-code): what the code a call runs looks like.
* [Submit a task: examples](/en/development/submit-task-example): submit a task with blockx-py or by calling gRPC directly.
