Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .agents/skills/bootstrap-diagnostics/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ When any diagnostic needs captain attention, report the plain consequence and re
A count that grows during a session means something is still recording against unresolvable sequences, which is a bug to escalate rather than a backlog of old damage.
- `WAKE_LEDGER: the wake ledger could not be read ...` - the file exists but could not be opened, so the count above is unavailable rather than zero.
Repair its permissions or path before quoting any supervision-cost figure; an unreadable ledger is reported precisely so it cannot pass as a clean one.
- `WAKE_LEDGER: <n> task(s) declared failure with no terminal record ...` - only a lock-holding session records those, so a read-only session names them and leaves the recording to the session that holds the lock.
Take no action on the count itself; the next locked session records it, and the tasks themselves are ordinary work whose state is read the usual way.
Terminal outcome counts stay diagnostic while any of them is unrecorded, so never quote a success rate from the ledger.
- `FLEET_SYNC: <repo>: skipped: <reason>` - a benign one-off skip (offline, no origin, local-only); bootstrap continued, investigate only if it blocks work.
A skip can also report the bounded fleet-refresh timeout (`FM_FLEET_SYNC_BOOTSTRAP_TIMEOUT`, or a fleet-size-aware default with a 20 second floor); a timeout never blocks startup.
- `FLEET_SYNC: <repo>: recovered: <detail>` - the clone had drifted onto a clean detached HEAD holding no unique commits and the sync self-healed it (re-attached the default branch and fast-forwarded); no action needed, it is reported only so the self-heal is visible.
Expand Down
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ state/ volatile runtime signals; gitignored
<id>.status appended by crewmates: "<state>: <note>" wake-event lines, not current-state truth
<id>.turn-ended touched by turn-end hooks
<id>.childcpu identity-bound sample of the CPU consumed by the task agent's descendants, so supervision can see work happening in a child process; written by the watcher, removed by teardown
<id>.terminal-recorded receipt proving the ledger already holds a terminal record for a task that declared failure and was never torn down, so the recording sweep never repeats it; written only by bin/fm-wake-ledger.sh, removed by teardown
<id>.grok-turnend-token firstmate-owned grok hook registry token for the task; removed by teardown
<id>.kimi-turnend-token firstmate-owned Kimi hook registry token for the task; removed by teardown
<id>.meta written by fm-spawn: window=, endpoint_task_id=, worktree=, project=, harness=, model=, effort=, kind=, mode=, yolo=, tasktmp=; a ship or scout also records the task's two base references as slot_base=, contribution_target=, and base_state= (bin/fm-task-base-lib.sh); a task dispatch also records the agent-justification fields reasoning_required=, reason_code=, capability_floor=, escalation_policy=, plus tooling_gap_item= for a TOOLING_GAP dispatch (bin/fm-reasoning-lib.sh, section 7), while a secondmate provisioning spawn records none of them and an absent field reads as unknown rather than as justified reasoning; an optional traceparent= only when trace context is enabled (docs/configuration.md "Trace context propagation"); kind=secondmate also records home= and projects=, plus remote_host=/remote_root=/remote_backend=/remote_herdr_session=/remote_target= for a remote route; a non-default runtime backend records further backend-specific fields (docs/configuration.md "Runtime backend"; bin/fm-backend.sh, section 8); fm-pr-check, including through fm-pr-merge, records one canonical pr= and the forge's pr_head= when available (GitHub pull requests and GitLab merge requests; docs/gitlab-merge-watch.md); fm-pr-merge records merge_verification= plus merge_verified_head= for the head it re-verified, or merge_verification=override for an explicitly unverified merge; fm-x-link appends x_request=, x_request_ts=, x_followups=, and optional x_platform=/x_reply_max_chars= for an X-mode-originated task (section 14)
Expand Down Expand Up @@ -152,7 +153,7 @@ A lock-refused session must not spawn, steer, merge, drain the wake queue, repai
1. **Lock** - acquires the per-home session lock first, before anything mutates shared state.
2. **Bootstrap** - detect-only checks (tool/version problems, GitHub auth, the worktree-tangle check, harness override, dispatch-profile validation, backlog-backend status, and shared validation-daemon liveness) always run, but routine confirmations stay silent by default.
When the lock could not be acquired, the worktree-tangle check uses read-only advisory wording without a checkout repair command.
Home-local stale Herdr projection cleanup and the six bootstrap MUTATING sweeps - non-executing legacy PR-check migration, fleet sync, secondmate convergence, secondmate liveness, pending remote handoff retry, and X-mode artifact writes - run only when this session actually holds the lock from step 1.
Home-local stale Herdr projection cleanup and bootstrap's MUTATING sweeps - enumerated only in `bin/fm-bootstrap.sh`'s header, their single owner - run only when this session actually holds the lock from step 1.
The secondmate liveness sweep deterministically accounts for every registered secondmate: it relaunches only from the recovery-grade `dead` or `missing` states, preserves ambiguous, unreadable, or unreachable remote targets, and reports skipped or failed guarantees as `SECONDMATE_LIVENESS:` lines (`bin/fm-bootstrap.sh`; `bin/fm-backend.sh`'s `fm_backend_agent_state`; `docs/remote-secondmates.md`).
3. **Wake queue** - when locked, drains the durable wake queue and prints the raw records prominently as this turn's first work queue; a bounded, clearly labeled historical status-event annotation may follow a valid `signal` record but never replaces it or current-state reconciliation, and a lapsed watcher chain still surfaces here via the same guard alarm.
Every locked drain also prints a bounded fleet-wide `OPEN DECISIONS` section when durable decision records remain open, including when the queue itself is empty; reconcile those entries before continuing.
Expand Down
38 changes: 35 additions & 3 deletions bin/fm-bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,14 @@
# refresh relays any completed fm-fleet-sync.sh output before the
# aggregate timeout skip line with timeout and elapsed seconds.
# Set FM_FLEET_PRUNE=0 to skip branch pruning during that refresh.
# Set FM_BOOTSTRAP_DETECT_ONLY=1 to skip the six MUTATING sweeps
# Set FM_BOOTSTRAP_DETECT_ONLY=1 to skip the MUTATING sweeps
# (PR-check migration, secondmate_sync, secondmate_liveness_sweep,
# secondmate_handoff_resume, x_mode_setup, fleet_sync) while still
# printing every read-only detect line
# above; the TANGLE line switches to advisory-only wording with no
# printing every read-only detect line above.
# wake_ledger_terminal_sweep mutates too and honours the same flag by
# switching to its dry run, so a read-only session still REPORTS the
# declared failures it declined to record.
# The TANGLE line switches to advisory-only wording with no
# checkout command. Used by
# fm-session-start.sh's read-only path when another live session holds
# the fleet lock, so a second concurrent session never race-mutates
Expand Down Expand Up @@ -1004,6 +1007,34 @@ wake_ledger_reconcile() {
echo "WAKE_LEDGER: $unjoined outcome record(s) join no wake record - supervision-cost figures drawn from this ledger overcount until they are purged (bin/fm-wake-ledger.sh reconcile)"
}

# Terminal records for tasks that declared failure and were never torn down.
# Only teardown writes a terminal line, so a failure nobody released is SILENT
# in the ledger - and silence there is indistinguishable from a task that never
# failed. A MUTATING sweep: it appends terminal records and writes a per-task
# receipt, so it runs only when this session holds the fleet lock; a read-only
# session reports the same tasks without recording them.
# bin/fm-wake-ledger.sh owns the vocabulary, the sweep, and its idempotence.
wake_ledger_terminal_sweep() {
local out n
[ -x "$SCRIPT_DIR/fm-wake-ledger.sh" ] || return 0
if [ "${FM_BOOTSTRAP_DETECT_ONLY:-0}" = 1 ]; then
out=$("$SCRIPT_DIR/fm-wake-ledger.sh" sweep --dry-run 2>/dev/null) || return 0
n=$(printf '%s\n' "$out" | grep -c '^unreleased failure: ' || true)
case "$n" in
''|*[!0-9]*|0) return 0 ;;
esac
echo "WAKE_LEDGER: $n task(s) declared failure with no terminal record - read-only session left the recording to the session holding the fleet lock (bin/fm-wake-ledger.sh sweep)"
return 0
fi
out=$("$SCRIPT_DIR/fm-wake-ledger.sh" sweep 2>/dev/null) || return 0
n=$(printf '%s\n' "$out" \
| sed -n 's/^recorded \([0-9][0-9]*\) unreleased failure(s) as terminal records$/\1/p')
case "$n" in
''|*[!0-9]*|0) return 0 ;;
esac
echo "BOOTSTRAP_INFO: recorded $n declared task failure(s) that no teardown would have recorded"
}

