Skip to content

Wave 3 (partial): pipelines, a live screen, and the shell trust boundary - #4

Merged
addisdev merged 1 commit into
developfrom
feat/wave-3-pipelines-and-hardware
Sep 5, 2026
Merged

addisdev merged 1 commit into
developfrom
feat/wave-3-pipelines-and-hardware

Conversation

@addisdev

@addisdev addisdev commented Sep 5, 2026

Copy link
Copy Markdown
Owner

A session limit killed three agents mid-flight. What is here is what survived being finished and verified by hand. The gaps are listed at the bottom rather than implied.

Four workloads on the machine agent

model-convert turns a checkpoint into GGUF, Core ML or TFLite on the machine with the memory for it, uploading each output so a dependent job can name it by ${jobs.<id>.artifact}. dataset-prep builds an eval set and carries its licence and source on the row, because a prepared set that loses its licence is one nobody can publish results from. serve hosts a model for the length of a lease and announces its endpoint, bound to loopback or the tailnet and never 0.0.0.0.

shell, and why it is gated the way it is

POST /jobs is unauthenticated by design, so "run this script" is the one workload that cannot take the collector's word for it.

  • The owner pins a sha256 by hand in a local file.
  • The capability is declared only when that file exists and is non-empty. A machine that has pinned nothing cannot be offered the job at all, rather than claiming it and refusing afterwards — refusing after the claim would still take the work off the queue.
  • The sha is checked before the artifact is fetched, never after.
  • A value that is not a well-formed sha256 is never allowed, which matters because that value arrives from an attacker-controllable job spec.

Verified end to end: with an empty allowlist the capability is absent; with the sha pinned it appears.

The executor's handlers start becoming directories

install moved first — smallest first — behind a loader that falls back to the existing chain for everything not yet moved, so the file that runs the real fleet is never half broken.

Verified by driving the actual executor against a throwaway collector with seven host workloads queued: install ran from its new directory and not one job reported "not supported". That check mattered, because a missing dispatch branch still typechecks — it just makes a workload silently unsupported.

Live mirror

The artifact viewer answers "what did it look like when it failed". This answers "what is it doing now", which is the question you actually have while a flow is running.

Frames live in a bounded in-memory ring and are dropped when the job ends. The collector already stores every measurement the fleet has ever taken; a video stream would trade a permanent cost for a transient benefit. MJPEG because an <img> renders it with no client code, and it degrades to a still frame rather than an error when the producer stalls. A closed browser tab cannot make a device test fail.

Real iPhones can finally be captured

docs/operations.md recorded the gap plainly: the visual matrix advertises real phone screens while half the real phones have no capture path. A WKWebView inside the runner app closes it.

The profile is named webkit, not safari, with a comment saying why — a WKWebView has no reader mode and no content blockers, and someone reading "safari" in a baseline matrix would believe a stronger claim than the capture supports.

Three fixes to what the dying agents left behind

  1. The machine's four workloads referenced metric names that exist in the collector's schema but had never been mirrored into the agent's own protocol type, so none of them compiled.
  2. Those same four files were never dispatched or capability-gated — they were dead code.
  3. One Swift expression mixed literals and Doubles densely enough that the type checker gave up: the compiler is unable to type-check this expression in reasonable time. Split into named steps.

Verification

Result
collector full suite green, 29 workloads / 29 icons
runner-machine 122 tests pass, including 12 new ones on the allowlist
runner-ios BUILD SUCCEEDED from a clean xcodegen off project.yml, 0 xcframework references
executor dispatch driven for real; zero workloads unsupported

Not done, and not pretended otherwise

  • push-latency needs FCM and APNs credentials.
  • camera-eval needs a physical rig pointing a phone at a display.
  • desktop-ui-test has no desktop app to test against.
  • Only install has moved into the plugin layout; the other handlers still live in executor.ts.
  • model-convert, dataset-prep and serve have never run against a real toolchain. None of the three converters resolves on this machine and the probes correctly decline to declare them, so the command lines are unit-tested and unproven.
  • The llama.cpp paths from Wave 2 remain uncompiled here; the xcframework and the Android submodule are both absent from the mono repo by design.

🤖 Generated with Claude Code

Wave 3, and a partial one. A session limit killed three agents mid-flight; what
is here is what survived being finished and verified by hand, and the gaps are
named at the bottom rather than implied.

The machine agent gains four workloads. model-convert turns a checkpoint into
GGUF, Core ML or TFLite on the machine with the memory for it, uploading each
output so a dependent job can name it. dataset-prep builds an eval set and
carries its licence and source on the row, because a prepared set that loses
its licence is one nobody can publish results from. serve hosts a model for the
length of a lease and announces its endpoint, bound to loopback or the tailnet
and never 0.0.0.0.

shell is the one with a boundary. POST /jobs is unauthenticated by design, so
"run this script" cannot take the collector's word for it. The machine's owner
pins a sha by hand; the capability is declared only when that file exists and
is non-empty, so a machine that has pinned nothing cannot be offered the job at
all rather than claiming it and refusing afterwards. Refusing after the claim
would still take the work off the queue. The sha is checked before the artifact
is fetched, never after.

The executor's handlers start becoming directories. install moved first,
smallest first, with a loader that falls back to the existing chain for
everything not yet moved — so the file that runs the real fleet is never half
broken. Verified by driving the actual executor against a throwaway collector
with seven host workloads queued: install ran from its new directory and not
one job reported "not supported".

The live mirror answers a different question from the artifact viewer. That one
says what it looked like when it failed; this says what it is doing now, which
is the question you have while a flow is running. Frames live in a bounded
in-memory ring and are dropped when the job ends: the collector already stores
every measurement the fleet has taken, and a video stream would trade a
permanent cost for a transient benefit. MJPEG because an img tag renders it
with no client code, and it degrades to a still frame rather than an error when
the producer stalls.

Real iPhones can finally be captured. The operations doc recorded the gap: the
visual matrix advertises real phone screens while half the real phones have no
capture path. A WKWebView inside the runner app closes it. The profile is named
webkit and not safari, in a comment that says why, because a WKWebView has no
reader mode and no content blockers and someone reading "safari" in a baseline
matrix would believe a stronger claim than the capture supports.

Three fixes to work the dying agents left behind. The machine's workloads
referenced metric names that exist in the collector's schema but had never been
mirrored into the agent's own protocol type, so none of them compiled; the four
files were also never dispatched or capability-gated, which made them dead
code. And one Swift expression mixed literals and Doubles densely enough that
the type checker gave up and failed the build, so it is split into named steps.

Verified: the collector's suite passes, the machine agent's 122 tests pass, and
the iOS app builds from a clean xcodegen run off project.yml with zero
xcframework references.

Not done, and not pretended otherwise. push-latency needs FCM and APNs
credentials. camera-eval needs a physical rig pointing a phone at a display.
desktop-ui-test has no desktop app to test against. Only install has moved into
the plugin layout. model-convert, dataset-prep and serve have never run against
a real toolchain, because none of the three converters resolves on this machine
and the probes correctly decline to declare them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@addisdev
addisdev merged commit e7bd873 into develop Sep 5, 2026
5 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