Run a worker
Every build for the pool happens on a worker somebody runs — a contributor's laptop for their own packages, a machine a maintainer trusts for the project's work — and every worker runs the same image: ghcr.io/firemanxbr/omarchy-worker, Arch Linux, built for x86_64 and aarch64 on GitHub Packages and signed. There is no technical difference between a contributor's container and a maintainer's; the registration behind the token decides what it may do. Nothing you run holds a key: the pool signs what it publishes, and your token only asks for work.
What the registration decides
| Your registration | What the container does | What it needs |
|---|---|---|
| community trust — every registration starts here | builds your registered packages, one task per container, right inside it, into your staging workspace as evidence for a maintainer. With WORKER_SHARED=1 it also builds other contributors' packages (donated compute), with your agent key (ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY or XAI_API_KEY) your agent drafts and corrects PKGBUILDs. It never sees a package in review or approved. | the token |
| project trust — two maintainers vouched for the registration, neither its owner | the project's work: the pool's own jobs (sync, promote, health, security, gc, the PKGBUILD reconcile) and the rebuild of packages maintainers approved — what users actually get. Each build and check runs in a fresh Arch container it starts as a sibling. With an agent key it also audits staged builds for the maintainers (the second agent). Never a contributor's build. | the token, the runtime's socket, a working directory at the same path on both sides |
A maintainer who also contributes packages registers a second worker and leaves it untrusted: one registration per role of a machine. Tags: latest is a multi-architecture manifest (your machine pulls its own), x86_64 and aarch64 pin one, and every pool release is a tag (v0.0.70). Verify before trusting it:
cosign verify ghcr.io/firemanxbr/omarchy-worker:latest \ --certificate-identity-regexp 'github.com/firemanxbr/omarchy-pool' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com
The three roles
The project runs its workers as three kinds of container, and anyone donating a machine can run the same. A role is set with OMARCHY_WORKER_ROLE; it narrows what the registration allows, never widens it, and the container refuses to start under a registration that does not match (a pool or review role needs project trust, a community role a community registration). The Workers page lists each worker under its role.
| Role | Registration | What it does | Agent key |
|---|---|---|---|
| pool | project trust | the pool's own jobs and nothing else: sync, render, promote, rollback, health, security, enqueue, gc, verify. Never a build, never an audit. | none |
| review | project trust | the maintainers' work and nothing else: the rebuild of approved packages in fresh sibling containers, and the audit of every staged build (the second agent). Never a pool job. | wanted — without one, audits wait |
| community | community registration | a shared community worker (WORKER_SHARED=1 implied): builds anyone's requested packages with its owner's agent, one task per container — the queue every request lands in. Anyone may share one; a dedicated worker builds only its owner's. | required — a worker whose agent does not answer the probe is not ready and gets no build |
Two of each — one per architecture — is what the project runs on its own host (RUNBOOK, The Studio host): x86_64 pool jobs are only a label and run natively on any machine; x86_64 builds on an aarch64 host run under user-mode emulation, correct but slower. Without a role the trust decides everything: a project worker takes pool jobs, rebuilds and audits alike; a community worker builds its owner's packages.
Before you start
A container runtime
Docker Desktop on macOS, Windows or Linux, or Podman — the podman command, or Podman Desktop with its graphical window. Every command below is shown for both; they differ only in the first word. Give the runtime at least 2 CPUs and 4 GB of memory (Docker Desktop: Settings → Resources; Podman on macOS: podman machine set --cpus 4 --memory 8192); a browser-class package needs far more.
Which architecture you build
A worker builds for its own architecture: an Apple silicon Mac or a Raspberry Pi builds aarch64, an Intel or AMD machine x86_64. Register the worker for the architecture of the machine it will run on; the image refuses a mismatch.
An account, a worker registration
Sign in with GitHub (top right): it lands on your own page, the workspace. Register a worker there: a name and its architecture. You get a token, shown once — that machine's identity. Revoke it on the same page if the machine is lost.
As a contributor: your own packages
1. Start it
One command, wherever the worker lives — a machine or a VM with Docker or podman (and compose). It writes the compose file and a .env (in ~/.config/omarchy-worker, mode 600), pulls the signed image and starts the set: three containers — the broker and the builder on a network of their own, the updater beside them. The broker holds what is yours — the worker token, your agent's key, a GitHub token — and only receives, processes and answers. The builder beside it is born with nothing: it asks the broker for a build of yours, builds it, uploads the package, the PKGBUILD and the log to your staging workspace through the broker, and exits; the restart policy starts the next one. The updater keeps both on the pool's latest image (every worker follows it).
curl -fsSLo omarchy-worker https://omarchy-pool.org/omarchy-worker && chmod +x omarchy-worker ./omarchy-worker start --token <omw_…> --github-token <github_pat_…, no permissions> ./omarchy-worker status # what runs here, what the pool thinks of it ./omarchy-worker logs # the builder's log (logs broker | updater) ./omarchy-worker stop # a drain: the build in hand finishes first
The same set by hand, with compose.yml (also served at /omarchy-worker/compose.yml) and a .env beside it; the updater mounts the directory at the same path, so its absolute path goes in:
printf 'OMARCHY_WORKER_TOKEN=%s\nGITHUB_TOKEN=%s\nOMARCHY_WORKER_DIR=%s\nCOMPOSE_PROFILES=community\n' omw_… github_pat_… "$PWD" > .env docker compose up -d # podman compose works the same
--github-token (on the broker): the drafter reads GitHub's API for every package it builds — the release, the files — through the broker. Without a token GitHub allows 60 requests an hour from your address, and a queue of ten builds is ten failures; a fine-grained token with no permissions at all gives 5000. Make one for this — never gh auth token, which is your account with write access to your repositories (see what a build can see). A stop is a drain (compose: stop_grace_period: 3h): the build in hand finishes and reports; killed mid-build, the task waits half an hour for its lease to expire. Change the settings between builds, not during one.
2. Give it work
Request a package (the project's URL, a description, the licence, the checklist). The build starts by itself, in the shared queue: the best idle shared worker of the architecture takes it — a worker of your own at once, shared or not. Build on your page names a worker, sends a queued build back to the shared queue or takes it out, and runs it again. The Builds table follows it, and the Workers table shows it alive. When the build is staged, a maintainer sees it on Review.
3. Donate the machine, bring your agent
Two switches, both yours to flip — the first on the builder, the second on the broker (options of omarchy-worker start; by hand, the same names in .env). The first is the pool's to keep: Share / Own only on your page (or omarchy-worker share on|off) sets the registration's mode, and from the worker's next claim — within the minute, nothing restarts — that is what counts, whatever the container was started with; the container's flag is only the first word.
# the builder: also build other contributors' packages (their bumps after 14 days, package requests at once)
./omarchy-worker start --shared # or: ./omarchy-worker share on | off, or Share on your page — the pool keeps it (.env's WORKER_SHARED is only the first word)
# the broker: an agent drafts and corrects PKGBUILDs, with your key — the pool never holds one, the builder never sees it;
# one of these is enough (Anthropic, OpenAI, Gemini, xAI), --model picks the model
./omarchy-worker start --anthropic-key sk-… --model claude-sonnet-5 # or --openai-key / --gemini-key / --xai-key
# (.env: ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, XAI_API_KEY, FACTORY_MODEL)
# or your Claude subscription instead of a key (see "A Claude subscription as the agent" below)
./omarchy-worker start --claude-token … # what 'claude setup-token' printed on your machine (.env: CLAUDE_CODE_OAUTH_TOKEN)
The Workers page shows which agent each worker reported (anthropic/claude-sonnet-5, claude-code/claude-sonnet-5, openai/gpt-5, …); the key itself never leaves the broker.
A shared worker with an agent is what turns a package request (the request page) into a first PKGBUILD and a first build: the request lands in the shared queue the moment its record is written, and the best idle shared worker of the architecture — native before emulated, then the most cores — takes it first; the others after three minutes. Without one, requests wait, and the page says where they stand. What your agent produces is evidence like any other build: a maintainer reads it before anything reaches users.
4. Watch it
./omarchy-worker status says what runs here and what the pool thinks of it; ./omarchy-worker logs follows the builder (logs broker, logs updater the others). The worker's own log — the lines between tasks: preparing, the agent's probe, an update required — also reaches the pool with each claim: the log icon beside its id on the Workers page and on your page opens the last kilobytes, for its owner and the maintainers (a build's output is on the build's page). In Docker Desktop, Containers lists the three under the omarchy-worker project, each with a Logs tab; in Podman Desktop, the same under Containers. The builder exits after each task (that is by design) and the restart policy brings it back.
omarchy-worker project and the builder's Logs tab; Podman Desktop → Containers, the same.As a maintainer: the pool's jobs and approved rebuilds
Once a maintainer trusts the registration (POST /api/v1/factory/workers/<id>/trust, through the API; who trusted it is in the worker id's tooltip on Workers), the same image switches to the project's work. Each build and check runs in a fresh Arch container it starts as a sibling through your runtime — so it needs the runtime's socket, and a working directory that has the same path on your machine and inside the container (the sibling containers mount subdirectories of it).
0. One command
The same omarchy-worker, with that registration's token and --project (the updater beside it, as for a contributor's):
./omarchy-worker start --token <omw_…> --project --role review # or --role pool; --work-dir for the working directory
1. Docker Desktop, by hand
The three steps below run one container without an updater: after every release, docker pull …:latest and recreate it — or the pool refuses it 45 minutes later (every worker follows the latest image). Step 0 does that for you.
mkdir -p "$HOME/omarchy-worker"
docker run -d --name omarchy-worker --restart unless-stopped \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$HOME/omarchy-worker:$HOME/omarchy-worker" -e OMARCHY_WORK_DIR="$HOME/omarchy-worker" \
-e OMARCHY_WORKER_TOKEN=<omw_…> \
ghcr.io/firemanxbr/omarchy-worker:latest --labels '{"where":"my-machine"}'
On Windows, use a path Docker Desktop shares (under your user profile) for the working directory, with the same spelling on both sides of the -v.
2. Podman
# Linux (rootless): the socket is your user's — enable it once systemctl --user enable --now podman.socket podman run -d --name omarchy-worker --restart unless-stopped --security-opt label=disable \ -v /run/user/$UID/podman/podman.sock:/var/run/docker.sock \ -v "$HOME/omarchy-worker:$HOME/omarchy-worker" -e OMARCHY_WORK_DIR="$HOME/omarchy-worker" \ -e OMARCHY_WORKER_TOKEN=<omw_…> \ ghcr.io/firemanxbr/omarchy-worker:latest # macOS (podman machine, rootful by default): the socket lives inside the VM podman run -d --name omarchy-worker --restart unless-stopped --security-opt label=disable \ -v /run/podman/podman.sock:/var/run/docker.sock \ -v "$HOME/omarchy-worker:$HOME/omarchy-worker" -e OMARCHY_WORK_DIR="$HOME/omarchy-worker" \ -e OMARCHY_WORKER_TOKEN=<omw_…> \ ghcr.io/firemanxbr/omarchy-worker:latest
--security-opt label=disable lets the container use the socket on SELinux hosts (Fedora, the podman machine). Mounting the socket path you see on macOS (…/podman-machine-default-api.sock) fails with operation not supported: it belongs to the host, not the VM — use the VM's path above.
3. Or without a container
On a Linux host with podman or docker, the release binaries do the same: download omarchy-pool-<version>-<arch>-linux.tar.gz from the latest release and run pkg-repo work --worker-token omw_… --arch aarch64. Same options as below. The binary is its release's: the pool refuses it 45 minutes after the next one is deployed, until you download that.
4. Options
Anything after the image name goes to pkg-repo work:
--kind build --kind health only these kinds (default: everything a project worker may run)
--idle-exit 300 exit after five minutes without work (a fallback worker)
--once one task, then exit
--labels '{"where":"…"}' where it runs; on the Workers page, on the id's hover
The worker reports with every claim what its machine uses — CPU, memory and the work directory's disk, an average it keeps over the last hour, from /proc and df — and which release its image is; the Workers page shows both per worker, with the last task it finished. Nothing is collected from you: the numbers come from the worker, in the claim it makes anyway.
A project worker never builds a contributor's package: those run on the contributor's own worker, or on the shared community workers — the project's and anyone's. What it builds is the rebuild a maintainer approved — never one the same maintainer brought — and the pool signs the result.
5. The second agent
Add your agent key — -e ANTHROPIC_API_KEY=sk-…, or OPENAI_API_KEY, GEMINI_API_KEY, XAI_API_KEY, or a Claude subscription as CLAUDE_CODE_OAUTH_TOKEN (below); your key, on your machine; -e FACTORY_MODEL=… picks the model — and the worker also takes the audit of every build a contributor stages: it reads the PKGBUILD, the log and the .PKGINFO the maintainer will read, asks the model for a structured review — supply chain, security, packaging practice, licence — and attaches the report to the evidence. Every agent — the drafter and the auditor alike — reads the pool's skills first: what every package must pass and what a desktop app or a prebuilt binary must do besides, the same text as What we test. Review shows the verdict next to the build; the maintainer still decides. No such worker running, and the column says waiting.
A Claude subscription as the agent
A Claude Pro or Max subscription can be the worker's agent instead of an API key: the worker runs Claude Code in print mode — claude -p, no tools, no session, the report as JSON — with a token from your own login. Nothing else changes: the same drafts, the same audits, the same evidence for the maintainer.
1. A token, on your machine
With Claude Code installed and logged in on the machine you use (the Studio, the laptop — not the worker), run
claude setup-token
It opens the browser for a one-time consent and prints a long-lived token (sk-ant-oat01-…). That token is your subscription: keep it like a password, revoke it from your Claude account when a machine is lost. The worker never needs your login, only this.
2. Give it to the worker
# a contributor's broker — the builder beside it never sees the token (docker works the same) podman run -d --name omarchy-broker --restart unless-stopped --network omarchy-worker \ -e OMARCHY_WORKER_ROLE=broker -e OMARCHY_WORKER_TOKEN=<omw_…> -e GITHUB_TOKEN=<github_pat_…, no permissions> \ -e CLAUDE_CODE_OAUTH_TOKEN=<sk-ant-oat01-…> \ ghcr.io/firemanxbr/omarchy-worker:latest # with compose: the same variable in the environment or a .env file CLAUDE_CODE_OAUTH_TOKEN=… OMARCHY_WORKER_TOKEN=… podman compose up -d # a project host (factory/host): the line goes in etc/agent.env, which the review # and community services read; FACTORY_PROVIDER makes the choice explicit when an # API key sits in the same file CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-… FACTORY_PROVIDER=claude-code
At start the broker installs Claude Code for its architecture (the official installer, checksum verified, into the container's home — about 200 MB, once per container; the image does not ship it) and reports claude-code/claude-sonnet-5 as its agent on the Workers page. FACTORY_MODEL picks another model (claude-opus-5); FACTORY_REASONING=low keeps a draft or an audit from thinking longer than it needs. A binary of your own, mounted at /usr/local/bin/claude or named by CLAUDE_CODE_BIN, skips the install.
3. What it does, exactly
Every completion is one process: claude -p --tools "" --max-turns 1 --no-session-persistence --output-format json --model … --system-prompt …, the PKGBUILD and the log on stdin, in an empty directory. No tool is available to the model — it cannot read a file, run a command or reach the network; it answers, and the worker reads the answer. The token goes to the child process; an ANTHROPIC_API_KEY in the same environment is withheld from it, so choosing the subscription means the subscription.
4. What it costs, and whose rules
Nothing on top of the subscription — and the subscription's limits apply: each draft and each audit is a message in the same five-hour and weekly windows as your own use of Claude, and a worker that hits the limit fails the task (You've hit your limit, back in the queue for the next window; the pool retries an audit three times). Your agreement with Anthropic is what allows this use: read their consumer terms on automated and shared use before you put the token on a shared worker or a project host — the API key (ANTHROPIC_API_KEY, a workspace with a spending limit in the Console) is the plain path, and switching is one variable.
What a build can see
A build is somebody else's code — the recipe, and the build system of the project it packages — and its log is public: on the API while the build is in staging, on the record once it is staged. So the rule the worker keeps, on your machine and on the project's: the build sees nothing the log cannot show.
What the broker holds, and the builder does not
The worker's token, your agent's key, your GITHUB_TOKEN live in the broker, a container that runs no build: it passes the pool's calls for the one task it claimed (the job token the pool hands out stays with it), answers the agent in the Anthropic shape over whichever provider you gave it, and reads GitHub. The builder is born with nothing — OMARCHY_BROKER and a label — and dies after a task; a variable of yours set on it by mistake is dropped at start and said so. Inside the builder, the build user starts from an empty environment anyway, and a worker started the old way, with the token on it, keeps the token out of every child's environment and lends the key to the drafter alone. A PKGBUILD that prints env prints PATH and HOME.
What the pool checks anyway
Every log, recipe and report uploaded to staging is read for what looks like a secret — the pool's tokens, agents' keys, GitHub's, a private key, a credential in a URL, a dump of the worker's variables — and refused if it carries one: the build fails with the kind and the line (never the match), and nothing reaches the record. That is for the worker the pool does not run; if it fires on yours, the container has something in its environment the worker did not put there — fix the container, queue the build again.
What you decide
Give the broker a GITHUB_TOKEN made for it, with no permissions — not your account's. Keep WORKER_SHARED off unless you mean to run strangers' recipes on this machine; on, give the broker no key you would mind losing. Do not mount your home or a directory of yours into the builder: it needs none. Caches, when you mount one, are kept per package inside — a build reads only what an earlier build of the same package wrote.
Keeping it running
Update — every worker follows the latest image
The image follows the pool's releases, several a day, and a worker on an old one wastes everyone's time: it drafts the wrong version, links against the wrong objects, misses the rules the rest of the pool keeps. So the pool hands work only to workers on its release: one behind for longer than the rollout's grace (45 minutes after a deploy) is refused at the claim (426), shows outdated on the Workers page and on its owner's, and the journal says so once per release. Nothing else changes: the moment it is updated, it works again.
The updater is what keeps it there, and it is part of the set, not an option: a container of the same image (OMARCHY_WORKER_ROLE=updater) with the runtime's socket and the compose directory, which every fifteen minutes pulls the image and replaces, one service at a time, what changed — a stop is a drain, the build in hand finishes first — itself last. omarchy-worker start runs it; the compose file has it; omarchy-worker update runs one round now. The project's host (the Studio) does the same with factory/host/rollout.sh on a timer. Without an updater, docker compose pull && docker compose up -d by hand does it — until the next release.
Stop, remove, revoke
./omarchy-worker stop drains and stops the set (a build in hand finishes first); ./omarchy-worker remove stops it and deletes the files here. The registration stays until you revoke it on your page (or a maintainer does); a revoked token claims nothing, immediately.
Disk
Every task builds in a fresh container that is removed afterwards; images and package caches stay. docker system prune / podman system prune reclaims them. A project worker's working directory holds the upstream keyrings, a checkout of the repository and the last builds — safe to delete when the worker is stopped.
Something is off
the pool did not accept this token: it was revoked, or mistyped. registered for aarch64 but this machine is x86_64: register a worker for this machine. mount its socket: the registration is project-trusted and needs the runtime's socket (above). permission denied … docker.sock: add --security-opt label=disable (Podman) or check the socket path. No task for a while: a contributor's worker only sees its owner's tasks unless started shared; a project worker only claims once trusted. The Pipeline lists every queued task, the Workers page every worker the pool has heard from.