Runners
The machines that claim and execute runs via the Smithers engine - registration, tags, capacity, health, and sandboxing.
A runner is a machine that executes runs. Runners run the Runyard runner process, which claims queued runs from the Hub and executes each workflow via the Smithers engine (smithers up <workflow> --input <json>), spawning the local agent CLI as the worker. Nothing is faked: the agent runs on the runner's machine, and the Hub is the durable record β events stream back as run events, and outputs and traces are uploaded as artifacts.
Registration, heartbeat, claim
Runners speak a small machine protocol authenticated with runner-scoped tokens (see Tokens & scopes):
POST /api/runners/registerβ announce name, hostname, platform, version, tags, and capacity.POST /api/runners/{id}/heartbeatβ periodic liveness; a stale heartbeat marks the runner offline. The response also hands backsupersededAttempts: attempts of this runner that a retry superseded and that still owe a cancellation acknowledgement (POST /api/runs/{id}/attempts/{attemptId}/cancel) β the run itself is already retrying under the same id.GET /api/runners/{id}/next-runβ claim the next matching queued run. The claim payload carries everything the run needs, including decrypted secrets for that run only.
Run lifecycle reports (start, events, complete, fail, artifact upload) are restricted to the runner that owns the run: the Hub checks the claiming token's identity, so one runner's token cannot touch another runner's runs.
Tags and execution modes
Runners advertise tags (for example local on a workstation, vps on a shared remote pool). Two things match runs to runners:
- A workflow's
requiredRunnerTagsmust all be present on the runner. - The caller's execution intent:
executionMode: "local" | "remote" | "auto"(optionally a specificrunnerLocation).remotetargets the shared remote/VPS pool (default locationvps);autotakes any matching runner.
Preflight checks this matching up front and reports no_matching_runner (blocker) or runners_offline (warning β the run would queue until one returns).
Portable Repository Source
Remote-capable repository mutation and build runs should send a portable source descriptor instead of a host path:
{
"repositorySource": {
"provider": "github",
"repository": "owner/repo",
"commitSha": "0123456789abcdef0123456789abcdef01234567",
"targetBranch": "main",
"baseBranch": "main",
"credentialSecretName": "RUNYARD_GITHUB_TOKEN"
}
}commitSha is the canonical source identity. Branches are metadata and push intent only; they are never the tree the runner inspects or builds. The runner materializes the exact SHA into an isolated checkout under RUNYARD_RUNNER_CHECKOUT_ROOT (default .runyard-checkouts inside SMITHERS_WORKSPACE), verifies HEAD equals the requested SHA, then passes that checkout path to workflows as their effective repoDir. The Hub's stored input keeps the immutable source descriptor; runner-local paths stay ephemeral.
Runners that can do this advertise the portable-checkout tag and may advertise non-secret repository target inventory: provider, repository, a bounded label, and materialization mode. That inventory is not approval. New assignments require Hub-owned admin approval for that exact runner target, stored durably on the Hub across heartbeats and restarts. Runner registration and heartbeat cannot self-approve or revoke a target; they only refresh sanitized inventory.
Approve or revoke a target from an admin/operator surface:
runyard runner-target approve runner_123 rrepo_github_owner_repo
runyard runner-target revoke runner_123 rrepo_github_owner_repoThe API/MCP equivalents are POST /api/runners/{id}/repo-targets/{targetId}/approve, POST /api/runners/{id}/repo-targets/{targetId}/revoke, approve_runner_repository_target, and revoke_runner_repository_target. pending and revoked inventory remains visible in GET /api/runners / list_runners for operators, but ordinary launch planning, preflight, repo options, and runner assignment use only approved online targets. Revocation blocks new assignments and does not edit previous run provenance.
Runs that carry repositorySource or repoSource only match runners with the portable-checkout tag and a Hub-approved compatible target. A literal repoDir remains supported for explicit same-host workflows, but it is intentionally non-portable and preflight warns when it is used for remote-capable execution. The runner never falls back to a nearby checkout.
Credentials are short-lived in process memory. Public repository checkout works with no credential. Private checkout can name a Hub secret through credentialSecretName; the decrypted value rides the claim's secret environment and is passed to git via an in-memory extra header, never in the remote URL, run input, events, logs, git config files, or artifacts. By default credentials are sent only to github.com; add trusted hosts with RUNYARD_RUNNER_SOURCE_GIT_HOSTS.
For a second-host trial, start repo.box with a narrow canary tag:
RUNYARD_RUNNER_REPO_BOX_CANARY=1
SMITHERS_RUNNER_TAGS=smithers,remote,repo-box
RUNYARD_RUNNER_CHECKOUT_ROOT=/var/lib/runyard/checkoutsWhile repo-box-canary is present, the Hub only places runs whose required tags also include repo-box-canary, even if the runner advertises smithers. Promotion into the shared smithers or CI pool is an explicit tag/config change after the canary proves clean.
Capacity and pool
Each runner declares a capacity (concurrent run slots) and reports its active runs; the Hub assigns work only to free slots. GET /api/runners returns every registered runner with heartbeat state, capacity, active slots, plus a pool summary β the same data the Runners page in the web app shows (#runners).
curl -H "Authorization: Bearer $RUNYARD_TOKEN" https://hub.example.com/api/runnersSandboxing
By default the runner executes workflows directly on its host. Setting RUNNER_SANDBOX=bubblewrap on the runner wraps each workflow launch in a generated Bubblewrap (bwrap) sandbox: the run's workspace is the only writable mount (bound at the same path inside and out), system directories are bound read-only, and the child gets a fresh workspace-local HOME β the host home directory, with its keys and credentials, is never mounted. Polling and control commands stay outside the sandbox. Installing bwrap is the deployer's responsibility; the runner verifies at startup that the sandbox actually works before claiming runs.
API & MCP
| Endpoint | MCP tool | Notes |
|---|---|---|
GET /api/runners | list_runners | Runners, heartbeat state, capacity, slots, pool summary. |
POST /api/runners/{id}/repo-targets/{targetId}/approve | approve_runner_repository_target | Admin approval for a portable repository target. |
POST /api/runners/{id}/repo-targets/{targetId}/revoke | revoke_runner_repository_target | Admin revocation for future assignments. |
POST /api/runners/register | β | Runner protocol (runner scope). |
POST /api/runners/{id}/heartbeat | β | Runner protocol. |
GET /api/runners/{id}/next-run | β | Runner protocol; claims the next run. |
POST /api/runs/{id}/start / /complete / /fail / /events / /artifacts | β | Runner protocol; run ownership enforced. |
The runner protocol endpoints intentionally have no MCP tools β they are Hubβrunner machinery, not an operator surface.