Worker contract
August 19, 2026 ยท View on GitHub
A Factory Worker is one stable identity, a set of coding-agent capabilities, and a pool of independent agent sessions. Its capacity limit controls the number of sessions that may prepare or run at once. It can run on a developer machine, VM, or Unix container. Workers are cattle: the control plane owns repository scope, and an eligible worker acquires an assigned repository into its bounded local cache. Windows is not supported.
Configuration
server = "http://127.0.0.1:7337"
name = "local-codex"
runtime = "codex"
runtimes = ["pi", "codex", "claude-code"]
# Optional. Defaults to 10.
max_concurrent = 10
[labels]
environment = "development"
host = "laptop"
runtimes lists the coding agents the Worker should discover. Supported values
are pi, codex, and claude-code. runtime remains the primary capability
and preserves compatibility with existing single-runtime configurations. When
runtime is omitted it defaults to the first entry in runtimes, or to codex
when neither field is set. Each task selects one ready capability and launches a
fresh runtime process with its own worktree, manifest, lease, and supervisor
process group. max_concurrent accepts values from 1 through 100;
preparing attempts consume slots as well as running attempts.
Factory migrates existing SQLite databases to the expanded worker capacity range when the control plane starts.
When data_directory is omitted, Factory derives an absolute path beside the
configuration as workers/<config filename without .toml>. For example,
~/.factory/worker.toml uses ~/.factory/workers/worker, while
~/.factory/claude-worker.toml uses ~/.factory/workers/claude-worker. The
config filename, not the mutable worker display name, selects durable local
state, so use a different config filename for each worker on one host.
Set data_directory to override the default. Explicit relative paths resolve
from the directory containing the worker TOML file; explicit absolute paths are
used as written. The worker probes gh auth status --hostname github.com
automatically. A successful probe advertises GitHub source access and the
ability to acquire centrally managed GitHub repositories. It contains no token.
The legacy [repositories.<key>] map remains optional for manual delegation to
an existing non-bare checkout. Its paths resolve relative to the worker TOML.
Each repository must have an origin. base_branch is an optional short branch
name such as main, develop, or release/2026.07; when omitted, Factory uses
the branch advertised as the origin default.
Newly discovered legacy checkouts enter the catalog disabled and support only
explicit assignment until an operator adds or enables them centrally. Reposting
a centrally managed repository does not override an explicit disable.
Repositories already known before this migration remain enabled for routing
compatibility.
The worker pins the normalized origin identity at startup and revalidates it
before and after resolving each attempt base. An intentional origin change
requires a worker restart; an unexpected change fails before agent launch.
Identity and registration
The first start creates a protected worker-id file in the worker data
directory. The worker reuses that ID on every restart. The local API does not
authenticate workers, so the ID is identity, not a credential.
Registration advertises:
- display name, primary runtime, and capability status for each configured tool and coding agent;
- maximum concurrent attempts;
- optional host labels;
- worker version;
- optional legacy repository keys, normalized remote identities, and retained counts;
- successfully probed source access such as GitHub;
- whether the worker can acquire managed repositories;
- the bounded set of cached control-plane repository IDs, used for exact cache headroom accounting. The public Worker view exposes only the count.
The server returns repository IDs used for task assignment. Heartbeats refresh health and current capacity. A worker is offline when its heartbeat expires.
Print the configured identity without starting the worker:
~/.factory/bin/factory-worker identity \
-config ~/.factory/worker.toml
Claiming
Workers poll the loopback API for compatible work. An idle worker makes at most one empty claim request per polling interval. Each successful claim immediately starts another claim while a slot remains, so queued work fills the pool without waiting one polling interval per slot. A claim succeeds only when:
- the Session is assigned to that worker;
- the repository assignment is frozen to that worker;
- worker capacity is available;
- the repository has fewer than ten retained worktrees, active attempts, and terminal attempts awaiting a local disposition on that worker.
An attempt has a lease. The worker renews it while the agent process is alive. If the worker disappears, the control plane marks the attempt lost after the lease expires.
Attempt lifecycle
The worker prepares a task as follows:
- validate the repository ID and canonical
github.com/owner/repositoryidentity, or validate a compatible legacy checkout; - clone a missing managed repository with
gh repo cloneor use its existing cache; - discover the origin default branch or use a legacy repository's configured
base_branch, fetch it, and freeze its exact commit; - create an owned worktree below its data directory;
- create a
factory/<task-prefix>-<attempt-prefix>branch; - write a protected, durable attempt manifest;
- launch the selected runtime in the worktree;
- stream bounded lifecycle and output events;
- report the result and outcome;
- inspect final Git state locally to decide whether cleanup is safe.
The legacy checkout and managed cache are never switched to the base branch, and agent work never runs in either. A worktree isolates Git state but is not a security sandbox: the runtime still has the worker process's filesystem, network, and credential access. A later sandbox can contain this same prepared workspace without changing the task contract.
Codex is launched non-interactively with structured result output. Claude Code is launched non-interactively with JSON output. The worker normalizes both into the same control-plane result contract.
Runtime output and API event payloads are bounded. Oversized output is truncated or summarized so one agent cannot grow a request without limit.
Managed caches live at DATA_DIRECTORY/repositories/REPOSITORY_ID. Preparation
is coordinated per repository: clone installation, origin and base resolution,
fetch, worktree add and remove, and managed branch cleanup serialize only with
other Git metadata operations for that same repository. Unrelated repositories
prepare concurrently. The repository lock is released before Codex or Claude
Code starts, so sessions using distinct worktrees from one repository can run
in parallel. Clone and fetch have a five-minute bound.
A short cache-accounting lock reserves capacity before a first-time clone
starts. This keeps the worker at no more than 100 managed repository entries
even when unrelated clones overlap. Failed or cancelled clones release their
reservation. This version does not evict caches automatically.
Interrupted .clone-* directories are removed during startup after the worker
has locked its data directory, so hard crashes cannot bypass the cache bound.
On the next registration, the control plane releases an uncached dynamic
reservation once no queued or active execution and no retained worktree uses it.
Cancellation and shutdown
Cancellation is cooperative but enforced:
- the control plane sets the execution's cancellation request flag;
- the worker observes the state while renewing the lease;
- it terminates the runtime process group;
- it reports the attempt as cancelled.
On SIGINT or SIGTERM the worker stops claiming, terminates active runtimes, reports their outcomes when possible, and exits.
Worktree cleanup
Successful clean work can be removed only after the worker proves it is safe. The worker validates:
- the protected manifest and its recorded worker identity;
- exact worker, task, attempt, repository, path, and branch identity;
- that the path is a direct child of its worktree root;
- the matching Git worktree registration;
- a clean working tree;
- publication of every new commit before automatic cleanup.
Dirty work, uncommitted changes, unpushed commits, and uncertain publication are retained. The Worker view reports the path, reason, and cleanup command. Its cleanup preview reports the branch and Git status.
Preview manual cleanup without changing the worktree:
factory-worker cleanup ATTEMPT_ID --config ~/.factory/worker.toml
If the preview is correct, confirm removal:
factory-worker cleanup ATTEMPT_ID \
--config ~/.factory/worker.toml \
--confirm
Confirmed operator cleanup preserves the local branch but removes the worktree with force, so uncommitted changes shown in the preview are lost. Cleanup never deletes the repository cache, a legacy configured checkout, or another worker's path.
Deployment model
The default control plane accepts loopback Workers only. On one host, run the server and one or more Workers as supervised Unix services.
An optional, separate HTTPS listener accepts authenticated Workers on remote VMs. A ten-minute, one-time enrollment creates a per-Worker credential, while the browser and operator API remain on loopback. See the remote VM guide.
Kubernetes Worker provisioning is not part of the current product.