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:
curl -fsSL https://antifailure.dev/install.sh | shaf runner install # the agent runner, which drives a real browser and needs nodeaf init # reads your repo, writes antifailure.yamlaf golden refresh # only if the manifest names a production database: set # that variable first, and this makes the masked copy onceaf up # database branch from the golden, built services, sealed networkaf test # agents run your workflows and return verdicts with evidenceaf down # every resource it created, goneaf start says whether the refresh, the one conditional step, is yours.
Install
Section titled “Install”curl -fsSL https://antifailure.dev/install.sh | shThe 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.
Installing a particular release
Section titled “Installing a particular release”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:
curl -fsSL https://antifailure.dev/install.sh | AF_VERSION=v1.6.0 shAF_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.
What it does to your PATH
Section titled “What it does to your PATH”~/.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:
export PATH="$HOME/.antifailure/bin:$PATH" && af startTo manage PATH yourself, decline in advance. Nothing is written, and the
installer prints the full path to af:
curl -fsSL https://antifailure.dev/install.sh | AF_NO_MODIFY_PATH=1 shIn GitHub Actions no profile is touched at all: the installer writes to
GITHUB_PATH, so af resolves in every later step of the job.
Installing somewhere else
Section titled “Installing somewhere else”AF_PREFIX moves the whole installation, both the binary and the runner the
release ships with:
curl -fsSL https://antifailure.dev/install.sh | AF_PREFIX=/opt/antifailure shAF_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:
curl -fsSL https://antifailure.dev/install.sh | AF_BIN_DIR=$HOME/.local/bin shThe 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.
On Windows
Section titled “On Windows”In PowerShell, either the Windows PowerShell every machine has or PowerShell 7:
irm https://antifailure.dev/install.ps1 | iexIt 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:
$env:AF_VERSION = '<tag>'; irm https://antifailure.dev/install.ps1 | iexA 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.
In WSL
Section titled “In WSL”WSL 2 answers as Linux, so the Linux installer is the one to use there, and it installs the Linux build:
curl -fsSL https://antifailure.dev/install.sh | shinstall.sh run from Git Bash, MSYS2 or Cygwin is not Linux, and it points
you at install.ps1 rather than installing anything.
Find out where you are
Section titled “Find out where you are”af startYou 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 installIt 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.
Check the machine
Section titled “Check the machine”af doctoraf 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.
af updateThis 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:
af update --checkInstall the agent runner
Section titled “Install the agent runner”af runner installThe 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.
af runner checkreports 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.
Describe the repository
Section titled “Describe the repository”af initDetection 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:
af init --non-interactiveThat 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:
af secret set PRODUCTION_DATABASE_URL # reads the value without echoing itaf golden refresh # copies, masks, verifies, and commits itThe 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.
Look at what would happen
Section titled “Look at what would happen”af explainThis 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.
Bring an environment up
Section titled “Bring an environment up”af upThat 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:
af statusaf logsRun the workflows
Section titled “Run the workflows”af testAgents 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 16sA manifest that declares no workflows is refused rather than reported as a run
that examined nothing. af start says so before af up.
The evidence
Section titled “The evidence”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.
A model key is optional
Section titled “A model key is optional”af model showNothing 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.
af model set anthropicreads 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.
Prove the containment
Section titled “Prove the containment”af net policyprints the decision for every host the policy knows, and
af net explain GET https://api.stripe.com/v1/chargesanswers 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.
Tear it down
Section titled “Tear it down”af downEverything 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.
What to read next
Section titled “What to read next”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.