Other

/crash-resume-index

Durable surface→session_id index so /orc RESUMES a crashed lead (repoGolem --resume) instead of duplicate-spawning a fresh one. Triggers: crash resume, resume not respawn, lead crashed, R-036, SPAWN_OVER_RESUMABLE green path, session_id lookup.

$ golems-cli skills install crash-resume-index

Updated 1 week ago

The data layer for resume-not-respawn (R-036). Maps each cmux surface/pane to the Claude session_id running in it, durably — so when a lead pane crashes (RAM reboot, OOM, cmux restart), /orc can repoGolem --resume <session-id> instead of spawning a fresh lead and discarding the crashed session's context.

Why it matters: orchestrator/263b3559#1 spawned-over-a-resumable and threw away ~386K tokens of accumulated lead context. The fix is to remember which session was on which surface, across a reboot, and resume it.

The flow: capture-on-boot → persist → resume-lookup

  1. Capture on boot — when a pane boots a Claude lead/worker, call record(idx, {surfaceId, sessionId, repo, role}). First sight stamps captured_at; every re-record bumps last_active.
  2. Persist durablysaveIndex(path, idx) writes (atomically: temp + rename) to a durable json path, default ~/.golems/crash-resume-index.json. NEVER /tmp — a reboot wipes /tmp, which is the exact failure this index survives (and a tmp-block hook denies /tmp writes anyway).
  3. Resume-lookup after a crash — on reboot, loadIndex(path) rehydrates from disk. lookup(idx, surfaceId) → the entry; resumableFor(idx, surfaceId, {maxAgeMs}) → the session_id to resume (or null if unknown / too stale). resumeCommand(...) emits the registered launcher form <repo>Claude --resume <session-id> (e.g. orcClaude --resume …, golemsClaude --resume …) — the actual repoGolem launchers, not a global repogolem binary.

Staleness is opt-in (capture-on-boot caveat)

maxAgeMs is opt-in, not a default: capture-on-boot only stamps last_active at boot, so a lead that crashed hours after boot would look "stale" and be wrongly denied resume if a maxAge were imposed by default (Cursor HIGH). So: resumableFor/resumeCommand with no maxAgeMs accept any recorded session. A caller that does want a liveness window should keep last_active fresh by calling touch(idx, surfaceId) on observed liveness (Track 3's hook does this on a screen read / inbox tick).

Concurrency (two panes booting at once)

record via the CLI / Track 3 goes through recordToFile(indexPath, entry) — a locked read-modify-write (a <index>.lock directory is the atomic mutex; a clearly-stale lock from a crashed holder is stolen). Without it, two simultaneous boots each load the same snapshot and the later saveIndex drops the earlier pane's entry (Cursor MEDIUM / Codex P2). Verified: 10 parallel CLI captures → all 10 entries persist.

How it FEEDS idle-dwell-gate's SPAWN_OVER_RESUMABLE green path

The merged idle-dwell-gate skill FLAGS SPAWN_OVER_RESUMABLE — a fresh spawn_agent over a resumable crashed lead. That gate's GREEN path is "there IS a resumable session, so resume instead of spawn." This module is the index that answers which session: resumableFor(surfaceId) returns the session_id the gate's green path resumes. The gate decides whether to resume; this module supplies what to resume.

Convergence with Track 3 (coordinate, don't duplicate)

This module is the data layer + lookup (pure functions + durable persistence). The live cmux capture-on-boot wiring — the MCP hook that actually calls record() when a pane boots a session — is Track 3's crash-resume work. Track 3 writes through this module; do not re-implement the index there.