Summary
The claim-reaper's HeartbeatStale investigate-before-reap path never converges: the agentic investigation recipe's terminal verdict is never read back or persisted, so ProductionInvestigator::investigate can only ever emit Pending/StillAlive — never Dead. The reap branch (src/overseer/claim_reaper.rs:341 if !outcome.verdict.should_reap()) is therefore structurally unreachable for a heartbeat-stale engineer. Every sweep re-investigates, re-archives evidence, and re-dispatches the recipe — forever.
This is a distinct root cause from #4462 (stale-assignment sweep non-idempotency), #4464 (claim not released on session completion), and #4449 (evidence collector archives the wrong files). Cross-referenced below.
Evidence (durable archive, one goal, ~14h)
Goal advance-rysweet-agent-kgpacks-rs-to-full-parity-f29bb15c was archived 10 times (/home/azureuser/.simard/reaped-engineers/…-f29bb15c-{1784692815…1784746010}). Manifests show the same worktree re-archived with monotonically climbing idle_age and never reaped:
worktree …-1784696317-da9e82 archived 5× : idle 2177 → 8040 → 13403 → 19082 → 28764s (never reaped)
worktree …-1784733114-bba449 archived 3× : idle 3196 → 8452 → 12894s (still pending)
Journal (…-1784746010/journal.txt) reaper lines, chronological:
12:58 NOT reaping … (verdict=pending, claim + evidence preserved) + evidence archived, dispatching investigation
13:20 NOT reaping … (verdict=pending)
14:23 reclaimed … (reason=no-worktree, age=n/a, verdict=no-investigation)
15:01 reclaimed … (reason=no-worktree, age=n/a, verdict=no-investigation)
16:05 NOT reaping … (verdict=pending) + evidence archived, dispatching investigation
16:53 NOT reaping … (verdict=pending) # epoch reused (<1h since 16:05)
17:32 NOT reaping … (verdict=pending) + evidence archived, dispatching investigation # re-mint (>1h)
18:13 NOT reaping … (verdict=pending)
verdict=pending recurs 6× for this claim with no convergence.
Root causes
A. Verdict write-back gap (primary). reconcile_inflight_investigations (src/overseer/mod.rs:1363-1378) polls the recipe handle and, on completion, only .remove()s it — the recipe's terminal conclusion is discarded, never parsed into an InvestigationVerdict and never persisted for the next sweep to consult. InvestigationVerdict::Dead is constructed only in tests. So investigate() (claim_reaper.rs:1248-1309) returns Pending on dispatch/reuse and StillAlive only on archive error — the reaper can never reap a heartbeat-stale engineer.
B. Freshness-window vs sweep-cadence mismatch (amplifier). ARCHIVE_FRESHNESS_WINDOW = 3600s (claim_reaper.rs:652) is the cross-tick dedupe that suppresses re-archive/re-dispatch. Observed sweep cadence is ~5300s (~88 min) > 3600s, so the epoch ages out before the next sweep → minted=true → fresh archive + fresh LaunchRecipe every sweep (hence 10 durable archives). When sweeps happen <1h apart the dedupe works (16:05→16:53 reused), proving the window is simply too short for the actual cadence.
C. NoWorktree fast-path has no claim-age grace → premature reclaim of nascent/in-flight claims. claim_reaper.rs:332-333 maps DeadReason::NoWorktree => None (unconditional reap, bypassing the #4400 investigation gate). At 14:23 & 15:01 the claim was reclaimed reason=no-worktree while a HeartbeatStale investigation was in-flight (13:20 pending) and before the replacement worktree bba449 materialized (created 15:11, i.e. AFTER the 15:01 reclaim). Two back-to-back no-worktree reclaims of the same claim_key indicate a claim-before-worktree race: a freshly-claimed goal whose worktree hasn't been created yet is indistinguishable from a dead engineer whose worktree was removed → premature reap + orphaned in-flight investigation → goal churn.
Impact
Suggested fix surface (smallest systemic)
- Persist + write back the terminal verdict. In
reconcile_inflight_investigations, on non-Running completion read the investigation recipe's conclusion and persist a per-claim_key terminal InvestigationOutcome (atomic single-snapshot, mirroring the no-progress loop's I3 persisted-dedupe / I7 atomic write). Have investigate() consult that record first: a persisted Dead reaps on the next sweep; a persisted non-Dead short-circuits re-dispatch/re-archival.
- Align the dedupe window to cadence — set
ARCHIVE_FRESHNESS_WINDOW ≥ the reaper sweep interval (or key reuse on "investigation still outstanding" rather than a fixed wall-clock hour).
- Add a claim-age grace to the NoWorktree branch — do not reclaim
no-worktree until the claim is older than a short worktree-materialization grace, and skip NoWorktree reclaim while an investigation for that claim is in-flight.
Related
Filed from a stale-engineer investigation (claim rysweet/Simard:advance-rysweet-agent-kgpacks-rs-to-full-parity-f29bb15c). Engineer verdict itself is separate; this issue is the reaper/investigation-loop defect.
Summary
The claim-reaper's HeartbeatStale investigate-before-reap path never converges: the agentic investigation recipe's terminal verdict is never read back or persisted, so
ProductionInvestigator::investigatecan only ever emitPending/StillAlive— neverDead. The reap branch (src/overseer/claim_reaper.rs:341if !outcome.verdict.should_reap()) is therefore structurally unreachable for a heartbeat-stale engineer. Every sweep re-investigates, re-archives evidence, and re-dispatches the recipe — forever.This is a distinct root cause from #4462 (stale-assignment sweep non-idempotency), #4464 (claim not released on session completion), and #4449 (evidence collector archives the wrong files). Cross-referenced below.
Evidence (durable archive, one goal, ~14h)
Goal
advance-rysweet-agent-kgpacks-rs-to-full-parity-f29bb15cwas archived 10 times (/home/azureuser/.simard/reaped-engineers/…-f29bb15c-{1784692815…1784746010}). Manifests show the same worktree re-archived with monotonically climbing idle_age and never reaped:Journal (
…-1784746010/journal.txt) reaper lines, chronological:verdict=pendingrecurs 6× for this claim with no convergence.Root causes
A. Verdict write-back gap (primary).
reconcile_inflight_investigations(src/overseer/mod.rs:1363-1378) polls the recipe handle and, on completion, only.remove()s it — the recipe's terminal conclusion is discarded, never parsed into anInvestigationVerdictand never persisted for the next sweep to consult.InvestigationVerdict::Deadis constructed only in tests. Soinvestigate()(claim_reaper.rs:1248-1309) returnsPendingon dispatch/reuse andStillAliveonly on archive error — the reaper can never reap a heartbeat-stale engineer.B. Freshness-window vs sweep-cadence mismatch (amplifier).
ARCHIVE_FRESHNESS_WINDOW = 3600s(claim_reaper.rs:652) is the cross-tick dedupe that suppresses re-archive/re-dispatch. Observed sweep cadence is ~5300s (~88 min) > 3600s, so the epoch ages out before the next sweep →minted=true→ fresh archive + freshLaunchRecipeevery sweep (hence 10 durable archives). When sweeps happen <1h apart the dedupe works (16:05→16:53 reused), proving the window is simply too short for the actual cadence.C. NoWorktree fast-path has no claim-age grace → premature reclaim of nascent/in-flight claims.
claim_reaper.rs:332-333mapsDeadReason::NoWorktree => None(unconditional reap, bypassing the #4400 investigation gate). At 14:23 & 15:01 the claim was reclaimedreason=no-worktreewhile a HeartbeatStale investigation was in-flight (13:20 pending) and before the replacement worktreebba449materialized (created 15:11, i.e. AFTER the 15:01 reclaim). Two back-to-back no-worktree reclaims of the same claim_key indicate a claim-before-worktree race: a freshly-claimed goal whose worktree hasn't been created yet is indistinguishable from a dead engineer whose worktree was removed → premature reap + orphaned in-flight investigation → goal churn.Impact
LaunchRecipedispatches burn agent budget.Suggested fix surface (smallest systemic)
reconcile_inflight_investigations, on non-Running completion read the investigation recipe's conclusion and persist a per-claim_keyterminalInvestigationOutcome(atomic single-snapshot, mirroring the no-progress loop's I3 persisted-dedupe / I7 atomic write). Haveinvestigate()consult that record first: a persistedDeadreaps on the next sweep; a persisted non-Dead short-circuits re-dispatch/re-archival.ARCHIVE_FRESHNESS_WINDOW≥ the reaper sweep interval (or key reuse on "investigation still outstanding" rather than a fixed wall-clock hour).no-worktreeuntil the claim is older than a short worktree-materialization grace, and skip NoWorktree reclaim while an investigation for that claim is in-flight.Related
evidence.txt)Filed from a stale-engineer investigation (claim
rysweet/Simard:advance-rysweet-agent-kgpacks-rs-to-full-parity-f29bb15c). Engineer verdict itself is separate; this issue is the reaper/investigation-loop defect.