Repository navigation
Releases: josegarridodigio/sluice
Release list
v0.9.0
Added
statussays where the memory reserve and an estimate come from (ADR-0026). The
memory:line marks a reserve raised by the observed growth outside gates
(reserve_basis), a running job that started alone beyond its estimate is flagged
(memory_beyond_estimate), and the daemon log writes "starts alone beyond its
estimate" when that happens.
Changed
- Memory admission keeps what it learned (ADR-0026). A command of a gate with no
history (for example the same test gate with--filteror another concurrency) is
estimated from the gate's other commands instead of a guess; only a guess may start as a
first-run probe, never a lower bound; a memory-killed run counts 1.25 x its peak instead
of 1.5 x; after a gate is killed for memory no job starts for at least 10 s and until the
gates' memory pressure decays (the PSI starvation guard included); and the memory
reserve grows to the P90 of what the rest of the machine grew while heavy jobs ran
(at most a quarter of the RAM). New tableoutside_growth(containment schema 4;
older binaries ignore it). - The agents' systemd-oomd window is 15 s (
min(15s, 3/4of the user manager's
duration), wasmin(5s, 1/4)); the gates' scope follows at 8 s. A gate kill no longer
leaves residual pressure that ends an agent's session. KILLED for lack of memoryadvice: the message names the memory used outside gates,
says when other arguments did not lower the gate's memory, and tells the agent to rerun
unchanged once memory is free or to tell the human (the agent rules and the skill agree).
Full Changelog: v0.8.0...v0.9.0
v0.8.0
Added
- The queue lands through a pull request where the target refuses direct pushes
(ADR-0025, issue #5).[queue] land = "pull-request"(defaultpush; in the target
commit's.sluice.tomlor the user config, never from a queued branch; one-way key:
0.7.0 and older binaries reject it). After the human approval the daemon pushes the
verified commit tosluice/train/<id>(lease on absence, the approver's environment,
the same classifier, no audit ref) instead of the target, and the train and its jobs go
to the new stateawaiting_merge.sluice queue approvethen opens one pull request
per train with the user's owngh(found, not duplicated, when it exists; no token is
stored or sent to the daemon) and--auto-mergealso arms the forge's auto-merge
(--match-head-commit, never admin rights). If the pull request cannot be opened the
train staysawaiting_mergewith a note andsluice queue reconcile(or approving it
again) finishes it. Sluice never merges.sluice queue status,sluice queue reconcile
and the MCPqueue_statusread each waiting train's pull request with the caller'sgh
(read-only, bounded, cached 20 s) and report it to the daemon, which decides with a new
pure table: merged and its commit on the target meanslanded(the merged tree is
compared with the verified one; another tree is stilllandedwith the note
tree_differs_from_verified), closed or branch gone means the jobs fail withpr_closed
(queue retryis the way back), a changed head, a moved target (target moved N commits since the train was built; the train waits, never requeues) and an unusablegh
(forge_unreachable: <reason>) are notes. A human'squeue cancelof a waiting job
dissolves the train, closes the pull request Sluice opened and deletes the branch it
pushed (lease on that commit);sluice doctorlists leftoversluice/train/*branches
from the store (queue-pr-branches, offline). Agents wait or tell the human: approving
(even withallow_agent_approval), opening and cancelling are humans' acts, and MCP only
reads. Additive protocol: the featurequeue-pr-landing, the messagequeue-pr-facts,
pr_landingon the train approval request, optionalland/pron the train view and
nullablepr_*marker columns; a client or daemon without the feature refuses a
pull-request train instead of ignoring it. The enqueue warnings about protected targets
now say how to land there and disappear for a repository that lands through a pull
request. - Backfilling behind a reserved head (ADR-0023). An overdue head that waits for
budget or memory still reserves, but a job behind it now starts when it cannot delay
it: the daemon predicts the head's start (its shadow time) from the running jobs'
learned durations, and lets a job start if it fits now and either the head still fits
beside it at that time (extra resources, no duration needed, works for first runs)
or its learned duration, times 1.5 or plus 10 s, ends before then (ends in time).
Nothing is killed for overrunning and a memory probe is never a backfill. The daemon
log saysstarts before the reserved head;sluice statusshows the head's eta
(reserved, eta 4m05s) and, in the totals, the jobs backfilled and how late heads
started against their first eta (backfilled,backfill_head_delay_msin
--json, additive).[scheduler] backfill = falserestores the strict reservation;
the key is one-way (0.7.0 and older binaries reject it). - Targets that refuse direct pushes: warned early, learned from a refused push
(ADR-0025, issue #5; pull-request landing follows).sluice queue enqueueand the MCP
queue_enqueueask the forge, with the caller's ownghlogin, whether the target
accepts direct pushes (GitHub first: the branch's rules, itsprotectedflag and, when
readable, the classic protection; GitHub Enterprise hosts you are logged in to and SSH
aliases, resolved withssh -G, are understood) and warn when it requires a pull request
or is protected, or say "could not check: " (Unknownnever errors). The enqueue
always proceeds. A few seconds at most, cached for 5 minutes.sluice doctor(still
offline) shows the last answer an enqueue got as atarget-protectioncheck (warnfor a
protection,okotherwise). A push refused with a
protected-branch text (GitHubGH006/protected branch hook declined, GitLab, a
pre-receive hook declinedthat saysprotected branch) now fails with the reason
target_protectedinstead ofpush_rejectedand is remembered per remote and target (the
additive tablequeue_protected_targets); the next enqueue warns from it even wheregh
cannot tell. Agents are told to tell the human and not retry. The queue still cannot land
on such a target. - Stalled jobs are reported, never killed (ADR-0024). Every 5 s the daemon reads
what each running job does: CPU time, block I/O, page faults and process count from
its leaf cgroup (the process group without containment), plus the log bytes. A job
with no progress formax(5 min, 3 x its expected duration)isstalled; a job
waiting for a CPU is not.statusshowsSTALLED 6m12s (flat: cpu io output)on the
job (with the memory and weight it holds;running[].stalledin--json),sluice runprints a notice when the job becomes stalled and when it is active again, the MCP
verifyprogress sayslooks STALLED, anddaemon.logrecords each stall and, for
every finished job, its longest quiet window. A stalled job counts as ending at an
unknown time in the backfill shadow time. Sluice never ends a stalled job and has no
setting to: the gatetimeoutstays the only limit. A gate can setstall_timeout
(a duration, at least 1 minute, or"off"); the key is one-way (0.7.0 and older
binaries reject it in.sluice.toml). Agents are told that a job reportedSTALLED
is probably hung and that they should look at the test, never re-run the gate
through the shell. - First-run memory probe (ADR-0022). A gate with no passing run may start
as a memory probe when a quarter of its estimated peak is free, one probe per
gate and one per machine at a time; it never over-admits. The daemon log says
starts as a memory probe, and its real peak becomes the estimate. - Lower bounds from failed runs. Runs that end
failortimeoutrecord
their peak (thegate_memory_boundtable,containment_schema3: additive,
released binaries ignore it and never read a bound as a measurement). The
estimate of a gate with no passing run is1.2 xthe largest recent bound. memory_expected_basis(learned,lower-bound,guessed) in the running
and queued jobs ofstatus --json, spelled out in the text status and in the
"waits for memory" log line. Additive;schemastays 1.
Changed
- A job waiting behind the reserved head is no longer
head-reservedfor as long as the
head waits when it cannot delay the head (on one recorded day, 91 % of the
head-reserved minutes were jobs expected to run under a minute). - The guess for a gate that never ran is the 75th percentile of the learned
peaks of the gates of the same or lighter weight, clamped between 1 GiB and the
gate's weight share of the usable memory, instead of the share lowered to the
largest such peak; the share alone when nothing is learned. Weights and
durations still learn from passing runs only.
Fixed
- A gate that waited hours for memory on a guess several times above what it
uses (a 13.8 GiB guess against a 0.3 GiB run on a 30 GiB machine), or that kept
failing and so never learned, now starts as a probe and converges after one run.
Full Changelog: v0.7.0...v0.8.0
v0.7.0
Added
- Hooks entry point (
sluice hooks on|off|status, optional and local to a
clone, ADR-0021). Sluice becomes the git hooks entry point of a clone without
editing the repository:ongives the clone a hooks directory under its git
directory (one script for each hook the repository has, kept in sync) and sets
core.hooksPaththrough a file included by the last[include]of the
clone's git config, so husky's owncore.hooksPathwrite on every install does
not displace it.offremoves it. A missingsluicebinary never disables
the repository's hooks.sluice init --hooks entryactivates it instead of
rewiring the hook files (--hooks rewire, the default, is the previous
behaviour). The agent plugin's session start restores an activated clone whose
entry point was displaced and says so; it never activates a clone by itself. - Hooks run through the queue and the cache. With the entry point on, a hook
Sluice understands completely runs its commands as jobs of the daemon, in the
manager's order; anything else, a daemon that cannot be reached or
SLUICE_HOOKS_DELEGATE=1runs the hook exactly as before (same arguments,
stdin, directory, environment and exit code). husky and plain hooks: simple
commands and&&lists; lefthook 2.x: named commands and jobs, each run by
lefthook run <hook> --command <name>, when none of them can be skipped;
pre-commit 4.x: each hook of thepre-commitandpre-merge-commitstages by
pre-commit run <id>, while the worktree equals the index. Commands that may
change files or the index (lint-staged, formatters with a write flag,git,
unknown programs) always run; read-only checks (tests, linters,npm run
scripts made of them, and the manager's equivalents) are cached by the working
tree they saw.sluice hooks statusshows, for each hook, whether it runs as
units or is delegated, and why. - Gates without
.sluice.toml. In a repository with none, the check units of
its hooks (pre-commit,pre-merge-commit,pre-push; named
<manager>:<hook>#<n>, e.g.husky:pre-commit#2) are the gates ofverify
andgate_list(MCP),sluice run <gate>and the newsluice gate list [--json], with the same cache entries as the hook runs: averifybeforegit commitmakes the commit's hook a cache hit, and the other way round.
gate_listalso lists the hooks and units that are not gates and why
(delegated hooks, mutating units, units that depend on what is staged). With a
.sluice.tomlits gates are unchanged; a plain-hook unit whose command equals a
cached, unscoped gate with noenvshares that gate's entry. The agent plugin's
session context, skill and MCP instructions cover such repositories. - Docs: new
docs/hooks.md(activation, units per manager, caching, opt-outs,
compatibility table, limits).docs/merge-queue.mdsays that the queue cannot
land on a target the forge protects against direct pushes (it fails at push
time withpush_rejected); land through pull requests there (issue #5).
Changed
- With the hooks entry point on, a hook run as units differs from the hook run by
its manager: the output of each command goes to a job log (a one-line summary,
and the tail on failure), checks run without git's own hook variables, and
theagent_hook_policy = "fail-fast"refusal does not apply, so an agent's
commit on a cache miss runs the units insidegit commit; agents should
verifyfirst. What runs does not change for a clone that never ransluice hooks on. sluice doctorno longer tells you to ignore a hook manager's hooks that
bypass Sluice: thehookscheck fails when a manager owns hooks that do not
call sluice (suggestingsluice hooks on), and fails with the reason when an
activated entry point is displaced or a hook of the repository has no script.
repo-configisokfor a missing.sluice.tomlin a clone whose hooks go
through Sluice.
Full Changelog: v0.6.3...v0.7.0
v0.6.3
Added
- The daemon log says when a job waits for memory: one
infoline when a job
first becomes memory-blocked (waits for memory, with the expected peak, free
memory and the running jobs' headroom) and one when it starts after waiting
(started after waiting ...s for memory). Memory waits stay folded into the
budget columns and wire reason; the log is how to measure them.
Full Changelog: v0.6.2...v0.6.3
v0.6.2
Fixed
- The merge trains'
PATHkeeps the daemon's own directories after the login
shell's: a login profile that rewritesPATH(/etc/profileunderbash -l)
made 0.6.1 drop every directory only the daemon's starter had (an activated
virtualenv, a direnv shell), and trains failed withkey_input_failed
(exit 127). 0.6.1 was tagged but never released (its release workflow failed
on this); 0.6.2 ships its changes.
Full Changelog: v0.6.1...v0.6.2
v0.6.0
Added
- Golden files recorded from the 0.4.1 and 0.5.0 release binaries, read by the
compatibility tests next to 0.1.0 through 0.4.0. [queue] prepare(withprepare_timeout,prepare_weight): a command run
once per merge train, through the scheduler, before its gates, so gates that
need git-ignored dependencies (node_modules,.venv) can run in the merge
worktree. A failure is attributed by preparing the base alone: nobody's fault
(members requeued,prepare_failed,failedafter three attempts) or the
member's (split,prepare_failed);prepare_dirtywhen it leaves files the
tree does not have.sluice initwrites it for detected lockfiles (#3).sluice queue enqueue|retry --trust-member-config: a human-only way to land a
fix of the queue's own configuration; the train runs the branch's
.sluice.toml, the approval shows its diff and binds its hash. Refused over
MCP and from an agent's environment, whateverallow_agent_approvalsays.- Memory safety of gate jobs (ADR-0019): admission by each gate's learned
memory working set ([admission] memory_aware,memory_reserve), so two
suites that do not fit together wait for each other; gate processes are the
preferred victims of the kernel's OOM killer ([containment] gates_oom_score_adj,memory.oom.groupon each job leaf) and of
systemd-oomd(a policy on the gates' scope that acts before the agents');
a gate killed for memory endskilledwithkilled_by = "memory"and a
message naming its peak; opt-in[admission] memory_guardcancels Sluice's
own largest gate under real exhaustion; optional[containment] gates_memory_max/gates_memory_swap_maxcap all gate jobs together on
jobs/(unset by default; a hit kills one job whole, reported as a memory
kill). client.loglines carrykey=; the daemon logs every hookrunrequest;
sluice run --explainreports the cache mode.sluice statusshows the memory admission sees (memory:line,memper
job,(budget: memory)for a gate that waits for memory) and the gates'
systemd-oomdpolicy;status --jsongains the optional fields
(budget.memory,wait_detail = "memory", per-job memory,
containment.gates_oom_policy).sluice doctorgainsgates-oom,
gates-memory-policyandmemory-guard.- A branch whose merged
.sluice.tomlasks thepre-pushhook for gates the
train does not verify, or does not parse, fails at build time
(hook_gate_unverified,member_config_invalid) instead of at the push; the
other members are requeued.
Changed
- The daemon's environment no longer inherits per-invocation variables from
the client that autostarted it (SLUICE_JOB,SLUICE_AGENT,SLUICE_CACHE,
SLUICE_DISABLE, the session leaf, agent-detection variables, the shim
directory inPATH);key_inputsrun with exactly the gate's environment. - A merge train verifies
[queue] gatesplus every gate with apre-push
hook. cargo checkandcargo docare heavy commands (they go through the queue).- New config keys (
[queue] prepare*,[admission] memory_*,
[containment] gates_oom_score_adj,gates_memory_max,
gates_memory_swap_max) are rejected by older binaries.
Fixed
- The daemon's own push of an approved train no longer re-runs the gates the
train verified: a Sluicepre-pushhook launched by that push is answered
from the train's record (checked by process ancestry and the worktree's
commit); a hook gate the train did not verify fails the push at once instead
of running intopush_timeoutandneeds_reconcile(#4). - A gate that modifies tracked files fails its train (
gate_modified_tree)
instead of looping through dissolve and re-approval; a pass that wrote no
marker is shown asnot cached. config_changedcompares against the merge base and is shown inqueue statusand the approval plan; a gate failure keeps it.- An unparsable
.sluice.tomlon the enqueued branch is reported
(invalid_config) instead of silently picking the default target. - Stored train settings missing a field keep the real defaults (a zero push
timeout could follow from an unreadable row). - The output of a push that hit
push_timeoutis kept in the train's note. - A
key_inputscommand failing in the merge worktree requeues the members
(key_input_failed) instead of failing them asinternal. - A train's gates are killed when the train drops them, also under
last_subscriber_gone = "finish". - Paths of changed files reported by the queue (
dirty_merge,
gate_modified_tree,prepare_dirty) no longer lose their first character.
Full Changelog: v0.5.0...v0.6.0
v0.5.0
Added
-
sluice statusshowscontainment: full|partial (...)|off (<reason>), one
line per live agent session (s-<id> cpu mem procs, from cgroupfs),
outside jobs: <x>% CPU(the agents' CPU over the last sampling window) and
the last interventions.status --jsongains the optionalcontainment,
sessions[]andinterventions[](schema 1, additive). -
sluice doctorgains thecontainmentgroup (cgroup v2, user manager, own
cgroup, delegated controllers bycpu.weightpresence,systemd-oomdand
the derived memory thresholds, cgroupfs writable) and thesessioncheck
(where this shell and the agent are, and why). -
The agent is told once about an intervention in its own session: the MCP
verifyandstatusresults and the guard's context note append a short
line. The daemon keepsinterventions.jsonlfor the guard. -
[containment]table of the user config (ADR-0017): weights,pids_max,
optional CPU ceiling,memory_policy,memory_pressure_limitand
memory_pressure_duration("auto"derives them from the system's
systemd-oomdpolicy), opt-inmemory_highandmemory_max. Configs
without the table load unchanged. -
sluice-gate-core::cgroup: detection of cgroup v2, the user manager, the
delegated controllers andsystemd-oomd(full,partial,offwith the
reasondoctorwill print); the Sluice cgroup tree created through the
systemd user manager (busctl,systemctl); session leaves with weights and
pids.max; placement of processes (direct write,AttachProcessesToUnit
fallback); pruning of empty cgroups; usage readings. Not wired into the
daemon yet. -
Hidden
sluice cg-exec <leaf> -- <argv>: moves itself into a cgroup and
execs, running the command even when the move fails. -
Session containment (ADR-0017): the plugin's session hook creates the
session's cgroup (uiandtoolsleaves undersluice-agents.scope), moves
the agent process and its current descendants intoui, and appends one
guarded, silent line toCLAUDE_ENV_FILEthat moves each shell intotools
(nice 19 when the cgroup files are not writable, as in the Bash sandbox).
Where the CPU cannot be weighted the line only renices. The hook's log line
gainscontained=full|partial|off. Shims place themselves in the session's
toolsleaf (SLUICE_SESSION_LEAF) before running a heavy tool. Fails open;
nothing is ever killed. -
Daemon containment (ADR-0017): the daemon adopts itself into
sluice-daemon.scope(daemon/leaf, with its reaper), creates (or reuses)
sluice-agents.scopearound asluice daemon holderprocess, sets the slice
weight and the derivedsystemd-oomdpolicy, and runs every gate andexec
job in its ownjobs/<job-id>leaf throughsluice cg-execplaced before
thenice/ionicechain. Empty session and job cgroups are pruned every
minute. Any failure is logged once and the daemon runs as before. -
Interventions: the daemon records kills by the kernel OOM killer
(memory.events) and bysystemd-oomd(journal) on its cgroups in a new
interventionstable (containment_schema, additive) and reports them in
thestatusreply as an optionalinterventionsfield (protocol 1).
Fixed
- The agent plugin's guard no longer denies harmless commands that mention
approval: when it cannot parse a command (heredocs, loops,$(...)) or
meets an opaque wrapper, it now needs the whole wordqueuefollowed by
the whole wordapprove(after the shell's joins of quotes, backslashes
and line continuations), instead of the substrings anywhere in the text.
"approved" in a commit message or a heredoc is allowed again; every real
approval, wrapped or not, is still denied, and a computed program name
($x queue approve) now is too (#2). - The session hook's stopgap holder is
sluice daemon holder --lifetime 600
instead ofsleep 86400: it ends by itself, when its scope is no longer
active, or when no scope adopted it; the hook verifies the scope is active
and holds the holder, else kills it and fails open. A holder once stayed
alive inside a dead scope. plugin/hooks/test-session-start.shran the real binary against the
developer's real user manager; it now uses an empty runtime directory.
Full Changelog: v0.4.1...v0.5.0
v0.4.1
Added
- Golden files recorded from the 0.4.0 release binary, read by the
compatibility tests next to 0.1.0, 0.2.0 and 0.3.0.
Changed
sluice initwrites every rewired hook line with a fallback to the original
command when Sluice is not installed (if command -v sluice ...; then sluice run --hook <hook> <gate>; else <original>; fi, throughsh -cfor the
pre-commit framework), so the rewired hooks can be committed for a team. The
.githooks/files it creates start withcommand -v sluice ... || exit 0.
init --forceupgrades the bare lines of 0.4.0 to the new form.sluice runprintssluice: cache off for this tree: <reason>once on
stderr (without-v, hooks included) when the tree cannot be hashed, and
client.loglines carryreason=<code>aftertree=-.
Fixed
sluice initwrites.sluice.tomland hook files atomically (temporary
file in the same directory, fsync, mode kept,rename; the target of an
in-repository symlink), so a hook that is running is never read half
written.npm exec X,npm x X,npm exec -- X,npm exec -y X,npx -- X,
pnpm dlx Xandyarn dlx Xare classified asX, so the guard and the
shims queuenpm exec playwright testandpnpm dlx vitest run.sluice doctorreads the scripts a husky v9 runner executes
(.husky/<hook>) instead of warning that no hook calls sluice.sluice doctorhas acachecheck that warns about skip-worktree and
assume-unchanged index entries, which silently disable the cache.
Full Changelog: v0.4.0...v0.4.1
v0.4.0
Added
- Golden files recorded from the 0.3.0 release binary, read by the
compatibility tests next to 0.1.0 and 0.2.0. weight = "auto"for gates: the ceiling is half the daemon's budget and
learned weights lower it, so there is no number to guess. A declared number
stays a ceiling and an omittedweightstays 1. A.sluice.tomlthat uses
"auto"needs 0.4.0 (older binaries accept only integers).sluice execjobs learn their weight from the measured demand after three
runs, per repository and--label;--weightor[agent] exec_weightis
the ceiling.sluice statusshows configured and effective weights (weight auto→5);
its JSON gains the optionalweight_effectiveandweight_auto(schema 1).- The built-in heavy list knows the Angular CLI, Nx, Turborepo, Playwright,
Cypress, react-scripts, Astro, Nuxt, .NET, Bazel, Ninja andcmake --build,
so the guard and the shims catch them with no user config. Dev servers and
watch modes (ng serve,nx serve,next dev, barevite,--watch)
are never heavy. npm run <script>(andnpm test,pnpm <script>,yarn <script>) is
classified by what the script in the nearestpackage.jsonruns, following
nested scripts, in both the guard and the shims.- A shim call that is exactly a gate command inside a repository with a
.sluice.tomlnow runssluice run <gate> -v(cache and coalescing)
instead ofsluice exec. - The agent plugin sets itself up when a session starts (
SessionStarthook,
sluice agent session-start, hidden): it installs the shell shims, puts
them first in the session'sPATHthroughCLAUDE_ENV_FILE(once, also on
resume) and gives the agent the rules as context: verify before commits,
no subshell or script around builds, no parallel dev servers during gates,
never approve merge trains. In a git repository without.sluice.tomlthe
agent proposessluice initto the user once. A missing or oldersluice
binary is reported to the agent, which asks the user before installing or
updating. Users no longer editPATHor theirCLAUDE.md. sluice initdetects the stack and the existing hooks. It takes gates from
the heavy commands of husky, lefthook, pre-commit framework and plain hooks,
fills the rest from Angular, Node (npm/pnpm/yarn/bun), Rust, Python and Go
projects, picks weights ("auto"for test runners) and timeouts, and
rewires the hooks in place tosluice run --hook <hook> <gate>(comments,
mode and CRLF kept; formatters and fixers untouched). With no hook manager
it creates.githooks/and setscore.hooksPathlocally.--dry-run
prints the gates, the files and the diff and writes nothing;--forceis
idempotent.sluice doctorchecks the zero-config setup:shims(installed and
current in the stable directory, first inPATHfor this shell; a warning
in an agent shell when not),plugin(the installed agent plugin's version
against the binary; a warning when they differ, never a failure),
weights(a warning when a declared weight is more than 50 % below what the
gate's runs need, suggesting a higher number orweight = "auto") and
init(a repository without.sluice.toml: a hint to runsluice init --dry-run).
Changed
- The MCP
statustext shows configured and effective weights like the CLI
(weight auto→5). docs/agents.md, the README quick start and the configuration, CLI and
debugging guides describe the zero-config flow: install the binary and the
agent plugin, thensluice init; the built-in heavy list, the never-heavy
rules and package-script resolution; the session hook and how to inspect
its decision.sluice initoutside a git repository is now an error, and writes at the
repository root instead of the current directory.
Fixed
- Gates and
sluice execjobs now start underniceandionice, so every
process of a job inheritsdaemon.niceand the I/O class from its first
instruction; before, children forked before the asynchronousreniceof
the leader stayed at nice 0 (#1). When the kernel refuses the I/O
priority (seccomp, some containers) the gate still runs, with the default
I/O class.
Full Changelog: v0.3.0...v0.4.0
v0.3.0
Added
sluice shim install|uninstall|status: PATH shims that route the heavy
commands (make, cargo build, pnpm build, docker build, …) of agents
without hook support (Codex, Cursor, Aider) through the queue in agent
sessions or withSLUICE_SHIMS=1, and run the real binary unchanged for
humans.sluice guard: the agent plugin's PreToolUse hook now rewrites gate
commands tosluice run <gate> -vand heavy commands to
sluice exec -- …by inserting a fixed prefix in front of the untouched
original, approving or asking according to the agent client's
permission settings;sluice queue approvestays denied to agents.
User config[agent] heavy_commands,heavy_commands_extraand
exec_weight;SLUICE_GUARD=denyturns rewrites into suggestions and
SLUICE_GUARD=0disables the hook. With binaries older than 0.3.0 the
hook keeps the previous deny-only behaviour.docs/compatibility.md: what stays compatible from 0.3.0 and how
changes are made; golden files recorded from the 0.1.0 and 0.2.0 release
binaries (configs, markers, status and queue JSON, protocol transcripts,
databases, CLI surface, MCP tools) with tests that read them.- Path-scoped gates: optional
paths/paths_ignore(git glob pathspecs,
root-relative;.sluice.tomlalways counts). The key hashes only the
selected files, so changes outside the scope keep the cached pass, with
no extra git call; unscoped keys and markers are unchanged.cache statusand MCPstatusshow the scope andsluice doctorwarns about
manifests and lockfiles outside it. A config using them needs Sluice
0.3.0 or newer. sluice exec [--weight N] [--label L] [--coalesce-key K] [--timeout DUR] [-q] -- <cmd>runs any command under the budget without cache, replays
its output byte for byte, propagates the exit code (128+N on signals),
forwards signals to the job's process group and falls back in-process
when the daemon is down or older than 0.3.0.- Gate field
mode = "queue-only": the gate always runs, is never cached,
coalesces simultaneous identical requests, and an agent hook waits for
it instead of failing fast. sluice logs --timeline [JOB_ID] [--since DUR]and, instatus --jsonand
verifyresults, a per-job scheduler timeline (requested, queued,
admitted, started, finished, wait reasons, measured CPU demand,
requesters) andidle_while_waiting_ms: why a job waited.SLUICE_JOBon every process Sluice runs, so a nestedexecorrun
runs directly; welcome featuresexecandqueue-only;kindand
labelinstatus --json.docs/gates-cookbook.md: per-stack.sluice.tomlexamples (Node,
Python, Rust, Go, Docker, monorepos) and guidance on per-worktree caches,
verdicts versus build artifacts and choosing hooks.- A compatibility CI matrix: Ubuntu 22.04/24.04, Debian 12, Fedora,
Alpine (musl), git 2.31.0 built from source and native aarch64, on main,
tags, weekly and on demand. - Issue templates that ask for
sluice doctor --bundle, this changelog,
release notes taken from it, and a "Status and limitations" README section. - Package metadata, per-crate READMEs and LICENSE files;
cargo publish --workspace --dry-runpasses for all six crates (crates.io readiness,
nothing published).
Changed
- Status moves from pre-alpha to alpha: every scenario of the alpha harness
passes on real Node and Python projects (kb/sluice/changes/alpha-readiness.md). sluice queue approverun from an agent's environment is refused unless
[queue] allow_agent_approval = true.- Scheduler throughput (ADR-0011): the default budget is the number of
available CPUs, at least 2 (affinity and cgroup limits honoured; 0.2.0:
CPUs minus one); gates learn their real CPU demand from passing runs and
are admitted with it (resolved at every admission pass; running jobs keep
the weight they started with); PSI-driven over-admission up to
1.5x the budget when the machine has idle CPUs; MCPverifyruns its
gates concurrently and stops at the first failure (fail_fast); jobs are
shared fairly between worktrees, humans first, with optional
shortest-job-first. Every mechanism has a config switch. - The daemon database gains additive, transactional migrations with a
backup copy before migrating; 0.2.0 binaries still open the migrated
file. - The README is short and current (no development phases, no pinned
release numbers); the details moved todocs/how-it-works.md,
docs/cli.md(generated from--help) anddocs/configuration.md. - Markers and miss records tolerate unknown fields; the TTL pruning of
markers also removes expired miss records and foreign-format files. - Wire enums parse unknown values as
unknown; an unknown outcome is an
error, never a pass. - A refusal to open a newer database names the release that wrote it (new
additivemetatable);sluice --helpdocuments every exit code. sluice logsand log retention handle.execspools;doctorand
hook printwarn when a queue-only gate is listed inhooks.
Fixed
- A non-UTF-8 environment variable no longer makes
sluicecommands panic
(such entries are skipped;sluice execthen runs in-process with the raw
environment). - A queued job dropped by its last subscriber or a stop can no longer
start unsupervised, and terminated process groups are never signalled
again (pid reuse). - Every git process Sluice spawns now runs with
-c maintenance.auto=false -c gc.auto=0: the merge queue'sfetchand
mergecould start git's automatic maintenance (repack, prune) in the
user's repository. The cache check itself never did. sluice queue statusretries once when the daemon drops the connection
before replying (for example right after it died), instead of failing.- A daemon killed while it applied
niceandioniceto a new gate left
that gate running: the gate was registered with the reaper only after
thereniceandionicehelpers had finished. It is now registered
first and the priorities follow. - The merge queue works with git 2.31 and 2.32 (2.31 is the declared
minimum): its worktree was created withgit worktree add --lock --reason, which needs git 2.33. - Merge queue: a wrong SSH key or HTTP password fails
sluice queue approveat once withpush_rejectedinstead of leaving the train
approved forever or parking it for reconcile; the SSH error text reaches
the user; a password embedded in the remote URL no longer reaches job
details, train notes ordaemon.log. - A daemon killed by SIGKILL or the OOM killer no longer leaves its gates
running: a detached reaper terminates them within seconds, the next
daemon start kills any leftover group (also when only a background child
survives) and records the jobs askilled, and waiting clients see "the
daemon died while running ; it was killed" (exit 143). - An unwritable logs or state directory no longer stops gates: the output
is kept in memory for the failure tail and the error is reported once. sluice doctorreports an unwritable markers directory (fail), damaged
marker files (warn) and a database the daemon cannot open (fail).- The daemon and its clients fall back to
/tmp/sluice-<uid>/daemon.sock
(created 0700; refused when it is a symlink, owned by another user or
group/world writable) when the runtime-dir socket path exceeds the Unix
limit, instead of silently running gates in-process; an over-long
SLUICE_SOCKETis a clear error;sluice doctorgains asocket-path
check.
Full Changelog: v0.2.0...v0.3.0