# The entitlement probe half of the observation floor. A MUTATING sweep: it makes
# live requests and writes state/model-health.json, so it runs only when this
# session actually holds the fleet lock, alongside the other mutating sweeps.
Expand Down Expand Up @@ -1215,6 +1246,7 @@ crew_dispatch_validate
model_registry_validate
admission_control_validate
wake_ledger_reconcile
wake_ledger_terminal_sweep
if [ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" = 1 ] \
&& ! fm_backlog_backend_manual "$CONFIG" && fm_tasks_axi_compatible; then
echo "BOOTSTRAP_INFO: tasks-axi available"
Expand Down
12 changes: 5 additions & 7 deletions bin/fm-session-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# standalone with unchanged default behavior - other flows (fm-bootstrap.sh
# install <tools> after consent, /updatefirstmate, the afk daemon, existing
# tests) still call them directly. The one seam this script needed -
# bootstrap running its detect-only diagnostics without its six mutating
# bootstrap running its detect-only diagnostics without its mutating
# sweeps - is an opt-in FM_BOOTSTRAP_DETECT_ONLY=1 flag on fm-bootstrap.sh
# itself (default unset/0 = unchanged behavior), not a fork.
#
Expand All @@ -29,11 +29,9 @@
# mutating step runs.
# 2. bootstrap - home-local stale Herdr projection cleanup runs only
# when this session actually holds the lock. Detect-only
# diagnostics always run. Bootstrap's six MUTATING sweeps
# (legacy PR-check migration, secondmate convergence,
# secondmate liveness, pending remote handoff retry,
# X-mode artifact writes, fleet sync) also run only when
# locked.
# diagnostics always run. Bootstrap's MUTATING sweeps
# also run only when locked; bin/fm-bootstrap.sh's own
# header owns that list.
# 3. wake-drain - mutates the durable wake queue, so it also only runs
# when locked.
# 4. context digest - data/projects.md, data/secondmates.md, data/captain.md,
Expand Down Expand Up @@ -70,7 +68,7 @@
# tasks-axi and quota-axi tool checks, and tasks-axi availability - none of
# which mutate shared state and all of which are safe to compute without
# verified lock ownership.
# Only projection cleanup, the five bootstrap mutating sweeps, the wake-queue
# Only projection cleanup, the bootstrap mutating sweeps, the wake-queue
# drain, and the ruling-index rebuild are skipped.
# The context digest below is always read-only, and so is every part of the
# fleet-state digest except its RULING_RECONCILE step: publishing the derived
Expand Down
25 changes: 22 additions & 3 deletions bin/fm-teardown.sh
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,8 @@ remote_secondmate_teardown() {
tmp="$SECONDMATE_REG.tmp.$$"
grep -vE "^- $ID( |$)" "$SECONDMATE_REG" > "$tmp" || true
mv -f -- "$tmp" "$SECONDMATE_REG"
rm -f -- "$STATE/$ID.status" "$STATE/$ID.meta" "$STATE/$ID.turn-ended" "$STATE/$ID.childcpu"
rm -f -- "$STATE/$ID.status" "$STATE/$ID.meta" "$STATE/$ID.turn-ended" "$STATE/$ID.childcpu" \
"$STATE/$ID.terminal-recorded"
printf 'teardown %s complete (remote %s:%s)\n' "$ID" "$remote_host" "$remote_home"
return 0
}
Expand Down Expand Up @@ -2393,9 +2394,25 @@ retire_busy_state "$STATE" "$ID" "$BUSY_GEN" || exit 1
# harness/model/effort join for this task is available anywhere.
# bin/fm-wake-ledger.sh owns the record format. Best effort by design - a
# telemetry write must never stand between the fleet and cleanup.
LEDGER_OUTCOME=landed
# The outcome comes from what the task DECLARED, not from a constant: a task
# that reported failed: must not leave a record that says it landed. The
# mapping and its evidence vocabulary belong to fm-wake-ledger.sh; this reads
# the status file before the removal below deletes it, and falls back to the
# ledger's own unevidenced default if that read fails.
LEDGER_DERIVED=$(FM_WAKE_LEDGER="${FM_WAKE_LEDGER:-$DATA/wake-ledger.tsv}" \
"$SCRIPT_DIR/fm-wake-ledger.sh" derive "$STATE/$ID.status" 2>/dev/null) \
|| LEDGER_DERIVED=""
case "$LEDGER_DERIVED" in
'landed '*|'failed '*) ;;
*) LEDGER_DERIVED="landed assumed" ;;
esac
LEDGER_OUTCOME=${LEDGER_DERIVED%% *}
LEDGER_SOURCE=${LEDGER_DERIVED##* }
# A discard is the operator's own act and outranks the worker's last word: the
# work was thrown away whatever the task believed about itself.
if [ "$FORCE" = "--force" ]; then
LEDGER_OUTCOME=abandoned
LEDGER_SOURCE=discarded
fi
LEDGER_ESCALATED=$(meta_value "$META" escalated)
case "$LEDGER_ESCALATED" in
Expand All @@ -2417,6 +2434,7 @@ fi
FM_WAKE_LEDGER="$LEDGER_PATH" \
"$SCRIPT_DIR/fm-wake-ledger.sh" task "$ID" \
--outcome "$LEDGER_OUTCOME" \
--source "$LEDGER_SOURCE" \
--harness "$(meta_value "$META" harness)" \
--model "$(meta_value "$META" model)" \
--effort "$(meta_value "$META" effort)" \
Expand All @@ -2430,7 +2448,8 @@ FM_WAKE_LEDGER="$LEDGER_PATH" \
|| echo "warning: wake ledger terminal line not recorded for $ID" >&2
rm -f "$STATE/$ID.status" "$STATE/$ID.turn-ended" "$STATE/$ID.meta" \
"$STATE/$ID.pi-ext.ts" "$STATE/$ID.grok-turnend-token" \
"$STATE/$ID.kimi-turnend-token" "$STATE/$ID.childcpu"
"$STATE/$ID.kimi-turnend-token" "$STATE/$ID.childcpu" \
"$STATE/$ID.terminal-recorded"
if [ "$KIND" != scout ] && [ "$KIND" != secondmate ] && [ "$MODE" != local-only ]; then
"$FM_ROOT/bin/fm-fleet-sync.sh" "$PROJ" || true
fi
Expand Down
Loading
Loading