← All docs

Reviewer checks — the gates on a story

satelle runs the agent model (see the satelle-agent-model principle): a story moves through a graph of steps, each run by a defined agent role, and the story's status decides what is valid now. The agent's goal is to drive the story to done; satelle is the gatekeeper of status — a status advances only through a reviewer's accept, and always through it.

  • executor — does the work and mutates the tree.
  • reviewer — is limited to reviewing: an isolated, fresh-context judge that reads the requested transition and returns one JSON verdict {"decision":"accept"|"reject","notes":"…"}. It is read-only and never mutates — a quality-management invariant, enforced by its grant, not by trust.

Each gate is an isolated, fresh-context call: satelle builds the payload (the work item + the requested transition), spawns a fresh agent with the step's skill as its prompt and a read-only grant, and aggregates the one verdict to gate the status. satelle does the context selection; the reviewer reads what it needs through its tools. This applies to stories and tasks alike — gating is by category, kind-agnostic.

The lifecycle is authored — a derived route

The active lifecycle is authored substrate: a repo's own .satelle/workflows, or the derived route the binary ships as the order-zero default. There is one authored form — two files:

  • done.md — the obligations per category, plus park and cancel.
  • step.md — the step catalogue and the always-on gates. Each step names its agent, its skills, and the reviewers gating ENTRY to it.

Order and topology are DERIVED, never authored. satelle help workflow-convert is the key-by-key reference.

A repo's own route outranks the shipped one. A workflows doc that declares no route governs nothing: satelle refuses transitions under it, naming that guide, rather than silently falling back and dropping every gate the repo authored.

