Mac · trust root

The second machine that keeps nothing.

Your Mac drives a spare machine as real compute. Every key stays on the Mac. The node runs the work, hands back the result, and is torn down with nothing durable left on it.

Rundocs/TEST_PLAN.md §3.103, §3.100
Collection and retrieval proved against a real Docker 28.5.1 container — the workload writes, Errand copies it back, bytes compared. Not covered: nothing has crossed a network socket; the node and daemon talk in-process in these runs.

Controller holds the key. Node holds nothing.

Controller Mac holds the key
sealed · signed · scoped the key does not cross · the data is opaque
Node Mac 0 bytes durable container · isolated from host OS
idle

The controller can be a Mac, iPad or iPhone; the authority is always the Mac daemon, so a phone driving a Windows node is still the Mac deciding and signing. The work runs inside a container, walled off from the node's own operating system — and the data it works on is sealed before it leaves your Mac, so the node stores blobs it cannot read. Amber is work in flight — never a key.

RunVolume data is opaque to the node. Per-block XChaCha20-Poly1305 with a length-prefixed binding, an opaque index, and a freshness counter that refuses a rolled-back volume by name. The daemon holds the key; the node sees blobs.

RunThe container is the boundary. A Linux or Windows container node is the default precisely because it is isolated and disposable.

Stated, not impliedA job on your own Mac is NOT a container. One kernel, one machine, yours. It runs as a sandbox-exec-confined process under a per-job root, with the home folder, the keychain and the network denied by profile — and the root and the profile are deleted when it ends.

Honest limitThe workload itself — its image and its argv — is visible to the node, because the node has to run it. What is sealed is the data, and what never travels at all is a credential.
RunThe phone reaches the daemon. A paired device signs its own envelopes, is admitted as device:<fingerprint>, and sees this Mac's jobs — proved by a live interop suite asserting on the id of a job the CLI created.

Not coveredThe carriage is a Unix socket, not a tailnet. A simulator shares the host's filesystem and a physical iPhone does not, so the relay transport ships refusing by name. No phone has driven anything over a network.

Not coveredMac↔Mac needs a second real Mac and is lab-gated. Linux is a real kernel under emulation on Apple silicon — real syscalls and real uid-0, but no claim about x86-64. Windows preflight ran on real Windows 11.

01 — the shape of it

Two machines. One of them is trusted.

Most distributed compute makes both ends equal and then spends its life defending that decision. Errand does not. The Mac decides, holds the keys, and signs. The node executes and forgets. A credential that reaches a machine is compromised even if it is deleted afterwards — so one never leaves.

Runcrates/anvil-core/src/secrets.rs
A job declaring a credential-shaped variable name is refused before egress — before the queue branch, before submission. Classified by name, never by inspecting a value. There is deliberately no bypass flag.

CeilingSubscription CLI routes authenticate against this Mac's logged-in sessions. There is no credential to carry even in principle, so those cannot be offloaded at all — whatever Errand does.

01

Declared

You name the work. The Mac parses it at the boundary — never casts, never assumes.

parsed

02

Sealed

A capability is minted for this job, this caller, this moment. Scoped, signed, expiring.

signed

03

Crossed

Work goes over. Secrets do not. What the node receives cannot be turned back into a key.

no key

04

Emptied

The result comes home. The working root, the profile and the container go with it.

torn down

02 — the surface

It lives in the menu bar, not a browser tab.

A native macOS app, a Rust systems core, and thin helpers on the node. No web console, no control plane you have to trust, no raw engine socket anywhere. The daemon on your Mac is the only thing that decides anything.

RunThree engines, and which one wins is a decision
Docker → podman → Apple container, in that order, pinned by a test. An engine named explicitly that does not answer is refused naming what did, never silently downgraded — a node told to use podman that quietly used Docker resolves images from the wrong store.

Not coveredVerified on one arm64 Mac. podman is 6.0.2 on a running machine; a stopped machine is a loud NOT COVERED, never a pass.
Errand — Jobs
sdxl-batch-04
node · lithe-badger · 5090 · 22 GB VRAM
running04:12
cargo-test-workspace
node · lithe-badger · 16 vCPU
doneexit 0
whisper-transcode
node · lithe-badger · stopped by you
interruptedoutcome unknown
index-rebuild
host · this Mac · sandboxed
heldneeds approval

“Interrupted · outcome unknown” is a real state, not a rounding of “failed”. A job that ended where the exit status could not be read is never reported as success — an absent code is never zero.

RunObserved::Exited { code: Option<i32> }
Optional at every hop and in both languages, so “ended, and Errand could not read its status” stays distinguishable from success end to end. A defect that recorded an unknown outcome as exit 0 was found and fixed by this rule.

