Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
66bfb5d
fix(bin): report supervision state truthfully
tknguyen29032002 Aug 28, 2026
467423f
fix(bin): report supervision state truthfully
tknguyen29032002 Aug 28, 2026
c371055
fix(bin): report supervision state truthfully
tknguyen29032002 Aug 28, 2026
80b688d
no-mistakes(review): bound inbox closure retries and stop at newest run
tknguyen29032002 Aug 28, 2026
49b2118
fix(bin): scope closure dedupe to the sidecar, escalate orphaned ones
tknguyen29032002 Aug 28, 2026
eb5632e
fix(bin): retire escalated orphan closures and bound the filing failure
tknguyen29032002 Aug 28, 2026
2457100
fix(bin): scope closure escalation to failed sidecars and commit at t…
tknguyen29032002 Aug 28, 2026
559c470
no-mistakes(review): commit closures before status retirement, name u…
tknguyen29032002 Aug 28, 2026
2669517
no-mistakes(review): retire surfaced orphans from every reader, retry…
tknguyen29032002 Aug 28, 2026
fd088f4
no-mistakes(review): stop reusing retired orphan sequences, spare wat…
tknguyen29032002 Aug 28, 2026
02f5d54
no-mistakes(review): count parked closures in sequence allocation, wi…
tknguyen29032002 Aug 28, 2026
971b1d4
no-mistakes(document): sync docs with steering-inbox closure and run …
tknguyen29032002 Aug 29, 2026
6be25d9
fix(tests): update fixtures and contract for acknowledgement-gated cl…
tknguyen29032002 Aug 30, 2026
2c89090
Merge remote-tracking branch 'origin/main' into fm/fm-supervision-rel…
tknguyen29032002 Aug 30, 2026
5815bec
no-mistakes(review): dedupe overdue escalation, reject non-file sidecars
tknguyen29032002 Aug 30, 2026
2fbf832
no-mistakes(document): correct stale keyed-answer heading, reflow fm-…
tknguyen29032002 Aug 30, 2026
4bdde97
no-mistakes: apply CI fixes
tknguyen29032002 Aug 30, 2026
4221523
Revert "no-mistakes: apply CI fixes"
tknguyen29032002 Aug 30, 2026
3235c32
Merge remote-tracking branch 'origin/main' into fm/fm-supervision-rel…
tknguyen29032002 Aug 30, 2026
fefdf60
no-mistakes(review): select captain-hold suite on task-inbox library …
tknguyen29032002 Aug 30, 2026
9ead1fa
no-mistakes(document): name teardown and drain as steering-inbox cons…
tknguyen29032002 Aug 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .agents/skills/captain-hold-lifecycle/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Never close anything the captain owns without recording what he actually said: `
When the captain says "later", that is an answer too: re-hold with `tasks-axi hold <id> ... --until <date>` so the item leaves the live Captain's Call and resurfaces on its date, instead of leaving a live-looking card or fabricating a closure.
"A keyed answer closes its matching captain-held task" is one capability with one owner, `bin/fm-captain-hold.sh answers`, and every channel that carries a captain answer feeds it the same task id and answer; a channel never maps keys to tasks, records a decision, or closes anything itself.
Chat already feeds it through `bin/fm-send.sh --resolve-key`, and a captured-answer source feeds it once bound with `bin/fm-captain-hold.sh bind <source-id>`; bind before arming the source, and key each structured question by the held task's id.
A `--resolve-key` answer steered to a local worker reaches that intake only once the worker acknowledges the record carrying it, so a call still reading open right after you answered it is that deferral, not a failed close; `docs/captain-hold-lifecycle.md` owns the mechanism.
An unbound source and a key that names no captain-held task both simply feed nothing: the answer is still captured and firstmate is still woken, and closing falls back to the direct command above.
A captain-held task closed outside this owner leaves no durable answer, so the completion gate keeps failing until `answer` records the decision the captain actually gave.
Resolved findings, recommendations that need no captain choice, and prose that merely sounds decision-like do not create held tasks.
Expand Down
5 changes: 5 additions & 0 deletions .agents/skills/stuck-crewmate-recovery/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ If the worktree or ownership cannot be reconciled safely, leave all state intact
Escalate in order:

1. Peek the pane, and check the task's steering inbox (`state/<id>.inbox/`) for unhandled `*.msg` records - a stale wake naming an unread firstmate instruction means the worker never acknowledged a durable steer, and the record itself shows exactly what was intended.
The wake names which bound it crossed: a spent delivery-attempt budget on an idle pane, a composer that visibly holds pending text, or the absolute unhandled bound, which fires even while the pane reads busy and can simply mean one long tool call - inspect such a worker before treating it as stopped.
No further doorbell follows any of them, so a composer-blocked record needs the composer cleared and the worker brought to a turn boundary, not another wait.
When the unread record carries an answer, the wake names the decision keys it holds open; they stay open until the worker acknowledges the record, so re-answering only queues a second record behind the same swallowed doorbell.
Two further stale wakes are about the answer rather than the worker: an answered decision that could not be closed means the worker did read the answer but its closing line failed to land, and a steering-inbox contract violation means the record was removed instead of moved into `handled/`, so its parked closure can never commit on its own.
Close those by hand - `bin/fm-captain-hold.sh answer` for a captain-held task, or an appended `resolved [key=<key>]: <how it was answered>` line in `state/<id>.status` for a status decision - and treat the worker as healthy unless the pane itself says otherwise.
2. If the crewmate is waiting on a question its brief already answers, answer in one line via `FM_HOME=<this-firstmate-home> bin/fm-send.sh` from an active firstmate session unless `FM_HOME` is already set to the active firstmate home.
3. If the crewmate is confused or looping, interrupt with `FM_HOME=<this-firstmate-home> bin/fm-control.sh <task-id> interrupt`, then redirect with one corrective line through `fm-send`.
4. If the crewmate is genuinely wedged after redirection, relaunch it with `FM_HOME=<this-firstmate-home> bin/fm-control.sh <task-id> relaunch --note '<progress so far>'`, which stops the agent, carries the brief plus that note into a replacement in the same local copy, and restores the prior record if the replacement cannot start.
Expand Down
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ state/ runtime records and signals; gitignored
<id>.muse-session muse busy-source binding (sessions root plus task worktree) written by fm-spawn; removed by teardown
<id>.cursor-session cursor busy-source binding (projects root, task worktree, prior conversations) written by fm-spawn; removed by teardown
<id>.reconcile-nudged epoch second of the last inventory-reconcile nudge sent to this secondmate; bin/fm-secondmate-reconcile.sh owns its per-home cooldown window
<id>.inbox/ durable steering inbox: sequenced firstmate instruction records the worker acknowledges by moving them into its handled/ subdirectory; written by fm-send, with ordinary records re-rung and escalated by the watcher while explicit fire-and-forget records are excluded from that ladder, and removed by teardown (bin/fm-task-inbox-lib.sh)
<id>.inbox/ durable steering inbox: sequenced firstmate instruction records the worker acknowledges by moving them into its handled/ subdirectory, plus the decision closure an answering steer parks beside its record until that acknowledgement; written by fm-send, with ordinary records re-rung and escalated by the watcher while explicit fire-and-forget records are excluded from that ladder, and removed by teardown (bin/fm-task-inbox-lib.sh)
<id>.meta task metadata; each producer script's header owns its exact fields and mutation contract, with docs/configuration.md routing operator-facing backend and trace-context details
<id>.herdr-presentation quarantinable attempt and restart-binding journal for Herdr's optional visual projection; never task or endpoint authority; see docs/herdr-backend.md "Presentation spaces"
<id>.check.sh authenticated slow poll; the watcher dispatches validated PR data and the byte-identified Relay shim through trusted repository scripts, runs registered custom checks from hash-validated private snapshots, and rejects every other state check without execution
Expand Down Expand Up @@ -308,7 +308,7 @@ A persistent secondmate is recorded in the secondmate registry and runtime state

Steer a worker with ordinary text through fail-closed `fm-send`: the message becomes a durable record in the task's steering inbox (multi-line text is legal, local and remote alike) and the worker's terminal receives only a constant doorbell line, with the watcher re-ringing an unacknowledged local message and escalating a stuck one (`bin/fm-task-inbox-lib.sh`; `bin/fm-send.sh` owns the typed-plane carve-outs).
A remote secondmate steer rides the same durable-inbox model through the remote transport; after an unconfirmed delivery, only the exact `FM_PENDING_REPLY_EXISTING_CORR=<id>` resend command printed by `fm-send` is safe because it preserves the request body for remote enqueue deduplication (`bin/fm-send.sh` header).
When a steer answers an open keyed decision or blocker, pass `fm-send`'s `--resolve-key` so the answer itself closes that decision record at answer time, identically for local and remote workers (contract: `bin/fm-send.sh` header).
When a steer answers an open keyed decision or blocker, pass `fm-send`'s `--resolve-key` so the answer itself closes that decision record without waiting on the worker to write a matching line; a local answer closes when the worker acknowledges it, so a decision the worker has not read still reads open (contract: `bin/fm-send.sh` header).
`fm-send` is the data plane for text the worker should read; never use its key or text paths for interrupt, exit, or other lifecycle control, because routing-marked lifecycle text becomes chat the worker reasons about instead of executing.
Drive a worker's lifecycle through `bin/fm-control.sh <task-id> interrupt|exit|relaunch`, which owns the per-runtime mechanics, verifies each action, and never tears down or discards anything ([`docs/agent-control.md`](docs/agent-control.md)).
A secondmate's routed reply returns through status or a document pointer, not by firstmate peeking into its chat.
Expand Down Expand Up @@ -351,7 +351,7 @@ Apart from that single supported abort, do not hand-edit, commit, restart, or st
Once ownership is settled, validate exactly once against that final head so no obsolete or intermediate head is ever treated as authoritative.

An ask-user finding returns as `needs-decision`; firstmate loads `ask-user-authority` and either decides or escalates per that skill.
Send the same worker one exact decision naming the decision key, step, action, affected finding IDs, instructions where needed, and exact response command, passing `--resolve-key` so the worker's open decision record closes at answer time.
Send the same worker one exact decision naming the decision key, step, action, affected finding IDs, instructions where needed, and exact response command, passing `--resolve-key` so the worker's open decision record closes on its own.
Require the matching `resolved` event, forbid `--yes`, and require the worker to process every synchronous return until completion or a genuinely new escalation.
Resume fleet supervision immediately after the decision lands.

Expand Down
6 changes: 3 additions & 3 deletions bin/fm-brief.sh
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ When a routed-work phase has a supervisor-actionable material change worth repor
If its first reportable event is \`working [key=<work-slug>]: {material phase}\`, use the same key on its later \`$PAUSED_VERB\`, \`done\`, \`failed\`, \`needs-decision\`, or \`blocked\` event so the earlier working phase is superseded.
When a keyed phase ends without another reportable state, append \`resolved [key=<work-slug>]: {why it is no longer active}\`.
\`resolved\` separately closes an escalated decision or blocker, and only a \`resolved\` line carrying that decision's exact key closes it: a later \`done\` or \`working\` event never does, even when the answer is what started that work.
The main firstmate's answer normally writes that closing line at answer time; when a blocker or wait clears WITHOUT an answer from the main firstmate, append \`resolved: {how it cleared}\` yourself (keyed with \`[key=<slug>]\` if you opened it with one) as your domain resumes.
The main firstmate's answer normally writes that closing line itself, not you; when a blocker or wait clears WITHOUT an answer from the main firstmate, append \`resolved: {how it cleared}\` yourself (keyed with \`[key=<slug>]\` if you opened it with one) as your domain resumes.
Routine internal supervision, heartbeats, retries, and crewmate churn stay inside your own home and must not touch that status file.

# Definition of done
Expand Down Expand Up @@ -354,7 +354,7 @@ The report is the only thing that survives, so anything worth keeping must be in
6. If a decision belongs to a human (product choices, destructive actions),
append \`needs-decision: {summary of options}\` and stop. Firstmate will reply with the decision.
A decision or blocker you opened stays open until a \`resolved\` line carrying its exact key lands; a later \`done:\` or \`working:\` line never closes it, even when the answer is what started that work.
Firstmate's reply normally writes that closing line at answer time; when a blocker or wait clears WITHOUT a firstmate reply, append \`resolved: {how it cleared}\` yourself (same \`[key=<slug>]\` if you opened it with one) as you resume.
Firstmate's reply normally writes that closing line itself, once you have acknowledged the instruction carrying it; when a blocker or wait clears WITHOUT a firstmate reply, append \`resolved: {how it cleared}\` yourself (same \`[key=<slug>]\` if you opened it with one) as you resume.
7. Never stop, restart, or update the shared \`no-mistakes\` daemon - it is one instance serving
every lane/home, so restarting it kills other lanes' in-flight pipeline runs. On ANY no-mistakes
daemon error, append \`blocked: {the daemon error}\` and stop; only firstmate manages the daemon.
Expand Down Expand Up @@ -433,7 +433,7 @@ $RULE1
6. If a decision belongs above the implementation worker (product choices, destructive actions, ask-user findings),
append \`needs-decision: {summary of options}\` and stop. Firstmate will reply with the decision.
A decision or blocker you opened stays open until a \`resolved\` line carrying its exact key lands; a later \`done:\` or \`working:\` line never closes it, even when the answer is what started that work.
Firstmate's reply normally writes that closing line at answer time; when a blocker or wait clears WITHOUT a firstmate reply, append \`resolved: {how it cleared}\` yourself (same \`[key=<slug>]\` if you opened it with one) as you resume.
Firstmate's reply normally writes that closing line itself, once you have acknowledged the instruction carrying it; when a blocker or wait clears WITHOUT a firstmate reply, append \`resolved: {how it cleared}\` yourself (same \`[key=<slug>]\` if you opened it with one) as you resume.
7. Never stop, restart, or update the shared \`no-mistakes\` daemon - it is one instance serving
every lane/home, so restarting it kills other lanes' in-flight pipeline runs. On ANY no-mistakes
daemon error, append \`blocked: {the daemon error}\` and stop; only firstmate manages the daemon.
Expand Down
9 changes: 6 additions & 3 deletions bin/fm-classify-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,12 @@ status_is_paused_or_captain_held() { # <status-line>
# captain-held backlog transfer referencing that key CLOSES it; a later unrelated
# terminal line never clears an open captain decision.
# Who WRITES the closing line is owned elsewhere: the answering firstmate closes
# at answer time through fm-send's --resolve-key (bin/fm-send.sh header), and a
# worker self-closes only a blocker that cleared without an answer (bin/fm-brief.sh
# rule 6), so closure never depends on a busy worker's discipline.
# through fm-send's --resolve-key, whose header owns WHEN each plane commits it
# (on the local inbox plane the closure is parked on the record and written only
# once the worker acknowledges it, so an unread answer keeps its decision open
# in this fold), and a worker self-closes only a blocker that cleared without an
# answer (bin/fm-brief.sh rule 6), so closure never depends on a busy worker's
# discipline.
#
# Decision key grammar (backward-compatible with the existing "<verb>: <note>"
# format): an OPTIONAL "[key=<slug>]" token names the decision. Its documented
Expand Down
Loading
Loading