emrg: planted-fire staleness alarm — last_planted age check (issue #1086) - #1088
Conversation
) Reader suggestion (heinrichneb, Dev.to 3doei): a planted fire that stops being scheduled rots as silently as the detector it guards. Add the third layer of the meta-loop: - Marker: _refresh_usage_anchor touches a single-line timestamp marker (~/.emrg/logs/planted-fire-heartbeat) on every round, including skip paths — proves round-loop liveness, not just detector runs. - Alarm: _check_planted_fire_stale reads the marker age; logs greppable 'planted-fire-stale' warning when age > 7 days (configurable _PLANTED_FIRE_STALE_DAYS next to _SILENT_DRIFT_THRESHOLD). Idle boundary per accepted option (a): a stale marker is the same actionable signal whether the detector died or the daemon was idle. Missing/ unparsable marker (fresh daemon) does not false-alarm. - Cadence: _planted_fire_alarm_loop every 6h, registered at daemon startup, failures debug-logged (never crash the daemon). +5 tests (positive + negative states): marker written per round incl. skip path; stale marker alarms; fresh marker silent; missing marker silent; unparsable marker silent. pytest 1190 passed + 1 skipped; Agent.md count 1186→1191.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle
Reviewed the diff (3 files, +177/-2) and verified locally on branch feature/planted-fire-staleness-alarm:
- daemon.py:
_touch_planted_fire_marker()runs at the top of_refresh_usage_anchorso every round (including skip paths) persists a single-line heartbeat;_check_planted_fire_stale()is stateless (age > 7 days → greppableplanted-fire-stalewarning); missing/corrupt marker → debug log, no false alarm; 6h_planted_fire_alarm_loopregistered at startup, exceptions never crash the daemon. - tests/test_daemon.py: 5 new tests covering both positive AND negative states — marker written on skip + normal paths, stale marker fires, fresh/missing/unparsable marker no-alarm. All 5 pass locally.
- Agent.md: test count 1186 → 1191, matches
pytest --collect-only(1191 collected). - CI: run 33376430101 — test + test-windows both SUCCESS. PR mergeable, state CLEAN.
The implementation matches issue #1086 acceptance criteria (persistent marker + age alarm, N=7 days, rides round cadence, idle boundary = same actionable signal, positive+negative tests).
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle
Re-verified on unchanged head e741c79 (1 commit, +177/-2):
- CI run 33376430101: test + test-windows both SUCCESS.
- Mergeable, state CLEAN.
- Local checks (previous cycle): 5 planted-fire tests pass,
pytest --collect-only= 1191 matches Agent.md.
Implementation matches issue #1086 acceptance criteria: per-round marker (incl. skip paths), stateless 7-day age check with no false-alarm on missing/corrupt marker, 6h alarm loop, positive+negative tests. LGTM 2/3.
pm25coder
commented
Aug 31, 2026
I tested this PR against the acceptance criteria from issue #1086 (which I filed), and found the implementation meets all of them — including the verification-type logic in both positive and negative states. Marker (persistence) — Alarm (stateless age check) — Idle boundary (option a) — verified both branches: missing marker (fresh daemon, never ran a round) → debug log + False (no false alarm on a brand-new install); stale marker (detector died OR daemon idle for N days) → same actionable warning. Timezone handling is safe: marker written with Negative states all covered — fresh marker → no alarm; missing marker → no alarm; unparsable marker ( Non-blocking observations:
No functional concerns on my side. |
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle
Third review from a distinct cycle. Head e741c79 unchanged (1 commit, +177/-2); CI run 33376430101 test + test-windows both SUCCESS; MERGEABLE/CLEAN. Local verification (prior cycles): 5 planted-fire tests pass, pytest --collect-only = 1191 matches Agent.md. Meets issue #1086 acceptance criteria. LGTM 3/3 — merging.
Uh oh!
There was an error while loading. Please reload this page.
Implements issue #1086 (reader suggestion from heinrichneb on post 4, Dev.to 3doei): a planted fire that stops being scheduled rots as silently as the detector it guards — the meta-loop needs a third, boring layer: an age alarm on the planted fire's last_planted timestamp.
Changes
Marker (persistence) —
_refresh_usage_anchornow touches a single-line timestamp marker (~/.emrg/logs/planted-fire-heartbeat) on every round, including the skip paths (no usage / no prompt_tokens). The marker tracks round-loop liveness, not just detector runs — consistent with the #1073 heartbeat philosophy. Overwrite (not append) so the age check is a one-line read; OSError is debug-logged (stats writes never take the daemon down).Alarm (stateless age check) —
_check_planted_fire_stale()reads the marker's age and logs the greppableplanted-fire-stalewarning whennow - last_heartbeat > Ndays (N = 7,_PLANTED_FIRE_STALE_DAYS, kept next to_SILENT_DRIFT_THRESHOLDper the issue discussion). Idle boundary per the accepted option (a): a stale marker is the same actionable signal whether the detector died or the daemon was idle (surface-the-silence, consistent with #585/#1073). Missing/unparsable marker (fresh daemon, never ran a round) does NOT false-alarm — debug log, returns False.Cadence —
_planted_fire_alarm_loop()runs every 6h (low-frequency; checking a timestamp's age is cheap), registered at daemon startup; failures are debug-logged and never crash the daemon.Tests (positive + negative states, per the issue's acceptance criteria)
test_planted_fire_marker_written_every_round— marker persisted on skip path AND normal round; single line, timestamp advances.test_planted_fire_stale_alarm_fires— marker age > N →planted-fire-stalewarning + True.test_planted_fire_fresh_marker_no_alarm— fresh marker → no alarm.test_planted_fire_no_marker_no_alarm— missing marker (fresh daemon) → no alarm.test_planted_fire_unparsable_marker_no_alarm— corrupted marker → no alarm, no crash.Full local verification: pytest 1190 passed + 1 skipped (Agent.md count 1186→1191), import + CLI green.
Fixes#1086