Motivation
On a subscription-billed Claude account the prompt cache has a one-hour TTL. A crewmate that has been idle for longer than that (parked at a gate, waiting on an owner decision, done and awaiting merge) reprocesses its entire context uncached on the next steer, which is real quota burn - and on a busy fleet those revivals add up to a large share of the daily budget.
Proposal: opt-in idle-worker pre-cache-expiry compaction
An opt-in housekeeping pass that compacts a genuinely idle Claude crewmate while its cache is still warm, so a later revival is cheap. Ships inert exactly like Relay: an absent, gitignored config/idle-compact means no behavior change anywhere. When present, its first non-empty line is the idle threshold in whole minutes (recommended 30); an empty-but-present file enables the documented default; a malformed value behaves like an absent file so a typo can never fail the watcher loop.
Shared owner bin/fm-idle-compact.sh, called from both bin/fm-watch.sh's main loop and bin/fm-supervise-daemon.sh's housekeeping tick on their existing cadence (one classifier, two callers - the same shape as fm-classify-lib.sh).
Eligibility is the strict intersection of:
kind != secondmate and harness is claude (the only harness with a verified /compact today);
bin/fm-crew-state.sh's reconciled state is parked, done, blocked, or paused - never working;
- the task's last activity of any kind (meta, status log, turn-ended marker, pane-activity stamp) is at least the threshold old;
- a live safety gate immediately before typing:
fm_busy_classify reports an exact idle verdict and the composer reads affirmatively empty; a busy pane, an unproven verdict, or a non-empty composer defers to the next sweep.
A durable per-task marker (state/.idle-compact-<task>) drives a 4-phase state machine so one idle episode produces at most one compaction:
- eligible -> guarded message asking the crewmate to write its open decision keys, current gate/step, next actions, and key file paths to
data/<id>/precompact-notes.md (survives teardown);
- notes present -> send
/compact;
- after the compaction -> steer the crewmate to re-read its notes;
- the marker doubles as the activity stamp the next idle window is measured from, so a compacted worker is not re-compacted until it has been active again.
Everything is verified by the existing daemon test shard plus a new tests/fm-daemon.test.sh case; docs in docs/configuration.md ("Idle-worker pre-compaction"), docs/architecture.md, docs/scripts.md, and docs/verification/runtime-backends.md.
Relationship to #2341
#2341 standardizes durable pre-compaction task notes as part of the worker brief contract. This proposal is the trigger side: it produces exactly that note before compacting and re-reads it afterward, so the two compose - #2341's note format could become the file this pass asks for.
Reference implementation
A working implementation on top of main (single commit, ~860 lines incl. tests and docs) is at https://github.com/NewAiCoder/firstmate/tree/keep/idle-compact-eec826e - happy to rebase it onto current main and open a PR if the shape is welcome, or to adapt it to whatever contract you prefer.
Motivation
On a subscription-billed Claude account the prompt cache has a one-hour TTL. A crewmate that has been idle for longer than that (parked at a gate, waiting on an owner decision, done and awaiting merge) reprocesses its entire context uncached on the next steer, which is real quota burn - and on a busy fleet those revivals add up to a large share of the daily budget.
Proposal: opt-in idle-worker pre-cache-expiry compaction
An opt-in housekeeping pass that compacts a genuinely idle Claude crewmate while its cache is still warm, so a later revival is cheap. Ships inert exactly like Relay: an absent, gitignored
config/idle-compactmeans no behavior change anywhere. When present, its first non-empty line is the idle threshold in whole minutes (recommended 30); an empty-but-present file enables the documented default; a malformed value behaves like an absent file so a typo can never fail the watcher loop.Shared owner
bin/fm-idle-compact.sh, called from bothbin/fm-watch.sh's main loop andbin/fm-supervise-daemon.sh's housekeeping tick on their existing cadence (one classifier, two callers - the same shape asfm-classify-lib.sh).Eligibility is the strict intersection of:
kind != secondmateand harness isclaude(the only harness with a verified/compacttoday);bin/fm-crew-state.sh's reconciled state isparked,done,blocked, orpaused- neverworking;fm_busy_classifyreports an exactidleverdict and the composer reads affirmatively empty; a busy pane, an unproven verdict, or a non-empty composer defers to the next sweep.A durable per-task marker (
state/.idle-compact-<task>) drives a 4-phase state machine so one idle episode produces at most one compaction:data/<id>/precompact-notes.md(survives teardown);/compact;Everything is verified by the existing daemon test shard plus a new
tests/fm-daemon.test.shcase; docs indocs/configuration.md("Idle-worker pre-compaction"),docs/architecture.md,docs/scripts.md, anddocs/verification/runtime-backends.md.Relationship to #2341
#2341 standardizes durable pre-compaction task notes as part of the worker brief contract. This proposal is the trigger side: it produces exactly that note before compacting and re-reads it afterward, so the two compose - #2341's note format could become the file this pass asks for.
Reference implementation
A working implementation on top of
main(single commit, ~860 lines incl. tests and docs) is at https://github.com/NewAiCoder/firstmate/tree/keep/idle-compact-eec826e - happy to rebase it onto currentmainand open a PR if the shape is welcome, or to adapt it to whatever contract you prefer.