Skip to content
Pilotoo
v0.1

Workers

A memory can choose a model that only a machine of yours can reach, such as a model served by Ollama on your laptop. Brain does not connect to that machine. A small program there, the worker, asks Brain for model calls, runs them on the local model and sends the answers back. The machine needs no open port and Brain holds no key for it.

Brain still runs every job. Only the model call moves. For how one source travels, see How a worker runs your own models.

Add the model to the file named by BRAIN_MODELS_FILE with "runs": "worker". Brain keeps no model, provider or key for it, so the entry takes no other field except id, label and, for an embedding model, dimensions.

models.json
{
"chat": [{ "id": "local-qwen", "label": "Qwen 3 on my Mac", "runs": "worker" }],
"embeddings": [{ "id": "local-embed", "label": "Nomic on my Mac", "runs": "worker", "dimensions": 768 }]
}

Set dimensions so Brain checks the width of every vector the worker returns. In Settings, a memory chooses the model like any other. While no worker serves it, the list marks it no worker connected.

A workspace admin opens Workers in the web app, names the token and chooses the memories it serves: every memory, or a list. The secret starts with pbw_ and is shown once. Brain keeps only its hash.

A worker token does one thing: it takes model calls of the memories it serves and answers them. It reads no memory, and every other route refuses it. An agent token (pb_) or a signed-in session is refused on the worker routes in turn. Revoking the token stops the worker at its next request.

The worker is the package @pilotoo/brain-worker, with the command pilotoo-brain-worker. A file names Brain and the models the machine serves, and the token stays in the environment.

worker.json
{
"brainUrl": "https://brain.example.com",
"tokenEnv": "BRAIN_WORKER_TOKEN",
"concurrency": 1,
"models": [
{ "id": "local-qwen", "kind": "chat", "runner": "ollama", "model": "qwen3.5:9b", "numCtx": 16384 },
{ "id": "local-embed", "kind": "embedding", "model": "nomic-embed-text", "baseUrl": "http://127.0.0.1:11434/v1", "dimensions": 768 }
]
}
Terminal
pnpm --filter @pilotoo/brain-worker build
BRAIN_WORKER_TOKEN=pbw_... node packages/brain-worker/dist/main.js worker.json

Each id is the id of a model in BRAIN_MODELS_FILE. The worker asks only for the ids it serves. On SIGTERM or SIGINT it finishes the call in hand and stops.

Field Default Meaning
brainUrl required The address of Brain. It must be https://; http:// is accepted only for localhost, 127.0.0.1 and [::1], because the token travels in every request.
tokenEnv BRAIN_WORKER_TOKEN The environment variable that holds the token. The file never does.
concurrency 1 Calls answered at once.
models[].id required The id of the model in BRAIN_MODELS_FILE.
models[].kind required chat or embedding.
runner required for chat ollama or openai-compatible.
model required The model’s name on that server.
baseUrl Ollama: http://127.0.0.1:11434. Others: required The server’s address. An embedding model uses an OpenAI-compatible endpoint, such as Ollama’s /v1.
apiKeyEnv none The name of an environment variable that holds a key, when the server wants one.
numCtx, numPredict, think Ollama’s ollama only: the context window, the longest reply, and whether the model thinks first.
maxTokens the server’s openai-compatible only.
timeoutSeconds 300 The longest a chat call may run on this machine, at most 1800: Brain holds a call for at most 30 minutes from its first claim.
dimensions none Embedding only: the width the server’s vectors must have.

A call waits for a worker to claim it. The job keeps its own lease alive while it waits.

What happens Result
No worker claims the call within 2 minutes. The call fails and the job goes back to the queue, to try again after a short wait.
A worker claims the call and stops, so it stops extending its claim. The claim runs out, the call fails and the job goes back to the queue.
The worker reports an error, for example the model’s reply has the wrong shape. The call fails the same way.
The job fails on its last attempt. The document fails with No worker answered for model <id>.
Someone asks a memory whose answer model runs on a worker. The question waits the same way, and fails with the same reason.
A result arrives for a claim that was lost. Brain refuses it and changes nothing.

Two workers that serve the same model share its calls: each call goes to one of them.

Brain checks a worker’s answer exactly as it checks the answer of a hosted model. The worker runs the same code Brain runs for a hosted model, on its own model.

Passages added while no worker answers are stored without vectors and counted as pending. Start Embed again in the memory’s settings once a worker is back, and Brain embeds them in batches. See Models.

GET /api/models marks a model that runs on a worker with "runs": "worker". It is available while a worker that serves it was seen in the last minute. Availability is only a display: a job still queues and waits for a worker, whatever the mark says.

The Workers page lists each token with the memories it serves, the models its worker declared at its last request and when it was last seen.

Setting Default Meaning
"runs": "worker" server On an entry of BRAIN_MODELS_FILE: only a worker answers it.
BRAIN_WORKER_TOKEN none On the worker’s machine: the token. The variable’s name is tokenEnv in the worker file.