Runner (Headless CI)
A runner is a headless Swarmfile engine that watches one branch and
executes a job when a commit or tag matches a rule - the same shape as
GitHub Actions or a self-hosted CI runner, but built on the engine's
existing on-demand streaming instead of a full git clone. A job's working
tree is materialized from the same content-addressed blocks every mount
already streams, so a job that touches ten files out of ten thousand pays
for ten files, not a checkout of the whole project.
What it is not#
There is no hosted job queue, no matrix builds, and no artifact storage - this is the trigger→execute→report loop only. Run history is inspectable via the CLI, the tray, and the web dashboard (below).
Where rules live#
Rules are not configured on the hub, in the tray, or via the CLI -
there is no swarmfile runner rules command, because there is nothing on
the hub to create. A rule is a plain YAML file, committed into the branch
it governs, at:
.swarmfile/runner.yml
This is deliberate: a rule change is reviewable in the same diff as the code it gates, exactly like a GitHub Actions workflow file. Whoever can commit to the branch controls what runs - the same trust boundary as any other file on that branch, not a new one.
rules:
- name: build-and-test
on: commit # commit | tag - default: commit
branches: # glob patterns; omit to match every branch
- main
- "release/*"
paths: # optional - matches the full relative path
- "*.dwg"
run: "scripts/ci.sh" # shell command, run with the checkout as CWD
timeout: 1800 # seconds - default 1800 (30 min)
A project can define several rules; each is evaluated independently against every commit/tag on the watched branch.
paths: matches each changed file's full relative path. *.dwg
matches plan.dwg wherever it lives in the tree; assets/** anchors to
that folder. Leave paths: off to run on every commit to a matching
branch.
on: tag rules ignore paths:. A tag names a single commit; there's
no "files changed since the last tag" the way there is for a fresh commit.
Running a runner#
A runner is a normal swarmfile-engine process started with
SWARMFILE_RUNNER_MODE=true and a project API key (see
CLI: swarmfile for swarmfile api-key create). Like
seed mode, it never mounts a drive and never prompts for interactive
sign-in; unlike seed mode, it materializes a real checkout directory (under
its cache dir) because a job needs actual files on disk to run against -
not a FUSE/WinFsp mount, which would require a kernel driver on a CI box
that may not have one.
export SWARMFILE_API_KEY=sf_key_...
export SWARMFILE_PROJECT_ID=proj_xyz789
export SWARMFILE_BRANCH=main
export SWARMFILE_RUNNER_MODE=true
swarmfile-engine
SWARMFILE_BRANCH is the same setting a normal branch-pinned mount already
uses (see Branches & Merging) - "which
branch does this runner watch" needs no setting of its own.
On startup the runner replays any commits it missed while it was offline (via the branch's commit log), then listens live. A crash or restart never silently skips a commit.
Inspecting run history#
swarmfile runner runs # every branch in the project
swarmfile runner runs --branch main # narrowed to one branch
Each row carries the matched rule's name, the triggering commit, status
(running / success / failure / timed_out), exit code, and a
truncated log. There is no swarmfile runner runs show <id> yet - the list
carries the log inline.
The same history is browsable without the CLI: in the tray, via Workspace ≡ → "View CI runs…"; on the web dashboard, under Project tools → Runs. Both are read-only - a run is always created and finished by a headless runner engine, never by clicking something in the tray or the browser.
Security#
A rule's run: command executes with the local privileges of whatever
machine runs the runner - the same trust model as a self-hosted GitHub
Actions runner. The runner's API key itself is narrowly scoped: it can
create and update its own run records, plus everything any other
project-scoped API key can already do (read metadata, download blocks,
read/write changesets and branches). It cannot post comments, touch ACLs,
or reach admin routes - the same restriction every API key has today.