feat(improvement): a verify disposition that can pass a tree and keep spending shots - #1008
Merged
Conversation
… spending shots `agenticGenerator`'s `Verifier` answered one boolean, and `ok` meant both "this tree is shippable" and "stop now". The shot loop returned inside `if (result.ok)` and no option overrode it, so best-of-n was not expressible. `VerifyResult` gains two optional fields. `keepGoing` spends the remaining shots after a passing verdict. `score` ranks a passing tree against the other passing trees of the same candidate. The loop snapshots a banked tree as a Git tree object, and restores the highest-scoring one into the worktree before it returns the candidate, so a caller never writes a passing tree back itself. A verifier that returns today's shape keeps today's semantics, and an explicit gate asserts it: 1 of 3 shots fires and the accepted shot's own tree lands. `onShotDisposition` gains the `kept` kind (score, best, feedback) and `accepted` gains `restoredFromShot`.
tangletools
approved these changes
Aug 23, 2026
tangletools
left a comment
Contributor
There was a problem hiding this comment.
Approved: widens VerifyResult with optional keepGoing/score, backward compatible with explicit COMPATIBILITY gates. agenticGenerator now owns the best-tree restore, proved by tree-id comparison before return.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The gap
agenticGenerator'sVerifieranswered one boolean, andokcarried two meanings at once — "this tree is shippable" and "stop now". The shot loop returns insideif (result.ok), andAgenticGeneratorOptionsexposed no field to override it. So a caller who wanted to spend the shot budget it was given and keep the best tree — ordinary best-of-n — could not say so.The consumer's workaround, which is the motivation
agent-lab's playproof study (
projects/playproof/verify-budget.ts) needed best-of-n and encoded it by hand:Step 3 exists only because the substrate ships the worktree: with one boolean, "ship the best tree" has to mean "make the best tree BE the worktree". Their own module says so — "The upstream change that would remove the restore is one field on
AgenticGeneratorOptions: a disposition richer than a boolean."Their measured runs are why it matters. Under first-acceptance-wins, 1 shot of 3 fired and the program that shipped was never once run by its author. Under the workaround, 3 of 3 fired and every shot ran its own program.
The shape chosen
VerifyResultis widened with two optional fields, not split into a union — an existing verifier's return value is still a validVerifyResultand still gets today's semantics.okkeepGoingscoreTwo fields rather than one, because they are independent:
keepGoingalone is "spend the budget, ship the final state";scorealone would be meaningless under first-acceptance-wins. The disposition lives on the RESULT rather than onAgenticGeneratorOptions, because it is a per-shot decision the caller already makes per shot — an option could only express a static policy, and could not say "this tree is good enough, stop early".The generator owns the restore
That is the half that removes the workaround. A passing tree whose verifier asks for another shot is snapshotted as a Git tree object (
git add --allinto a private index file, so the index the driver commits from is untouched). When the budget ends, the highest-scoring tree is restored —read-tree -u --resetplusclean -f -d, so content, added files, and the removal of files only a losing shot wrote all land — and the restore is proved by re-snapshotting and comparing tree ids before the candidate is returned. The caller ranks; the runtime moves the bytes.A last shot that breaks or reverts the change no longer costs the candidate: a banked tree passed verification, so it ships. The failing shot's own
rejecteddisposition is still emitted first, so its evidence survives, and the invariant holds — a tree that failed verification is never what ships.Compatibility is an explicit gate
tests/agentic-generator.test.tsdrives the realagenticGeneratorwith a verifier returning today's shape and asserts today's behaviour:COMPATIBILITY: a verifier returning todays shape still stops at the first passing tree— 1 of 3 shots fires, the disposition stream is exactly['accepted']withrestoredFromShot: null, and the accepted shot's own tree is what lands.COMPATIBILITY: a failing verifier still feeds the next shot and never ships—verification FAILEDreaches shot 2, all three dispositions arerejected,applied: false.Six more gates cover the new path: every shot fires under
keepGoing; the best tree lands when the best is not the last (including the losing shot's extra file being removed); a banked tree ships when the last shot breaks the change; a banked tree ships when the last shot reverts every edit; nothing ships when no shot ever verifies; a set of trees that cannot be ordered (scored and unscored mixed) and a non-finite score both throw.Surface changes
VerifyResult:+keepGoing?: boolean,+score?: number.AgenticGeneratorShotDisposition: newkeptvariant (score,best,feedback);acceptedgainsrestoredFromShot: number | null.src/improvement/worktree-tree.ts(not exported).cli-worktree,cli-in-placeandcommandVerifierare untouched.Version bump
check:version-bumprefused the branch for the two export-shape changes.pnpm run release:prepare 0.174.0is an isolated final commit (chore(release): 0.174.0) — drop it if you would rather cut the version yourself. No tag was cut.Proof
The one failure is the known pre-existing macOS flake,
tests/improvement-driver.test.ts > forks isolated generation-two candidates from the promoted generation-one surface, which times out at 20s under full-suite load and passes in isolation (11 passed (11), 13.31s). It fails identically atorigin/mainand is green on CI Linux. Untouched by this branch.Both sides of every path comparison are canonicalised before comparing, per this repo's
/var→/private/varhistory.