The per-noun satelle <noun> validate runs a DETERMINISTIC structure check on every authored doc — frontmatter (OKF type), naming, a usable definition, and for a route half its own grammar plus resolvable executor rubrics. The structure check is code, not an LLM rubric, so it is harness-independent and never flaky. The done gate is not mandated — it is whatever the route declares (the author's choice).

An edge is gated only when the workflow names a reviewer skill and that skill's rubric is installed; a named-but-absent rubric is advisory, so a fresh repo keeps working until the rubrics ship.

The agents layer — how a step runs

What is injected (the skill + context subset) is satelle's; how and where an agent role runs is the agents layer (.satelle/workflows/agents.toml). It binds each agent role to a backend and grant, defaulting to today's behaviour — the executor runs in-loop, the reviewer runs as an isolated agent -p with the read-only Read,Grep,Glob grant. A repo may rebind a backend or grant without touching the workflow; the read-only limit travels with the binding.

Engagement baseline and satelle story diff (scope gates)

On first entry into a performing/engaging state, satelle ledgers an engagement_baseline row (git HEAD + dirty flag). Scope judges enumerate via:

satelle story diff <id>
# or from a functional check (payload on stdin, no argv id):
satelle story diff   # reads story.id from {story, from, to} on stdin

Output is JSON: files (sorted, includes untracked), stat, optional patch. Report only — no pass/fail. The gate skill decides. Missing baseline → clear error (pre-feature stories degrade gracefully).

Two gate kinds: LLM reviewers and functional checks

A gate is either:

  • an LLM reviewer — the skill's markdown body rides as a fresh-context agent's system prompt and the agent returns the verdict (judgment: structure, intent, acceptance); or
  • a functional check — a self-contained ```check script (or a check: in frontmatter). The gate runs it in the repo root; exit 0 accepts, non-zero rejects with the output tail as notes. No LLM — the command is the decision. Like the push gate, a functional check may run real mechanism.

Shared suite evidence — record a run once, cite it from siblings

When a repo's verification suite is expensive (long integration runs, image builds), several small stories delivered at the same commit should not each re-run it. Record the run once as SHA-keyed evidence and let the siblings cite it; the gate then checks the citation instead of the clock.

Record (the story that actually ran the suite):

satelle ledger record-run --story <sty_id> --command 'make integration' --outcome green \
  [--sha <commit>] [--started-at <RFC3339>] [--finished-at <RFC3339>]

--sha defaults to the current HEAD. --outcome is green or red. The command prints the created suite_run entry — keep its id.

Cite (every sibling riding that run):

satelle ledger cite-run --story <sty_id> --run <evt_id>

A citation is a suite_citation ledger row whose refs names the run. Citing does not validate the target — a dangling citation is a fact the gate must be able to see. Newest citation wins if a story cites more than once.

Enumerate (what a gate reads):

satelle ledger citation <sty_id>
# or from a functional check (payload on stdin, no argv id):
satelle ledger citation   # reads story.id from {story, from, to} on stdin

Output is JSON — report only, no pass/fail. Every enumerable state exits 0:

| Field | Meaning | | --- | --- | | cited / citations | a citation exists on this story / how many | | run_found / dangling | the cited id resolves to a suite_run / it does not | | run.sha, run.command, run.outcome | what ran, where, and how it ended | | run.started_at, run.finished_at, run.recorded_at | when | | head_sha, dirty | the worktree now | | sha_matches_head | the cited run covers this exact commit |

Non-zero exit is reserved for genuine errors (unknown story, git unavailable).

Sample gate check block

The gate owns the rule and the refusal names — the binary only reports. Drop this in a reviewer skill's ```check block and set EXPECTED to the suite that gate requires. Stories carry no delivery SHA, so "this story's delivery" is defined as clean HEAD at check time; relax or tighten the rule by editing this script, not the binary.

#!/usr/bin/env bash
# Suite-citation gate: accept when a green run of EXPECTED covers this commit.
set -uo pipefail
EXPECTED="${EXPECTED:-make integration}"   # the suite command this gate requires

f=$(satelle ledger citation) || { echo "cannot enumerate the suite citation"; exit 1; }
field() {
  printf '%s' "$f" |
    grep -oE "\"$1\"[[:space:]]*:[[:space:]]*(\"[^\"]*\"|true|false|[0-9]+)" |
    head -1 | sed -E "s/^\"$1\"[[:space:]]*:[[:space:]]*//; s/^\"//; s/\"$//"
}

[ "$(field cited)" = true ] || {
  echo "missing_citation: no suite run cited — record one and cite it:"
  echo "  satelle ledger record-run --story <id> --command '$EXPECTED' --outcome green"
  echo "  satelle ledger cite-run --story <id> --run <evt_id>"
  exit 1; }
[ "$(field run_found)" = true ] || {
  echo "dangling_citation: cited run $(field run_id) is not a recorded suite_run"; exit 1; }
[ "$(field outcome)" = green ] || {
  echo "red_run: the cited suite run finished $(field outcome)"; exit 1; }
[ "$(field command)" = "$EXPECTED" ] || {
  echo "command_mismatch: cited run ran '$(field command)', this gate requires '$EXPECTED'"; exit 1; }
[ "$(field dirty)" = false ] || {
  echo "dirty_worktree: uncommitted changes are not covered by the cited run"; exit 1; }
[ "$(field sha_matches_head)" = true ] || {
  echo "stale_sha: cited run is at $(field sha), HEAD is $(field head_sha) — re-run the suite and cite the new run"; exit 1; }

echo "suite citation accepted: $(field run_id) green at $(field head_sha)"
exit 0

Whether the cited command is the right suite for this story stays reviewer judgment — the check only proves the named suite was green at this commit. Note the extractor compares the JSON-encoded command, so a suite command containing quotes or backslashes needs a real JSON parser instead.

Create gate — deterministic story structure (code)

When a draft is created (opt-in per repo via [review] gate_create), satelle checks required structure deterministically in code (no LLM): a specific title, a clear goal in the body, and at least one numbered, testable acceptance criterion. The structure reviewers for skills/workflows/principles are likewise deterministic code (internal/structure), not LLM rubrics — conformance is mechanical, so a swapped harness can never change what "valid" means.

Begin-work gate — satelle-story-intent-review (→ in_progress)

Judges readiness of intent before work starts — concrete title, clear goal, testable criteria. Unclear intent is rejected; the story stays in backlog.

Release step — release (in-loop executor)

One in-loop executor step (the driving session, not a dispatched sub-process). It formats and stages the slice, bumps satelle.version (patch) and stamps satelle.build in .version — mandatory, because .version is the single source the release tag and build identity derive from — makes a conventional commit ending in the story id (no AI attribution), and pushes to main (trunk-based release). Pushing triggers the GitHub Actions test run and, on its success, the version-gated release run that publishes v<version>. Rather than block watching both runs, the step refreshes the local service during the CI window and then records the test + release run URLs, their conclusions, and the published tag as a PR-style summary with the story — an attachment via satelle story attach … --file (stored on the home-keyed runtime plane, readable via satelle story docs <id>). The satelle-story-release-review gate is the authority on "CI is green": it judges that recorded evidence and rejects a failing, absent, or unconcluded run.

Close gate — satelle-story-done-review (→ done)

An isolated, read-only reviewer that reads the repository to verify each numbered acceptance criterion against concrete evidence. Unmet criteria are rejected with specifics. done is always terminal (see satelle-done-is-last). The close gate is declared by the workflow, not mandated by the binary — a workflow may name it, name another, or drop it: if the user breaks their own process, so be it. The reviewer's grant is read-only (Read,Grep,Glob); it reads the substrate it reasons about as markdown under .satelle/ (no shell, no CLI).

Declared scoped gates — estimate/actual + integration check

Always-on gates are declared in the route, not injected by a skill tag — the route is the sole gating authority (no hidden reviewer:always layer). A ## gate <skill> section in step.md carries an on: list of steps and runs on the transitions into them, after that step's own reviewers:. satelle-estimate-actual-review (on: in_progress, done) requires a recorded plan estimate entering in_progress and the recorded actual entering done (satelle story estimate / satelle story actual); satelle-integration-check (on: commit) runs make integration before a commit. A step may also name several reviewers directly (reviewers: a, b). satelle-story-cancel-review records why an item is abandoned.

Step summary — satelle-step-summary (transparent, opt-in)

Not a gate. The step summary is declared by the route, not a hidden always-on behaviour: a route opts in with a ## gate satelle-step-summary section in step.md, optionally mandatory: true. Where declared, after each transition this read-only observer records a 1–3 sentence step_summary ledger row; a mandatory summary failure is surfaced on the ledger rather than swallowed. A route without the gate records no summaries.

Where the rubrics live

The summariser is an embedded canonical default (internal/config/substrate/ skills) and is materialised into .satelle/skills by satelle init. The deterministic structure checks (skills/workflows/principles/story drafts) are code (internal/structure), not rubrics. A repo MAY override a materialised skill — or add its own gates (this repo's push reviewer) — under .satelle/skills/. The binary runs the gates; the substrate declares them.

See also: satelle help create-story.


Mirrored from satelle’s built-in help. Read it in the binary with satelle help reviewer-checks, or see the canonical source in the satelle repo.