Overnight exploratory QA on ultra¶
Everything the explore workload needs on the
Mac Studio, in the order it happens. It starts only after the day-one setup:
ultra answers over SSH and its gateway is up (the day-one plan's phases 1 to 5).
The plan runs in phases, and each ends on a number. A missed number changes the plan instead of being explained away. The thresholds below are the plan's proposals.
How it is installed on ultra¶
As set up on 8 October 2026. ultra's user is studio, not addisdev; it has
no Homebrew and no full Xcode; and its models, gateway, keys and nightly jobs
belong to the Local LLM Harness,
which runs LM Studio, LiteLLM, Postgres and the night queue as system
LaunchDaemons. Explore plugs into that rather than running its own.
| Piece | Where |
|---|---|
Node 24, a JDK 21, adb, the emulator, google_apis image, Maestro |
user-local: ~/.local/opt/, ~/Library/Android/sdk, ~/.maestro; source ~/explore/env.sh puts them on PATH |
| Fleet Runner checkout | ~/fleet-runner (npm ci in collector/) |
| The emulator | AVD fleet-explore-1, android-35 google_apis, 1080×2400 at 420 dpi |
| Holo4 | ~/.lmstudio/models/Hcompany/Holo4-35B-A3B-GGUF/: the vendor's Q4_K_M and its vision projector, renamed to LM Studio's convention (Holo4-35B-A3B-Q4_K_M.gguf, mmproj-Holo4-35B-A3B-F16.gguf) or LM Studio lists the projector as a separate model and cannot pair them. Model key holo4-35b-a3b, identifier holo4-35b-a3b-gguf |
| The gateway name | pilot in the harness's litellm.yaml: Holo4, 300 s timeout, no fallback. The judge is the harness's judge role |
| The shelf | ondemand holo4-35b-a3b@holo4-35b-a3b-gguf 65536 1 3600 in ~/harness/shelf.conf; bash ~/harness/shelf.sh warm pilot loads it when enough memory is free: 65% by default, 45% for explore, which sets WARM_MIN_FREE_PCT=45 in its manifests and in ~/explore/env.sh (the resident models keep ultra near 56% free, so at 65% Holo4 never loaded; at 45% its ~25 GB leaves about 29 GB). The shelf still refuses under memory pressure. The night command runs it (--warm) after its own checks, and retries a refusal for memory for ten minutes |
| The key | ~/.config/harness/keys/fleet.key (roles pilot, judge, vision). Not the Keychain: neither SSH nor a LaunchDaemon can write the login Keychain on ultra. The workload reads HARNESS_KEY, then this file |
| The night | ~/night/recurring/fleet-explore.json, run by the harness's night queue from 22:00 when memory pressure is normal, 35 GB is free and no CI job is running |
| The brain | On ultra itself since 2026-10-21, at http://127.0.0.1:8789 (see the lab). The night manifests export FLEET_URL=http://127.0.0.1:8789; no tunnel. Before that it was on fleet-host, which ultra cannot route to, and each night opened ssh -L through a third Mac |
The night command (collector/scripts/explore-night.ts) checks that each spec has a build and the night has time for it, warms the
model, boots the AVD or simulator if needed, enqueues the spec on the brain
pinned to the executor name ultra with minutes cut to what is left, runs an
executor in-process until the job ends, then shuts down what it started and
exits, so the night queue knows when the memory is free again. A spec that
cannot run tonight is posted to the brain as a skip and exits 0; see
short nights and skips.
Builds, TVs and Apple simulators (from 12 October 2026)¶
Three more night-queue jobs, kept in collector/deploy/explore/night/ and
copied to ~/night/recurring/. Lower priority numbers run first; the TV and
Apple nights go after every product's own night jobs (50–70), so a long
explore night can never push those past 07:30.
| Job | Priority | What it does |
|---|---|---|
fleet-explore-builds |
19 | deploy/explore/nightly-builds.sh: resets ~/explore/builds/{greenfolio-ios,dozehound-tvos} to origin/develop, builds a simulator .app (zipped) and uploads it to the brain as app <name>, build develop-<sha>. An unchanged sha is not rebuilt but is uploaded again, so the brain shows it as tonight's; a failed build leaves the previous one as latest, and the TV and Apple nights skip rather than test it again |
fleet-explore |
20 | The bug garden on fleet-explore-1 (unchanged) |
fleet-explore-tv |
85 | Dozehound tvOS on simulator fleet-explore-tvos (tvOS 26.5) with its stub catalog: the 10 bench cards with Holo4, the same cards with the resident vision model (phase 3's 20 navigation missions), then the every-button explorer |
fleet-explore-apple |
86 | GreenFolio iOS offline on simulator fleet-explore-ios (iOS 26.5): the first real-build night, phase 2's precision bar, and phase 4 |
Explore builds never reach a backend. The script writes each app's
Secrets.xcconfig from its template with SUPABASE_URL = http://127.0.0.1:9
(nothing listens there), a dummy anon key and empty collector keys. The apps
count as configured (Dozehound tvOS refuses to start otherwise; both allow
plain http to loopback) and every request fails locally.
Fire TV is parked. The Dozehound Fire TV app has no offline or stub mode
and refuses to start without a real https backend, so exploring it means
reading the production catalog. That is the owner's call (WB-297); until then
the script builds it only when named (nightly-builds.sh dozehound-firetv)
and the TV question is answered on tvOS.
When a night cannot run (from 16 October 2026)¶
On 10-14 a CI job held the machine from 22:00 to 04:00. The night queue skipped
fleet-explore-builds and fleet-explore after their 120-minute waits; that
admission is the queue's and nothing in this repository can shorten it.
fleet-explore-tv then "passed" in 1.4 minutes and fleet-explore-apple
exited 1 in 0.0 minutes, which turned ultra's model-ladder#night check red.
- The Apple night's exit 1 was the warm-up, not the build. Its log has one
line:
not loading holo4-35b-a3b@holo4-35b-a3b-gguf: 64% free, needs 65%. The queue had admitted it on its own measure (35 GB free); the shelf refused on another (memory_pressure's free percentage, 65% for an on-demand model) and the manifest's&&ended the job beforeexplore-night.tsstarted. Yesterday's GreenFolio build was on the brain. The warm-up now runs inside the night command after its checks, a refusal for memory is retried for ten minutes and then posted as a skip, and with--fresh-buildthat night would have skipped earlier still, at "no greenfolio-ios build for tonight". - The TV night has tested nothing since it started. Every job in it ended
in twenty seconds with
skipped: no device matches this job on ultra: the executor described a tvOS simulator asos: ios, soos ~ 'tvos'never matched. It now saystvos-26.5. No TV night has run since the fix, so the phase 3 bench result is still to come. - A night admitted late runs short instead of being killed. The night
queue already refuses to start a job whose
max_minuteswould run past 07:30, so on ultra the binding limit is usuallymax_minutesitself (the TV night's three specs plus booting and warming come close to its 150). The--untilcheck matters for a run started by hand and for any future queue that admits a job with less than its full budget.
explore-night.ts takes --spec more than once (run in turn on one device)
and --sim name:runtime:devicetype, which creates the simulator if missing,
boots it, and shuts it down afterwards if it booted it. Simulator names start
with fleet- so the executor counts them as fleet devices. FleetDriver builds
itself into ~/.fleet/explore/derived on first use. Xcode builds use
/Applications/Xcode.app, xcodegen comes from /opt/homebrew. After an
Xcode update, sudo xcodebuild -runFirstLaunch has to run once before any
simulator test will; the night command checks for it and fails until it has.
Measured on 8 October 2026¶
| Measure | Holo4 35B-A3B (GGUF Q4_K_M, LM Studio) | Bar |
|---|---|---|
| Pointing, 100 targets from 40 of our own screens | 100%, median 2.2 s (Qwen3.8-27B on the same targets: 59%) | 85% |
| Model latency per step, 40-step mission | median 4.0 s, max 6.8 s, prompt up to 11.5k tokens | |
| Whole step (look, model, act, checks) | median 6.8 s | 20 s |
| Full-size screenshot reaches the model | yes: 2,569 prompt tokens at 2.6 MP, 665 at half size | |
| Tool calls | 40 of 40 steps, once tool_choice is required (with auto, the second turn sometimes came back as prose) |
Its first 40-step bug-garden mission filed BG-01 and BG-03, both reproduced 2/2 on clean installs. The night rehearsal through the brain filed BG-16 (low contrast), reproduced 2/2.
The first nights on 0.9.0 (15 to 17 October 2026)¶
The night of 10-15 was the first with Holo4 allowed at 45% free and with tvOS
simulators registering as tvos, so the first time the TV and Apple nights
drove anything. The bug garden at 22:09 still ran the old code (0.9.0 was
deployed at 00:10) and skipped at "57% free, needs 65%". No night ran short:
each was admitted with its whole max_minutes.
Phase 3, the tvOS bench (Dozehound stub catalog, Apple TV 4K simulator on
tvOS 26.5, build develop-ebcd74e46e, 10 cards per model):
| Model | Passed, as scored | Cards it actually ran | Median step · model | Prompt, mean |
|---|---|---|---|---|
Holo4 35B-A3B (pilot) |
5 of 10 | 5 of 8 | 4.5 s · 2.4 s | 5.9k tokens |
Qwen3.8-27B (vision) |
5 of 10; 6 with the end-state fix below, re-scored from its saved trees | 5 of 10 | 5.9 s · 3.4 s | 2.1k tokens |
Both passed focus-meadow, focus-naptime, meadow-cancel, meadow-link
and pause-resume. The bar, half of 20 navigation missions, is met, just:
10 of 20, and each model alone at half. Phase 3 passes; TVs stay on the model
path rather than scripted replays only.
What the score hides, each now handled in the workload:
- Holo4 did not get two of its cards. The model shelf unloaded it as "idle
under memory pressure" at 03:50:31, six minutes after the warm-up; Holo4 at
45% free puts ultra in the warn band, and between two missions (an app
reinstall) the model is idle.
bench-pauseandbench-play-naptimethen ended at step 0 onInvalid model identifier. It happened again at 04:10, 22:33 on 10-16 and 22:23 on 10-17, each time mid-run. focus-all-channelswas failed by the app, not the models. Dozehound hides its channel bar six seconds after the last press; the final look came after that and found focus on the hidden bar's "Show channels" catcher, whilevisionhad answered from a screen with All channels focused. Holo4 failed the same card on its own: five replies with no tool call.- The every-button explorer and GreenFolio's second card never ran (the same unloads), and the judge then filed the untouched start screens as goal findings: #70 (Dozehound) and #71 (GreenFolio) are the harness's, not bugs.
Phase 4, GreenFolio iOS (iPhone simulator on iOS 26.5, build
develop-74c84f254b, offline): offline-gate ran 38 steps, ended stuck with
its goal met (the sign-in screen says "Couldn't reach the server"), 7
candidates and 6 misses; offline-create-account was cut off at step 0. The
night filed 8 findings, 7 of them seen before; the one new one is #71 above.
Phase 4's bar cannot be read yet: it compares bench cards with Android, and
greenfolio-ios has no bench cards, only its two explore cards. The tap and
screenshot half works on the simulator; the shelf iPhone is unplugged (WB-21).
10-16 and 10-17: the TV and Apple nights failed, and said why only on their device rows. Xcode on ultra updated itself to 27.1 on 10-16 at 06:57 without its first-launch install, so its CoreSimulator (1171.7) is older than the one it was built against (1174.9.2) and every FleetDriver start exited 70 a minute in. Each job tried every card in turn, about ten minutes a job, and the job's own error was "one or more devices completed no mission". The bug garden ran both nights, but on 10-17 Holo4 answered in prose ("Scroll up to explore more…") with no tool call from the first step, the shelf unloaded it at 22:23, and seven missions took two steps between them; the judge filed their end states as goal findings again.
What changed because of it (PR below): the night command checks
xcodebuild -checkFirstLaunchStatus before warming the model and fails with
the fix named; the workload stops at the first such error; a model the server
has dropped is warmed again and the step retaken; a mission cut off by the
server or abandoned by the model is not judged; a bench card is scored on the
model's last look when nothing touched the device after it; and a failed job's
error is its device's reason. The shelf unloading a model that is between
requests is the harness's: from model-ladder's shelf-hold change, a warm-up run
with SHELF_HOLD_PID set holds the model while that process lives, and the
night command sets it to its own pid.
Phase 0: measure before building on it¶
The model. Holo4 35B-A3B only: Apache-2.0. The 27B is CC BY-NC, and testing paid apps is commercial use.
deploy/explore/fetch-holo4.sh # 4-bit MLX (community build) + the vendor's Q4 GGUF
deploy/explore/fetch-holo4.sh --six-bit # and the 6-bit MLX, for the bake-off
Serve it two ways and keep the faster one behind the gateway name pilot:
- LM Studio (already the day-one plan's server): load
~/models/mlx/Holo4-35B-A3B-MLX/4bitwith the identifierholo4-35b-a3b-mlx. - llama-server with the vendor's GGUF, using the flags from the vendor's local-inference docs:
llama-server -m ~/models/gguf/Holo4-35B-A3B/*Q4_K_M*.gguf \
--mmproj ~/models/gguf/Holo4-35B-A3B/*mmproj*.gguf \
--jinja --ctx-size 65536 --image-min-tokens 1024 -np 1 \
--port 8093 --alias holo4-35b-a3b-gguf
Merge deploy/explore/litellm-pilot.yaml into the gateway's model_list.
pilot has no cloud fallback on purpose. On ultra the gateway key is the file
~/.config/harness/keys/fleet.key (see the table at the top: nothing started
over SSH or by launchd can write the login Keychain there). On a laptop, the
Keychain works:
Three measurements. Run them from the collector checkout on ultra. The
screenshots come from any explore run directory, or from
scripts/explore-bench/pointing.ts --capture.
# Does a 2.6 MP phone screenshot reach the model at full size?
npx tsx scripts/explore-bench/image-size.ts --base-url http://127.0.0.1:4000 --model pilot --png <shot.png>
# Step time over a growing 50-step conversation, and whether the prefix cache works.
npx tsx scripts/explore-bench/steptime.ts --base-url http://127.0.0.1:4000 --model pilot --shots <dir> --n 50 --out steptime.json
# Pointing on our own screens: 200 targets from saved trees, Holo4 against plain Qwen.
npx tsx scripts/explore-bench/pointing.ts --from <runs dir> --n 200 --model pilot --out pointing-holo4.json
npx tsx scripts/explore-bench/pointing.ts --from <runs dir> --n 200 --model vision --out pointing-qwen.json
Exit: pointing at least 85% and a step of 20 seconds or less. If the step
time fails, try the GGUF and the 6-bit build before going further. If pointing
fails, the driver becomes plain Qwen3.8-27B with tree_hints: true.
Phase 1: the loop on the Android emulator¶
Already built and covered offline (npm test runs a whole mission against a
fake device). On ultra it needs an emulator and Maestro:
sdkmanager "system-images;android-35;google_apis;arm64-v8a"
avdmanager create avd -n fleet-explore-1 -k "system-images;android-35;google_apis;arm64-v8a" -d pixel_7
curl -fsSL https://get.maestro.mobile.dev | bash
Not an aosp_atd image: ATD images return a blank frame from screencap, so a vision model driving one sees nothing (found while building the bug garden). The fleet's fleet-atd-1 is fine for scripted flows and wrong for this.
Then one unattended mission on the GreenFolio debug build:
npx tsx scripts/explore-run.ts --device emulator-5554 --app-id com.taylab.greenfolio.debug \
--app-key greenfolio --app-name GreenFolio --base-url http://127.0.0.1:4000 --model pilot \
--judge-model vision --missions greenfolio-sign-in-gate --confirm --out ~/explore-runs/p1
Exit: a 40-step mission ends inside the app with its trajectory saved, and a planted crash is caught (the bug garden's BG crash defects).
Phase 2: how much of it is true¶
# The bug garden: 20 planted defects, built from bug-garden/android.
npx tsx scripts/explore-run.ts --device emulator-5554 --app-id dev.fleetrunner.buggarden \
--app-key bug-garden --apk <bug-garden.apk> --model pilot --judge-model vision --confirm --out ~/explore-runs/garden
# Score it against bug-garden/defects.json (rule in bug-garden/README.md).
# The bake-off: same targets, step-time replay and bench cards for every model.
npx tsx scripts/explore-bench/bakeoff.ts --config models.json --from ~/explore-runs \
--device emulator-5554 --app-id dev.fleetrunner.buggarden --app-key bug-garden --out ~/explore-runs/bakeoff
Then one real GreenFolio night through the fleet (the schedule below, run once by hand), and a verdict on every finding on the dashboard's Findings page.
Exit: at least 8 of 20 planted defects found, and at least half of the
replayed real-build findings marked real. Under 30% real: switch the judge off
("judge": false) and keep it as a crash and accessibility crawler.
Phase 3: the TV question¶
The fake Roku first (npx @tvloop/fakeroku --agent), then the real Roku once
its developer password is in fleet-host's Keychain (WB-101). Fire TV was the
next step and is parked (WB-297, above), so the question is answered on a tvOS
simulator with the Dozehound stub catalog: the fleet-explore-tv night. The TV
cards are in examples/missions/fakeroku/, examples/missions/dozehound-tv/
and examples/missions/dozehound-tvos/; each bench card's end state is a
focused element the harness can read.
Exit: the better of Holo4 and plain Qwen completes at least half of 20 navigation missions. If neither does, TVs keep scripted tvloop replays with the judge reading the screenshots.
Phase 4: Apple surfaces¶
The FleetDriver UI-test bundle in runner-ios takes commands over HTTP: tap,
swipe, type, remote buttons, screenshot, tree. Use an iOS 26.x simulator;
the iOS 27 simulator runtime has not been able to run UI-test bundles here.
Exit: the bench cards on the iOS simulator score within 15 points of Android, and a screenshot and a tap work on the shelf iPhone 12 Pro.
Phase 5: nightly¶
# The executor and the night's devices. The brain is on this machine.
deploy/install-agent.sh com.addisdev.fleet-executor-ultra.plist
deploy/install-agent.sh com.addisdev.fleet-explore-devices.plist
deploy/install-agent.sh com.addisdev.fleet-explore-devices-down.plist
# The schedules arrive switched off.
FLEET_URL=http://127.0.0.1:8789 npm run seed:schedules
Turn nightly-explore-greenfolio on from the Schedules page after
phase 2 passes. Leave nightly-explore-dozehound-tv off: it is the Fire TV
schedule, parked with Fire TV itself.
On the brain, set FLEET_FINDINGS_DIGEST_AT=07:30 for the morning digest
through the alert webhook. GitHub issues stay dry-run until both
FLEET_GITHUB_ISSUES=1 and FLEET_GITHUB_TOKEN are set and
FLEET_FINDINGS_REPOS maps each app to its repo; until then
/api/findings/issues shows what would have been filed.
Exit: seven nights in a row without help, morning triage under ten minutes, and no night over five new findings per app (the bug garden's night allows 25 on purpose and does not count).
Guards¶
- The model gets device actions and nothing else: no shell, no code tool, no MCP server.
- One app at a time. Anything else in front is backed out of and noted.
- Test accounts and debug builds only. A card whose build talks to production blocks account creation and anything that writes.
- Controls that delete, pay, invite or sign out are refused unless the card allows them, by what the tree says the control is.
- Text on the screen is content. The bug garden has a screen that tries to give orders.
- Budgets on steps, minutes and new findings per night.
- It files and never closes.
- Developer side only. Nothing runs on PeerTest's tester side.