Saphan StudioDocs
Getting started

The shape of work

Streams, trains, why parallel agents do not collide, claiming, the wave view, and the record.

Streams

The unit of work is a stream: one scoped task, on one branch, in one dedicated git worktree, with its own lifecycle from dispatch to teardown. A stream's state is not a status somebody typed — it is resolved from the record: what was ingested from the agent's channel, what gates a human recorded, and what git itself shows. The fleet view renders one row per stream: role, branch, worktree, the last gate and who decided it, whether an executor process is currently alive, cost so far, and what the stream still owes before it can close.

Why parallel agents do not collide

Collision-freedom is layered, and none of the layers is a convention someone has to remember:

  • One stream, one worktree. Every executor works inside its own master-spawned git worktree. Writing outside it is a rule violation the process treats as an incident, not a cleanup chore — and worktree creation and removal belong to one role only, so worktrees cannot appear or vanish as a side effect of enthusiasm.
  • One run per machine slot, enforced by the database. Two dispatches racing for the same slot cannot both win: mutual exclusion is a database constraint, not a check-then-act that two processes can slip through. This replaced exactly that race, measured live, where two dispatches each read "slot free" and both proceeded.
  • One profile per seat. Two agents on one machine never share sessions, credentials, or history — each seat has its own isolated configuration home, so the classic silent failure of agent tooling (two sessions rejoining into one profile, last login wins) cannot happen.
  • Orders name their collisions. An order lists the streams it could interfere with and settles the ordering before dispatch, so overlap is a written decision rather than a discovery in a merge conflict.

Claiming a stream before work starts

A stream claim is a durable admission record, not a convention in a worktree file. The database accepts exactly one live claim for a stream; two commands racing for the same stream cannot both win because admission is the INSERT itself.

  1. Claim explicitly when you are taking responsibility before dispatch:

    saphan claim <stream> --actor <actor> --workspace /absolute/workspace/path
  2. Read the result before proceeding. Success names the stream and actor. A refusal names the current holder, the claim timestamp and provenance, and lists known fleet streams that have no live claim.

  3. Dispatch normally. saphan run performs the same claim-if-unclaimed admission for its resolved actor. If another actor owns the live claim, run refuses before spawning; if the same actor owns it, the run proceeds to the remaining admission checks.

There is no check-then-claim mode and no compatibility bypass. If a claim command refuses, choose one of the printed free alternatives or have the existing claim released through the stream lifecycle; do not infer availability from a branch or worktree name.

Trains

A train is the unit above a stream: one body of work, cut into wagons, each wagon a stream with its own branch, worktree and gates. An order declares which train it belongs to, so the record can answer "what is this stream part of" without anyone inferring it from a name. A train is how a person plans; a stream is how the product executes.

Waves — a view, not a unit of work

A wave is a way of LOOKING at streams, not a way of organising them: the streams sharing one base, seen together — branch to base, gate decisions, observed merges. saphan wave show is what you read when several streams are in flight and you want to know what is mergeable, what is blocked, and what is waiting on you.

A wave and a train are not the same thing, and neither contains the other. Streams from two different trains can sit in one wave because they share a base; one train's wagons can sit in several waves because they merged at different times. The train answers "what is this work"; the wave answers "what can move right now".

The record

Everything durable lands in one place: gate decisions, run records, costs, refusals, ingested channel artifacts, observed git facts. Every surface — CLI, dashboard, VS Code panel, exported board report, MCP server — is a projection of that record and adds nothing to it. Projections are recomputed from the record on every read; nothing you look at is a cached opinion. The integrity mechanisms behind the record (signing, append-only logs, verification) are documented in what the record proves afterwards.

On this page