Documentation

falllow turns machines you already have into one compute pool. A control plane takes jobs and decides where they run; runners do the work and report back.

Quickstart

You need a control plane and at least one enrolled machine, Install covers both. With those in place, submitting work is one command from any working directory.

Submit your first job
# on your machine, once
falllow login --server https://app.falllow.com --token flw_a_…

# from any project
falllow run -- pytest -q

The pieces

  • Control plane, one process plus Postgres. It holds the queue, decides placement, keeps logs and artifacts, and serves both the API and the manager.
  • Runner (falllow-runnerd), one per machine. It dials out to the control plane and keeps the connection open, so nothing has to listen on the machine and a laptop behind CGNAT joins the same way a server does.
  • CLI (falllow), how work is submitted and followed. Built to be driven by scripts and agents as much as by hand: every command takes --json.
  • Manager, the web app the control plane serves. Devices, jobs, logs, tokens and the GitHub bridge.

What a job is

A job is a command, a working directory and a resource reservation. The directory is packed and shipped, the command runs inside a fresh copy of it on one machine, and its output streams back while it happens. A job never spans machines: it is limited by the device it lands on.

Reservations are threads and memory, not slots. A job asking for four threads and 8 GB takes exactly that much of a machine's budget, and the machine keeps accepting work until its budget is spent.

States

StateMeaning
queuedAccepted, waiting for a machine with room.
assignedLeased to a runner, not started yet.
preparingWorkspace and environment being set up.
runningThe command is executing.
succeededExited zero.
failedExited non-zero, timed out, or the machinery failed.
cancelledStopped on request. The process tree is killed.

Failure means two different things

This is the distinction falllow is built around. A test suite that failed and a runner that vanished are both "the job did not succeed", and treating them the same is how you either retry a real failure into a green build or give up on a machine that only lost its network for a moment.

ClassCauseRetriedExit code
job_failureThe command exited non-zero. Its verdict.NeverThe command's own
infra_failureA runner died, a lease expired, a transfer broke.Yes, with backoff170
timeoutPast --timeout. The tree is killed.No170
cancelledSomeone stopped it.No170

So falllow run -- pytest substitutes for pytest in a script: the exit code you get is the one you would have got locally. 170 is reserved and means only one thing, the pool failed you, the command did not.

Where work goes

The scheduler prefers a machine that already has the environment warm, but only while waiting for it is cheaper than setting up cold somewhere else. It learns what a cold setup actually costs per environment and machine from the runs that had to do one, so the trade-off is measured rather than assumed.

Environments are identified by an env_key, a hash of your lockfiles, toolchain pins, OS and architecture, computed before the job is submitted. Two jobs with the same key need the same environment, which is what makes "warm" mean anything.

Keep reading

  • Install, control plane, then machines.
  • CLI, every command, the environment a job sees, and the JSON output.
  • GitHub Actions, self-hosted runners on demand.