Skip to content

Wave 2: job chains, builds, energy, and five workloads that refuse to guess - #3

Merged
addisdev merged 1 commit into
developfrom
feat/wave-2-chains-and-evals
Sep 4, 2026
Merged

addisdev merged 1 commit into
developfrom
feat/wave-2-chains-and-evals

Conversation

@addisdev

@addisdev addisdev commented Sep 4, 2026

Copy link
Copy Markdown
Owner

The first wave in the mono repo, so it is one PR instead of three.

Job chains

depends_on makes build → install → ui-test a single enqueue. A job names ids it waits for, is inserted waiting, and is promoted when the last closes. A failed or cancelled dependency fails its waiters down the whole chain rather than parking them forever. A promoted spec can carry ${jobs.<id>.artifact} and ${jobs.<id>.metrics.<key>}, substituted whole so a metric stays a number rather than becoming a string.

waiting is counted separately from queued on the overview. A chain parked on a dependency is not work the fleet is failing to pick up, and folding the two would make a healthy pipeline look like a backlog.

Preemption

A twelve-hour drain can step aside for a two-minute install. The beacon answers preempt: true only when a strictly higher-priority queued job would go to the same claimant; the runner checkpoints, uploads, and posts preempted: true, which requeues with params.resume_from and hands the attempt back. Stepping aside never counts against max_attempts, because it is not a failure.

build

Closes the loop the artifact store was built for. Check out a ref, build it, publish it with the headers that stamp publish_seq, and a later job asking "sha256": "latest" resolves to it — verified end to end.

Two findings corrected my plan:

  • report_to.github_status was never unfed. The collector already posts success/failure on every final row. The agent uses its own context fleet-runner/build rather than sharing one, because two writers on one GitHub context means the loser's verdict silently disappears. What it genuinely adds is pending at claim, a state the collector cannot post because it does not know when work started.
  • No publish endpoint was needed. POST /artifacts with x-artifact-app/x-artifact-build/x-artifact-platform is what stamps a published build.

Energy, measured honestly or not at all

  • energy_method is declared in config, never inferred. A pool that omits it gets no figure.
  • plug-shared reports the pool's draw and refuses to divide it. The devices are not identical, they are not all busy, and a per-device number arrived at by division would be indistinguishable in storage from a measured one.
  • The integration skips gaps rather than bridging them, refuses to extrapolate past its window, and returns null rather than zero below two samples. An unreachable plug must not become fabricated draw.
  • The reported quantity is watt-hours at the wall, above a measured idle baseline, including charging — stated everywhere it appears, because a wall measurement cannot separate a charging battery from a working one.

Evals as a page

/api/evals and an Evals page pivot eval sets by model and device, with a Markdown export. It counts and lists the rows it excluded, naming the metrics each one does carry. The plant-ID write-up's accuracy rode in decode_tok_s because vision had no field of its own; a page that quietly dropped those rows would repeat the mistake it exists to prevent.

Five workloads that refuse rather than approximate

speech-eval and embed-eval on the phones, vantage from wherever an agent sits, locale-shots, app-soak and a11y-audit on the host, self-check on the hosts themselves.

