Roles and the working rhythm
Master, executor, review, owner - and the channel files that are the wire between them.
Saphan instantiates a deliberate division of labour. The names are conventions, but the boundaries are enforced by the tooling:
| Role | Does | Never does |
|---|---|---|
| Master | Plans, writes the dispatch material, creates worktrees, adjudicates what comes back from gates, keeps the board current | Implements, reviews its own dispatches, merges |
| Executor | Implements the assigned scope inside its master-spawned worktree; commits locally; returns a structured result | Creates or removes worktrees; pushes; merges; writes outside its worktree |
| Review | Independently, adversarially evaluates an executor's return | Writes code; advances the stream itself |
| Owner (human) | Every gate decision, every merge, every push, everything irreversible | — |
The rhythm per stream:
A fast path may skip the plan stage for small work; the pre-merge human gate is never skipped.
The review lane, specifically
Review in Saphan is not a thumbs-up emoji. It is a separate, adversarial role with a narrow, enforced footprint:
- The reviewer reads the executor's return against the order — the scope, the test plan,
the DoD — and produces a recommendation with concrete reasons:
accept·accept-with-conditions·reject. - The reviewer writes exactly one file — its status artifact in the reviewed worktree — and nothing else. A reviewer that starts "fixing things" has left its role.
- Branch diffs are read against the merge-base (three-dot), never two-dot — a moved base otherwise shows phantom reverts, and reviewers have rejected perfectly good work over phantoms.
- Green is not correctness. A test suite that passes proves determinism, not truth — the proof at STOP-2 must show the actual artifact matching the claim (the diff, the golden, the output itself), never infer "fixed" from a green run. Correctness is an independent eye on the artifact.
- The verdict is recorded as judgment, never the decision: the human's gate is a
separate recorded act, and merge refuses unless the recorded review gate says
acceptoraccept-with-conditions. Conditions are not a refusal — they are more work owed, honestly recorded.
The wire between roles: channel files
Master and executor do not talk through chat memory — and there is no lateral channel between agents at all: no master-to-master, no executor-to-executor, nothing off the record (Enterprise §6). They talk through channel files in the stream's worktree — gitignored, never committed, so the code history stays clean while the fleet stays observable; the engine ingests them into the record:
_MASTER_ORDER.md— written by the master, read by the executor: the worktree's current order (absolute worktree path, branch, base, one-line task, where the full order lives) and a mandatory REASON field — an executor receiving a new order mid-stream must be able to tell why (fix after review? scope change? continuation?)._EXEC_STATUS.md— written by the executor, read by the master and ingested by the engine. Updated at every meaningful transition, and load-bearing at both ends of a run:- The started-ack is the executor's first act — before recon, before code. The fleet is never blind between kickoff and the first voluntary update; a stream whose tab never started must be distinguishable from one that is quietly working.
state: blockedis a first-class state — the executor stops to ask, and names the exact blocker or question. Waiting on the owner is honest fleet state, visible infleet show, not dead air.state: stop-2-readyis the close gate. Writing the file and verifying it on disk is a precondition of the executor's close signal; a "done" living only in a chat transcript is an incomplete return, by rule. The master verifies the file exists before accepting any STOP-2 — terminal actions are gated on durable records, not on messages.
_REVIEW_STATUS.md— the review role's one permitted file, in the reviewed stream's worktree: spawned when the review starts, then the verdict and where the full review return lives.
Returns are identified by their identity line — stream + head commit + close line — so a duplicated paste is acknowledged rather than re-gated, and a stale one is flagged rather than executed.