Worked examples
Real commands, real defaults, and the engine's own refusal classes quoted verbatim.
Real commands, real defaults, and the engine's own refusal classes quoted verbatim. Nothing below is illustrative pseudocode. Where a refusal is shown, that is the string the engine emits.
Founding the trust root
One time, on an air-gapped machine, with a witness present.
The command opens with a confirmation, and nothing key-shaped is created before you answer:
Declining is a clean exit: ceremony root: not confirmed — nothing was created.
On confirmation the engine mints the Ed25519 root, prints the key path and its pin (the public half you will distribute to verifiers), then prints the numbered word list — also saved to a file until the wipe. It is explicit about what it will not do:
That sentence is deliberate. No key material is ever passed to an external process, not even a QR encoder.
Write the words on paper. Then the witness reads them back and the engine compares position by position. Only on an exact match are the key, its public sibling and the word list erased from disk.
Two flags exist and neither weakens the ceremony:
| Flag | Effect |
|---|---|
--yes | Accepts the air-gap/witness banner non-interactively. Never skips proof-of-capture. |
--readback-file <path> | Takes the read-back from a file instead of the terminal — for a witness on a separate machine. Pointing it at the ceremony's own word list is refused: ceremony-readback-self-referential. |
Re-running the ceremony on a host that already has a root refuses with
ceremony-root-exists. There is no force flag. Re-minting would silently invalidate every
prior signature.
Then mint the intermediate CA, which does the day-to-day issuing:
P-256 keypair plus a self-signed CA certificate, 365 days by default (--days), written to
~/.saphan/trust/intermediate.key and intermediate.crt. Overwriting an existing intermediate
requires --force.
Paths worth knowing — these are your backup and protection targets:
| Path | Contents |
|---|---|
~/.saphan/trust/root.key | Root private key — absent after a successful ceremony, by design |
~/.saphan/trust/root.key.pub | The root pin. Distribute this to anyone who needs to verify. |
~/.saphan/trust/intermediate.key / .crt | Intermediate CA — HSM or enclave in production |
~/.saphan/machine/device.key | That machine's own device key. Never leaves the machine. |
Admitting a remote machine
Two steps, on two different hosts, because the device private key never moves.
Step one, on the machine being admitted:
Mints a P-256 device key at ~/.saphan/machine/device.key and builds a certificate signing
request whose Common Name carries the machine id. For a later renewal, --reuse-key builds a
fresh CSR from the existing key and the key file is never touched.
Copy m2.csr to the control plane. That is all that travels.
Step two, on the control plane:
What happens, in order: the CSR's self-signature is checked; a short-lived certificate is
issued through the intermediate; the certificate subject is compared with --machine; the SSH
identity is probed live; and a root-signed machine row is appended to the identity log and
projected into the machine registry.
Refusals you will meet, with the engine's own wording:
| Situation | Refusal |
|---|---|
| Certificate subject is not the machine id | machine-cert-subject-mismatch — "cert subject does not match --machine", with the remediation "→ admit with the credential issued for this exact machine id" |
| No credential given | "exactly one of --csr, --cert, or --mint-device-key is required" |
| Required identity fields missing | "--machine, --host-ref, and --admitted-by are all required" |
| SSH transport with no host | machine-ssh-host-required |
Two defaults an administrator should know. --ssh-key autodetects ~/.ssh/id_ed25519 then
~/.ssh/id_rsa; --ssh-port is 22. And there is one deliberate escape hatch:
--force-unverified writes the row even when the SSH identity fails its live probe — and
annotates the row as unverified, so the record carries the fact rather than hiding it.
Confirm the result:
which prints each admitted machine with its transport, slot count, seated identities, admitting actor and admission timestamp. Before any machine exists it says so plainly rather than printing an empty table: "machine registry — <path> (absent; no machines admitted yet)".
Admitting the control plane's own machine
The two-step dance is unnecessary when the machine is the one you are typing on:
This mints the device key, builds the CSR, issues the certificate and admits, in one command.
It is local-only and refuses --transport ssh; it refuses to combine with --csr or
--cert; and if a device key already exists at the target path it refuses
machine-device-key-exists with the remediation printed verbatim (naming --transport local
explicitly, since this step falls under the same --transport guard the two-step dance above
does, now that no transport is guessed by default):
Seating identities — several accounts on one machine
This is the soft chroot from section 7, in practice. Two executor identities on one machine, two slots, two profile homes:
Each seat gets its own profile home on the far host, defaulting to $HOME/.claude-<identity>
— or $HOME/.codex-<identity> when --backend codex. Override with --config-dir. Now
authenticate each seat once, inside its own profile home, and the two accounts coexist:
separate credentials, separate sessions, separate history, running concurrently.
Each slot carries one concurrent run unless you say otherwise. To let a seat run several sessions at once — the usual reason being a subscription that permits it — set its capacity explicitly, per slot:
Slot 1 is unaffected: capacity belongs to the slot, so two accounts on one machine can carry different numbers. A dispatch onto a slot already at capacity is refused, never queued, and the refusal names both numbers.
The slot index must fall within the machine's admitted slot count. And when a run is later cast
onto a seat, its --backend must match the seat's, or the cast refuses
seat-backend-mismatch — because exporting a profile home under a variable the other tool
never reads would land the session back in the machine's shared directory, silently.
The seat's --role binds the same way. It defaults to executor and takes one of
master | executor | review | design | scribe | advisor. That default matters more than a
default usually does: executor is UNRESERVED — nobody deliberately chose it, and every seat
that carries it (8 of 8, as of this writing) takes ANY declared role, including none. Any
OTHER value RESERVES the seat: someone deliberately signed it for one role, and a run cast onto
it is placed only if it declares that exact role. Two things follow, and both are deliberate:
- A run that declares no
--roleat all is placed onto an UNRESERVED (executor) seat. An absent role is an honest unknown there, not a mismatch — the first dispatch into a new stream carries none, so refusing it would leave no legal way to open one. - A run that declares no
--roleat all is REFUSED at placement (R25) onto a RESERVED seat, exactly as a run declaring a mismatched role is. Dropping--roledoes not unbind a run from a seat someone reserved — there is no flagless way onto it. - A seat with no registered role takes no role-declaring run. An unknown is never an
all-clear; re-seat the identity with an explicit
--role(below) to make it dispatchable again. - A run that declares no
--roleat all is placed when no seat resolves for it, or when the seat that does resolve carries no registered role (owner ruling, 2026-08-10). Slot topology — no such seat, or two identities colliding at one slot — is R8c's question, not R25's, and a role-less dispatch puts no role question for R25 to answer in the first place.
Every R25 refusal is recorded, exit 3 — never placed silently, and never quietly downgraded to the seat's own role. Re-seating is one remediation, and it is an owner act — the refusal prints this line filled in with the run's own values:
The other way out is to route the run to a seat already registered for the role it declares —
or, for a run declaring none, to an UNRESERVED (executor) seat; saphan machine list prints
each seat's role beside its slot. Declaring a --role to get onto a reserved seat also ARMS
R8d, the (stream, role) dispatch lock (R8d is a no-op only while no role is declared) — that
remedy is for a stream carrying no live run of that role, not a blanket fix.
Proving a capability before work is routed there
A manifest claim is not evidence. Turn it into evidence:
The flag help states the model better than a paragraph would: --declared-path is "the
manifest's declared absolute path for this capability's binary — a claim, never evidence; the
probe proves or refutes it", and --declared-version is "carried as provenance, never
compared for ranking". --probed-by defaults to the control plane and is documented as "the
actor that FIRED this probe (never the subject machine)" — declaring and proving are two
different parties, and the record keeps them apart.
The probe runs one row of a closed catalogue and never installs. Until it passes, that capability stays at declared and the machine will not receive the corresponding work.
The container probe uses the same ceremony, but proves an outcome for a pinned image digest:
On success the command reports machine probe: m2/docker-oci -> pass. The signed record then
contains a probe-confirmed docker-oci capability, a separate image-capability row keyed by the
exact digest the runtime says it started, and the probe-observed machine facts runtime,
vm_cpus and vm_memory_bytes. Only that successful probe writes the image-specific row and
machine facts. Its lease is evaluated when a cast reads it, so expiry makes the exact-digest row
ineligible without storing a separate "currently stale" decision.
A container cast with no fresh row for its exact digest refuses as RS4A-NO-CAPABLE; the refusal
prints a complete saphan machine probe remediation populated with the actual machine, runtime,
work-home and workspace. Follow the refusal rather than composing a replacement command from
memory. To find the arc's silent-failure condition — a container run that happened without the
required measurement at its own start time — run:
The check prints checks container-contradictions: ok only when it finds no contradiction. A
later probe cannot make an earlier unmeasured run valid.
Delegating signing authority, then signing and verifying
Grant a delegate a time-boxed window:
The grant is root-signed into the registry's delegations section, and the signature covers every field including the window and the streams — so it cannot be widened afterwards. Note that both bounds are required: there is no open-ended grant.
The delegate signs a document's exact bytes, producing a detached sidecar — and, in the same act, registers those bytes so they have an address (§13.10, which is where the preconditions and the refusals live):
Anyone can verify against the chain:
An input containing @ is a registered order ref. A full 64-character digest is also accepted
when paired with --ref. A path remains accepted; its class is inferred from whether a .sig
sidecar is present. --stream is what the delegation's scope is matched against.
For an order, PASS is one indivisible predicate: the signature chain is sound, a registration
exists, the registered commit still yields the recorded digest, and the projected registration
status is active. A valid signature without a registration refuses as order-unregistered;
a registration without a sidecar refuses on the existing instr: order has no sidecar signature error. Verification prints only authenticated fields.
For standing law, ratify a set of documents into a root-signed manifest, then verify a single document against it:
Health-checking the trust material
Checks that the root pin is present and loadable, that key custody guards are clean (owner-only permissions, no symlink substitution, correct length), and that the instruction-enforcement setting holds a valid value. Run it on any machine that signs or verifies.
Naming a seat's billing class
This is the economic guard from section 7, in practice. Seat an identity onto a subscription account:
And a second identity, on a metered, pay-per-token account:
And a third, on a model endpoint the owner runs directly, with no vendor account to meter:
--billing-class accepts exactly subscription, metered, or local-energy; anything else
refuses named, nothing written:
Omitting the flag entirely is legal — the seat is written with no billing class at all, and
every read of it, from machine list to fleet show, projects unknown. This is the correct
state for "I haven't decided yet"; it is deliberately not the same as subscription, and
nothing in this system treats it that way. Changing a seat's billing class later is the same
act again — a fresh machine seat for that identity, naming the new class — appended to the
signed log like any other seating.
Measuring a remote machine's confinement capability
Before a machine can take engine-confined work over SSH, its confinement capability must be measured — never declared:
The probe dials the machine over its own recorded transport, measures the kernel's Landlock
level live, and writes the result onto the machine's signed registry row. There is no flag
that accepts a hand-typed level and no override. saphan machine list then prints the
measured level beside the machine — or that the machine is incapable or unprobed. A remote
confined dispatch reads that row as a gate: unprobed, incapable, or below the floor all
refuse by name, with the probe command as the remediation.
Registering an order
Signing proves who wrote these bytes. Registration answers the other question — is this the current order, and which exact bytes were they? — by recording an immutable row: the document's digest, the commit that carries it, who signed it, and until when it stands. What you get back is a ref: a short, hand-copyable address you can paste instead of the document's body.
saphan order sign performs both halves. There is no separate register verb and no flag that
turns registration off, because a signed document nobody can address is the state this
mechanism exists to remove.
1. Commit the document first. Registration records a commit, so the bytes must already be
in the repository's history. (The .sig sidecar need not be committed.)
2. Sign and register in one act.
The verb prints two lines: the sidecar it wrote, and the ref it registered, e.g.
ref master-order@1f0e3dad9990 (commit 9eff90a80000 · status active). Keep the ref —
that string is the address.
3. Name the registration yourself, or bound its life. --slug replaces the default name
(the document's basename stem); --expires takes an RFC3339 instant after which the
registration reads expired with no further act by anyone.
4. Re-running is safe. Re-signing the same bytes finds the existing registration and
writes nothing — the verb says already registered and prints the same ref. Re-signing
changed bytes is a new digest and therefore a new registration; the old one is untouched
and stays resolvable, which is what makes a ref an address rather than a pointer at whatever
the file says today.
5. Verify the registered predicate. Prefer the ref the signing act printed:
Use --ref for a bare full digest. You may also pass the document path, but an order path does
not weaken the check: it must still resolve to an active registration whose committed bytes
match. show and verify deliberately emit the same order-unregistered class for a missing
registration, so automation does not have to translate between two names for one condition.
What you may be told, and what to do about it
| the verb says | what happened | your next act |
|---|---|---|
order-uncommitted | The signature was written, but no landed commit carries these exact bytes. | Commit the document (step 1) and re-run. The sidecar stays on disk — it is correct, and re-running simply rewrites it. |
owner-key-absent | The registration row is signed by the workspace key, and this session has none. | Run from the workspace that owns the record, or mint this workspace's key with saphan actor init (§ First mint, below). saphan actor list shows the key but never mints it — it is a read. Nothing was written. |
no-workspace | Nothing resolved a workspace to record into. | Re-run with --workspace <path>. |
A registration's currency is one of exactly four words — active, superseded,
revoked, expired — and it is computed from an append-only log of acts, never stored as a
mutable field. Nothing overwrites a registration; withdrawal is a new act, which is why the
record can still answer what did this order say when that run was dispatched?