Skip to content

feat(supervision): surface open captain decisions and waits - #4

Merged
ItsFlow merged 18 commits into
mainfrom
fm/fm-meta-blindness
Aug 4, 2026
Merged

feat(supervision): surface open captain decisions and waits#4
ItsFlow merged 18 commits into
mainfrom
fm/fm-meta-blindness

Conversation

@ItsFlow

@ItsFlow ItsFlow commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Intent

Make every open captain decision and meaningful wait visible in one captain-facing place and in ordinary Firstmate replies, in plain language carrying the concrete choice, why it matters now, what waiting costs, real options, and a recommendation. Waits and decisions must be deduplicated, stay visible until resolved, and clear when resolved; a routine external delay must say what is awaited and when it is next checked. Integrate this with the existing primary-activity blind-spot state so unaccounted primary work appears suspicious rather than idle, keeping one authoritative contract rather than another disconnected status surface.

What Changed

  • Added a captain-attention subsystem (bin/fm-attention.sh + bin/fm-attention-lib.sh) that derives one deduplicated, authoritative set of open captain decisions and meaningful waits and renders them in plain language — each decision carrying the concrete choice, why it matters now, what waiting costs, real options, and a recommendation; each wait naming what is awaited and when it is next checked. Items stay listed until resolved through a receipt check and clear once resolved.
  • Wired that set into ordinary reply surfaces (session-start digest, guard changed-set banner, supervision one-line captain's-call count) and into the turn-end guards (shell scripts plus the OpenCode/Pi extensions) so a turn cannot end without relaying an open decision to the captain; added bounded .captain-attention-unknown and .captain-attention-decisions markers so harnesses that cannot deliver assistant-reply evidence surface once rather than loop.
  • Integrated the set with the existing primary-activity blind-spot state so a captain-gated work item counts as a decision and unaccounted primary work reads as suspicious rather than idle; ported the decision-hold body helpers from node to jq, and added docs/captain-attention.md and inventory updates plus a new tests/fm-attention.test.sh suite (all supervision tests pass under the pipeline).

Risk Assessment

✅ Low: All five round-1 findings are correctly and verifiably fixed — the evidence-less decision stop is now bounded by a surfaced-once marker (terminating the pre-native Grok recursion at one resume), the decision-hold helpers are ported to jq with semantics I verified by executing both filters, docs match the real behavior, and new regression coverage exercises the bounded stop — leaving only a minor AGENTS.md state-listing docs gap.

Testing

Ran the focused 9-suite regression set (all pass, no gate skips), then demonstrated the contract end-to-end with the real CLIs in temp Firstmate homes: the captain view carries the concrete choice, stakes, cost of waiting, options, and recommendation in plain language; waits show what is awaited and when it is next checked; the same set reaches session-start, guard, supervision-instructions, and the turn-end stop; receipts require actual captain-visible delivery; reworded delays deduplicate; items persist until resolved and clear on resolution; and a decision-holding home reads as not idle while a broken derivation reads as unknown. Evidence is CLI transcripts because the end-user surface is a terminal CLI; no rendered UI exists to screenshot.

Evidence: Captain view: briefed decision + external wait with next check

$ bin/fm-attention.sh CAPTAIN'S CALL 1 decision needs you. 1 thing is waiting. NEEDS YOUR DECISION 1. Sync the fork main branch with the author upstream The choice: Sync the fork main from the author upstream now, or keep it frozen and rebase the task board onto the current fork main. Why it matters now: The task board is the last thing before the live view ships and cannot be validated against a stale fork main. If this waits: The task board stays parked and nothing else will move it. Options: - Sync the fork main from upstream now, then rebase and re-validate. - Leave the fork frozen and rebase the board onto the current fork main. Recommended: Sync the fork main from upstream now; it is the smaller change and unblocks everything downstream. WAITING ON SOMETHING ELSE - waiting for the upstream fork synchronization to land before the board can be rebased Waiting for: waiting for the upstream fork synchronization to land before the board can be rebased Next check: in about 60 minutes Everything above stays listed here until it is answered or clears.

### Registering the captain decision the way firstmate does (fm-decision-hold.sh hold with the full briefing)

$ bin/fm-decision-hold.sh hold fork-sync fork-main-sync --title ... --choice ... --why-now ... --cost-of-waiting ... --option ... --option ... --recommend ...
fork-sync-decision-fork-main-sync

### A second crewmate is parked on a routine external delay (its status log), with a live watcher beat

$ bin/fm-attention.sh   # the single captain-facing place, plain English, relayable as written
CAPTAIN'S CALL
1 decision needs you. 1 thing is waiting.

NEEDS YOUR DECISION

1. Sync the fork main branch with the author upstream
   The choice:
     Sync the fork main from the author upstream now, or keep it frozen and
     rebase the task board onto the current fork main.
   Why it matters now:
     The task board is the last thing before the live view ships and cannot be
     validated against a stale fork main.
   If this waits:
     The task board stays parked and nothing else will move it.
   Options:
     - Sync the fork main from upstream now, then rebase and re-validate.
     - Leave the fork frozen and rebase the board onto the current fork main.
   Recommended:
     Sync the fork main from upstream now; it is the smaller change and
     unblocks everything downstream.

WAITING ON SOMETHING ELSE

- waiting for the upstream fork synchronization to land before the board can
  be rebased
  Waiting for:
    waiting for the upstream fork synchronization to land before the board can
    be rebased
  Next check: in about 60 minutes

Everything above stays listed here until it is answered or clears.
Evidence: Firstmate brief and status counters

$ bin/fm-attention.sh --brief   # firstmate-facing, carries identifiers
CAPTAIN'S CALL: 1 decision(s) need the captain, 1 wait(s) open.
  decision fork-sync-decision-fork-main-sync - Sync the fork main branch with the author upstream
  wait     task-board - waiting for the upstream fork synchronization to land before the board can be rebased [next check: in about 60 minutes]
  Relay these to the captain in plain language: bin/fm-attention.sh

$ bin/fm-attention.sh --status
attention=2 decisions=1 waits=1 new=true decisions_new=true
Evidence: Integrated surfaces: supervision-instructions count line, guard banner, session-start digest

$ bin/fm-supervision-instructions.sh   # ordinary-reply operating block carries the one-line count
================================================================================
SUPERVISION OPERATING INSTRUCTIONS - primary harness: claude
================================================================================
Current state:
- Lock: held by this session; this session owns normal supervision unless away mode says otherwise.
- Away mode: inactive.
- X mode: inactive; use the default watcher cadence.
- Captain's call: 1 decision(s) need the captain and 1 wait(s) are open; relay them in plain language with bin/fm-attention.sh before this turn ends.
- Ordinary wake: the Stop-owned auto-arm (bin/fm-claude-stop-autoarm.sh) already owns watcher continuity; drain and handle the wake, and do not arm another cycle yourself.

Mode: Claude Stop-hook-owned supervision.

When this session owns supervision and away mode is not active:
1. Drain first with `bin/fm-wake-drain.sh`.
2. Routine watcher arm and re-arm are owned by the Stop `asyncRewake` hook (`bin/fm-claude-stop-autoarm.sh`), never by you.
   Every turn end while supervision is needed launches or attaches one home-scoped watcher cycle with no model command and no model tokens.
   An actionable close wakes you through the hook's exit-2 rewake, delivered as a `Stop hook feedback` message.
3. On a `Stop hook feedback` wake (`signal:`, `stale:`, `check:`, or `heartbeat`), run `bin/fm-wake-drain.sh` first and handle the wake.
   Do not run `bin/fm-watch-arm.sh` after an ordinary wake; the next turn end re-arms automatically when supervision is still needed.
   Do not invent a wake from an attach-status line alone; drain and act only on real wake records or a real watcher reason line.
4. On a `Stop hook feedback` watcher-failure wake (`watcher: FAILED ...`), treat it as an alarm: drain, then repair supervision before ending the turn.
5. Manual arm is recovery only.
   When a repair is genuinely needed - the Stop hook did not claim this home, or a forced restart is required - run `bin/fm-watch-arm.sh` (or `bin/fm-watch-arm.sh --restart`) as its own Claude Code background task, never bundled with other commands, never with shell `&`.
   Source `/var/folders/tc/llfmckm54ls5tzxm08q758lm0000gn/T//fm-attention-demo.lvugr1/home/config/x-mode.env` first when X mode is active.
   A shell `&`, a truncating pipe, or bundling is denied automatically by the PreToolUse seatbelt (`bin/fm-arm-pretool-check.sh`) registered in `.claude/settings.json`.
6. Treat `watcher: started ...` and `watcher: attached ...` inside arm output as proof that one live cycle exists.
   On attach, the arm follows verified identity-matched successors instead of exiting when the first cycle ends.
7. The durable wake queue preserves actionable events between a rewake and the next Stop-launched arm, while the bounded turn-end guard prevents a blind Stop when recovery did not start.
   No PreToolUse hook denies fleet commands based on watcher status.
   [`watcher-continuity.md`](../watcher-continuity.md) owns the exact session-lock recovery boundary.
8. The turn-end guard (`bin/fm-turnend-guard.sh --claude`) remains the final backstop.
   It allows the stop when a watcher is healthy, when the auto-arm already owns recovery for this event epoch, or when a fresh rewake is recorded; it re-blocks only when none of those materialize, within a bounded budget.
9. Waiting on the hook-owned cycle is silent: do not send idle progress while the watcher is parked.

The watcher itself remains `bin/fm-watch.sh`, and `bin/fm-watch-arm.sh` remains the verified arm wrapper that the Stop hook foregrounds.
Re-arm attaches to an existing healthy cycle when one is already present and follows its verified successor chain.
See [`watcher-continuity.md`](../watcher-continuity.md) for the arm-layer successor and clean-close failure contract and the Claude ownership model.


$ bin/fm-guard.sh   # every guarded command re-renders a changed set
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
●  CAPTAIN'S CALL CHANGED - 1 decision(s) and 1 wait(s) are open
●  CAPTAIN'S CALL
●  1 decision needs you. 1 thing is waiting.
●  
●  NEEDS YOUR DECISION
●  
●  1. Sync the fork main branch with the author upstream
●     The choice:
●       Sync the fork main from the author upstream now, or keep it frozen and
●       rebase the task board onto the current fork main.
●     Why it matters now:
●       The task board is the last thing before the live view ships and cannot be
●       validated against a stale fork main.
●     If this waits:
●       The task board stays parked and nothing else will move it.
●     Options:
●       - Sync the fork main from upstream now, then rebase and re-validate.
●       - Leave the fork frozen and rebase the board onto the current fork main.
●     Recommended:
●       Sync the fork main from upstream now; it is the smaller change and
●       unblocks everything downstream.
●  
●  WAITING ON SOMETHING ELSE
●  
●  - waiting for the upstream fork synchronization to land before the board can
●    be rebased
●    Waiting for:
●      waiting for the upstream fork synchronization to land before the board can
●      be rebased
●    Next check: in about 60 minutes
●  
●  Everything above stays listed here until it is answered or clears.
●  Relay every open decision and wait to the captain in plain language in your next reply.
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

$ bin/fm-session-start.sh   # session digest prints the same section, before the supervision block (excerpt)
CAPTAIN'S CALL
--------------------------------------------------------------------------------
CAPTAIN'S CALL
1 decision needs you. 1 thing is waiting.

NEEDS YOUR DECISION

1. Sync the fork main branch with the author upstream
   The choice:
     Sync the fork main from the author upstream now, or keep it frozen and
     rebase the task board onto the current fork main.
   Why it matters now:
     The task board is the last thing before the live view ships and cannot be
     validated against a stale fork main.
   If this waits:
     The task board stays parked and nothing else will move it.
   Options:
     - Sync the fork main from upstream now, then rebase and re-validate.
     - Leave the fork frozen and rebase the board onto the current fork main.
   Recommended:
     Sync the fork main from upstream now; it is the smaller change and
     unblocks everything downstream.

WAITING ON SOMETHING ELSE

- waiting for the upstream fork synchronization to land before the board can
  be rebased
  Waiting for:
    waiting for the upstream fork synchronization to land before the board can
    be rebased
  Next check: in about 60 minutes

Everything above stays listed here until it is answered or clears.
Evidence: Receipt lifecycle: only captain-visible delivery spends it; items persist
### Only actual captain-visible delivery can spend the receipt

$ bin/fm-attention.sh --status   # before any captain-visible reply
attention=2 decisions=1 waits=1 new=true decisions_new=true

$ printf 'Steady as she goes, captain - nothing to report.' | bin/fm-attention.sh --record-visible   # a reply without the alert is rejected
exit=4

$ bin/fm-attention.sh --no-mark >/dev/null; bin/fm-attention.sh --status   # a firstmate-internal render cannot spend it either
attention=2 decisions=1 waits=1 new=true decisions_new=true

### firstmate relays the rendered alert in its actual reply; the turn-end adapter feeds that reply back

$ printf '%s' "<the reply containing the relayed CAPTAIN'S CALL>" | bin/fm-attention.sh --record-visible
exit=0

$ bin/fm-attention.sh --status   # the receipt is recorded: the set is no longer 'new'
attention=2 decisions=1 waits=1 new=false decisions_new=false

$ cat state/.captain-attention
attention=79f2a15e41e3625015e5273f49099c6b96e8d1394c5787de7964975f930478ee
decisions=5b433a7439629b98bd17bc2fc43fc225ef715533e081f839f7bc7dfc93ea2af3

### The items are NOT gone - only the interrupt is spent; every pull surface still lists them

$ bin/fm-attention.sh
CAPTAIN'S CALL
1 decision needs you. 1 thing is waiting.

NEEDS YOUR DECISION

1. Sync the fork main branch with the author upstream
   The choice:
     Sync the fork main from the author upstream now, or keep it frozen and
     rebase the task board onto the current fork main.
   Why it matters now:
     The task board is the last thing before the live view ships and cannot be
     validated against a stale fork main.
   If this waits:
     The task board stays parked and nothing else will move it.
   Options:
     - Sync the fork main from upstream now, then rebase and re-validate.
     - Leave the fork frozen and rebase the board onto the current fork main.
   Recommended:
     Sync the fork main from upstream now; it is the smaller change and
     unblocks everything downstream.

WAITING ON SOMETHING ELSE

- waiting for the upstream fork synchronization to land before the board can
  be rebased
  Waiting for:
    waiting for the upstream fork synchronization to land before the board can
    be rebased
  Next check: in about 60 minutes

Everything above stays listed here until it is answered or clears.
Evidence: Turn-end stop on an unshown decision, released by the relayed reply

$ ... last_assistant_message:'All quiet, captain.' | bin/fm-turnend-guard.sh ● TURN WOULD END WITHOUT TELLING THE CAPTAIN - 1 DECISION(S) ARE WAITING ON HIM ● CAPTAIN'S CALL ● 1 decision needs you. 0 things are waiting. ... ● Relay each one to the captain in plain language before ending this turn: ● the concrete choice, why it matters now, what waiting costs, and your recommendation. exit=2 $ ... last_assistant_message:<the relayed CAPTAIN'S CALL> | bin/fm-turnend-guard.sh exit=0 (turn may end: the captain has actually seen the decision)

### The scout that found the decision registers it, then is torn down: no metadata is left to count

$ ... last_assistant_message:'All quiet, captain.' | bin/fm-turnend-guard.sh   # the reply never showed the decision
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
●  TURN WOULD END WITHOUT TELLING THE CAPTAIN - 1 DECISION(S) ARE WAITING ON HIM
●  CAPTAIN'S CALL
●  1 decision needs you. 0 things are waiting.
●  
●  NEEDS YOUR DECISION
●  
●  1. Sync the fork main branch with the author upstream
●     The choice:
●       Sync the fork main from the author upstream now, or keep it frozen and
●       rebase the task board onto the current fork main.
●     Why it matters now:
●       The task board is the last thing before the live view ships and cannot be
●       validated against a stale fork main.
●     If this waits:
●       The task board stays parked and nothing else will move it.
●     Options:
●       - Sync the fork main from upstream now, then rebase and re-validate.
●       - Leave the fork frozen and rebase the board onto the current fork main.
●     Recommended:
●       Sync the fork main from upstream now; it is the smaller change and
●       unblocks everything downstream.
●  
●  Everything above stays listed here until it is answered or clears.
●  Relay each one to the captain in plain language before ending this turn:
●  the concrete choice, why it matters now, what waiting costs, and your recommendation.
●  bin/fm-attention.sh prints exactly that, already captain-safe.
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
exit=2


$ ... last_assistant_message:<the relayed CAPTAIN'S CALL> | bin/fm-turnend-guard.sh
exit=0 (turn may end: the captain has actually seen the decision)

$ bin/fm-attention.sh --status
attention=1 decisions=1 waits=0 new=false decisions_new=false
Evidence: Dedup: reworded hourly delay stays one quiet item
### An hourly re-report of the same delay with new wording stays ONE item and stays quiet

$ bin/fm-attention.sh --status   # after rewording the delay: still 1 wait, still not new
attention=2 decisions=1 waits=1 new=false decisions_new=false

$ bin/fm-attention.sh --brief
CAPTAIN'S CALL: 1 decision(s) need the captain, 1 wait(s) open.
  decision fork-sync-decision-fork-main-sync - Sync the fork main branch with the author upstream
  wait     task-board - still waiting on the fork synchronization to land upstream (hourly retry 7) [next check: in about 60 minutes]
  Relay these to the captain in plain language: bin/fm-attention.sh
Evidence: Resolution clears the decision and the wait
### The captain answers. The decision is resolved and routed; the fork sync lands.

$ tasks-axi add board-rebase 'Rebase and revalidate the task board' --kind ship --repo firstmate
ok: added board-rebase (ship, repo firstmate) -> Queued
task:
  id: board-rebase
  title: Rebase and revalidate the task board
  state: queued
  blocked: no
  blocked_by: none
  held: no
  hold_reason: "-"
  hold_kind: "-"
  hold_until: "-"
  kind: ship
  repo: firstmate
  priority: "-"
  created: 2026-08-04
  closed: "-"
  deps: none
  links: none
  body: ""
help[2]:
  - Run `tasks-axi start board-rebase` to move it to in flight
  - Run `tasks-axi block board-rebase --by <other>` to record a dependency

$ tasks-axi block board-rebase --by fork-sync-decision-fork-main-sync
ok: block board-rebase -> blocked-by fork-sync-decision-fork-main-sync
help[2]:
  - Run `tasks-axi unblock board-rebase --by <other>` to clear it
  - Run `tasks-axi ready` to see what is still dispatchable

$ bin/fm-decision-hold.sh resolve fork-sync fork-main-sync --decision-file decision.txt --routed-to board-rebase
resolved: fork-sync-decision-fork-main-sync -> board-rebase
exit=0

### The awaited fork sync lands; the parked crewmate finishes

$ bin/fm-attention.sh --status
attention=0 decisions=0 waits=0 new=true decisions_new=true

$ bin/fm-attention.sh   # resolved items clear from the captain view
CAPTAIN'S CALL
Nothing needs your decision, and nothing is waiting.
Evidence: Blind spot: decision-holding home is NOT idle while watcher-need stays false
### The primary-activity blind spot: a home with NO spawned-task metadata, holding only
### a captain-gated piece of ordinary work, created via the documented tasks-axi path.

$ tasks-axi add task-board 'Live watchable view of the task and priority list' --kind ship --repo sample
ok: added task-board (ship, repo sample) -> Queued
task:
  id: task-board
  title: Live watchable view of the task and priority list
  state: queued
  blocked: no
  blocked_by: none
  held: no
  hold_reason: "-"
  hold_kind: "-"
  hold_until: "-"
  kind: ship
  repo: sample
  priority: "-"
  created: 2026-08-04
  closed: "-"
  deps: none
  links: none
  body: ""
help[2]:
  - Run `tasks-axi start task-board` to move it to in flight
  - Run `tasks-axi block task-board --by <other>` to record a dependency

$ tasks-axi hold task-board --reason 'The fork main must be synced with upstream before this can be rebased' --kind captain
ok: hold task-board -> held (captain)
task:
  id: task-board
  title: Live watchable view of the task and priority list
  state: queued
  blocked: no
  blocked_by: none
  held: yes
  hold_reason: The fork main must be synced with upstream before this can be rebased
  hold_kind: captain
  hold_until: "-"
  kind: ship
  repo: sample
  priority: "-"
  created: 2026-08-04
  closed: "-"
  deps: none
  links: none
  body: ""
help[2]:
  - Run `tasks-axi unhold task-board` to resume dispatch
  - Run `tasks-axi ready --include-held` to review paused work

$ fm_supervision_status   # the old watcher-need predicate is unchanged: nothing in flight, no watcher needed
in_flight=0 needed=false

$ fm_attention_home_idle   # the idleness owner disagrees: this home is holding captain work
home reports: NOT idle (unaccounted primary work)

$ bin/fm-guard.sh   # the pull guard speaks even though nothing is spawned; the captain-gated ship item is a DECISION
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
●  CAPTAIN'S CALL CHANGED - 1 decision(s) and 0 wait(s) are open
●  CAPTAIN'S CALL
●  1 decision needs you. 0 things are waiting.
●  
●  NEEDS YOUR DECISION
●  
●  1. Live watchable view of the task and priority list
●     No plain-language explanation has been written for this one yet.
●     What was recorded:
●       The fork main must be synced with upstream before this can be rebased
●  
●  Everything above stays listed here until it is answered or clears.
●  Relay every open decision and wait to the captain in plain language in your next reply.
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Evidence: Broken derivation reads unknown, never all-clear
### A broken derivation must read as unknown, never as all-clear

$ bin/fm-attention.sh   # state directory is a dangling symlink
CAPTAIN'S CALL
I could not determine whether anything needs your decision or is waiting.
Treat this as unresolved until Firstmate can read the open decision and wait list.
exit=3
Evidence: Reproducible demo script that generated all transcripts
#!/usr/bin/env bash
# End-to-end demonstration of the captain-attention contract, driven exactly the
# way firstmate drives it in production: real fm-decision-hold.sh / tasks-axi
# mutations, then every captain-facing surface rendered from the derived set.
set -u

ROOT="/Users/panda/.no-mistakes/worktrees/f2eaa43f8583/01KZ6AJHSVTBKVC67HK3RNM8T0"
EV="/var/folders/tc/llfmckm54ls5tzxm08q758lm0000gn/T/no-mistakes-evidence/01KZ6AJHSVTBKVC67HK3RNM8T0"
DEMO=$(mktemp -d "${TMPDIR:-/tmp}/fm-attention-demo.XXXXXX")
H="$DEMO/home"

new_home() {  # <path>
  local h=$1
  mkdir -p "$h/data" "$h/state" "$h/config" "$h/projects" "$h/bin"
  cp "$ROOT/.tasks.toml" "$h/.tasks.toml"
  cat > "$h/data/backlog.md" <<'EOF'
# Backlog

## In flight

## Queued

## Done
EOF
  printf 'sm-attention-demo\n' > "$h/.fm-secondmate-home"
  : > "$h/AGENTS.md"
}
new_home "$H"

att() { FM_HOME="$H" FM_STATE_OVERRIDE="$H/state" FM_DATA_OVERRIDE="$H/data" "$ROOT/bin/fm-attention.sh" "$@"; }
hold() { FM_HOME="$H" FM_STATE_OVERRIDE="$H/state" FM_DATA_OVERRIDE="$H/data" "$ROOT/bin/fm-decision-hold.sh" "$@"; }
guard() { FM_HOME="$H" FM_ROOT_OVERRIDE="$H" FM_STATE_OVERRIDE="$H/state" FM_DATA_OVERRIDE="$H/data" FM_CONFIG_OVERRIDE="$H/config" "$ROOT/bin/fm-guard.sh" 2>&1; }
turnend() {  # [assistant-message]
  jq -cn --arg m "${1:-}" '{stop_hook_active:false,session_id:"demo",last_assistant_message:$m}' \
    | FM_HOME="$H" FM_ROOT_OVERRIDE="$H" FM_STATE_OVERRIDE="$H/state" \
      FM_DATA_OVERRIDE="$H/data" FM_CONFIG_OVERRIDE="$H/config" "$ROOT/bin/fm-turnend-guard.sh" 2>&1
}

say() { printf '\n$ %s\n' "$*"; }

# ---------------------------------------------------------------- scene 1
# The real reproduction: a crewmate audit surfaced a decision only the captain
# can make, and a second crewmate is parked behind an external fork sync.
{
  echo '### Registering the captain decision the way firstmate does (fm-decision-hold.sh hold with the full briefing)'
  printf 'window=fixture:fm-fork-sync\nproject=%s/projects/firstmate\nkind=ship\n' "$H" > "$H/state/fork-sync.meta"
  printf 'working: auditing the fork topology\n' > "$H/state/fork-sync.status"

  say "bin/fm-decision-hold.sh hold fork-sync fork-main-sync --title ... --choice ... --why-now ... --cost-of-waiting ... --option ... --option ... --recommend ..."
  hold hold fork-sync fork-main-sync \
    --title 'Sync the fork main branch with the author upstream' \
    --reason 'The task board cannot be rebased or re-validated until the fork main matches upstream' \
    --repo firstmate \
    --semantic-revision fork-main-sync-v1 \
    --choice 'Sync the fork main from the author upstream now, or keep it frozen and rebase the task board onto the current fork main.' \
    --why-now 'The task board is the last thing before the live view ships and cannot be validated against a stale fork main.' \
    --cost-of-waiting 'The task board stays parked and nothing else will move it.' \
    --option 'Sync the fork main from upstream now, then rebase and re-validate.' \
    --option 'Leave the fork frozen and rebase the board onto the current fork main.' \
    --recommend 'Sync the fork main from upstream now; it is the smaller change and unblocks everything downstream.'

  echo
  echo '### A second crewmate is parked on a routine external delay (its status log), with a live watcher beat'
  printf 'window=fixture:fm-task-board\nproject=%s/projects/sample\nkind=ship\n' "$H" > "$H/state/task-board.meta"
  printf 'paused: waiting for the upstream fork synchronization to land before the board can be rebased\n' > "$H/state/task-board.status"
  touch "$H/state/.last-watcher-beat"

  say "bin/fm-attention.sh   # the single captain-facing place, plain English, relayable as written"
  att
} > "$EV/01-captain-view.txt" 2>&1

# ---------------------------------------------------------------- scene 2
{
  say "bin/fm-attention.sh --brief   # firstmate-facing, carries identifiers"
  att --brief
  say "bin/fm-attention.sh --status"
  att --status
} > "$EV/02-brief-and-status.txt" 2>&1

# ---------------------------------------------------------------- scene 3
{
  say "bin/fm-supervision-instructions.sh   # ordinary-reply operating block carries the one-line count"
  FM_HOME="$H" FM_ROOT_OVERRIDE="$H" FM_STATE_OVERRIDE="$H/state" FM_DATA_OVERRIDE="$H/data" \
    FM_CONFIG_OVERRIDE="$H/config" "$ROOT/bin/fm-supervision-instructions.sh" 2>&1
  say "bin/fm-guard.sh   # every guarded command re-renders a changed set"
  guard
  say "bin/fm-session-start.sh   # session digest prints the same section, before the supervision block (excerpt)"
  FM_HOME="$H" FM_ROOT_OVERRIDE="$H" FM_STATE_OVERRIDE="$H/state" FM_DATA_OVERRIDE="$H/data" \
    FM_CONFIG_OVERRIDE="$H/config" "$ROOT/bin/fm-session-start.sh" 2>&1 \
    | awk "/^CAPTAIN'S CALL\$/,/^====/" | sed '$d' | head -45
} > "$EV/03-integrated-surfaces.txt" 2>&1

# ---------------------------------------------------------------- scene 4
{
  echo '### Only actual captain-visible delivery can spend the receipt'
  say "bin/fm-attention.sh --status   # before any captain-visible reply"
  att --status

  say "printf 'Steady as she goes, captain - nothing to report.' | bin/fm-attention.sh --record-visible   # a reply without the alert is rejected"
  printf 'Steady as she goes, captain - nothing to report.' | att --record-visible
  echo "exit=$?"

  say "bin/fm-attention.sh --no-mark >/dev/null; bin/fm-attention.sh --status   # a firstmate-internal render cannot spend it either"
  att --no-mark >/dev/null
  att --status

  echo
  echo '### firstmate relays the rendered alert in its actual reply; the turn-end adapter feeds that reply back'
  reply=$(att --no-mark)
  say "printf '%s' \"<the reply containing the relayed CAPTAIN'S CALL>\" | bin/fm-attention.sh --record-visible"
  printf 'Captain, before I continue:\n\n%s' "$reply" | att --record-visible
  echo "exit=$?"

  say "bin/fm-attention.sh --status   # the receipt is recorded: the set is no longer 'new'"
  att --status
  say "cat state/.captain-attention"
  cat "$H/state/.captain-attention"

  echo
  echo '### The items are NOT gone - only the interrupt is spent; every pull surface still lists them'
  say "bin/fm-attention.sh"
  att
} > "$EV/04-receipt-lifecycle.txt" 2>&1

# ---------------------------------------------------------------- scene 4b
# The meta-blindness case proper: the scout that surfaced the decision has been
# torn down, so the home has NO task metadata at all - only the open decision.
D="$DEMO/turnend-home"
new_home "$D"
attD() { FM_HOME="$D" FM_STATE_OVERRIDE="$D/state" FM_DATA_OVERRIDE="$D/data" "$ROOT/bin/fm-attention.sh" "$@"; }
turnendD() {
  jq -cn --arg m "${1:-}" '{stop_hook_active:false,session_id:"demo",last_assistant_message:$m}' \
    | FM_HOME="$D" FM_ROOT_OVERRIDE="$D" FM_STATE_OVERRIDE="$D/state" \
      FM_DATA_OVERRIDE="$D/data" FM_CONFIG_OVERRIDE="$D/config" "$ROOT/bin/fm-turnend-guard.sh" 2>&1
}
{
  echo '### The scout that found the decision registers it, then is torn down: no metadata is left to count'
  printf 'window=fixture:fm-fork-sync\nproject=%s/projects/firstmate\nkind=ship\n' "$D" > "$D/state/fork-sync.meta"
  printf 'working: auditing the fork topology\n' > "$D/state/fork-sync.status"
  FM_HOME="$D" FM_STATE_OVERRIDE="$D/state" FM_DATA_OVERRIDE="$D/data" "$ROOT/bin/fm-decision-hold.sh" \
    hold fork-sync fork-main-sync \
    --title 'Sync the fork main branch with the author upstream' \
    --reason 'The task board cannot be rebased or re-validated until the fork main matches upstream' \
    --repo firstmate \
    --semantic-revision fork-main-sync-v1 \
    --choice 'Sync the fork main from the author upstream now, or keep it frozen and rebase the task board onto the current fork main.' \
    --why-now 'The task board is the last thing before the live view ships and cannot be validated against a stale fork main.' \
    --cost-of-waiting 'The task board stays parked and nothing else will move it.' \
    --option 'Sync the fork main from upstream now, then rebase and re-validate.' \
    --option 'Leave the fork frozen and rebase the board onto the current fork main.' \
    --recommend 'Sync the fork main from upstream now; it is the smaller change and unblocks everything downstream.' \
    >/dev/null
  rm -f "$D/state/fork-sync.meta" "$D/state/fork-sync.status"

  say "... last_assistant_message:'All quiet, captain.' | bin/fm-turnend-guard.sh   # the reply never showed the decision"
  turnendD 'All quiet, captain.'
  echo "exit=$?"

  echo
  say "... last_assistant_message:<the relayed CAPTAIN'S CALL> | bin/fm-turnend-guard.sh"
  reply=$(attD --no-mark)
  turnendD "Captain, before I continue:

$reply"
  echo "exit=$? (turn may end: the captain has actually seen the decision)"

  say "bin/fm-attention.sh --status"
  attD --status
} > "$EV/04b-turnend-decision-stop.txt" 2>&1

# ---------------------------------------------------------------- scene 5
{
  echo '### An hourly re-report of the same delay with new wording stays ONE item and stays quiet'
  printf 'paused: still waiting on the fork synchronization to land upstream (hourly retry 7)\n' >> "$H/state/task-board.status"
  say "bin/fm-attention.sh --status   # after rewording the delay: still 1 wait, still not new"
  att --status
  say "bin/fm-attention.sh --brief"
  att --brief
} > "$EV/05-dedup-reworded-delay.txt" 2>&1

# ---------------------------------------------------------------- scene 6
{
  echo '### The captain answers. The decision is resolved and routed; the fork sync lands.'
  say "tasks-axi add board-rebase 'Rebase and revalidate the task board' --kind ship --repo firstmate"
  ( cd "$H" && tasks-axi add board-rebase 'Rebase and revalidate the task board' --kind ship --repo firstmate )
  say "tasks-axi block board-rebase --by fork-sync-decision-fork-main-sync"
  ( cd "$H" && tasks-axi block board-rebase --by fork-sync-decision-fork-main-sync )
  printf 'Sync the fork main from upstream now.\n' > "$DEMO/decision.txt"
  say "bin/fm-decision-hold.sh resolve fork-sync fork-main-sync --decision-file decision.txt --routed-to board-rebase"
  hold resolve fork-sync fork-main-sync --decision-file "$DEMO/decision.txt" --routed-to board-rebase
  echo "exit=$?"

  echo
  echo '### The awaited fork sync lands; the parked crewmate finishes'
  printf 'done: the fork synchronization landed and the board was rebased\n' >> "$H/state/task-board.status"
  printf 'done: fork audit complete\n' >> "$H/state/fork-sync.status"

  say "bin/fm-attention.sh --status"
  att --status
  say "bin/fm-attention.sh   # resolved items clear from the captain view"
  att
} > "$EV/06-resolution-clears.txt" 2>&1

# ---------------------------------------------------------------- scene 7
B="$DEMO/blind-spot-home"
new_home "$B"
{
  echo '### The primary-activity blind spot: a home with NO spawned-task metadata, holding only'
  echo '### a captain-gated piece of ordinary work, created via the documented tasks-axi path.'
  say "tasks-axi add task-board 'Live watchable view of the task and priority list' --kind ship --repo sample"
  ( cd "$B" && tasks-axi add task-board 'Live watchable view of the task and priority list' --kind ship --repo sample )
  say "tasks-axi hold task-board --reason 'The fork main must be synced with upstream before this can be rebased' --kind captain"
  ( cd "$B" && tasks-axi hold task-board --reason 'The fork main must be synced with upstream before this can be rebased' --kind captain )

  say "fm_supervision_status   # the old watcher-need predicate is unchanged: nothing in flight, no watcher needed"
  FM_STATE_OVERRIDE="$B/state" bash -c '. "$1"; fm_supervision_status "$2"; printf "in_flight=%s needed=%s\n" "$FM_SUP_IN_FLIGHT" "$FM_SUP_NEEDED"' \
    _ "$ROOT/bin/fm-supervision-lib.sh" "$B/state"

  say "fm_attention_home_idle   # the idleness owner disagrees: this home is holding captain work"
  if FM_STATE_OVERRIDE="$B/state" FM_DATA_OVERRIDE="$B/data" bash -c \
    '. "$1"; fm_attention_home_idle "$2"' _ "$ROOT/bin/fm-attention-lib.sh" "$B"; then
    echo "home reports: idle"
  else
    echo "home reports: NOT idle (unaccounted primary work)"
  fi

  say "bin/fm-guard.sh   # the pull guard speaks even though nothing is spawned; the captain-gated ship item is a DECISION"
  FM_HOME="$B" FM_ROOT_OVERRIDE="$B" FM_STATE_OVERRIDE="$B/state" FM_DATA_OVERRIDE="$B/data" \
    FM_CONFIG_OVERRIDE="$B/config" "$ROOT/bin/fm-guard.sh" 2>&1
} > "$EV/07-blind-spot-not-idle.txt" 2>&1

# ---------------------------------------------------------------- scene 8
C="$DEMO/unknown-home"
new_home "$C"
rm -rf "$C/state"
ln -s "$C/state-gone" "$C/state"
{
  echo '### A broken derivation must read as unknown, never as all-clear'
  say "bin/fm-attention.sh   # state directory is a dangling symlink"
  FM_HOME="$C" FM_STATE_OVERRIDE="$C/state" FM_DATA_OVERRIDE="$C/data" "$ROOT/bin/fm-attention.sh"
  echo "exit=$?"
} > "$EV/08-unknown-not-empty.txt" 2>&1

echo "DEMO_DIR=$DEMO"
echo done

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • 🚨 bin/fm-turnend-guard-grok.sh:77 - Pre-native Grok path can recurse without bound: the captain-attention headlines now pierce the GROK_TURNEND_GUARD_ACTIVE one-resume latch, but on this legacy path the Stop payload provably lacks last_assistant_message (a payload carrying stopHookActive/lastAssistantMessage takes the native branch), so fm-attention.sh --record-visible can never validate a receipt and FM_ATT_DECISIONS_NEW never clears. Each grok --resume run ends, fires its own Stop hook with GROK_TURNEND_GUARD_ACTIVE=1, matches the 'TURN WOULD END WITHOUT TELLING THE CAPTAIN' headline again, and spawns another nested resume — an infinite recursive loop while any captain decision is open, even one that was correctly briefed and faithfully relayed. This breaks the adapter's documented bound ('at most one forced continuation per turn - never a wedged, un-endable session'). The unknown headline is bounded by the .captain-attention-unknown marker; the decision headline has no equivalent bound here. Fix by keeping the latch authoritative on this evidence-free path (or bounding attention resumes with a marker), since the pull surfaces still keep the decision visible.
  • ⚠️ bin/fm-turnend-guard.sh:195 - The captain-decision turn-end stop is unbounded on any harness that cannot deliver last_assistant_message. attention_gate blocks (exit 2) whenever FM_ATT_DECISIONS_NEW is true, and unlike the watcher stop (BLOCK_BUDGET) and the unknown stop (.captain-attention-unknown marker) it has no budget or surfaced-once marker; the only clearing path is a validated receipt from the assistant message in the hook payload. docs/turnend-guard.md:63 states 'a true value still passes through the attention gate, so a continuation cannot end without a validated captain receipt', but the repo's own verification table claims assistant-reply evidence only for OpenCode, Pi, and native Grok — for Claude and Codex it only says exit 2 blocks. If those harnesses' Stop payloads omit last_assistant_message, a session with an open decision and an absent captain is forced to continue on every stop attempt (Claude bounded per attempt only by its external block override; Codex potentially unbounded), burning tokens until the decision is externally resolved. Confirm each harness actually supplies the field, or add a bounded interrupt (e.g. a decision-surfaced marker written after N blocks) for evidence-less harnesses.
  • ⚠️ bin/fm-decision-hold.sh:185 - fm-decision-hold.sh now hard-requires node for every subcommand (require_tasks_axi fails without it), used only by briefing_complete_body and write_updated_body_file to JSON-decode/re-encode the hold body. jq is already the repo's established JSON dependency and can decode a quoted scalar (jq -r .), filter lines, and re-emit raw text, so the same logic could run without adding a second runtime requirement to the core decision lifecycle. A home without node can no longer register, retry, complete, or resolve any captain hold. If node is a deliberate choice (e.g. exact byte-preserving writes), keep it; otherwise port the two helpers to jq.
  • ℹ️ bin/fm-attention-lib.sh:753 - fm_attention_status contains two nearly identical ~15-line unknown-fallback blocks (the jq/derivation failure branch at lines 753-766 and the empty-summary branch at lines 794-808), both setting the same nine FM_ATT_* fields and re-reading the unknown marker. Extracting one _fm_attention_mark_unknown helper removes the duplication and keeps the two branches from drifting.
  • ℹ️ bin/fm-attention-lib.sh:836 - fm_attention_home_idle is documented (here, in bin/fm-supervision-lib.sh, and in docs/captain-attention.md) as the owner of the idleness predicate, but no production code calls it — only tests/fm-attention.test.sh. The guards integrate the attention set directly instead. This is fine as a stated contract point, but note it is currently API-for-docs/tests; if a future consumer asks "is this home idle" via the old supervision counts, nothing forces it through this predicate.
  • ℹ️ tests/fm-attention.test.sh:1 - tests/fm-attention.test.sh is committed mode 100644 while every other tests/*.test.sh is 100755, and docs/verification/supervision.md lists tests/fm-attention.test.sh as a direct entry point (the runner invokes via bash so selection still works, but the documented direct invocation fails with permission denied). Set the executable bit for consistency.
  • ℹ️ bin/fm-guard.sh:163 - Every guarded command (fm-send, fm-spawn, fm-peek, fm-teardown, wake drain, etc.) now runs the full attention derivation (fleet-snapshot --backlog-json plus several jq passes over every status log) via fm_attention_status, and when the banner fires it derives the whole set a second time by shelling out to fm-attention.sh --no-mark. Acceptable for current fleet sizes, but the banner path could render from the already-computed FM_ATT_JSON instead of re-deriving.

🔧 Fix: Bound evidence-less captain-decision stops; port decision-hold JSON helpers to jq
1 info still open:

  • ℹ️ AGENTS.md:111 - The branch introduces three volatile state files, but AGENTS.md's state/ contract listing (line 111) documents only .captain-attention. The .captain-attention-unknown marker (bounds the unknown turn-end stop) and the new .captain-attention-decisions marker (bounds the evidence-less decision stop, added in 45c29fd) are absent from that listing even though both are documented in docs/turnend-guard.md and docs/captain-attention.md. Adding them to the same AGENTS.md line keeps the state-file inventory complete and consistent with the repo's practice of enumerating every state file with its owner.
✅ **Test** - passed

✅ No issues found.

  • bin/fm-test-run.sh tests/fm-attention.test.sh tests/fm-turnend-guard.test.sh tests/fm-decision-hold-lifecycle.test.sh tests/fm-session-start.test.sh tests/fm-supervision-instructions.test.sh tests/fm-inbox-view.test.sh tests/fm-fleet-snapshot-view.test.sh tests/fm-pi-watch-extension.test.sh tests/fm-backend.test.sh — 9/9 pass, 0 failures, 0 gate skips (jq and tasks-axi present)
  • Manual E2E: registered a real captain decision via bin/fm-decision-hold.sh hold with the full briefing and a parked crewmate delay, then captured bin/fm-attention.sh (plain-language captain view with choice/why-now/cost/options/recommendation and wait next-check), --brief, and --status
  • Manual E2E: verified the set reaches ordinary replies via bin/fm-session-start.sh (digest section), bin/fm-guard.sh (changed-set banner), and bin/fm-supervision-instructions.sh (one-line captain's-call count)
  • Manual E2E: receipt lifecycle via bin/fm-attention.sh --record-visible — reply without the alert rejected (exit 4), internal render cannot spend the receipt, relayed reply records it, items stay listed until resolved
  • Manual E2E: bin/fm-turnend-guard.sh blocks (exit 2) a turn ending with an unshown decision on a zero-metadata home and allows (exit 0) once the relayed alert is in the assistant reply
  • Manual E2E: dedup — rewording the hourly external delay keeps one wait and new=false; resolution via bin/fm-decision-hold.sh resolve plus terminal work outcomes clears the view to 'Nothing needs your decision, and nothing is waiting.'
  • Manual E2E: blind-spot — home with only a captain-gated tasks-axi hold reports in_flight=0 needed=false from fm-supervision-lib but NOT idle from fm_attention_home_idle, and the guard still renders the decision; dangling state symlink renders unknown (exit 3), never all-clear
⚠️ **Document** - 2 infos
  • ℹ️ docs/scripts.md:8 - Pre-existing inventory gap, out of scope for this change: docs/scripts.md omits about ten older bin/ scripts (fm-lint.sh, fm-doc-audience-check.sh, fm-cd-pretool-check.sh, fm-cd-command-policy.mjs, fm-transition-lib.sh, fm-push-transition-lib.sh, fm-herdr-session-cleanup.sh, fm-install-shellcheck.sh, backends/herdr-eventwait.py, backends/herdr-workspace-move.py) even though comparable policy/lib scripts are listed. A follow-up should either complete the inventory or state its inclusion rule; nothing enforces coverage today.
  • ℹ️ docs/verification/supervision.md:180 - Judgment call: the 'Captain's call: decisions and waits' verification section keeps its 2026-07-30 review date while the final commit (2026-08-04) added the bounded evidence-less decision stop. I verified the harness table's statements against the final adapters and guard code and confirmed the extended suite-coverage sentence by running tests/fm-attention.test.sh (all pass), but did not re-run the cross-harness live review the date refers to.
🔧 **Lint** - 1 issue found → auto-fixed ✅
  • ⚠️ linter found issues (exit code 1)

🔧 Fix: fm-attention: resolve shellcheck SC2034/SC2016 findings
✅ Re-checked - no issues remain.

✅ **Push** - passed

✅ No issues found.

ItsFlow added 18 commits July 29, 2026 01:27
Firstmate could stop on a decision or an external delay without the captain
ever receiving a self-contained explanation of what was needed, why it
mattered, or what would happen next. Work sat parked and the fleet read as
permanently waiting for no understandable reason.

Three separate mechanisms dropped that information. A captain decision was
recorded as a one-line title plus a one-line reason, so every renderer could
only truncate it - and the backlog reader truncated the reason again at its
first comma. A declared external delay was excluded from the captain-relevant
verb set, which correctly stops wedge-nagging an idle pane but also removed the
delay from every captain-facing surface. Supervision counted state/*.meta, so a
home whose only live work was an unanswered decision reported as idle and the
whole guard stack went silent.

Add one derived, read-only contract over state firstmate already keeps
durably - the backlog and the status event logs - rather than another status
surface. Every open item is a decision or a wait; a wait re-declared past a
threshold without clearing becomes a decision, derived from the keyed event
fold rather than from its wording. Identities carry no prose, so a delay
re-reported hourly stays one item and surfaces once.

Record the plain language durably: fm-decision-hold.sh hold gains briefing
fields for the concrete choice, why it matters now, what waiting costs, the
options, and a recommendation, stored in the hold body and cleared when the
hold resolves. A hold without one renders marked as not yet written rather
than dressing up a raw operational note.

Surface it in one captain-facing place and in ordinary replies: fm-attention.sh
renders it already translated; fm-guard.sh surfaces a changed set before any
in-flight test, closing the blind spot where it used to return early;
fm-turnend-guard.sh stops a turn that would end on a decision the captain has
never been shown, once per distinct set and never on a routine delay; and the
session-start digest and supervision block carry it. Rendering is surfacing, so
an ordinary read that changes nothing is never an alarm.

Every supported primary harness and runtime backend is reviewed in
docs/verification/supervision.md. Each passive adapter now selects its follow-up
headline from the guard's own banner, so a captain decision is no longer
announced as a supervision lapse.
…sion holds

Point the maintainer-architecture and decision-hold mechanism surfaces at
captain-attention.md rather than restating the contract, and record the
durable briefing that fm-decision-hold.sh now stores on a captain hold.
The captain inbox board that landed on main carries evidence this contract
missed: selecting decisions on the snapshot's captain_actionable flag also
requires the backlog item's own kind to be "captain", but the documented way
to gate ordinary work on the captain is

  tasks-axi hold <id> --reason "<reason>" --kind captain

which leaves kind as ship. The flag is therefore false for exactly the threads
this contract exists for. Reproduced on the real shape of the reported case: a
ship item held for the captain rendered as "0 decisions need you, 1 thing is
waiting", filed under waiting on something else with "next check: when the work
it is waiting on finishes" - the fleet-is-always-waiting symptom, reproduced
through the new surface itself.

Select on the captain hold instead, whatever the item's kind says, and keep a
hold with an unresolved blocker as a wait so a future-gated hold does not nag
now. captain_actionable is deliberately left alone: bearings and the secondmate
summary consume it with their own meanings and tests.

This is also the rule the inbox board already uses, so the two captain-facing
surfaces cannot disagree about what needs the captain. Record the relationship
in both directions rather than leaving them reading as rival owners: this
contract owns the derived set, its plain language, and the interrupt budget;
the board is where the captain answers one in a browser.

Correct one rationale the parser fix in the previous commit made stale:
fm-inbox-view.sh no longer needs tasks-axi because the snapshot truncates a
hold reason at its first comma, but it does still need it for the length cap
and for the durable briefing, which the snapshot carries no body for.
tests/fm-backend.test.sh builds a synthetic old bin/ from historical copies of
the refactored entrypoints plus CURRENT copies of their siblings. Two of those
siblings, fm-guard.sh and fm-decision-hold.sh, now source fm-attention-lib.sh,
which was not in the sibling list, so under set -eu the source aborted, the
unresolved-decision gate could not run, and the old fm-teardown.sh scout case
was refused:

  fm-guard.sh: line 53: .../fm-attention-lib.sh: No such file or directory
  fm-guard.sh: line 164: FM_ATT_AVAILABLE: unbound variable
  REFUSED: scout task teardownconform1 has not passed the unresolved-decision
  completion gate.

Add the dependency to OLD_BIN_UNCHANGED_SIBLINGS.

Also close the selection gap that let this reach CI. bin/fm-attention-lib.sh
mapped only to watcher-wake-lock, snapshot-bearings, session-bootstrap, and
pure-contract-unit, so no local run ever selected the backend-dispatch family
that owns this fixture. Map the attention files to backend-dispatch as well, so
the suite that consumes them is selected when they change. The same fixture
shape already required this dependency in tests/fm-turnend-guard.test.sh; this
was the second instance.
…single focused verification across the four touched contracts now
@ItsFlow
ItsFlow force-pushed the fm/fm-meta-blindness branch from e609cd4 to 5085634 Compare August 4, 2026 21:48
@ItsFlow ItsFlow changed the title feat(supervision): surface captain decisions and waits feat(supervision): surface open captain decisions and waits Aug 4, 2026
@ItsFlow
ItsFlow merged commit e7588f0 into main Aug 4, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant