Skip to content

Type to search pages.

View .md

Quickstart

This goes from nothing to a running environment on your own machine. It needs Docker. A Postgres connection string you are allowed to read from is optional: with one, every environment holds a masked copy of that database, and without one it holds the schema your migrations create. It does not need an account, a control plane, or a cloud provider.

The whole sequence:

Terminal window
curl -fsSL https://antifailure.dev/install.sh | sh
af runner install # the agent runner, which drives a real browser and needs node
af init # reads your repo, writes antifailure.yaml
af golden refresh # only if the manifest names a production database: set
# that variable first, and this makes the masked copy once
af up # database branch from the golden, built services, sealed network
af test # agents run your workflows and return verdicts with evidence
af down # every resource it created, gone

af start says whether the refresh, the one conditional step, is yours.

Terminal window
curl -fsSL https://antifailure.dev/install.sh | sh

The installer downloads the release for your platform, checks it against the published checksum, and puts af and its runner under ~/.antifailure. It is POSIX sh rather than bash, so it works in an Alpine container as well as on a laptop. The file served at that URL is the source in the repository.

The installer finds out which release is the newest by following the redirect on github.com/antifailure/antifailure/releases/latest, which points at the tag GitHub marks as the latest release. To install a different one, name its tag:

Terminal window
curl -fsSL https://antifailure.dev/install.sh | AF_VERSION=v1.6.0 sh

AF_VERSION is also the way through if the installer cannot work out which release is the newest, and it tells you which of those things happened rather than guessing. Nothing answering at all, an address that has asked GitHub for too much, a repository with no published release, and a release with no build for your platform are four different sentences, because only some of them are worth trying again.

~/.antifailure/bin is on nobody’s PATH by default, so the installer puts it there. It appends one line to the file your login shell reads at startup, prints that line, and names the file:

Added this to ~/.zshrc, so every new terminal finds af:
export PATH="$HOME/.antifailure/bin:$PATH"

Delete that line to undo it. zsh gets .zshrc under ZDOTDIR, bash gets .bash_profile on macOS and .bashrc on Linux, fish gets fish_add_path in config.fish, and a shell the installer does not recognise is told so rather than having a file guessed for it. Running the installer again does not add the line a second time.

The current terminal cannot see the file just written, so the installer ends with one line to paste that fixes that shell and runs the first command:

Terminal window
export PATH="$HOME/.antifailure/bin:$PATH" && af start

To manage PATH yourself, decline in advance. Nothing is written, and the installer prints the full path to af:

Terminal window
curl -fsSL https://antifailure.dev/install.sh | AF_NO_MODIFY_PATH=1 sh

In GitHub Actions no profile is touched at all: the installer writes to GITHUB_PATH, so af resolves in every later step of the job.

AF_PREFIX moves the whole installation, both the binary and the runner the release ships with:

Terminal window
curl -fsSL https://antifailure.dev/install.sh | AF_PREFIX=/opt/antifailure sh

AF_BIN_DIR moves the binary on its own, and is the one to reach for when you want af in a directory that is already on your PATH:

Terminal window
curl -fsSL https://antifailure.dev/install.sh | AF_BIN_DIR=$HOME/.local/bin sh

The runner goes beside it, in share/antifailure/runner next to the directory you named, so ~/.local/bin puts it in ~/.local/share/antifailure/runner. That is where af looks for the runner it shipped with, relative to itself, and the PATH line the installer prints names the directory you chose. Both of these want a directory you can write to without sudo; if the write fails the installer says which path it could not write and stops rather than installing half of a release.

In PowerShell, either the Windows PowerShell every machine has or PowerShell 7:

Terminal window
irm https://antifailure.dev/install.ps1 | iex

It is the same installer with the same promises, written for PowerShell rather than translated into it. It finds the newest release the same way, refuses a download that does not match checksums.txt or that checksums.txt does not name, and says what GitHub answered when something does not arrive. It installs the build for your machine’s architecture, amd64 or arm64, and on an Arm laptop it asks the machine rather than the PowerShell process, so an emulated x64 shell still gets the native build.

af.exe goes in %USERPROFILE%\.antifailure\bin and the runner in %USERPROFILE%\.antifailure\share\antifailure\runner. The bin directory is added to your user PATH, as %USERPROFILE%\.antifailure\bin so a moved profile does not leave a dead entry, and to the terminal you ran it in, so af start works straight away. Remove the entry under Edit environment variables for your account to undo it. The settings are the same as on the other platforms, set as environment variables first:

Terminal window
$env:AF_VERSION = '<tag>'; irm https://antifailure.dev/install.ps1 | iex

A release from before the Windows builds existed has no zip to install, and the installer says that the release does not include a build for Windows rather than installing something else.

