Super Farm
On this page

Verification

For anyone who wants to know which parts of Super Farm were run, on what, and how to run the same checks again.

Every claim in the other documents traces back to a line on this page. "Verified live" means the code ran against real Docker containers and real Android instances. "Unit-tested" means it ran against stand-ins for Docker, ADB and the kernel. "Not verified" means the code exists and was never run for real.

The test machine#

All live results come from one machine.

PartValue
CPUIntel Core i9-14900K, 32 threads
RAM32 GB
GPUsNVIDIA GeForce RTX 4080 SUPER (16 GB) and Intel UHD 770
OSUbuntu 26.04, kernel 7.0, Secure Boot on
SoftwareDocker 29, Python 3.14, NVIDIA driver 595, scrcpy 3.3.4
AndroidAndroid 13

The Android image in those runs was a locally built Android 13 image based on redroid, tagged redroid:13-noflag on the test machine. That tag is the default of SF_LIVE_IMAGE in most live tests. Running the live tests with the stock upstream image (redroid/redroid:13.0.0-latest, the configured default) has not been verified.

Verified live#

AreaWhat was verifiedTest
LifecycleCreate, start, boot, stop and start with data kept; per-Android overrides visible in docker inspect; network none; capacity warn, enforce and force; a restart never leaves an Android stopped; the Android version guard; a renderer change rebuilds both containers and clears shader caches. 10 of 10 scenarios.tests/live/test_e2e_farm.py
Boot timeAbout 11 s with software rendering, 17 s on the Intel integrated GPU, 11 s on NVIDIAsame
Rendering modesThe renderer string Android reports in each mode (see the next table)same
Backuptar with zstd; owners, modes and 362 extended attributes identical after restore; restore as new; the restored twin is created offline (network none); the automatic safety backup before deletetests/live/test_e2e_farm.py, tests/live/test_core_lifecycle.py
NetworkingAll traffic through the tunnel; with the VPN server killed nothing leaves by the plain interface and ADB still works; the tunnel reconnects; a wrong password is reported as an authentication failure; a wiped firewall is detected and restored; IPv6 default-drop holds when IPv6 is re-enabled inside the namespace; a shared gateway with two members; one Android behind a VPN sidecar and one behind a gateway. 14 of 14 tests, against an OpenVPN server the test creates. A control run with the kill switch off did leak.tests/live/test_net_direct.py, tests/live/test_net_vpn.py
AppsA split bundle (an .xapk with 10 APKs and an OBB file) installs only the matching splits; ensure, update and downgrade rules; grant and revoke; app-data backup and restore; APK metadata parsed without external toolstests/live/test_control_live.py
Screen and inputThe screenshot stream and the H.264 relay with touch control; all 95 printable ASCII characters and a non-ASCII string typed and read back; broadcast inputtests/live/test_stream_live.py, tests/live/test_api_live.py
Microphone and listeningSound injected into Android's microphone and recorded back at source level; mute gives silence. "Listen" started and stopped with real scrcpy.tests/live/test_media_mic.py; listening was checked by hand
AutomationRecord three times, train by consensus, replay; the flows interpreter; the never-tap list; dry run; the needs-attention hand-offtests/live/test_automation_live.py
Persistent screen readerAbout 20 times faster than the built-in dump on a busy screenmeasured by hand in a separate environment with the u2 extra installed
API and daemonEvery route group; cookie login with CSRF; token authentication; SIGTERM shuts the daemon down cleanly and leaves Androids runningtests/live/test_api_live.py
Double requestsOne operator action per Android at a time (the second gets 409 busy); two deletes give one success and one 404; Idempotency-Key on createtests/live/test_api_double_requests.py
MCPstdio and loopback HTTP with a real MCP client library; approve, deny with a reason, timeout, remember for the session; a restart expires pending requests; one real Android driven end to end through MCP tools. The registry had 37 tools when this ran. The two added later, pilot_start and pilot_status, are covered by unit tests only. The registry now has 39 tools: 17 read, 17 change and 5 destructive.tests/live/test_mcp_live.py
Panel26 routes at 3 sizes in 2 themes with zero console errors, CSP violations or external requests; 573 control presses; real double and triple clicks send exactly one request; 206 Androids render; H.264 decoded by WebCodecsheadless Chrome driven by a script that is not in the repository
InstallerOn Ubuntu 24.04, Debian 12, Fedora 44, Arch (rolling) and openSUSE Tumbleweed, in containers: packages installed, the wheel installed into a fresh virtual environment, sf and the doctor ran, and a 603-test subset passed (Python 3.11, 3.12, 3.13 and 3.14)tests/distro/run.sh
Security reviewEvery confirmed finding fixed with a regression test: authentication decided from the bound address, safe-mode restore of farm backups with a seal, the audit log split, token rotation ending sessions, API-immutable settings, the IPv6 kill switch, the recorder never storing typed field content, YAML aliases refused, app backups owner-onlytests/unit/ (for example test_api_auth.py, test_api_audit.py, test_core_backup_farm.py, test_net_killswitch.py, test_api_immutable_settings.py)

