Skip to content

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

Open
ItsFlow wants to merge 8 commits into
mainfrom
fm/fm-meta-blindness
Open

feat(supervision): surface captain decisions and waits#4
ItsFlow wants to merge 8 commits into
mainfrom
fm/fm-meta-blindness

Conversation

@ItsFlow

@ItsFlow ItsFlow commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Intent

Firstmate stops on a decision or an external wait without the captain ever getting a clear, self-contained explanation of what is needed, why it matters now, what waiting costs, and what happens next, so work sits silently stuck and the fleet reads as permanently waiting. Make every open captain decision and meaningful wait visible in one captain-facing place and in ordinary replies, deduplicated, still listed until answered, and cleared when resolved - a durable mechanism, not another prose reminder.

What Changed

  • Add a captain attention mechanism that derives, deduplicates, renders, and tracks open captain decisions and meaningful external waits until they are resolved.
  • Integrate captain attention surfacing into session start, fleet snapshots, inbox views, supervision instructions, decision holds, and turn-end guard blocking so ordinary replies and guard paths expose unresolved captain-facing work.
  • Document the captain attention contract and add focused shell coverage for attention derivation, surfacing, marker reset behavior, and turn-end guard integration.

Risk Assessment

✅ Low: The latest change is narrowly scoped to resetting the unknown attention marker after successful derivation and preserves the accepted single-writer and turn-end gate contracts without a material source-verifiable regression.

Testing

The direct shell-test invocation hit a non-executable-file setup issue, then the focused attention, turn-end guard, and session-start suites passed via bash; the evidence script created a demo captain-attention home and transcript showing the rendered decision/waits, guard surfacing, deduplication after rewording, continued listing while open, and all-clear after resolution, with no working-tree artifacts left behind.

Evidence: captain_attention_cli_transcript
Captain attention CLI evidence
home=/var/folders/tc/llfmckm54ls5tzxm08q758lm0000gn/T/no-mistakes-evidence/01KYNC2P9QQR5M2Y4BMNX3C2DK/captain-attention-home

$ bin/fm-attention.sh --no-mark
CAPTAIN'S CALL
1 decision needs you. 2 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.

WAITING ON SOMETHING ELSE

- Rebase and revalidate the task board
  Waiting for:
    other work to finish
  Next check: when the work it is waiting on finishes

- Live watchable view of the task and priority list
  Waiting for:
    the fork synchronization has not landed yet, so the board cannot be
    rebased
  Next check: in about 60 minutes

Everything above stays listed here until it is answered or clears.
[exit 0]

$ bin/fm-attention.sh --status --no-mark
attention=3 decisions=1 waits=2 new=true decisions_new=true
[exit 0]

$ bin/fm-guard.sh (ordinary guarded command surfaces and marks the changed set)
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
●  CAPTAIN'S CALL CHANGED - 1 decision(s) and 2 wait(s) are open
●  CAPTAIN'S CALL
●  1 decision needs you. 2 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.
●  
●  WAITING ON SOMETHING ELSE
●  
●  - Rebase and revalidate the task board
●    Waiting for:
●      other work to finish
●    Next check: when the work it is waiting on finishes
●  
●  - Live watchable view of the task and priority list
●    Waiting for:
●      the fork synchronization has not landed yet, so the board cannot 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.
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[exit 0]

$ bin/fm-attention.sh --status --no-mark (same set is still open but no longer new)
attention=3 decisions=1 waits=2 new=false decisions_new=false
[exit 0]

$ bin/fm-guard.sh after the same wait is reworded
(no output)
[exit 0]

$ bin/fm-attention.sh --status --no-mark after reworded wait
attention=3 decisions=1 waits=2 new=false decisions_new=false
[exit 0]

$ bin/fm-attention.sh --no-mark (open items remain listed after surfacing)
CAPTAIN'S CALL
1 decision needs you. 2 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.

WAITING ON SOMETHING ELSE

- Rebase and revalidate the task board
  Waiting for:
    other work to finish
  Next check: when the work it is waiting on finishes

- Live watchable view of the task and priority list
  Waiting for:
    same fork wait, reworded on a later check with no real progress yet
  Next check: in about 60 minutes

