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

# Quickstart

> Run your first BlockX task on your machine: start a worker, submit a task, and understand the result

This page is the shortest path with no branches: from cloning the repository to seeing the return value of your first task, without depending on etcd, BlockDB, Meta, or a chain node at any point. Once you are done, you can read the architecture docs to understand what just happened.

If you want the big picture first, see the [Architecture overview](/en/architecture/overview). If you need the full dependency table, Makefile targets, and environment variable list, see [Local development environment](/en/development/getting-started).

## Prerequisites

* Go (`go.mod` declares `go 1.26.4`) and a working C compiler — `cmd/worker` links DuckDB, so CGO must be enabled
* Python >= 3.12 and [`uv`](https://docs.astral.sh/uv/)
* Git access to the private `github.com/Chaintable/*` repositories (both the Go and Python dependencies include private packages)

## Five steps to a running task

<Steps>
  <Step title="Clone and install dependencies">
    ```bash theme={null}
    git clone git@github.com:Chaintable/blockx.git
    cd blockx
    go mod download
    uv sync --project python
    ```

    The final command creates the venv used by the executor in `python/.venv/`; `PYTHON_BIN` in the next step must point to it.
  </Step>

  <Step title="Build the worker">
    ```bash theme={null}
    go build -o /tmp/blockx-worker ./cmd/worker
    ```

    The first build links the DuckDB static library and is slower than other packages; later builds hit the build cache.
  </Step>

  <Step title="Start the worker">
    ```bash theme={null}
    WORKER_LISTEN=127.0.0.1:9221 \
    WORKER_ADDR=127.0.0.1:9221 \
    EXECUTOR_COUNT=1 \
    PYTHON_BIN=$PWD/python/.venv/bin/python \
    PYTHONPATH=$PWD/python \
    ETCD_ENDPOINTS=none \
    ICEBERG_NAMESPACE=chaintable_test \
    BLOCKX_USAGE_DISABLED=true \
    BLOCKDB_BATCH_WRITE_ADDR=127.0.0.1:1 \
    STUB_BLOCKDB_DELAY_MS=0 \
    /tmp/blockx-worker
    ```

    The worker is ready once the log shows `worker listening` and `executor started`. All ten variables are required: without `ICEBERG_NAMESPACE` or `BLOCKDB_BATCH_WRITE_ADDR`, the process exits during config validation (`WorkerFullConfig.Validate` and `validateProfileRuntimeConfig`). `ETCD_ENDPOINTS=none` means the worker does not register with etcd, so no Coordinator is involved; `BLOCKDB_BATCH_WRITE_ADDR` can be any unreachable port, and `STUB_BLOCKDB_DELAY_MS=0` switches the read path back to the devstub.
  </Step>

  <Step title="Start the local proxy (in another terminal)">
    ```bash theme={null}
    cd examples/local_test_service
    uv sync
    uv run python local_proxy.py
    ```

    blockx-py only connects through the UDS proxy specified by `PROXY_SOCKET_PATH` (in production that hop is a component of the notebook execution engine and is not shipped with this repository); `local_proxy.py` fills it in locally: it forwards the four `WorkerService` RPCs to the worker byte for byte, and shims both coordinators' `ReserveWorkerSlot` onto the worker's own `RequestTaskSlot`.

    The proxy is ready once it prints `[proxy] listening on unix:/tmp/blockx-proxy.sock -> worker 127.0.0.1:9221`.
  </Step>

  <Step title="Submit a task">
    ```bash theme={null}
    uv run python submit.py
    ```

    ```text theme={null}
    [{'message': 'hello, ct2'}, {'message': 'hello, blockx'}]
    ```

    Results are collected in the **completion order** of the individual calls and are not guaranteed to match the row order of `callList` — the calls run concurrently inside the executor. When you need to map results back to their inputs, have the return value carry the input itself; in this example, `message` carries `name`.
  </Step>
</Steps>

## What you just submitted

The core of `submit.py` is a piece of function code plus a call configuration:

```python theme={null}
source_code = """
from blockx import LocalTestService

def _(name):
    return {"message": LocalTestService().helloworld(name)}
"""

task = TaskBuilder.build(
    CallListCallConfig(
        func=Function(source_code=source_code),
        callList=[["ct2"], ["blockx"]],
    ),
    ReturnValueResultHandler(),
)
result = task.submit(timeout=30)
```

Three key points:

* **The entry function must be named `_`**, and each call receives one row of `callList` as its positional arguments. Two rows means two calls.
* **`CallListCallConfig` is a Builder**: it decides which calls this task expands into. `ReturnValueResultHandler` is a Plugin: it decides how the results are finalized.
* **`LocalTestService` is a sample capability backend built into the worker**. In function code it is an ordinary gRPC client; when the executor starts it swaps the channel for a BridgeChannel, so calls are intercepted and dispatched inside the worker process — this is the pattern for integrating a new capability backend, see [Backend Adapter](/en/components/backend-adapter).

## What you can see in the result

The worker's `task_finished` log breaks this execution into three phases:

```json theme={null}
{"msg":"task finished","state":"SUCCEEDED","task_duration_ms":14,"output_count":2,
 "phases":[{"phase":"builder","status":"success","call_count":2},
           {"phase":"calls","status":"success","call_count":2},
           {"phase":"writer","status":"success","plugin_name":"ReturnValueResultHandler"}],
 "io_backends":[{"backend":"localtestservice",
                 "operation":"/localtestservice.v1.LocalTestService/HelloWorld","ops_success":2}]}
```

`builder → calls → writer` is the Worker's three-phase state machine: the Builder expands the configuration into a call list, the Calls phase runs the function code concurrently in the executor, and the Writer phase hands off to the Plugin to finalize. `io_backends` records the capability backends this task hit and how many calls it made. See [Task lifecycle](/en/architecture/task-lifecycle) for the full story.

<Tip>
  If you would rather not start the proxy, you can also run a single process E2E test to see the real call chain:
  `go test -v -timeout 300s ./cmd/worker/ -run TestWorkerProcess_RequestSlotAndSubmit`
</Tip>

## If you get stuck

| Symptom                                                                    | Cause                                                                                                                                                                                  |
| -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The worker exits right after starting with `invalid worker runtime config` | Add the variable named in the error string: `icebergNamespace must not be empty` means add `ICEBERG_NAMESPACE`; `requires blockdb.batchWriteAddr` means add `BLOCKDB_BATCH_WRITE_ADDR` |
| `build constraints exclude all Go files in duckdb-go-bindings/lib`         | A symptom of `CGO_ENABLED=0`. The worker requires CGO enabled and a C compiler                                                                                                         |
| The executor keeps restarting                                              | `PYTHON_BIN` does not point to `python/.venv/bin/python`, or you skipped `uv sync --project python` in the repository root                                                             |
| `PROXY_SOCKET_PATH not set` / `UNAVAILABLE`                                | The proxy is not running, or the socket path differs from the one in `submit.py` (both default to `/tmp/blockx-proxy.sock`)                                                            |
| The task fails with `failure_code` set to `CALL_FAILED`                    | The function code raised an exception. If you started the worker with `LOCAL_TEST_SERVICE_DISABLED=true`, the worker log shows `no adapter registered for backend: localtestservice`   |
| `go mod download` cannot fetch `github.com/Chaintable/*`                   | Private modules. Configure your git credentials and set `GOPRIVATE=github.com/Chaintable/*`                                                                                            |

For more issues, see the FAQ section of [Local development environment](/en/development/getting-started).

## Next steps

<Columns cols={2}>
  <Card title="Architecture overview" icon="layers" href="/en/architecture/overview">
    Components, core constraints, and the suggested reading order.
  </Card>

  <Card title="Task lifecycle" icon="route" href="/en/architecture/task-lifecycle">
    The complete sequence of the task you just ran as it moved through the system.
  </Card>

  <Card title="Local development environment" icon="terminal" href="/en/development/getting-started">
    The full dependency table, Makefile targets, and environment variable list.
  </Card>

  <Card title="Contributing" icon="git-pull-request" href="/en/development/contributing">
    Read this page before you change any code.
  </Card>
</Columns>