Every one names its limits instead of filing a number that means something else: no PSS on an iOS simulator (only host RSS is available, which counts shared pages Android's PSS does not), no locale on a physical iPhone, no bold text below Android 12, no in-device shaping on an unrooted phone, no cold-start on iOS where simctl returns at process spawn rather than first frame.

Two checks worth calling out because they catch a green run that measured nothing:

  • locale-shots fails a device when two locales produce byte-identical screenshots. An app whose locale never reached it is otherwise indistinguishable from a correct run: the setting reads back fine, every folder exists, and every folder is in English.
  • embed-eval asserts that two identical strings embed to cosine 1 and that two different strings do not. A constant non-zero vector passes the first check perfectly and scores a corpus at chance. This is the same class of failure as the iOS Simulator's all-zero logits tensor, which only a second device disagreeing ever caught.

Three bugs found by doing the work

  1. Swift's convertToSnakeCase splits on capitals, and a digit is not one. recallAt1 encoded as recall_at1 — one underscore short of the declared name, i.e. a metric that silently never arrives. Explicit CodingKeys now.
  2. The old runSoak ran simctl against a physical iPhone's UDID, so a real-device soak reported the process DEAD at every check.
  3. The mono repo migration left .gitmodules inside runner-android/, where git never looks. The gitlink survived with no URL registered against it, so git submodule update --init in a fresh clone silently found nothing to do. Moved to the root with its path rewritten, and verified it registers.

Verification

Result
collector full suite green, 552 smoke assertions
runner-machine 110 tests pass
runner-android unit tests and assembleDebug succeed
runner-ios BUILD SUCCEEDED from a clean xcodegen off project.yml; committed project has 0 xcframework references

Known gaps, stated rather than implied

  • ⚠️ The llama.cpp paths in this PR have never been compiled. runner-ios/Frameworks/ does not exist in the mono repo (the 854 MB xcframework is gitignored and did not come across), and runner-android/third_party/llama.cpp is an empty directory until the submodule is initialised. The new embed() entry points on both platforms are written against the same modern API the existing code uses, but need a real native build to confirm. Your old clones still have both if you want them locally.
  • build has only ever run kind: "npm". The gradle and xcode command lines are unit-tested but have not driven a real compiler.
  • The host dnctl/pfctl shaping path from Wave 1 remains untested and opt-in.
  • a11y-audit on iOS needs a small XCUITest addition in runner-ios/ to print the accessibility tree; the parser is already here and the failure names exactly what to add.

🤖 Generated with Claude Code

Wave 2. Chains, builds, energy, and five new workloads — plus a migration
defect this wave surfaced.

depends_on makes a chain one enqueue. A job names ids it waits for, is inserted
'waiting', and is promoted when the last of them closes; a failed or cancelled
dependency fails its waiters down the whole chain rather than parking them
forever. A promoted spec can carry ${jobs.<id>.artifact} and
${jobs.<id>.metrics.<key>}, substituted whole so a metric stays a number. That
is build → install → ui-test without a script watching the queue.

preemptible lets a twelve-hour drain step aside for a two-minute install. The
beacon answers preempt:true only when a strictly higher-priority queued job
would go to the same claimant; the runner checkpoints, uploads, and posts
preempted:true, which requeues with params.resume_from and hands the attempt
back. Stepping aside never counts against max_attempts, because it is not a
failure. Equal priority does not preempt.

build on the machine agent closes the loop the artifact store was built for:
check out a ref, build it, publish it with the headers that stamp publish_seq,
and a later job asking for "latest" resolves to it. It posts pending at claim
under its own status context rather than sharing the collector's, because two
writers on one GitHub context means the loser's verdict silently disappears.

Energy is measured honestly or not at all. energy_method is declared in config,
never inferred; a shared plug reports the pool's draw and refuses to divide it;
the integration skips gaps rather than bridging them and returns null rather
than zero when it has fewer than two samples. What it reports is watt-hours at
the wall above a measured idle baseline, including charging — stated everywhere
it appears, because a wall measurement cannot separate the two.

The Evals page counts what it excludes. The plant-ID write-up's accuracy rode in
decode_tok_s because vision had no field of its own, and those rows cannot be
queried today; a page that quietly dropped them would repeat the mistake it
exists to prevent.

Five workloads arrive with their metric names, their views and their refusals:
speech-eval and embed-eval on the phones, vantage from wherever an agent sits,
locale-shots, app-soak and a11y-audit on the host, self-check on the hosts
themselves. Every one of them refuses rather than approximates where a platform
cannot answer: no PSS on an iOS simulator, no locale on a physical iPhone, no
bold text below Android 12, no in-device shaping on an unrooted phone.

Three bugs found by doing the work.

Swift's convertToSnakeCase splits on capitals and a digit is not one, so
recallAt1 encoded as recall_at1 — one underscore short of the declared name,
which means a metric that silently never arrives. Explicit CodingKeys now.

The old runSoak ran simctl against a physical iPhone's UDID, so a real-device
soak reported the process DEAD at every check.

And the mono repo migration left .gitmodules inside runner-android/, where git
never looks. The gitlink survived with no URL registered against it, so
`git submodule update --init` in a fresh clone silently found nothing to do.
Moved to the root with its path rewritten; verified it registers.

Verified: the collector's suite passes with 552 assertions, the machine agent's
110 tests pass, the Android unit tests and debug build succeed, and the iOS app
builds from a clean xcodegen run off project.yml with zero xcframework
references in the committed project.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@addisdev
addisdev merged commit f2353c5 into develop Sep 4, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant