Scripting & CI
Rauf is built to be supervised by another program — a CI step, a wrapper script, or a
supervising agent. The contract is simple: branch on exit codes without parsing any
output, and when you do need detail, parse the machine surfaces (--json, --ndjson,
events.ndjson). Never scrape the human renderer, and never parse rauf.log — those are
for people, not pipelines.
The diagram above is the whole reason this works: the loop runner only ever appends to a handful of files. The CLI, the web dashboard, and your pipeline are all just readers of those same files. You are a first-class observer — you reconstruct your view the same way every built-in surface does.
Branch on exit codes
rauf status and rauf loop run share one unified exit-code scheme. Branch on $?
without touching stdout — the code alone tells you what happened.
| Exit | Meaning |
|---|---|
0 | Success — clean terminal |
1 | Error — generic failure |
2 | Usage — bad args / precondition (incl. loop-already-running) |
3 | Needs human |
4 | Limit / usage-paused / sleeping |
5 | Blocked — clean terminal with genuinely blocked items |
6 | Running (query-time only — status) |
A supervising script branches like this:
rauf loop run . --ndjsoncase "$?" in 0) echo "done — clean terminal" ;; 1) echo "error — inspect and retry"; exit 1 ;; 2) echo "usage error — bad args or loop already running"; exit 2 ;; 3) echo "needs human — answer the open question" ;; 4) echo "limit / usage-paused / sleeping — resume after reset" ;; 5) echo "blocked — items are genuinely stuck" ;; *) echo "unexpected exit $?"; exit 1 ;;esacMachine surfaces
Two surfaces carry a stable, versioned, additive-only contract. Parse these — never the
human renderer or rauf.log. The versioned definitions live in the
Backlog-Tool Contract (§A.7).
loop run … --ndjson
Adds --ndjson to a run to emit one JSON object per line for every LoopEvent, followed by
a single trailing LoopResult line. The result line has no type field — that absence is
how you distinguish it from events. Passing --ndjson suppresses the human renderer (it implies
--no-color), so the stream is clean for parsing.
Key event types to handle:
item_completeditem_blockedneeds_humanloop_pausedloop_completedloop_errorllm_stuck_warning
status … --json
rauf status --json emits a DerivedStatus snapshot — a point-in-time view reconstructed from
the substrate. It carries:
loopStateiterationcurrentItemlockbacklogSummary { pending, inProgress, blocked, needsHuman?, deferred?, done, total }
One subtlety in backlogSummary: blocked is the TOTAL. needsHuman and deferred are
disjoint subsets of that total — they count items already included in blocked, not items in
addition to it. Treat the three separately and don’t sum them.
events.ndjson
events.ndjson is the persisted per-run event log that every observer reconstructs from. It
carries a seq + schemaVersion envelope on each line. The follow command reads it, the web
dashboard reads it, and you may tail it directly — it’s the canonical record, not a
side-effect.
Two gotchas a supervisor must handle
- A
RAUF_REVIEWsignal surfaces assignal_parsed.signal === "review"— not"done". If you’re matching on the signal, account for"review"as a distinct, valid terminal. - A circuit-breaker halt is a
loop_errorwhoseerrorstring starts withCircuit breaker: …. There is no dedicatedcircuit_breakerevent type — match on theloop_error+ the error prefix.
The human-in-the-loop supervisor pattern
When an item needs a human decision (an API key, a design call, a missing requirement), the
default behavior is to set the item aside and keep going. For a supervised run you usually want
the opposite — halt and ask. That’s what --pause-on-needs-human is for:
rauf loop run . --ndjson --pause-on-needs-human# on a needs_human / loop_paused event (or exit code 3):rauf resume . --answer <id> "<the human's answer>"How it fits together:
--pause-on-needs-humanmakes the runner halt inpaused_human(exit code3) instead of setting the item aside and continuing. Your supervisor sees theneeds_human/loop_pausedevent on the stream (or simply the exit code3) and stops to collect an answer.resume --answer <id> "<text>"re-queues that item with the answer threaded into its next prompt, so the loop picks up exactly where it stalled. The flag is repeatable — answer several open items in oneresume.- The threaded answer auto-clears when the item completes, so a stale answer never leaks into a later iteration.
Headless notes
Almost everything is file-backed and works with no server running — follow, status, and
log all read the substrate directly. Only two operations require the server:
loop run --detached(-d) — runs the loop as a background daemon.--detachedauto-starts the server daemon for you.loop stop— signals that running daemon to stop.
So in CI, a plain rauf loop run . --ndjson needs nothing extra: it runs in the foreground,
streams NDJSON, and returns a unified exit code. Reach for --detached only when you want the run
to outlive the invoking process.
Sources
- Backlog-Tool Contract — the versioned
LoopEvent/LoopResult/DerivedStatusdefinitions, exit codes, and the additive-only compatibility promise (§A.7). - CLI Reference — every flag and the unified exit-code scheme.
- Recovery — what to do when a run stops in
error,blocked, or a limit state. - Monitoring — observing a live run with
follow,status, and the dashboard.