Releases: hamish-mackie/sloop
Release list
0.4.0-rc.1 - 2026-07-25
Release Notes
Added
result_check: { panel: {...} }puts 2 to 5 independent reviewers on a stage
and derives its verdict from a quorum of their reports:Passiff at least
quorumseats reportedPass. Reviewers run one at a time in the run
worktree with one shared prompt, so a panel never exceeds
max_parallel_tasks, and their spawns count towards the worst-case execution
budget a flow is admitted against. Each seat gets one-shot credentials bound
to(run, stage, attempt, reviewer), so which report asloop verdictcall
lands on comes from the credential and never from its arguments. A reviewer
that exits without reporting counts as aFailwithno verdict reported.
The aggregate is never stored — one append-only evidence row per reviewer is,
and the verdict is recomputed from those rows by a pure function, so a
restarted daemon reaches the same reading.sloop verdicttakes--confidence low|medium|high, defaulting tomedium.
It is recorded as evidence and never weighted into a panel's aggregation;
floats are rejected.sloop show <run>lists each panel seat's verdict,
confidence, and reason under its stage, silent seats included.- A new
{ builtin: sync }action merges the default branch into the run
branch, inside the run worktree. It passes when that merge commits cleanly or
there was nothing to integrate, and fails on a conflict — aborting the merge
so areturn_totarget starts from a clean tree, with git's conflict output
captured in the run log and so in the re-entered agent's prompt. Any number
of sync stages, anywhere before the merge; the shared default-branch checkout
is only ever read. - The merge stage takes
{ builtin: merge, ff_only: true }, which refuses the
merge commit: the default branch either fast-forwards to the run branch head,
or the stage fails having touched nothing. Absent, the merge policy is
unchanged.ff_onlyis meaningful nowhere else and is a parse error on any
other action. sloop initwrites atrainflow besidedefault.yaml, opt-in with
flow: train. It is the merge train:build → sync → verify → ff_only merge, withfail_action: { return_to: sync }on the merge, so a default
branch that moves between the verification and the merge loops the train
rather than landing a tree no stage tested. Itsverifystage uses the
repository's configuredflow.test_cmdwhen it has one.- Flow stages can now bind the two
fail_actionforms that previously parsed
but were rejected.fail_action: continuemakes a stage advisory: its
failure is recorded and visible insloop show, the walk carries on, and the
run's outcome is unchanged.fail_action: { return_to: <stage>, attempts: N }
is a bounded backward edge: the walk re-enters an earlier stage and re-runs
the whole span from there, so no verdict earned before the loop can reach the
merge. Edges must point backwards,attemptsis capped at 3, and a flow
whose budgets could execute more than 32 stages is refused at parse time. - A re-entered
agentstage is told why it is running again. Sloop appends a
delimitedprevious attempt failedblock to its prompt — after the ticket
body and the worker instructions — naming the stage that failed, its resolved
reason, and the last 100 lines of that execution's captured output. The block
is rebuilt from the run's persisted evidence, so a daemon that restarts
mid-loop composes the identical prompt.execactions are handed nothing:
their command line is fixed by the flow. sloop showrenders each stage execution separately, suffixing re-runs with
their attempt (build,build#2), and a non-merged run's derivedreason
names the failure the walk actually stopped on rather than one a later
attempt superseded.sloop showtells an advisory failure apart from the one that ended the run:
the stage table marks itadvisoryand the scan strip marks itwarnrather
thanFAIL. A run's derivedreasonnever names an advisory stage, since
the walk provably continued past it, and a terminal run whose only failures
were advisory says so. Stage rows carry a newadvisoryfield.- A run whose walk stopped short says which halt it was.
sloop show <run>
appends; return_to budget spentor; the stage log does not replay against this flowto the derived reason where the failed stage alone does
not explain the ending, andvalue.haltcarries the same distinction as a
stable token (fail_action,return_budget_exhausted,corrupt_log). It is
re-derived by replaying the walk's own fold, so it cannot disagree with the
driver. sloop logs --stageaccepts<stage>#<attempt>, selecting one execution of a
stage areturn_toedge re-entered. Parsing happens in the daemon, so every
socket client gets it; a bare stage name still selects every execution. Log
lines label re-runs the same way ([agent:build#2]), so the labelsloop showprints is the selector to type next.
Changed
-
What Sloop called an activation is now a trigger. The scheduling model
has three nouns — a ticket is what to do, a run is one attempt at it, and the
third is the durable record that demand exists, the thing that makes the
dispatcher pick a ticket up. "Activation" named the act of activating rather
than the row waiting in a queue, so every mention of it had to be glossed
first; "the trigger fired" and "the trigger is pinned to TICK-63" read without
one. Scheduling, selection, claiming, and rearm behaviour are unchanged.Operator-visible consequences:
sloop runprintstrigger TR91 queued (now, ticket TICK-63); trigger ids widen fromA<ordinal>toTR<ordinal>,
becauseT91reads as a ticket id besideTICK-91; and the dashboard field
queued_activationsbecomesqueued_triggers. On the wire,postandrun
take atriggerargument where they tookactivation, andpostanswers
withtriggerandtrigger_suppressed. See "The activation → trigger
rename" indocs/protocol.mdfor the full field-by-field table.Upgrading rewrites the database in place, ids and all. Queued triggers are
pending work recorded in no committed file and no commit, sosloop reindex
cannot rebuild one; the migration carries every row across instead. -
sloop verdict --confidenceon areportedstage is now recorded rather
than discarded, so the flag means the same thing from a stage worker as from
a panel reviewer.sloop show <run>prints it beside that stage's verdict
source, and the stage row carries a newconfidencefield. -
sloop template flowprints the current grammar as its canonical example:
action,result_check, andfail_actionwritten out on every stage, with
onereturn_toedge, one advisory stage, and a commented panel block. The
pre-actionkind:/verdict:spelling and the deprecatedon_failare
demoted to a short "legacy spellings" note, andsloop template ticketnames
the two flows that ship with the binary.sloop verdict --helpandsloop logs --helpnow document who may call them and what their selectors accept. -
Denials and parse errors use one vocabulary for one concept: a stage that
cannot take a report says it does not useresult_check: reportedrather
than naming a "verdict policy". -
The ticket-state table in
sloop --help --allstates the trigger
precondition first:readysaid it was "eligible for dispatch once a run is
queued", which reads as a caveat on a state that sounds like a promise. It
now leads with the fact that areadyticket runs only once a trigger is
queued for it, andfailednames both halves of the recovery —sloop retry
returns it to ready,sloop runstarts it again. -
Captured output records, stage-process checkpoints, agent-exit checkpoints,
and reported verdicts are all scoped to(stage, attempt)rather than stage
alone, so a re-entered stage gets its own output, its own worker report, and
its own exit checkpoint instead of inheriting an earlier attempt's.
Deprecated
- The
queued_activationsfield onstatusand on theshowdashboard, in
favour ofqueued_triggers. Both are emitted carrying the identical value,
following the precedent set by thelist→showaliases;
queued_activationsis removed in 0.5.0. The other envelope fields the
rename touched are swaps rather than aliases — seedocs/protocol.md. on_failrepair blocks.fail_action: { return_to: <stage> }covers the
same ground with the flow's own stages — a failing check stage returning to
the stage it guards — and the daemon now logs aflow_on_fail_deprecated
note when it admits a run whose flow useson_fail. Nothing is removed:
existing flows keep parsing, repairing, and settling exactly as before.
Fixed
- A daemon spawned by a client command now calls
setsid, so it leads its own
session and process group instead of inheriting them from that client. The
client exits as soon as its request is answered, so a daemon that stayed in
its session died to any signal aimed at that short-lived process — a terminal
hangup, or a supervisor reaping a finished command's process group — and the
next client command silently started a replacement. The symptom was a daemon
that vanished with nodaemon_stoppedrecord, and, if a run was in flight at
the time, lost work. sloop postaccepts an absolute path to a ticket that reaches the repository
through a symlink. The repository root is always canonicalized, but the path
the operator typed was compared to it unresolved, so on macOS — where/tmp
and/var/foldersare both links into/private— posting a ticket by
absolute path was rejected as "outside the repository". Both sides now
resolve before the comparison. A ticket file that does not exist yet is
unaffected: the longest existing ancestor resolves and ...
0.3.0
Release Notes
Added
- The read surface is now one verb. Bare
slooporsloop showprints a
dashboard: daemon status, scheduler state, and recent activity.
sloop show <REF>resolves ticket IDs, ticket names, run IDs and aliases,
unique run-id prefixes, and project IDs; an exact reference always wins.
Anything else becomes a case-insensitive ticket pattern over IDs and
names — a substring by default, an unanchored regular expression when the
text contains regex metacharacters — rendered as a ticket list.
-n/--limit Nor the shorthand-Ncaps list rows.--followstreams
the shown scope's events; ticket and run followers exit when the subject
settles, and--follow --quietreturns only the outcome for scripting.
Exit codes are stable:0for success or a merged subject,1for
another terminal outcome or a daemon error,2for usage errors.
sloop show --helpdocuments the whole resolution ladder. Pattern
resolution happens in the daemon: theshowverb accepts patterns, the
eventsverb gained an optionalscope, and thelistverb gained an
optionallimit, all additive within protocol version 1. sloop template <kind>prints fully commented canonical templates for
the config, flow, project, and ticket files, so a working example is
always one command away.sloop show <TICKET>now lists the ticket's runs, newest attempt first:
alias, outcome, wall-clock span, and a strip of the run's flow stages
markedok,FAIL,..(running), or-(not reached). A ticket that
has never run printsruns: none.sloop show <RUN>now shows the run's timeline, a per-stage table (state,
attempts includingon_failretries, duration, exit code, and verdict
source), and a derivedreasonfor any non-merged terminal run — for
examplestage `test` failed (exit 1) after agent completed with commits. The reason comes from the stored stage and evidence rows, never
from an agent's own claim about its work. Stage names come from the run's
admitted flow snapshot, so a run reports the stages it actually had even
after the flow file changes.- The
showresponse gainedvalue.runson tickets andvalue.stages,
value.attempt,value.agent_exit_code, and the timeline fields on runs,
all additive within protocol version 1. sloop logsgained--stage <NAME>,--tail <N>, and--follow.
--stageselects one flow stage by the name the flow gives it, including
the agent stage, and rejects a name the run's flow does not define instead
of returning an empty page.--tailkeeps the last N entries;--follow
streams new entries until the run settles. The three combine, so
sloop logs <RUN> --stage test --tail 50answers "why did the test stage
fail" in one command. Filtering happens in the daemon: thelogsverb
gainedstage,tail, andafterarguments and aterminalresponse
field, all additive within protocol version 1.
Changed
- The default flow's review stage is now a real gate. It previously
inheritedverdict: exitand passed whenever the reviewer process exited
zero, so every freshsloop initdeployment silently approved all work.
The shipped stage now usesverdict: reported, and the review prompt
requires the reviewer to callsloop verdict pass|fail --reasonexactly
once; the command, not the prose, decides the stage. - Compact
sloop --helpnow lists only the everyday operator commands
(init,template,daemon,post,show,logs). Everything else,
including the worker verbbrief, remains available and documented under
sloop --help --all. sloop show <RUN>labels the agent stage's exit asagent exit:rather
than a bareexit:, which could read as "the whole run passed" on a run
whose later stage failed. The JSONexit_codefield is unchanged.- Ticket lists — the dashboard, pattern results, and the deprecated
list
alias — order tickets by registration time, newest first, instead of
oldest first, so recently posted and currently running work leads the
output. State does not affect the order. Tickets registered in the same
millisecond fall back to their id's numeric ordinal, newest first. sloop postnow reports every problem with a ticket file in a single
invalid_argumentserror, one per line under the file path, instead of
stopping at the first one. A file whose frontmatter cannot be parsed at
all still fails fast with the parse error, and a file with exactly one
problem reads as it always has.
Deprecated
status,list,watch, andwaitremain accepted as hidden
deprecated aliases ofsloop show. They no longer appear in normal help,
and each invocation writes a note to stderr naming its replacement:
statusandlistpoint tosloop show,watchtosloop show --follow(its optional scope and tail still work), andwaitto
sloop show --follow --quiet(its run and timeout still work). The
aliases will be removed in a future release.
Fixed
sloop reindexnow recognizes patch-equivalent merges. A run branch
whose commits were squashed or rebased onto the default branch is
detected withgit cherryand indexed as merged; previously only true
ancestor merges counted, and reindex flipped squash-merged tickets to
needs_review.
Install sloop 0.3.0
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/hamish-mackie/sloop/releases/download/v0.3.0/sloop-installer.sh | shInstall prebuilt binaries via Homebrew
brew install sloopDownload sloop 0.3.0
| File | Platform | Checksum |
|---|---|---|
| sloop-aarch64-apple-darwin.tar.gz | Apple Silicon macOS | checksum |
| sloop-x86_64-apple-darwin.tar.gz | Intel macOS | checksum |
| sloop-aarch64-unknown-linux-musl.tar.gz | ARM64 MUSL Linux | checksum |
| sloop-x86_64-unknown-linux-musl.tar.gz | x64 MUSL Linux | checksum |
Verifying GitHub Artifact Attestations
The artifacts in this release have attestations generated with GitHub Artifact Attestations. These can be verified by using the GitHub CLI:
gh attestation verify <file-path of downloaded artifact> --repo hamish-mackie/sloopYou can also download the attestation from GitHub and verify against that directly:
gh attestation verify <file-path of downloaded artifact> --bundle <file-path of downloaded attestation>0.2.1
Release Notes
Fixed
- Keep worker socket paths within the 104-byte macOS socket path limit by
placing them directly in the runtime directory under the short run id.
On macOS the previous layout exceeded the limit, every agent spawn failed
at socket bind, and no run could start.
Install sloop 0.2.1
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/hamish-mackie/sloop/releases/download/v0.2.1/sloop-installer.sh | shInstall prebuilt binaries via Homebrew
brew install sloopDownload sloop 0.2.1
| File | Platform | Checksum |
|---|---|---|
| sloop-aarch64-apple-darwin.tar.gz | Apple Silicon macOS | checksum |
| sloop-x86_64-apple-darwin.tar.gz | Intel macOS | checksum |
| sloop-aarch64-unknown-linux-musl.tar.gz | ARM64 MUSL Linux | checksum |
| sloop-x86_64-unknown-linux-musl.tar.gz | x64 MUSL Linux | checksum |
Verifying GitHub Artifact Attestations
The artifacts in this release have attestations generated with GitHub Artifact Attestations. These can be verified by using the GitHub CLI:
gh attestation verify <file-path of downloaded artifact> --repo hamish-mackie/sloopYou can also download the attestation from GitHub and verify against that directly:
gh attestation verify <file-path of downloaded artifact> --bundle <file-path of downloaded attestation>