Problem
Queued backlog items with no formal blocker decay silently. The re-evaluation loop (teardown + heartbeat) only promotes items whose blocked-by: <id> cleared. An item queued behind attention — no dependency, just never picked — has nothing to clear, so it sits indefinitely and is effectively lost. Observed in practice: multiple explicitly-requested tasks sat 48h+ unblocked and undispatched, discovered only when the captain happened to ask about them. The schema makes this legal: ## Queued with no stated reason is a silent-decay bucket.
This especially hurts captains juggling many parallel projects (the exact user firstmate serves): out-of-sight/out-of-mind is the failure mode the tool should be compensating for, and instead the backlog encodes it.
Solution
Two tiers — the second captures ~90% of the value in ~5 lines and can ship independently:
Tier 1 (schema): every ## Queued item must carry exactly one of blocked-by: <id>, deferred-until: <date|condition>, or parked: <reason>. No reason ⇒ the item is ready and dispatch must consider it. Enforce in tasks-axi / a bin/fm-backlog-lint.sh.
Tier 2 (STARVED WORK digest section): bin/fm-session-start.sh prints any queued item older than a threshold with no blocker, so every session opens with the rotting work in the captain's face. Sketch:
# bin/fm-stale-sweep.sh — surface queued items with no blocker older than FM_STARVED_HOURS (default 24)
thresh=$(( $(date +%s) - ${FM_STARVED_HOURS:-24} * 3600 ))
awk '/^## Queued/{q=1;next} /^## /{q=0} q && /^- \[ \]/' data/backlog.md |
grep -vE 'blocked-by:|deferred-until:|parked:' |
while IFS= read -r line; do
d=$(sed -nE 's/.*\(since ([0-9-]+)\).*/\1/p' <<<"$line")
[ -n "$d" ] && [ "$(date -j -f %Y-%m-%d "$d" +%s 2>/dev/null || date -d "$d" +%s)" -lt "$thresh" ] &&
printf 'STARVED: %s\n' "$line"
done
Session-start calls it under a STARVED WORK header; optionally fm-watch.sh gains a starved: wake kind. AGENTS.md instructs: each starved item is a forced decision — dispatch / reprioritize / parked: with a reason.
Tradeoffs (why it wasn't built, and calibration warnings)
- The gap is the shadow of a deliberate invariant: firstmate never self-initiates work, so "nag about idle work" was left human. Cost of that default: it never nags about what the captain forgot. Fix must surface, never auto-act — auto-dispatching stale work would erode the captain-control safety model.
- Alert fatigue is the real risk. Fire too eagerly and it becomes an ignorable ping stream — worse than nothing. Threshold should start conservative (24h+).
parked: must be first-class and guilt-free ("deliberately not deciding yet"), or the forcing function creates decision pressure instead of relief.
- Small complexity cost to the bash+markdown simplicity; Tier 2 alone is ~5 lines and no schema change.
Problem
Queued backlog items with no formal blocker decay silently. The re-evaluation loop (teardown + heartbeat) only promotes items whose
blocked-by: <id>cleared. An item queued behind attention — no dependency, just never picked — has nothing to clear, so it sits indefinitely and is effectively lost. Observed in practice: multiple explicitly-requested tasks sat 48h+ unblocked and undispatched, discovered only when the captain happened to ask about them. The schema makes this legal:## Queuedwith no stated reason is a silent-decay bucket.This especially hurts captains juggling many parallel projects (the exact user firstmate serves): out-of-sight/out-of-mind is the failure mode the tool should be compensating for, and instead the backlog encodes it.
Solution
Two tiers — the second captures ~90% of the value in ~5 lines and can ship independently:
Tier 1 (schema): every
## Queueditem must carry exactly one ofblocked-by: <id>,deferred-until: <date|condition>, orparked: <reason>. No reason ⇒ the item is ready and dispatch must consider it. Enforce in tasks-axi / abin/fm-backlog-lint.sh.Tier 2 (STARVED WORK digest section):
bin/fm-session-start.shprints any queued item older than a threshold with no blocker, so every session opens with the rotting work in the captain's face. Sketch:Session-start calls it under a
STARVED WORKheader; optionallyfm-watch.shgains astarved:wake kind. AGENTS.md instructs: each starved item is a forced decision — dispatch / reprioritize /parked:with a reason.Tradeoffs (why it wasn't built, and calibration warnings)
parked:must be first-class and guilt-free ("deliberately not deciding yet"), or the forcing function creates decision pressure instead of relief.