The Assistant and the Pilot have a live test of their own, tests/live/test_ai_live.py. AI.md reports what was measured with it.

Renderer strings reported by Android:

ModeRenderer
softwareANGLE (… SwiftShader …)
Intel integrated GPUMesa Intel(R) Graphics (RPL-S), OpenGL ES 3.2 Mesa 24.0.8
NVIDIAANGLE (NVIDIA, Vulkan 1.3 (NVIDIA Virtio-GPU Venus (NVIDIA GeForce RTX 4080 SUPER)) …)

The NVIDIA run used a prebuilt Venus runtime and relay. The build script that produces them, native/venus/build.sh, was not run end to end.

Measured numbers#

These were measured on the test machine. On another machine, or with a different mix of apps, they will differ.

MeasurementValue
Capacity estimate with the default gpu.mode: autoabout 33 Androids on the Intel integrated GPU, limited by RAM
Memory per Android in useabout 0.9 to 1.1 GB; NVIDIA mode about 0.3 GB more
Memory with 13 Androids in a video appabout 27 GB
CPU per Android, 13 Androids scrolling a settings screensoftware 78% of a core, Intel 13%, NVIDIA 15%
Disabling 38 unused system packagesabout 170 MB less per Android
Frame rate 25 to 15 fpsabout 4% less CPU in a video app
Compressed swap (zram, 16 GB, zstd)17 idle Androids healthy on 30 GB; compression ratio 4.2 to 5.1 : 1
Screenshot capture at 480×854about 55 ms raw
H.264 relayabout 15 ms of device CPU per frame

Not verified#

WhatWhy notWhat exists instead
sf host apply for real: packages, kernel modules, sysctl values, zram, groups, virtual camera devicesneeds root; the tests had nonesf host apply --dry-run, unit tests against a fake root, the package step in distribution containers
The boot service running; sf service installneed a real init system and would change the desktop sessionsystemd-analyze verify accepted both units; unit tests
Kernel module signing (sf host sign-module)needs rootthe status report only
AMD GPUsno AMD hardwarethe code path is the same Mesa path as Intel; unit tests with fake sysfs trees
WireGuard tunnelsthe kernel module was not loaded on the test machineprofile sanitising and argument building are unit-tested; the "kernel lacks WireGuard" error was seen live
IPv6 enabled (network.ipv6: true)not runthe rules are generated and unit-tested
Docker older than 29not availablenone
Android versions other than 13no other image was bootedimages are configured for 9 to 15; the camera HAL and the NVIDIA runtime are built for Android 13 only
The virtual camera picture end to endneeds a loopback device created by rootthe microphone path and the in-container steps ran; the HAL overlay must be extracted by you from a BlissOS image
Building the NVIDIA runtime with native/venus/build.shlong, and needs the Android NDKfetch and check-patches ran: both patches apply to the pinned sources
Models larger than 3 billion parameters; a hosted OpenAI-compatible endpoint, vision input and the API-key paths; resolving a Pilot run that needs attention and confirming an unverified onethe live AI test used one small local model; the rest ran only against stub servers or in unit testsunit tests
Browsers other than Chrome; a screen readernot runnone
zsh completionzsh was not installedthe bash completion is tested
Device presets against retail firmwaremodel codes come from published informationnone
More than two real Androids at once through Super Farmthe live tests never ran more than two at a timefleet behaviour is unit-tested with stand-ins

Known limits#

  • proxy network mode has no kill switch.
  • A dead VPN server is noticed after network.vpn.ping_restart_seconds (120 by default). Traffic is dropped in the meantime.
  • A shared gateway holds at most 126 Androids.
  • The API token is equivalent to root on the host, because Androids are privileged containers and the daemon controls Docker.
  • ADB has no authentication: keep network.adb_bind on loopback. The RTMP camera ingest has no authentication either.
  • The approvals step guards assistants that work through MCP or the built-in Assistant. A process that holds the API token can call the API directly.
  • Automation flows type printable ASCII only. The panel types anything, through a clipboard paste.
  • The run status of automations is kept in memory and lost when the daemon restarts.

