Workers and trust

What a worker is, which tasks it may run, and how the cell decides the trust tier of a job.

A worker

A worker is one static binary, ciresk-ci-worker, that connects outbound to your cell, leases one task at a time per slot, runs it, streams the log and reports the result. It accepts no inbound connections (its health endpoint and package cache listen on localhost only), needs no inbound rule, and belongs to exactly one organization. A Linux box in an office, a PC under WSL2, an Azure VM and an EC2 instance are the same code path; see Run your own worker.

Your own worker changes where tasks run, not where their data lives: the tree a task runs on, its log, its artifacts and its cache entries travel through presigned URLs into the cell's own object store in the EU.

A worker belongs to a factory: static for a host you run, or a cloud pool that starts and stops instances on queue depth. A static host stays up.

Where a task runs

A task goes only to a worker that matches its labels and trust tier and has room for its resources: free slots, CPU, memory, disk and scratch space. The disk a worker offers counts the idle build output it would evict for the task, so old target/ directories never keep a task waiting. Among the workers that qualify, the queue prefers, in this order:

PreferenceWhat it does
The worker that holds the repository warmA trusted_interactive task goes to a worker with a warm workspace for its repository first.
The worker that last ran the same taskOther workers leave a warm task for up to 20 s while that worker is online and has room, because its slot still holds the task's build output.
The busiest member of a cloud poolA pool member leaves a task for up to 5 s to a member of the same pool with fewer free slots, so work fills the busiest members and the idle ones stop sooner. A static host never waits.

A preference never outranks labels, trust or room, and a task waits for a preferred worker no longer than the time above.

Labels

A worker advertises labels; the queue matches a task's os and arch against them. A task without os or arch takes any worker.

LabelSourceUsed for
os, archDetected (linux, x86_64 or aarch64)Manifest labels and the cache key
vcpus, memory_bytesDetectedSizing
toolchainsAlways miseTasks that need a provisioned tool
workspace_warmwarm_workspaces = true (the default)Warm workspaces, which only trusted_interactive tasks get
isolationprocess unless you set isolation=vm on a host that gives each job a fresh instanceuntrusted tasks run only on vm or disposable workers
disposableSet by you on a static host you re-image yourselfThe way a static host runs untrusted tasks
key=valueA [labels] table in worker.toml or CIRESK_CI_LABELS=gpu=a10,site=officeShown with the worker; tasks are not routed on them yet

Trust tiers

Trust is derived in the cell from who submitted, which ref and your organization's policy; the client never raises it. It is stored on the job and carried in the task's cache token.

TierWhoWorkspaceIsolationCache
trusted_interactiveYour developers and their local agents, on your own branches and working treesWarm, persistentUnprivileged user, cgroup caps, egress proxy in your policy's mode (audit by default)Reads its own tier and trusted_ci; writes its namespace
trusted_ciProtected branches (refs/heads/main by default) from a service token or a key marked for CI, CI systems through OIDC exchangeEphemeralUnprivileged user, cgroup caps, egress proxy in your policy's mode (audit by default)Reads and writes; the only writer other tiers read
untrustedFork PRs and refs your repository does not own, policy-marked refs and principals, service tokens off protected branches, agent jobs when your policy says soEphemeralUnprivileged user, cgroup caps, its own network namespace whose only exit is the egress proxy in block modeReads trusted_ci entries; writes to an isolated namespace per job

ciresk ci run prints the tier. A job started inside Claude Code or Codex is attributed to agent:claude-code or agent:codex and runs at your own tier: a coding agent under your key is your tool. An organization that wants agent jobs isolated turns on the agent_branches_untrusted policy, which runs every agent:* job as untrusted.

A static host (an office box, a PC under WSL2) accepts trusted_ci and trusted_interactive work only; it runs untrusted tasks only when labelled disposable.

What a task gets on the worker

ControlMechanism
UserTasks run as an unprivileged account (ciresk-run), no sudo, private home and temp directory.
CPU and memoryOn a root worker with systemd, systemd-run --scope with CPUQuota, MemoryMax, TasksMax, capped by the manifest's resources and the worker's own limits; the whole scope is killed on timeout, cancel or lease loss.
ToolchainsProvisioned with mise into a directory shared by every task on the worker.
EgressA per-task proxy with your organization's allowlist (default: crates.io, the npm registry and github.com). Audit mode, the default, lets unlisted destinations through and records each in the task log; block mode refuses them. A trusted_* task reaches the proxy through the proxy variables in its environment, so a program that ignores them is not stopped; an untrusted task is always in block mode and has no other route out. Cloud metadata, loopback, link-local and private addresses are refused by the proxy in every mode.
LoopbackA trusted_* task shares the host's loopback; an untrusted task gets its own network namespace, so tests that start their own servers on 127.0.0.1 work in every tier.
Secretspass_env values come from the worker's own secrets backend, are injected into the task process and masked in log chunks; never in keys, results, events or summaries.

What a worker can and cannot see

Can seeCannot see
Its own organization's tasks that match its labelsAny other organization's tasks, keys, blobs or cache entries
The lease: the command, the hashed env values, presigned URLs for one prefix of its organization for the lease's lifetimeThe cell's database; it talks to the API and, through presigned URLs, to the cell's object store, over TLS
The repository mirror it fetched with the credential you placed in its own secrets backendThat credential ever leaving the mirror step: it is not exported to a task or logged
Its worker token (ciw_, rotated every 24 hours), readable by root onlyThe task reading it
Outbound destinations through the built-in proxyInbound connections; there are none

When the control plane is down

A running task keeps running: the worker buffers log chunks locally (up to 256 MiB) and retries with backoff. If the outage outlasts the lease TTL (300 s, renewed every 30 s; see Limits) the worker kills the task and the cell requeues it when it returns, up to three times, after which the job is infra_error. New submissions fail with exit 8 after retries. Workers are disposable; the API is stateless.