foremerge: reap stale intents (kanban nix-config#944) #1970

Merged
hermes merged 1 commit from kb/NCFG-944-foremerge-stale-reaper into main 2026-10-13 09:16:13 -06:00
Collaborator

Kanban card nix-config#944.

What

A ledger garbage collector for foremerge, shipped as a second fleet-local patch on the pinned v0.5.0 binary (ec74d7c): patches/foremerge-stale-reap.patch adds foremerge work reap, applied in overlays/foremerge.nix beside the existing foremerge-changeset-release.patch, plus its offline test .forgejo/tests/test-foremerge-stale-reap.sh and the foremerge-stale-reap CI job.

Why (measured 2026-10-13)

Nothing in v0.5.0 ends work whose agent is simply gone: work discard refuses work that is not the caller's, and work adopt needs a successor that is still here. So intents outlive their sessions and hold their claims forever. Publishing one intent on hosts/vpn/default.nix returned 73 open conflicts, 53 of them HIGH, all false positives against prior days' intents; across the four fleet ledgers, 148 dead open intents against 23 live ones. The per-repo collision signal a worker uses to decide whether a peer is really there was pure noise on the fleet's busiest file.

The three gates (the same ones work adopt already enforces)

An intent ends only when all three hold:

  1. its owner is gone - silent past --stale-seconds (default: the ledger's own AGENT_STALE_AFTER_SECONDS, shared with work adopt and the status report, via one silent_for helper);
  2. it holds no claim under a live lease; and
  3. it has no ChangeSet on the acceptance ladder (PROVISIONAL/VALIDATED/ACCEPTED).

Everything else is reported as retained, with the gate that stopped it, so a run distinguishes a clean ledger from a busy one. A reap is never a delete: the intent goes DISCARDED, its claims are released, its open conflicts are dismissed, and one work.reaped event records the reason. --dry-run reports and changes nothing. CLI-only on purpose, like changeset release - a GC is an operator or scheduled action, not a worker's own lifecycle verb.

Test

.forgejo/tests/test-foremerge-stale-reap.sh runs the real binary in a throwaway repo (no fleet ledger, no network, ~4s): default threshold reaps nothing while every owner is live (owner_active); --dry-run at an aggressive threshold reports the abandoned intent and leaves the ledger reading its original state; a live lease withholds (live_claim); an open ChangeSet withholds (open_changeset); the apply ends the unclaimed intent AND the claimed one whose lease had expired, both reading back DISCARDED, with the dead claim released and two work.reaped events on the chain; the reaped intent's open conflict is DISMISSED; a reaped intent is out of the candidate set on the next run; and a negative --stale-seconds is refused.

Wired as job foremerge-stale-reap in nix-build-check.yml, gated on vpn being in scope like the accept-order job. The patch pair was checked to apply in EITHER order to a clean checkout of the pinned rev (a first cut anchored a hunk next to RecordCommitRequest, which made the two patches unappliable together - fixed, and the stacking constraint is now recorded in overlays/foremerge.nix).

The reap path itself is compiled by this job; the live ledger on vpn is exercised after the deploy.

Kanban card nix-config#944. ## What A ledger garbage collector for foremerge, shipped as a second fleet-local patch on the pinned v0.5.0 binary (ec74d7c): `patches/foremerge-stale-reap.patch` adds `foremerge work reap`, applied in `overlays/foremerge.nix` beside the existing `foremerge-changeset-release.patch`, plus its offline test `.forgejo/tests/test-foremerge-stale-reap.sh` and the `foremerge-stale-reap` CI job. ## Why (measured 2026-10-13) Nothing in v0.5.0 ends work whose agent is simply gone: `work discard` refuses work that is not the caller's, and `work adopt` needs a successor that is still here. So intents outlive their sessions and hold their claims forever. Publishing one intent on `hosts/vpn/default.nix` returned **73 open conflicts, 53 of them HIGH, all false positives** against prior days' intents; across the four fleet ledgers, **148 dead open intents against 23 live ones**. The per-repo collision signal a worker uses to decide whether a peer is really there was pure noise on the fleet's busiest file. ## The three gates (the same ones `work adopt` already enforces) An intent ends only when all three hold: 1. its owner is gone - silent past `--stale-seconds` (default: the ledger's own `AGENT_STALE_AFTER_SECONDS`, shared with `work adopt` and the status report, via one `silent_for` helper); 2. it holds no claim under a live lease; and 3. it has no ChangeSet on the acceptance ladder (PROVISIONAL/VALIDATED/ACCEPTED). Everything else is reported as `retained`, with the gate that stopped it, so a run distinguishes a clean ledger from a busy one. A reap is never a delete: the intent goes DISCARDED, its claims are released, its open conflicts are dismissed, and one `work.reaped` event records the reason. `--dry-run` reports and changes nothing. CLI-only on purpose, like `changeset release` - a GC is an operator or scheduled action, not a worker's own lifecycle verb. ## Test `.forgejo/tests/test-foremerge-stale-reap.sh` runs the real binary in a throwaway repo (no fleet ledger, no network, ~4s): default threshold reaps nothing while every owner is live (`owner_active`); `--dry-run` at an aggressive threshold reports the abandoned intent and leaves the ledger reading its original state; a live lease withholds (`live_claim`); an open ChangeSet withholds (`open_changeset`); the apply ends the unclaimed intent AND the claimed one whose lease had expired, both reading back DISCARDED, with the dead claim released and two `work.reaped` events on the chain; the reaped intent's open conflict is DISMISSED; a reaped intent is out of the candidate set on the next run; and a negative `--stale-seconds` is refused. Wired as job `foremerge-stale-reap` in `nix-build-check.yml`, gated on vpn being in scope like the accept-order job. The patch pair was checked to apply in EITHER order to a clean checkout of the pinned rev (a first cut anchored a hunk next to `RecordCommitRequest`, which made the two patches unappliable together - fixed, and the stacking constraint is now recorded in `overlays/foremerge.nix`). The reap path itself is compiled by this job; the live ledger on vpn is exercised after the deploy.
foremerge: reap stale intents (kanban nix-config#944)
Some checks failed
PR base guard / Base drift / conflict check (pull_request) Successful in 3s
NixOS Build Check / Flake input regression guard (pull_request) Successful in 5s
NixOS Build Check / Scope changed deployable hosts (pull_request) Successful in 8s
Secret Scan / gitleaks scan (pull_request) Successful in 8s
NixOS Build Check / Hermes runtime gates (one eval of vpn's runtime) (pull_request) Successful in 1m7s
NixOS Build Check / Worker-scope reaper worker signature (mutation-tested) (pull_request) Successful in 3s
NixOS Build Check / Kanban pool capacity knob (runtime override + clamp, mutation-tested) (pull_request) Successful in 3s
NixOS Build Check / Sentinel pass budget contract (prompt forbids the full board walk, mutation-tested) (pull_request) Successful in 4s
NixOS Build Check / CI-scratch reaper class table (mutation-tested) (pull_request) Successful in 7s
foremerge intent (advisory) all 4 changed path(s) covered by a live foremerge intent
NixOS Build Check / Repo gates (the folded selftest battery) (pull_request) Successful in 3m43s
NixOS Build Check / Build changed host closures (native, one eval) (pull_request) Failing after 10m4s
NixOS Build Check / .forgejo/tests python suites (hermetic) (pull_request) Successful in 29s
NixOS Build Check / foremerge stale-intent reaper (real binary) (pull_request) Failing after 10m10s
NixOS Build Check / foremerge accept-order contract (real binary) (pull_request) Successful in 14m40s
f3d077da2d
The foremerge ledger has no garbage collector. `work discard` refuses work
that is not the caller's and `work adopt` needs a live successor, so an
agent that published an intent, claimed it, and then died leaves
CLAIMED/IN_PROGRESS/PROVISIONAL rows holding their claims forever and every
later publication on those files collides with a ghost. Measured
2026-10-13: publishing one intent on hosts/vpn/default.nix returned 73 open
conflicts (53 HIGH), all against prior days' intents whose owners had no
session anywhere, and the four fleet ledgers held 148 dead intents against
23 live ones - the collision signal a worker uses to decide whether a peer
is really there was pure noise on the fleet's busiest file.

patches/foremerge-stale-reap.patch adds `foremerge work reap`, a second
fleet-local patch on the pinned v0.5.0 binary (ec74d7c, applied in
overlays/foremerge.nix beside the changeset-release patch). It ends an
intent only when the same three gates the per-intent door enforces all
hold - owner silent past --stale-seconds (default: the ledger's own agent
staleness threshold), no claim under a live lease, no ChangeSet on the
acceptance ladder - and reports everything else as retained with the gate
that stopped it. An intent is never deleted: DISCARDED, claims released,
open conflicts dismissed, one work.reaped event per intent. CLI-only on
purpose, like `changeset release`: a GC is an operator or scheduled action,
not a worker's own lifecycle verb.

.forgejo/tests/test-foremerge-stale-reap.sh exercises it against the real
binary in a throwaway repo: default threshold reaps nothing (owner_active),
dry-run reports without changing the ledger, a live lease withholds
(live_claim), an open ChangeSet withholds (open_changeset), the apply ends
the unclaimed intent and the one whose lease expired, the dead intent's
conflict is dismissed, a reaped intent is out of the candidate set, and a
negative threshold is refused. Wired as the `foremerge-stale-reap` job in
nix-build-check.yml, gated on vpn being in scope like the accept-order job.

Co-Authored-By: Hermes Agent <noreply@nousresearch.com>
hermes force-pushed kb/NCFG-944-foremerge-stale-reaper from f3d077da2d
Some checks failed
PR base guard / Base drift / conflict check (pull_request) Successful in 3s
NixOS Build Check / Flake input regression guard (pull_request) Successful in 5s
NixOS Build Check / Scope changed deployable hosts (pull_request) Successful in 8s
Secret Scan / gitleaks scan (pull_request) Successful in 8s
NixOS Build Check / Hermes runtime gates (one eval of vpn's runtime) (pull_request) Successful in 1m7s
NixOS Build Check / Worker-scope reaper worker signature (mutation-tested) (pull_request) Successful in 3s
NixOS Build Check / Kanban pool capacity knob (runtime override + clamp, mutation-tested) (pull_request) Successful in 3s
NixOS Build Check / Sentinel pass budget contract (prompt forbids the full board walk, mutation-tested) (pull_request) Successful in 4s
NixOS Build Check / CI-scratch reaper class table (mutation-tested) (pull_request) Successful in 7s
foremerge intent (advisory) all 4 changed path(s) covered by a live foremerge intent
NixOS Build Check / Repo gates (the folded selftest battery) (pull_request) Successful in 3m43s
NixOS Build Check / Build changed host closures (native, one eval) (pull_request) Failing after 10m4s
NixOS Build Check / .forgejo/tests python suites (hermetic) (pull_request) Successful in 29s
NixOS Build Check / foremerge stale-intent reaper (real binary) (pull_request) Failing after 10m10s
NixOS Build Check / foremerge accept-order contract (real binary) (pull_request) Successful in 14m40s
to 86860a7bc8
All checks were successful
NixOS Build Check / Flake input regression guard (pull_request) Successful in 4s
foremerge intent (advisory) all 4 changed path(s) covered by a live foremerge intent
PR base guard / Base drift / conflict check (pull_request) Successful in 3s
NixOS Build Check / Scope changed deployable hosts (pull_request) Successful in 8s
Secret Scan / gitleaks scan (pull_request) Successful in 9s
NixOS Build Check / Hermes runtime gates (one eval of vpn's runtime) (pull_request) Successful in 1m6s
NixOS Build Check / Worker-scope reaper worker signature (mutation-tested) (pull_request) Successful in 2s
NixOS Build Check / Kanban pool capacity knob (runtime override + clamp, mutation-tested) (pull_request) Successful in 1s
NixOS Build Check / Sentinel pass budget contract (prompt forbids the full board walk, mutation-tested) (pull_request) Successful in 1s
NixOS Build Check / CI-scratch reaper class table (mutation-tested) (pull_request) Successful in 2s
NixOS Build Check / foremerge accept-order contract (real binary) (pull_request) Successful in 1m9s
NixOS Build Check / Repo gates (the folded selftest battery) (pull_request) Successful in 2m49s
NixOS Build Check / .forgejo/tests python suites (hermetic) (pull_request) Successful in 35s
NixOS Build Check / Build changed host closures (native, one eval) (pull_request) Successful in 3m43s
2026-10-13 09:05:52 -06:00
Compare
hermes merged commit 6a37b0061a into main 2026-10-13 09:16:13 -06:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ymrtech/nix-config!1970
No description provided.