AF_PREFIX, AF_BIN_DIR and AF_NO_MODIFY_PATH work as they do above, and in GitHub Actions the bin directory goes to GITHUB_PATH instead. To upgrade, run the same line again: Windows will not overwrite a running program, so an af.exe that an editor holds open as its MCP server is moved aside and the new one takes its name. The next af to start removes the old one once nothing is running it, as it does after af update.

Environments run in Linux containers, so Docker Desktop has to be in its Linux containers mode, which is its default. af doctor says so when it is not.

af.exe is not code signed yet. Installed this way it carries no mark of having been downloaded, which is what SmartScreen’s warning keys on. A zip saved from the releases page in a browser does carry that mark, and the binary extracted from it can be stopped with “Windows protected your PC”; run Unblock-File on the zip before extracting it. On Windows 11 with Smart App Control turned on, an unsigned program can be refused outright, and the way through is to install from WSL instead.

WSL 2 answers as Linux, so the Linux installer is the one to use there, and it installs the Linux build:

Terminal window
curl -fsSL https://antifailure.dev/install.sh | sh

install.sh run from Git Bash, MSYS2 or Cygwin is not Linux, and it points you at install.ps1 rather than installing anything.

Terminal window
af start

You can run this at any point. It reports every step below as observed on this machine right now, and names the single next command.

Your first run
ok af on your PATH ~/.antifailure/bin/af
ok Docker version 28.5.1, linux containers
... the agent runner runner: no runner at ~/.antifailure/runner
... a manifest no antifailure.yaml here or in any parent directory
skip the database source after the manifest
skip masking rules after the manifest
skip a golden after the manifest
skip an environment after the manifest
skip workflows to run after the manifest
ok a model key none set, so agents use the deterministic planner
skip evidence on disk after the manifest
ok nothing left behind none are being held
Next
af runner install

It runs nothing and writes nothing, and every answer comes from the machine rather than from a record of what it last did.

Five states, and it never collapses one into another. ok was observed to be finished. ... was observed not to be, and is where you are. warn is something missing that the next command does not need: the variable naming production, when a verified golden for this project already exists. fail is something broken that has to be fixed before the next command can work. skip is a step it deliberately did not look at, and it says why and what to run instead. With the Docker provider the golden step is answered from the daemon, selected by the same rule af up uses, so it never names a golden made for another project or one that was never verified; with a hosted provider it is skipped, because that listing needs credentials and this branch’s lock.

Exit 0 means every step is either done or not reached yet, which is the normal state of a first run in progress. Exit 3 means something is broken.

Terminal window
af doctor

af doctor is the wider check: disk, ports, DNS, outbound reachability, kernel isolation, proxy settings, git, and the environments this machine is still holding. Every problem it names carries what to do about it.

It also validates the manifest when one exists and compares a stable CLI version with the latest published GitHub release, with a three second network timeout. An outdated version or invalid manifest fails the check. No network, a development build, or no manifest is reported explicitly rather than as a pass, and a missing manifest does not fail the check.

Terminal window
af update

This downloads the latest stable release for this platform, verifies its published checksum, and replaces the installed binary and its bundled runner source. The old binary stays in place until the replacement is ready. Shell profiles and project files are left alone. If a package manager owns the binary, upgrade through that manager instead. Enterprise binaries use their enterprise distribution, not the public community release. Afterwards, run af runner install to refresh the installed runner and af doctor to check the installation. To see the latest release without changing files:

Terminal window
af update --check
Terminal window
af runner install

The runner drives a real browser, so it is a separate program in a separate language and it needs node 22.6 or newer. It is copied from the source that ships beside af rather than downloaded, and its dependencies come from the lockfile that ships with it. It then downloads chromium, which is the slow part.

Terminal window
af runner check

reports each thing separately: the source, every dependency the runner declares against what is actually under node_modules, whether the lockfile pinned them, node against the range the runner requires, and the browser. It does not claim the runner executes. Anything it cannot determine it reports as not checked rather than as ok.

It reports on the runner af test would use from where you are standing, and prints that path. A run looks for a runner in your own checkout before it looks at ~/.antifailure/runner, and it takes the nearest one that can actually run rather than the nearest one that exists, so a runner/ directory whose dependencies were never installed is passed over. The check names the directory it went past and says what is missing from it.

A failed browser download is not fatal. Until a browser arrives, a workflow that needs a page read comes back unverified.

Everything up to af up works without the runner; only af test needs it.

Terminal window
af init

Detection reads the repository and writes antifailure.yaml: the services it found, the port each listens on, the migration command, and a network policy derived from the SDKs in your dependency list. If your package.json has stripe in it, the Stripe hosts arrive in the manifest without being asked.

