Run a fleet worker
Ce contenu n’est pas encore disponible dans votre langue.
Run cc_worker on a spare machine to add execution capacity to your fleet. It is
a pure-Dart binary — no Flutter engine — that pairs with a cc_server, declares
the host’s capabilities, heartbeats, pulls leased jobs, executes them and
streams process events back. It holds no durable state: a supervisor restarts it
and it re-registers.
You need a running cc_server first — see Run a headless server.
A paired worker registers, heartbeats and waits for leased jobs. Submit work
with fleet.submitJob. Agent dispatch on this server does not route through
the fleet; the success signal for this guide is the worker appearing in the
fleet panel with a fresh heartbeat. The in-process worker registered at boot
has an empty runner map, so a job placed on it returns No local runner for job kind ….
Prerequisites
Section titled “Prerequisites”- The Dart SDK. This repo pins its SDK with fvm, so prefix commands with
fvm. - This repository checked out on the worker machine.
- A reachable
cc_server(loopback for dev, LAN or remote for production). giton thePATH— the worker materializes repos and runsgitprobes.
Step 1: Build the binary
Section titled “Step 1: Build the binary”cd apps/cc_workerfvm dart build cliThis produces a self-contained bundle at ./build/cli/<os_arch>/bundle/bin/cc_worker.
Step 2: Mint the worker’s credential
Section titled “Step 2: Mint the worker’s credential”A cc_server only accepts a worker that presents a paired-device id and
pre-shared key — including a loopback server. Mint them on the server
machine (pair can run while the server is up; the new device is picked up
without a restart):
cc_server pair --data-dir <dir> --device my-worker --label "Build box"It prints a Device id and a Pairing key — those are what --device-id and
--psk / --psk-file / CC_WORKER_PSK take.
Step 3: Start the worker
Section titled “Step 3: Start the worker”Against a paired server:
./build/cli/<os_arch>/bundle/bin/cc_worker \ --server wss://host:9030 --device-id my-worker --psk-file /path/to/worker.pskAgainst a loopback server (still paired — cc_server always runs the PSK
handshake):
fvm dart run cc_worker --server ws://localhost:9030 \ --device-id my-worker --psk-file /path/to/worker.psk| Flag | Description | Default |
|---|---|---|
--server <url> |
cc_server URL (ws:///wss://; http(s) and a missing /rpc path are coerced). Required. |
— |
--name <name> |
Operator-facing worker name. | host name |
--device-id <id> |
Stable paired-device id, also used as the worker id. | cc-worker |
--psk-file <path> |
File whose first non-empty line is the pairing key. Preferred over --psk. |
— |
--psk <key> |
Pairing key on the command line. Visible in ps and shell history — the binary warns. |
— |
--max-jobs <n> |
Hard ceiling on jobs this process executes concurrently (positive integer). | 4 |
Resolution order for the key is --psk-file → CC_WORKER_PSK → --psk.
Omitting every source skips the worker’s auth handshake; a real cc_server
then denies the session (auth_denied) rather than accepting an unauthenticated
worker. Unknown flags are a usage error (exit 64) — unlike cc_server, this
binary uses package:args and does not ignore typos.
CC_WORKER_CACHE overrides the worktree materialization cache directory
(default: a cc_worker_cache directory under the system temp dir).
The worker registers with fleet.registerWorker, then heartbeats every 20s and
polls for leases every 2s. It runs until SIGINT/SIGTERM; put it under systemd
or launchd so it restarts and re-registers after a crash or reboot.
Step 4: Verify it in the fleet panel
Section titled “Step 4: Verify it in the fleet panel”Open Observability in the desktop or web app, switch to the Live tab and scroll to the bottom. Workers lists every registered worker with its platform, cores, capability keys and last heartbeat; Jobs lists the jobs distributed across them.
Your worker appearing there with a recent heartbeat is the confirmation that pairing and registration worked.
From a worker’s tile you can drain it (finish current jobs, take no new leases), resume a draining worker, revoke its credential, or remove the row.
Worker and job rows live in the server’s global.db — the worker itself writes
nothing to disk beyond the materialization cache.
What the worker advertises
Section titled “What the worker advertises”On registration the worker auto-detects its capabilities and the scheduler matches jobs against them. You do not configure these by hand:
- OS and arch (
macos/linux/windows,arm64/x64) — exact probes, with fallbacks touname -m/PROCESSOR_ARCHITECTURE - Cores and RAM — exact core count; RAM degrades to
0when unprobeable flutter— set when a Flutter SDK is reachable (bareflutter, thenfvm flutter); golden-render jobs require itsandbox—native-macoson macOS,native-linuxon Linux. This is a placement hint, not a promise: the worker advertises the backend its platform has, but it does not wrap the jobs it runs in one. Unlikecc_server,cc_workerspawns a leased job directlyalways-onandparallel— a dedicatedcc_workeralways advertises both, so it is eligible for eval batches and other throwaway parallel capacity
Detection is best-effort: anything it cannot probe degrades to a safe default rather than failing startup.
What the worker would execute
Section titled “What the worker would execute”The lease protocol carries six job kinds:
| Kind | What it is |
|---|---|
agentRun |
A single agent run (dispatch) |
pipelineStep |
A pipeline step execution |
codeIndex |
A repo code-index build |
goldenRender |
A UI visual golden render (requires flutter) |
benchmark |
A performance benchmark run |
evalBatch |
An eval batch (prefers parallel capacity) |
Execution is a real subprocess-streaming implementation, not a full embedded
agent runtime. When a lease carries repoRemote, the worker does
git clone --depth 1 into a remote-and-SHA-keyed cache directory, then
git fetch and git checkout. For agentRun it runs the command in the lease
env’s CC_JOB_COMMAND in the work directory, streaming stdout and stderr back as
events; with no CC_JOB_COMMAND it echoes the prompt so the transport is still
exercised end to end. The other kinds run small real commands
(git rev-parse HEAD, git --version) as honest probes. Job-scoped credentials
in the lease env are injected into every subprocess and never written to a log.
Events are batched and flushed every 250ms or every 32 events; each job ends with a done event and a completion report. The model is heartbeats plus short-TTL leases: if a worker loses contact its lease expires and the server reaps the job for retry. There is no durable state on the worker and no consensus or work-stealing between workers.