Run it from source
There are two ways to get a running browser. You can download a build. Or you can build it yourself. This page covers the second way. It also covers how the downloadable builds are made. Every command below comes from the repository's justfile, scripts or workflows. Where something has not been run, the page says so. New words are explained in Words we use.
Two builds#
| Build | What it is for | Cost |
|---|---|---|
| Without Servo (the default) | The interface, the agent loop, the defense and the evaluation. Servo is the web engine Ferrite uses to draw pages. This build cannot draw web pages. The engine stub refuses to start. | About two minutes from a cold start. The test suite needs no network and no API key. |
With Servo (--features ferrite-servo/servo) | Real web pages. The downloadable builds are this build, in the release profile. | The first build takes 20 to 60 minutes. It needs 10 GB or more of disk. Later builds are incremental. |
You need these tools:
git- a Rust toolchain (the repository pins stable with
rustfmt,clippyandllvm-tools) python3- a C toolchain with
cmakeandpkg-config
On Linux, the Servo build also needs the engine's own system libraries. The setup script points to the list. It does not install them. just is the tool that runs the commands. just --list prints every recipe. Each recipe is a thin wrapper, so you can run the plain command instead.
Windows
The build, test, evaluation and probe recipes are plain cargo or python3 commands. The local helper recipes call bash scripts. Nobody has run them on Windows. The helper recipes are setup*, run-local, run-fast, run-all, models-local, laya-*, doctor, collect-logs and crash-report. On Windows, use the downloaded build. Or run cargo run --release -p ferrite-shell --features ferrite-servo/servo.
The shortest path to real pages#
$ cargo run --release -p ferrite-shell --features ferrite-servo/servo
This opens the browser. You need no environment settings. Connect a model afterwards in Settings (Cmd/Ctrl+,). With just, the same thing through the helper scripts is:
$ just setup-servo # checks your toolchain, builds with Servo, writes .ferrite/env.local
$ just run-fast --no-model-check
First-time setup#
| Recipe | What it does |
|---|---|
just setup | Checks the toolchain. Builds ferrite-shell without Servo. Can set up the local Laya server. Creates .ferrite/env.local from scripts/local.env.example (it never overwrites that file). It is safe to run again, because each step checks first. If you pass neither --with-servo nor --no-servo, an interactive run asks you. A run that is not interactive builds without Servo. |
just setup-servo | The same, with the real engine. Ctrl-C is safe. Run it again and cargo continues where it stopped. |
just setup-all | Servo and Laya together. All of it stays inside the repository folder. |
just doctor | A read-only checklist: tools, build, configuration, Laya and disk. It never prints a key. --fix-hints says how to fix each problem. |
Laya is an optional small local model. It picks routine browsing steps to save calls to the main model. Models and settings explains it.
Flags pass through. For example, just setup --dry-run --yes prints every action it would take and runs none. The scripts never use sudo. They offer to install rustup and Homebrew packages only after you say yes or pass --yes. All local state is under $FERRITE_HOME. The scripts default it to the repository's .ferrite/ folder, which git ignores. So rm -rf .ferrite removes it.
Which run recipe#
| You want | Run | Notes |
|---|---|---|
| Real pages, every day, fast | just run-fast | The release profile with Servo. The optimised engine runs several times faster. Nobody has measured the exact speed-up of this recipe. |
| Real pages, while you work on Ferrite | just run-all | The dev profile with Servo, plus Laya if it is set up. |
| Whatever setup built last | just run-local | Reads env.local. Starts Laya if it is set up. |
| The interface only | just run | Without Servo, so no web pages. Arguments after run go to the program. |
| A downloaded build | double-click, or ./ferrite | See below. |
The helper recipes load $FERRITE_HOME/env.local. It holds plain KEY=VALUE lines. The recipes read them as data and never run them. Only FERRITE_*, OLLAMA_*, GEMINI_* and RUST_LOG are used. Anything you already exported in your shell wins. The recipes then check that FERRITE_MODEL_SMALL and FERRITE_MODEL_MAIN are set. If they are not, the recipes exit and print instructions. Pass --no-model-check to open the browser anyway. Then connect a model from Settings. The other flags are --no-laya, --servo or --no-servo, --wait SECONDS and --dry-run.
Model names never have a default, because providers retire them. just models-local lists the tags that your Ollama endpoint serves. It needs a key, or a local Ollama.
A downloaded app does not read env.local
The packaged apps ignore env.local. They also ignore any FERRITE_* variable that you did not export before you started them. Connect a model from the Settings drawer. (The README.txt inside the Linux package still says to put your key in ~/.ferrite/env.local. That is out of date. The app never reads that file.)
The renderer and WebGL#
The engine draws pages into an off-screen buffer. The app writes two lines to the log, once for each run:
[ferrite-render] CPU rendering (software); there is no GPU renderer
[ferrite-webgl] on (on by default)
- The renderer
- Ferrite has no GPU renderer. Pages are always drawn on the CPU. A GPU renderer was tried and then removed. On an Apple M1 its self-test passed, but with it Google never finished loading and could not be scrolled or clicked. With the CPU renderer, GitHub worked fully.
FERRITE_RENDERERis no longer read. The app says so if it is set. FERRITE_INTERSECTION_OBSERVER=on|off- Whether pages get
IntersectionObserver. The default is on. It is the feature behind lazy-loaded images and infinite scroll. Useoffas a quick test when a page never stops loading. A page that checks for the feature then skips it. The app prints one[ferrite-observer]line at startup. FERRITE_WEBGL=off|webgl1|on|auto- Which WebGL pages get. The default is
auto, which is WebGL 1 only.ongives WebGL 1 and 2. Withoff, a page gets no WebGL and falls back to its non-3D version. WebGL 2 is off by default because of a known engine bug on macOS: a WebGL 2 page leaves a GL error pending, the next buffer swap fails, and the engine's WebGL thread panics. Ferrite has two fixes in vendored engine crates, and neither has run on a Mac.
Speed is not measured
The CPU renderer may be slower than a GPU one would have been. Nobody has timed it on a Mac. The same release also tells the engine the window's display scale. Before, a Retina page was laid out twice as wide as it looks. It also sizes the style, layout and worker thread pools to the core count. Nobody has timed either change on real hardware.
Release builds#
Two GitHub workflows make the downloadable builds. A person starts both by hand:
- CI is manual only (Actions, Run workflow). It has no push, pull-request or schedule trigger. On Linux, Windows and macOS it runs these checks: format, lint (all targets, warnings denied), the full test suite and a build without Servo. On Linux it also checks for unused dependencies, licences and doc drift. If all of that passes everywhere, a second job builds the release binary with Servo on each OS and packages it.
- Release runs by itself when a manual CI run finishes. It publishes only if that run succeeded on every job. It makes two releases. One is permanent and has its own tag,
build-<run number>-<commit>, and is never replaced, so if a new build is bad you can download an older one from the releases page. The other is the prerelease taggedlatest, which is replaced each time and is what the download buttons point at. The notes name the commit, branch and CI run. Release never checks out or runs the CI run's code.
| Platform | File | Contents and first launch |
|---|---|---|
| macOS (Apple silicon) | ferrite-<commit>-macos-arm64.zip | Ferrite.app. It has an ad-hoc signature but is not notarized. The log-collection script is in Contents/Resources. The first launch is blocked. Open System Settings, Privacy & Security, and click Open Anyway. Or run xattr -dr com.apple.quarantine /Applications/Ferrite.app. |
| Windows (x64) | ferrite-<commit>-windows-x64.zip | ferrite.exe with its icon built in. SmartScreen may ask once. Choose Run anyway. |
| Linux (x64) | ferrite-<commit>-linux-x64.tar.gz | ferrite, an icon, a .desktop entry, README.txt and install.sh. install.sh copies the app into ~/.local. You need a graphical session (X11 or Wayland) with OpenGL/EGL. You also need the shared libraries that README.txt names: fontconfig, freetype, libxkbcommon, and the X11 and Wayland client libraries. |
The packaging command is scripts/package.sh <macos|windows|linux> <label> <binary>. CI passes the short commit hash as the label. The project has not checked the packaged Windows and Linux apps on real machines. The macOS app is the one the owner runs. It has been reported to crash and to need a force-quit. See Limits. The latest published release was built from commit f4ed744 on 2026-10-02. It does not yet have the page controls, DevTools, resizable panels, crash banner or the display-scale fix. It does have the log file.
Checks before you change anything#
| Recipe | What it does |
|---|---|
just check | Runs cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings and cargo machete (which finds unused dependencies). It does not build Servo. |
just test | Runs cargo test --workspace. It needs no network and no API key. If you are short on disk, test one crate at a time (cargo test -p ferrite-ui). Linking the whole workspace is what runs out of space. |
just audit | Runs cargo deny check: licences, security advisories and duplicate versions. It needs the network for the advisory database. |
just ci | Runs check and then test. It is the local version of CI's lint and test job. |
Run the live evaluation with a real model#
The live runner puts a real AI model in the evaluation loop. It needs a key (or a local Ollama) and a model tag. A model tag is the exact name of a model on the provider, such as gemma4:31b. Ferrite never has a default tag. A key comes from OLLAMA_API_KEY or FERRITE_GEMINI_API_KEY, or from the OS keyring. You never pass a key as a flag. Live evaluation explains the method and every flag. Start with the plan, which calls nothing and needs no key:
$ just live-eval-plan --provider ollama --model gemma4:31b --corpus all --seed 1 --batch-size 100
This is a command like the one the owner used for the live run in Limits. The owner ran it in batches of 100 cases. The first batch used the default --max-calls of 100, which stopped after 18 cases. The value 800 below is a suggestion for finishing a batch of 100 cases. It is not a recorded value:
$ just live-eval --provider ollama --model gemma4:31b --corpus all --seed 1 --batch-size 100 --max-calls 800 --pause-ms 2000
--corpus allis all 1,984 cases (the 938-case corpus and the 1,046-case AgentDojo import).--seed 1shuffles the cases in a fixed way, so a small batch samples every suite.--pause-ms 2000waits at least 2,000 ms between calls.- Run the same command again for the next batch. Ferrite skips the results that are already stored.
Tip: --batch-size and --max-calls count different things
--batch-size counts cases. --max-calls counts model calls that reach the provider, and the default is 100. One case can need several calls. So a batch of 100 cases can need more than the default 100 calls. A value of 800 should let a batch of 100 cases finish. If the cap is reached, the run stops with exit code 3. Nothing is lost. The case in flight stays pending, and you run the same command again.
The exit codes tell a script why the run ended:
| Code | Meaning | What to do |
|---|---|---|
0 | The batch is done and every result is stored. | Go on to the next batch. |
1 | An I/O or internal failure. | Stop and look. |
2 | A usage or configuration problem, such as a bad flag, a missing key or a missing model tag. | Stop and fix it. |
3 | The --max-calls cap was reached. Nothing is lost. | Raise the cap, or run the same command again. |
4 | The provider kept failing, for example from a rate limit or an outage. | Wait, then run the same command again. Stored results are kept. |
5 | The batch finished, but some cases failed. | Run again with --retry-failed after a pause. |
To read the results, run just live-eval --report. It writes REPORT.md and report.csv under target/live-eval.
Looking at a page without the app#
page_shot loads a URL in the real engine. It prints what happened: the title, the load time, the console messages and a count of requests by kind. It also writes a PNG. It needs the Servo build.
$ cargo run -p ferrite-servo --features servo --example page_shot -- \
https://example.com 8000 out.png 1280 800 1
The arguments are these:
- the URL
- how long to run the engine, in milliseconds
- the output file
- the frame size in device pixels
- the display scale
A display scale of 2 draws the page as a Retina screen would. Then a 1280-pixel frame is a 640 CSS-pixel viewport. The other probes are probe-input, probe-web-api, probe-engine, probe-controls and probe-profile. They drive the same real engine headlessly against local pages. just --list names them. They are not part of CI.