Skip to content

How sluice is built

A small core that knows git, and five seams it reaches everything else through: where a combination is checked out, how its checks run and what holds them, how the branch moves, how agents submit, and where the state lives. Each seam is bound by a profile, so one core works with moon or a plain command, a sandbox or none, local refs or a forge, files on one host or refs on a shared remote.

The design is a statement of invariants in agentplot/blueprints, design/sluice/requirements.md: what is always true of sluice and what it never does, as numbered clauses, written so that any model satisfying them is sluice. This repository cites its clauses by number, keeps the rules a change must respect, and records each decision the statement left open as an ADR. A clause that no longer holds is changed in the statement first, never worked around in code.

The core forms combinations, keeps the chain and how many are tested at once, records results, blames failures and lands, using git alone. It is a step function over the state (ADR 0005): a round reads a branch’s state and the seams’ answers, decides the next actions, performs them, writes the state back, and holds nothing between rounds. Every effect is written to the state before it is performed, so a round that crashes at any point is finished correctly by the next one.

A round takes the branch’s lock, so two rounds never act on one branch at once. Any process may run one: a waiting sluice submit between reads of its own submission’s state (ADR 0011), sluice step for a tool with a loop of its own, or sluice run, a loop around the step for a host. Nothing behaves differently because of which one ran it.

seamwhat the core asks of itprofiles
the checkouta clean checkout of exactly one combination, unique and stable for the life of its run, released when the result is reada detached git worktree (ADR 0013); a container, designed for many hosts
the checkwhich tasks a combination affects; a run of them in a checkout, each task with its key, outcome and whether it was replayed; and the run held to the limits the policy states, by an executor that reports what it enforced (ADR 0012)moon, or one command; executors open, sandbox (Seatbelt on macOS; bubblewrap and Landlock on Linux, planned) and container
the branchits current revision; a merge tree of the branch and a head; a move from an expected revision to a new one, refused if the branch is elsewhere; protection against every other writerlocal refs, with a reference-transaction hook; pushed refs on a forge, with the forge’s protection and sluice’s identity the only bypass
the driversubmit, withdraw, amend, state, and telling the agent the outcomesluice submit from any shell; wt submit through worktrunk; a tool’s own merge stage calling the same
the statea branch’s state as last written, with its version; a write from that version, refused when the version moved (ADR 0008)local files under the git directory; refs on the shared remote, for peers; a server’s files, projected to the remote

The core names no tool: moon, wt, devenv and the rest appear only in profiles, and a profile implements its seam’s interface and nothing of the core.

  • It starts no service and binds no port (ADR 0010). The checks it runs are one process group, ended with their checkout or their time limit. Whatever the tests need is brought up and torn down by the test command, as every CI system leaves it to the job.
  • It runs no check of its own and changes nothing a gate requires. The policy is the repository’s, read from the branch’s current revision, never from a submission.
  • It holds nothing a reader needs in a running process. Every record is readable with no sluice process running, with plain tools, from every host that submits to its branch.
  • It never moves a branch past what passed, and never from a revision other than the one the combination was tested on.
  • It takes no word. Whether work may be submitted at all, approval included, is decided before it reaches sluice.