It never executes anything from the repository: detection reads files.

Anything it is unsure about becomes a question rather than a silent guess, and everything it reports names the file it came from. You can answer the questions without a prompt if you are scripting it:

Terminal window
af init --non-interactive

That accepts every default and prints what it assumed.

Read the manifest before going further. The manifest reference explains every key.

Name the database to copy, if there is one

Section titled “Name the database to copy, if there is one”

af init writes database.source_url_env only when the repository already names its production variable, so read the database block it wrote. If it names a variable, put production’s read only connection string there, in this shell, in .env, or in the encrypted store, and build the golden once:

Terminal window
af secret set PRODUCTION_DATABASE_URL # reads the value without echoing it
af golden refresh # copies, masks, verifies, and commits it

The value is read on this machine for one pg_dump and never written anywhere an environment can reach. The refresh runs masking.yaml over the copy, or the built in rules when there is no file, and refuses to commit a golden the verifier found sensitive data in. Goldens and masking cover both.

If the block names no variable, skip this. The first af up builds the golden itself, from database.seed when the manifest sets one and otherwise empty, and every branch after that is made from it. Skip it as well when af start reports a golden already made for this project: af up branches that one, and the variable is needed by the next refresh rather than by you now.

Terminal window
af explain

This resolves the manifest and prints the plan: which golden a branch would come from, what each service would build from, and the mode every host in the network policy has been given. Nothing is created.

Terminal window
af up

That builds the services, creates a branch of the golden, and starts everything inside a network namespace that reaches nothing except the hosts your policy allows. The first run is the slow one, because the images are built. Later runs branch from what already exists.

While it runs, or afterwards:

Terminal window
af status
af logs
Terminal window
af test

Agents drive the application the way a person does, through the accessibility tree, and return one of five verdicts for each workflow in the manifest with a video, a trace, and steps to reproduce it.

The verdict that matters is blocked. A browser that crashed, a page that never loaded, or a persona with no password is not evidence about your application. Of the five verdicts, only a failure exits non zero. A run that never reached a verdict exits on the configuration problem that stopped it.

ok sign in pass in 4.1s
ok place an order pass in 11.7s
2 passed, 0 failed, 0 flaky, 0 blocked, 0 unverified, in 16s

A manifest that declares no workflows is refused rather than reported as a run that examined nothing. af start says so before af up.

Everything a run produced is under .antifailure/artifacts/<environment> in the repository: a video and a Playwright trace per workflow, screenshots, the console log, and the list of requests the page could not make, which is usually the egress policy doing its job. af start reports whether anything is there.

Terminal window
af model show

Nothing above needs one. With no key the agents plan deterministically, the workflows still run, and the verdicts are real. A key lets an agent read a page it has not seen before, and where one is set it is reported by fingerprint, from which source, and whether it has been checked.

Terminal window
af model set anthropic

reads the key without echo and puts it in your operating system’s keyring. It is never passed on a command line, never written to the manifest, and there is no command that prints it back.

Terminal window
af net policy

prints the decision for every host the policy knows, and

Terminal window
af net explain GET https://api.stripe.com/v1/charges

answers for one specific request: which rule matched, which mode it is in, and what would happen. If something reached the network unexpectedly, af net log has the record of it, including the denials.

The modes are covered in egress. BLOCK refuses with a decision you can read, SANDBOX swaps in test credentials and trips a wire if a live key ever appears, CAPTURE records mail and messages into an inbox your tests can read, and MOCK answers from an offline pack with no network at all.

Terminal window
af down

Everything it created is removed, and the removal is checked rather than assumed. If a previous run was killed halfway, the journal reconciles it: see the journal for why that matters and af env prune for sweeping up after a machine that lost power.

Goldens and masking are the two ideas everything else rests on: how a masked copy of production is built once and branched cheaply, and how identifiers are replaced deterministically so the same customer is the same fake customer in every table and every refresh.

Verification explains why an unverified golden cannot be branched at all.

Building services covers what happens when detection guessed wrong about how your services are built.

Watching a run is the live view: af up --hud draws the same run as a dashboard, and where there is no terminal it writes one line per event instead.

Running it somewhere other than your laptop

Section titled “Running it somewhere other than your laptop”

Everything above is the same wherever the engine runs.

An environment per pull request is Antifailure inside GitHub Actions: the same af up, in a workflow, with one comment on the pull request that is updated in place rather than appended to. If the checkout had a GitHub remote, af init already wrote that workflow beside the manifest, and committing it is the whole setup. No server is needed. GitHub is the reference behind it: the two modes, what the App must be granted, forks, and teardown.

The control plane is the optional hosted piece. Read it when you want environments that outlive a workflow run, a shared address for them, or a record across repositories.