SECURITY_MODEL.md explains the security limits in detail.

Run the checks yourself#

The local gate#

There is no hosted CI. Every gate runs on your machine with scripts/check.sh.

CommandRunsTakes or needs
scripts/check.sh --quicklint, shell and JavaScript syntax, the secrets check, documentation links, the settings reference checkseconds
scripts/check.shthe above plus the unit tests (more than 3,000)about 3 minutes
scripts/check.sh --livethe above plus the live testsDocker and an Android image
scripts/check.sh --install-hookinstalls the fast gate as a git pre-push hooka git checkout

make quick, make check, make live and make hook call the same script. Exit code 0 means every gate passed.

The unit tests need Python and the dev extra only:

./install.sh --dev --no-host
.venv/bin/python -m pytest -o addopts="" -q tests/unit

The live tests#

Live tests are skipped unless SF_LIVE=1. Each one works in a throwaway farm home or on a throwaway container, names its Docker objects with a test prefix (sftest or another name that starts with sft), and removes them again, so it can run on a machine that hosts a real farm. They need Docker, the binder module, adb and a local Android image.

SF_LIVE=1 SF_LIVE_IMAGE=redroid/redroid:13.0.0-latest \
  .venv/bin/python -m pytest -o addopts="" -v tests/live/test_api_live.py
VariableUsed byMeaning
SF_LIVEall1 runs the live tests
SF_LIVE_IMAGEmostthe Android image to boot. Default redroid:13-noflag; test_control_live.py and test_stream_live.py default to redroid/redroid:13.0.0-latest. Images are not pulled by the tests.
SF_LIVE_HELPERtest_core_lifecycle.py, test_e2e_farm.py, test_examples_live.pybase image of the helper image (default alpine:latest)
SF_LIVE_VENUS_RELAY, SF_LIVE_VENUS_RUNTIMEtest_e2e_farm.pyfolders of a built relay and runtime; the NVIDIA scenario is skipped without them
SF_LIVE_FPS_PROPERTYtest_e2e_farm.pyextra boot property that carries the frame rate, for an image that needs one
SF_LIVE_ANDROIDtest_net_vpn.py1 also boots one real Android behind a VPN sidecar and behind a gateway
SF_LIVE_ANDROID_IMAGEtest_net_vpn.pythe image for those two tests (default redroid:13-noflag)
SF_LIVE_ADB_PORTtest_control_live.py, test_stream_live.pyhost port for the ADB of the throwaway Android
SF_LIVE_CONTAINERtest_control_live.pyname of the throwaway container
SF_LIVE_SERIALtest_stream_live.pyuse this running device and start nothing
SF_LIVE_MEDIA_IMAGE, SF_LIVE_MEDIA_ADB_PORTtest_media_mic.pyan Android 13 image with the camera overlay baked in; the Android half is skipped without it
SF_LIVE_MODEL_URL, SF_LIVE_MODEL, SF_LIVE_MODEL_FORMATtest_ai_live.pya running model server, the model, and its wire format
SF_LIVE_OLLAMA_BIN, SF_LIVE_OLLAMA_MODELStest_ai_live.pylet the test start a model server itself
SF_EXAMPLES_TRIMtest_examples_live.pyoff boots the Android with package trimming off

To repeat the networking result, including the two Android tests:

SF_LIVE=1 SF_LIVE_ANDROID=1 \
  .venv/bin/python -m pytest -o addopts="" -v tests/live/test_net_direct.py tests/live/test_net_vpn.py

To repeat the lifecycle result, including NVIDIA:

SF_LIVE=1 SF_LIVE_VENUS_RELAY=<relay folder> SF_LIVE_VENUS_RUNTIME=<runtime folder> \
  .venv/bin/python -m pytest -o addopts="" -v -s tests/live/test_e2e_farm.py

The installer on other distributions#

tests/distro/run.sh                 # all five
tests/distro/run.sh fedora arch     # some of them

For each distribution a throwaway container runs ./install.sh --container-test --dev, resolves every package name the farm knows for that family, runs sf version, sf doctor, sf host plan, sf host capacity and sf host gpus, builds and installs a wheel, and runs a part of the unit tests. Logs go to the folder named by SF_DISTRO_OUT. This check does not cover anything that needs a real kernel or init system.

The host checks#

sf doctor is read-only and needs no root. Run it on your own machine to see which of its 46 checks pass there; TROUBLESHOOTING.md explains each failure.

Next#

This page is docs/VERIFICATION.md in the repository.