Smoke run 3 showed agents reaped at the 3-min stale-claim window
while they were actively retrying rejected verbs. Two causes:
1. The reaper threshold was hardcoded at 180s via claim_stale_seconds.
LLM inference + retry loops routinely take longer than that between
verb-successes. Added settings.stale_claim_reap_seconds (default
600s); override via ROBOCO_STALE_CLAIM_REAP_SECONDS env var.
claim_stale_seconds (spawn-filter cutoff) is unchanged at 180s.
2. last_heartbeat_at only refreshed on verb SUCCESS. A verb stuck
in a rejection loop (e.g. tracing_gap missing journal:decision)
showed no heartbeat updates even though the agent was alive.
Added a best-effort heartbeat refresh inside _emit_rejection so
EVERY verb dispatch — success or rejection — counts as activity.
Heartbeat approach: option (b) — touch inside _emit_rejection (single
centralized rejection path). Requires no middleware layer, no HTTP body
parsing, and no new files. The _touch guard for task_id=None means
agent-level rejections (no task context) are a safe no-op.
Net effect: agents stop being reaped mid-retry. Genuinely-stuck
containers (no verb dispatch at all) still reap normally at 600s.
Spec ref: Wave C Task C3.