Decisions & captain holds
What the first mate decides itself, what reaches you, the exact shape of an escalation, and how the captain-hold primitive makes sure no question is ever silently dropped.
What reaches you, and what doesn't#
VISION.md: "An escalation exists for a decision only a human can make; progress, retries, and internal mechanics are never news."
The first mate contacts you immediately only for:
- work ready for review, with the full PR URL,
- finished investigation findings,
- a validation finding it escalated (see below),
- a real blocker or failure after its playbooks are exhausted,
- anything destructive, irreversible or security-sensitive,
- a credential or login it needs.
Everything else waits for the next natural reply. It never batches unrelated decisions into one message.
It also translates its internal vocabulary before talking to you: a worktree becomes a "local copy", teardown becomes "cleanup", a wake or heartbeat becomes "monitoring", and a brief becomes "instructions". If you see raw mechanics in its messages, it is breaking its own contract.
Who decides a validation finding#
When no-mistakes raises an ask-user finding, the worker never answers it. It reports needs-decision. The first mate loads the ask-user-authority skill.
It decides itself when the finding is unambiguous toward the already-accepted contract, even if the fix is hard. That covers bug fixes and restoring behaviour you already accepted.
It escalates to you when the finding would:
- materially expand the contract (a new guarantee, subsystem or abstraction),
- settle a product or architecture question your intent didn't settle,
- repeat a theme that keeps preserving a questionable abstraction,
- or be destructive, irreversible or security-sensitive.
An escalation always carries five things:
- the original accepted criterion,
- the proposed expansion,
- the smallest compliant alternative,
- the consequences of accepting vs declining,
- a recommendation, with its reasoning.
Captain holds: one primitive#
There used to be separate "decision" records. They were collapsed into one thing:
A decision is not a separate thing: it is simply a task waiting on the captain. — .agents/skills/captain-hold-lifecycle/SKILL.md
A captain hold is an ordinary backlog task (in data/backlog.md, managed by tasks-axi), marked as held for you and identified by its task id. bin/fm-captain-hold.sh owns it. The old bin/fm-decision-hold.sh survives for one release as a compatibility shim.
Creating a hold#
fm-captain-hold.sh hold <task-id> --reason "<question + options>" \
[--title <t>] [--repo <r>] [--origin <investigation-id>] [--until YYYY-MM-DD]
- Hold the work the question gates. A new row is created only when no work item exists to hold.
- One hold per gate. A review with five questions is one held task, not five.
- Scripts never infer a question from prose. The agent judges what is truly your call. The script only makes it durable.
- The investigation completion gate: after a scout or review, the first mate must run
fm-captain-hold.sh complete <origin-id> <held-ids...>(or--none). An investigation cannot end while a question in it is silently dropped.
Where you see it#
bin/fm-fleet-snapshot.sh puts every hold into exactly one bucket, using structured fields only. It never reads the reason text.
| Bucket | Condition | Shown in /bearings as |
|---|---|---|
blocked |
an unresolved blocker exists | Charted Next, with the blocker |
dated |
--until is in the future |
Charted Next, "until |
aged |
undated and ≥ 14 days old (FM_SNAPSHOT_UNDATED_HOLD_AGE_DAYS) |
Charted Next, with its age |
live |
none of the above | Captain's Call |
/bearings lavish also renders each held task as a clickable card.
Answering#
You answer in chat, on the board, or through Relay. Every channel feeds the same intake, so the same guards apply:
fm-captain-hold.sh answer <task-id> --decision-file <your-words.txt> [--release]
- Your literal words are recorded (up to 8 KB) in a resolution block on the task.
- Close (the default) is for when the held row was the question, e.g. "should feature X exist?". Answering is the deliverable.
--releaseis for when the row is work gated by a question. It lifts the hold and the work resumes. A merge approval uses--release. The task closes later, at landing. Closing at approval would claim completion before the code shipped.- "Later" is an answer too. It becomes a re-hold
--until <date>, so the item leaves Captain's Call and comes back on that date.
Things that can't close a hold by accident#
- Teardown asks
fm-captain-hold.sh open <id>before closing a finished task's row. If the call is still open, the row is kept, the deliverable is attached, and it goes back to Queued, still held. Finishing the work never answers your question. If the check can't tell, teardown treats that as a refusal. reconcileis a reserved answer meaning "go re-check reality". Every intake refuses to treat it as your decision. A board "Reconcile" click files a request. The first mate then closes it with evidence (reconcile close --evidence-file) or notes it's still live (reconcile note).divergedis a read-only report of calls where the status log saysresolvedbut the backlog task is still held. The wake drain prints it asRECORD DIVERGENCE. The first mate reviews these. It never auto-closes them.
Secondmates#
A hold created inside a secondmate home publishes needs-decision [key=captain-hold-<task>-<n>] up the parent channel. Answering publishes the matching resolved line. You still only ever talk to the first mate.