And once by the inverse: create-before-ack recovery matched exited containers by a deterministic label, so a workload that never started was reported Done · exit 0 · Completed. Found by an end-to-end test, not by reading.

03 — the interesting part

The refusals are the product.

Anything can say yes. What a system is worth is what it says no to, and whether it tells you why. Every refusal here states a cause and a next step — never a code, never a shrug.

secret_required

This job declares AWS_SECRET_ACCESS_KEY. Nothing was sent and no key was burned.

Run it on this Mac, or use a route that authenticates without a credential to carry.

macrun_agent_withheld

An AI agent asked to start work natively on your Mac. Every other thing an agent can start lands in a container on another machine.

Start it yourself, or send it to a node.

no_macos_node

This work needs Apple frameworks and there is no Mac available to place it on. It was not quietly put on Linux.

Pair a Mac, or drop the requires = "macos" declaration.

caller_ceiling_reached

This caller has spent its daily allowance. Nothing was done.

Wait for the window, or raise the ceiling for this caller.

Some of them are about you, and some are about us. A credential the node never sees is the easy case. The harder one is that GPU sharing is a performance mode, not an isolation boundary — one VRAM pool, one fault domain — so the planner refuses to co-schedule anything marked as needing isolation, and says so every time.

UnverifiedNo GPU has ever been measured by this code.
The reading path is written to be suspicious — it samples twice, requires the driver's stamp to have advanced, anchors card identity on first sight and never re-anchors, and re-probes MPS after starting because an acknowledgement is not evidence. A reading it cannot confirm renders as “GPU status unknown”, which is deliberately not the same sentence as “no GPU here”.

The catalogue of ways a GPU can lie is a hypothesis awaiting real hardware.
$ errand run --gpu sdxl:latest -- python batch.py
placing · requires = gpu, isolation = shared
node lithe-badger · docker 28.5.1 · capability minted
started · job_7f2a91c4
$ errand push ./weights.tar --as ckpt --class weights
1.42 GB in · 318 MB on wire · 4.5×
staged · owner cli:local
$ errand run --env AWS_SECRET_ACCESS_KEY=… alpine:3
refused · secret_required
Nothing was sent and no idempotency key was burned.
Run it on this Mac, or use a key-free route.
$

04 — the pipe

Compressed on the way, verified on arrival.

Framing, threaded compression by payload class, and a negotiated dictionary. A segment that expands past its declared length is refused as a bomb at a single ceiling — two ceilings is how one of them rots.

MeasuredParallel decode: 6.93–7.40×
2308–2460 MB/s serial → 17053–17155 MB/s across 8 workers. Mac16,5, 16 cores, arm64, load average 5.92. Peak memory bounded at 64 MiB by wave width — the only sender-controlled term saturates, so 128 segments cost the same peak as 16.

True ofThis Mac, arm64, only. Never measured on x86-64. An emulated Linux on Apple silicon supports no claim about x86-64 codegen or timing, so it was not run and not claimed.

05 — with conditions attached

Numbers, and what they are true of.

A figure without its conditions is not a measurement. Each of these carries the machine, the load and the boundary it was taken at.

0
Rust tests
Unit, socket, restart, and real-container end-to-end. Counted separately from Swift and never summed.
0
Swift tests
85 suites, no Xcode. Green here says nothing about what the app does on screen.
0
Dedup headroom
What content-defined chunking would add, net of the dictionary, on real repository history. Not built.
0
Secrets on a node
Refused before egress, by name, with no bypass flag. The one number with no conditions.

06 — permanent furniture

What isn’t proven, kept where you can see it.

This does not belong in a footnote. A gap you can read is worth more than a claim you can’t check, and the list moves as the work does.

Not coveredThe GPU. No card has been measured. Every GPU path is written to refuse rather than guess, and the failure catalogue is a hypothesis.
Not coveredAnything on a second machine over a network. Pairing, discovery and the transport ladder are invisible from a single-Mac gate.
Not coveredThe terminal on screen. It has been driven against a real container from the client — nobody has yet clicked the button and typed into the window.
LimitNative runs on the host Mac are not a container. One kernel, one machine, yours. The home folder, the keychain and the network are denied by profile; the cap on simultaneous native jobs is a refusal, not a queue.
LimitThe daemon does not start itself. No launchd job yet. The transport is a Unix socket, not XPC, so any process running as you can act as the local client.
LimitThe signing key is a 0600 file, not a Secure-Enclave key. Documented at its call site rather than implied away.
Not builtNothing here is downloadable. There is no installer, no signed build, no account. This page describes work in progress and says so.