Skip to content

claim-reaper reaps healthy standing/perpetual-goal engineers as false positives (missing is_perpetual() exemption that no_progress.rs already applies) #4437

Description

@rysweet

Summary (self-improvement signal from a stale-engineer investigation, #4400 path)

The periodic claim-reaper (src/overseer/claim_reaper.rs) reaps healthy standing/perpetual-goal engineers as false positives. Its liveness probe judges death purely from worktree presence + newest-file mtime idle age and never inherits the is_perpetual() benign-idle exemption the OODA no-progress breaker already applies (src/ooda_loop/no_progress.rs:407-420, issue #2589). The two subsystems therefore disagree: the daemon logs "standing/perpetual goal idled … goal stays active" while the reaper reclaims the same claim_key on heartbeat-stale/no-worktree.

Diagnosed during an overseer stale-engineer investigation of goal advance-rysweet-agent-kgpacks-rs-to-full-parity-f29bb15c (a perpetual goal). Archived evidence at ~/.simard/reaped-engineers/rysweet_Simard_advance-rysweet-agent-kgpacks-rs-to-full-parity-f29bb15c-1784692815/.

Evidence (line-cited, from the durable archive)

The engineer was ALIVE, not dead — zero crash signals:

  • journal.txt: 0 real panics / SIGKILL / signal:9 / OOM / core-dumped / non-zero-exit / recipe-runner-failure / brain-call-failure. All 65 raw panic hits are tokens=[…] memory-array noise (65 hits, 65 on tokens=[ lines → 0 real).
  • OODA cycles advanced monotonically 2369 → 2387; last real activity journal.txt cycle 2387 @ 2026-07-22T03:57:39Z: OODA consolidation: stored procedure 'pr-merge:advance-rysweet-agent-kgpacks-rs-to-full-parity-f29bb15c | triggers…' immediately before benign-idle @ 03:58:08.
  • 14× benign-idle: no-progress breaker: standing/perpetual goal idled this cycle (normal, not a fault) — counter reset, goal stays active.
  • manifest.json: idle_age_secs=2171 (worktree newest-file mtime age = normal bursty quiescence), archived 1784692815 (2026-07-22T04:00:15Z, ~2 min after last activity).

The reaper reclaimed the live claim anyway (the bug):

Root cause (code seam)

Precedent for the safer pattern: the tombstoned-goal reaper (#4242) reaps only on an authoritative signal (tombstone), never on mere absence/quiescence.

Scoped corrective brief (minimal, additive, non-breaking, CI-green)

Make the reaper honor the daemon's benign-idle classification for perpetual goals:

  1. Give reap_stale_claims access to a perpetual-goal predicate (e.g. pass a &dyn PerpetualGoalOracle / goal-board handle, or a is_perpetual: impl Fn(&str)->bool), resolving the goal via goal_id_from_claim_key.
  2. In the loop (claim_reaper.rs:162-183), after a confident Dead verdict but before reclaim, skip (continue, count as exempted) any goal where is_perpetual() is true and the verdict is HeartbeatStale (benign idle). Reuse the SAME is_perpetual() flag (goal_curation/types.rs:350) — never a second notion. NoWorktree on a perpetual goal should route through the Make Simard investigate a quiet/idle engineer BEFORE ever reaping it (evidence-preserve + agentic WHY, feed self-improvement; only reap if genuinely dead). READ THE FULL AUTHORITATIVE BRIEF and follow #4400 investigation rather than a bare verdict=no-investigation reclaim.
  3. Emit a fail-visible exemption log mirroring no_progress.rs ("standing/perpetual goal idle — reaper exempt").
  4. Keep fail-closed behavior and the SIMARD_CLAIM_REAP_ENABLED off-switch intact. Add a unit test: a perpetual goal past stale_secs is exempted; a non-perpetual goal past stale_secs is still reclaimed.

Constraints: no Bridge naming; structured tracing + OTel, no stray print!; no silent fallbacks.

Dedup

Searched rysweet/Simard issues for reaper/perpetual/heartbeat-stale/false-positive — no existing open tracking issue. #4099 (reaper impl, CLOSED) and #4400 (investigate-before-reap, CLOSED) are related predecessors; this tracks the missing is_perpetual() exemption they left open.

Verdict of the investigation

still-alive (false positive) — fail-closed. The engineer was NOT reaped for death; no claim release / worktree removal was performed by this investigation. The worktree's absence was the reaper's own prior action (reason=no-worktree), not death evidence.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingworkflow:defaultCreated by default-workflow recipe

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions