Browse docs
Docs / Guides / Runner (Headless CI)

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.