Everything above stays listed here until it is answered or clears.
[exit 0]

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

$ bin/fm-attention.sh --status --no-mark after resolving the decision
attention=1 decisions=0 waits=1 new=true decisions_new=true
[exit 0]

$ bin/fm-attention.sh --no-mark after the wait clears too
CAPTAIN'S CALL
Nothing needs your decision, and nothing is waiting.
[exit 0]

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 3 issues found → auto-fixed (3) ✅
  • 🚨 bin/fm-attention.sh:262 - Intent requires a clear, self-contained captain explanation in ordinary replies, but --brief is explicitly firstmate-facing and only prints id/headline while this path still records the surfaced digest. Because session start uses marked --brief and fm-guard.sh also marks after a brief banner, .captain-attention can be spent before the captain-safe explanation is relayed, letting the turn-end guard allow the same decision silently.
  • 🚨 bin/fm-attention-lib.sh:339 - Status-derived identities are only <class>:<task-id>:<key>, so an unkeyed paused: or needs-decision: that clears and later reopens uses the same default identity. If the earlier item was surfaced and no empty set was rendered in between, the digest at .captain-attention still matches and the new wait/decision will not surface or block; status identities need a phase/generation component or equivalent clearing behavior.
  • ⚠️ bin/fm-attention-lib.sh:263 - Intent requires each decision to carry what is needed, why it matters now, what waiting costs, and what happens next, but briefing() treats any body containing Captain briefing v1: as fully briefed even if those fields are absent. A partial hold update, for example only --recommend, will render as a briefed decision while omitting the choice/stakes/cost instead of warning that the captain briefing is incomplete.

🔧 Fix: Fix captain attention surfacing gaps
2 issues (1 error, 1 warning) still open:

  • 🚨 bin/fm-turnend-guard.sh:258 - Intent requires Firstmate to stop on an unsurfaced captain decision, but in Claude mode the guard exits successfully as soon as the Stop-owned auto-arm proves recovery on an unhealthy watcher path, before running attention_would_block. When in-flight work has no healthy watcher and an unsurfaced decision is also open, this budget_reset; exit 0 path lets the turn end without the captain-call stop or the captain-safe rendering.
  • ⚠️ bin/fm-attention-lib.sh:325 - If the backlog projection command fails, attention substitutes {"records":[]} and continues, so backlog-backed captain holds and waits can disappear while fm-attention.sh still renders and marks a partial or empty set. Treat snapshot failure as unavailable instead of recording a surfaced digest for incomplete attention state.

🔧 Fix: Harden captain attention surfacing
1 warning still open:

  • ⚠️ bin/fm-attention-lib.sh:519 - The unknown turn-end stop is bounded by a constant digest, and successful derivations never clear or rotate state/.captain-attention-unknown. After one projection failure has been surfaced, any later failure is treated as already surfaced even if the set was readable in between, so a fresh broken projection can end a turn without the explicit unknown stop; reset the unknown marker on successful derivation or include a generation tied to known/unknown transitions.

🔧 Fix: Reset unknown attention marker after recovery
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • tests/fm-attention.test.sh direct invocation returned permission denied because the test file is not executable; reran through bash without changing file mode.
  • bash tests/fm-attention.test.sh
  • bash tests/fm-turnend-guard.test.sh
  • bash tests/fm-session-start.test.sh
  • /var/folders/tc/llfmckm54ls5tzxm08q758lm0000gn/T/no-mistakes-evidence/01KYNC2P9QQR5M2Y4BMNX3C2DK/captain_attention_evidence.sh
  • Final cleanup checks: git status --short and find . -maxdepth 2 -type d \( -name '.pytest_cache' -o -name 'node_modules' -o -name 'dist' -o -name 'build' -o -name '__pycache__' \) -print
✅ **Document** - passed

✅ No issues found.

🔧 **Lint** - 1 issue found → auto-fixed ✅
  • ⚠️ linter found issues (exit code 1)

🔧 Fix: Fix attention lint warnings
✅ Re-checked - no issues remain.

✅ **Push** - passed

✅ No issues found.

ItsFlow added 8 commits July 28, 2026 23:51
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.
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