Skip to content

v2.4.0-rc.6 — Review waits its turn

Pre-release
Pre-release

Choose a tag to compare

@Alan-TheGentleman Alan-TheGentleman released this 11 Aug 10:28
· 25 commits to main since this release
Immutable release. Only release title and notes can be modified.
e612ef3

HOTFIX over rc.4

v2.4.0-rc.4 was tagged and published but never carried binaries, so this candidate supersedes it before it reached anyone. v2.4.0-rc.5 was burned by a tooling mistake on our side and never published; nothing was released under it. Two defects landed after it, both in the path this candidate asks you to exercise.

A filesystem root is no longer answered as if it were a project. sdd-status --cwd / returned a successful, empty, entirely plausible status: changeName: null, artifactStore: openspec, planningHome: "/openspec". A phase-failure continuation that resolved its working directory to the drive root therefore read as "SDD lost my project" rather than "that command was pointed at the wrong directory". It now refuses and names the runnable continuation.

Engram-backed changes are closed by their archive report. A change that had ever persisted an artifact was reported active forever, because the two stores archive differently and only one leaves a trace the resolver reads: OpenSpec moves the directory out of changes/, Engram moves nothing. Archive already wrote sdd/{change}/archive-report and the title pattern did not recognize it, so the one artifact proving a change was finished was the one never read. Measured on a real store, thirty changes for a single project were reported active, seven of them archived weeks earlier. Naming an archived change still resolves it; only discovery excludes it.

Everything below is unchanged from rc.4.

What to test

Almost everything below was fixed against a written reproduction, not against real use. The paths that most need a human are in the SDD attempt lifecycle:

  • Run a work unit end to end: sdd-attempt acquire, do the work, sdd-attempt settle. Before acquiring, pass the work-unit scope to sdd-attempt status and check it tells you the same thing acquire will.
  • Fail a verification, then correct it. Confirm the correction settles and that nothing demands paperwork you were never told about while the attempt was still open.
  • Spend an objective's attempts until the budget runs out. It should ask you whether to open a fresh one, with a command that runs as printed.
  • Do the same with receipt-driven review disabled at clone scope, and confirm it stays out of the way completely.

The one change to watch. A review binding no longer blocks a passing implementation attempt from closing. The argument is that the delivery gates re-derive their verdict from the candidate actually being delivered, so an unreviewed candidate is still refused at commit, push, PR and release. That argument is verified in tests and unproven in the field. If an unreviewed candidate reaches delivery, that is this change, and it is the single most useful thing this candidate can learn.

What the previous candidates taught us

v2.4.0-rc.1 made refusals name their exit. v2.4.0-rc.3 bound the proof to the candidate the operator is actually delivering. This candidate turns to the surface those two never covered: the SDD attempt ledger, where a refusal could be correct, name a real exit, and still leave the operator with nowhere to go.

The changes remain within the 2.4.0 line and do not widen the public Go API. One fail-closed rule was deliberately removed rather than tightened, and the section below says which and why; every other admission that held in rc.3 still holds.

Review acts after implementation, not during it

Receipt-driven review runs on the finished result: you implement, verify, and only then is review offered, freezing what is already done. Two rules disagreed with that.

A passing implementation attempt was refused whenever the review binding covered the bytes from before the attempt — which it always does, because changing the candidate is what an attempt is for. That is review deciding whether implementation may finish. The one named exit demanded an approved review of the corrected candidate, and producing it opened a fresh correction budget and a fresh reviewer that the contract forbids after independent verification. Four doors, all locked.

The kill switch was also a half-measure. Unmanaged remediation required review to be off and no binding to exist, so a binding created before the operator turned review off kept blocking corrections. While review is off it does not exist, and a leftover binding is an implication.

Neither change weakens delivery. The delivery gates re-derive their verdict from the candidate actually being delivered, so an unreviewed candidate is still refused there, after SDD finishes. The binding stays recorded: review stops deciding, it does not stop being tracked.

The attempt ledger stops contradicting itself

Status reported next_action: begin while acquire blocked on the same request, because Begin evaluated its repository-side preconditions inside its mutation closure where no read-only surface could reach them. Both halves now run through one predicate, so the two surfaces cannot disagree.

Three refusals compared two values and disclosed neither. They now print both sides: the approved tree against the charged tree with a git diff between them, the bound revision against the current one, and the failure a correction names against the one the chain actually holds unremediated. One of them was telling operators their input was wrong when it was correct and merely obsolete.

An exhausted budget used to end the conversation in prose naming a reset the operator had to assemble from six flags. It now asks, with the accounting in front of them and a grant that runs verbatim. Attempts that never ran the work are named as such, because an exhausted budget means something different when none of it was spent on the candidate.

Negotiated transitions come from one authority

Canonical STATUS target and recovery projection moved into reviewtransaction, so CLI adapters render a decision instead of reconstructing it. Staged base-diff requests are canonicalized before target identity is derived, making the START that STATUS emits executable against the same target.

Effective review mode is resolved once, before STATUS publishes either eligibility or a transition, so a disabled clone stops being offered a review it cannot start. An unborn workspace reaches the explicit untracked-selection transition instead of a generic retry. An approved committed base-diff receipt survives a conflict-free local parent merge that carries the identical reviewed patch, reusing the existing compatibility proof rather than minting a new lineage.

The defect reporter stops filing other projects' bugs

The automated handoff asked whether the workflow appeared blocked by a Gentle AI defect, and from the operator's seat everything that blocks them appears to qualify: a model refusing an oversized image, a client runtime needing its session restarted, a sub-agent returning nothing.

The test is now what produced the failure, not what the work was doing when it happened. A Gentle AI invocation producing it is the whole gate; a Gentle AI workflow merely hosting one is not, because the client runtime carries out the work. Everything else is silent — no report, no naming the component believed responsible, no suggesting where else to file it.

Also in this candidate

OpenCode plugin upgrades verify the installed manifest instead of assuming the requested version materialized. Claude workspace MCP servers are written to .mcp.json, the file Claude Code actually reads for project scope, and the inert settings.json block is cleaned up conservatively. The selected persona syncs to Pi, distinguishing an omitted legacy field from an explicit empty one. Historical compact dispositions, retired snapshot identities, frozen lineage resume, and atomic reviewer-slot reads are all tightened.

Install

go install github.com/gentleman-programming/gentle-ai/v2/cmd/gentle-ai@v2.4.0-rc.6

Or download a binary below and verify it against SHA256SUMS.txt.