Skip to content
10 changes: 10 additions & 0 deletions .agents/skills/stow/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,16 @@ Extend the completion receipt with one entry per secondmate alongside the primar
Keep those entries in the same plain captain-facing language the rest of the receipt uses.
The session is reset-safe only when every home is within its own budget with no unresolved exception.

## Automatic /stow markers

Every `/stow` invocation in every home - primary or secondmate - updates the staleness markers below after that home's own pass (and, in a primary home, after the cascade above).
A secondmate home still performs this step even though it never cascades further.

When, and only when, the whole pass for this home - including the cascade above in a primary home - is reset-safe, touch `state/.last-stow` (`touch state/.last-stow`); never touch it when reset-safe cannot be claimed.
Then touch `state/.last-stow-attempt` (`touch state/.last-stow-attempt`) as the pass's true final step, unconditionally, on every `/stow` invocation - reset-safe or not, and whatever exceptions stayed unresolved.
Both are bare-mtime markers mirroring `state/.last-heartbeat` (`bin/fm-watch.sh`): `state/.last-stow` records the last fully reset-safe pass, while `state/.last-stow-attempt` records that a pass ran at all and is the marker the automatic `/stow` triggers in `AGENTS.md` read to decide whether another pass is due.
A home carrying a sticky exception it cannot clear on its own - a `deferred` secondmate, an unresolved over-budget home, a shared preference still routing to the primary - therefore stays throttled to one automatic pass per interval instead of re-running on every heartbeat.

## Scope exclusion: no skill storage by the pass

The stow pass itself must never store, create, or edit a skill as a destination for any finding.
Expand Down
5 changes: 4 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ state/ runtime records and signals; gitignored
.hash-* .count-* .stale-* .stale-since-* .paused-* .wedge-escalations-* .writing-* .seen-* .hb-surfaced-* .last-* .heartbeat-streak watcher internals; never touch
.watch-triage.log watcher's absorbed-wake debug log (size-capped); never relied on, safe to delete
.last-watcher-beat watcher liveness beacon, touched every poll (including while absorbing benign wakes); guard scripts read it
.last-stow bare-mtime marker touched only by the stow skill, only at the end of a reset-safe pass; the durable record of the last clean /stow
.last-stow-attempt bare-mtime marker touched by the stow skill at the end of every /stow pass, reset-safe or not; read by fm-session-start.sh's compact/clear re-emit and by section 8 rule 4's heartbeat check to gate automatic /stow
.subsuper-* .supervise-daemon.* sub-supervisor internals; never touch
.no-mistakes/ local validation state and evidence; gitignored
```
Expand Down Expand Up @@ -257,7 +259,7 @@ Route durable knowledge to its most specific owner:
Firstmate never writes a project's `AGENTS.md` directly.
A crewmate creates or updates it lazily through the project's selected delivery path, using `bin/fm-ensure-agents-md.sh` and preferring pointers to authoritative sources over copied detail.
Keep fleet delivery posture and captain-private strategy out of project memory.
When the captain invokes `/stow`, load the `stow` skill for its memory curation, knowledge routing, and persistence of the open work records this session is holding; it files and corrects only the open work that session is holding, and never reconciles the backlog against repository or PR reality.
When the captain invokes `/stow` or an automatic trigger says it is due, load the `stow` skill for its memory curation, knowledge routing, and persistence of the open work records this session is holding; it files and corrects only the open work that session is holding, and never reconciles the backlog against repository or PR reality.

## 7. Task lifecycle

Expand Down Expand Up @@ -409,6 +411,7 @@ Handle actionable wakes as follows:
2. For `stale:`, inspect the recorded endpoint and load `stuck-crewmate-recovery` for a stopped, looping, confused, or unresponsive worker; a deep-inspection reason also requires current-state and validation-log inspection.
3. For `check:`, act on the named poll result, including merges, Relay events, process-to-event source results, and captain inbox notes; a handled inbox note is also acknowledged with `bin/fm-inbox.sh drain --ack <id>`, or it stays counted as still waiting for firstmate.
4. For `heartbeat:`, review the whole fleet from the structured fleet view, reconcile suspicious tasks and PR state, update the backlog, and never report an unchanged fleet as progress.
When the captain has explicitly enabled automatic stow for this home with `config/auto-stow`, also check `state/.last-stow-attempt`'s age against `FM_AUTO_STOW_INTERVAL_SECS` (default ~24h, a separate and larger clock than the heartbeat's own cadence); when due, run `/stow` first, before the rest of this review, so an automatic pass does not run on every heartbeat. That marker records an attempted pass rather than a reset-safe one, so a home holding an exception `/stow` cannot clear still waits out the full interval before the next automatic pass.

When any wake reports a merged PR for a project cloned in this home, refresh that clone through the guarded fleet-sync path.
When Relay-linked work reaches a milestone or terminal state, load `fmx-respond`; before terminal teardown, use its promised-final reconciliation when a typed public commitment exists, otherwise post the final completion follow-up so the link clears even if earlier follow-ups were spent.
Expand Down
46 changes: 46 additions & 0 deletions bin/fm-session-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,45 @@ QUEUED_LIMIT=${FM_SESSION_START_QUEUED_LIMIT:-20}
case "$QUEUED_LIMIT" in ''|*[!0-9]*|0) QUEUED_LIMIT=20 ;; esac
BACKLOG_FIELDS=blocked_by,hold_kind,hold_reason

# Automatic /stow, trigger 1 (the compact/clear re-emit path below): an
# explicit home-local config/auto-stow grant plus a staleness gate on
# state/.last-stow-attempt, touched by the stow skill itself
# at the end of every pass whether or not it reached reset-safe (mirrors
# state/.last-heartbeat's bare-mtime marker, bin/fm-watch.sh). Reading the
# attempt marker rather than its reset-safe-only sibling state/.last-stow is
# what holds the once-per-interval throttle in a home whose exceptions /stow
# cannot clear. Read only here, never written by this script. Trigger 2 is the
# heartbeat-handling check in AGENTS.md section 8 rule 4, which reads the same
# marker against the same interval.
STOW_INTERVAL=${FM_AUTO_STOW_INTERVAL_SECS:-86400}
if [[ "$STOW_INTERVAL" =~ ^0*([1-9][0-9]{0,7})$ ]]; then
STOW_INTERVAL=${BASH_REMATCH[1]}
else
STOW_INTERVAL=86400
fi
if [ "${#STOW_INTERVAL}" -eq 8 ] && [ "$STOW_INTERVAL" -gt 31536000 ]; then
STOW_INTERVAL=86400
fi

# stow_due_line: one "STOW DUE: ..." line when state/.last-stow-attempt is
# missing, unreadable, or at least STOW_INTERVAL seconds old, silent (prints
# nothing, exit 0) when current. Detect-only and cheap - a single mtime stat -
# matching the "always check, only speak up when it matters" idiom the
# bootstrap stage already uses.
stow_due_line() {
local marker="$STATE/.last-stow-attempt" m age
m=$(fm_path_mtime "$marker")
if [ -z "$m" ]; then
printf 'STOW DUE: no recorded /stow pass (source=%s); run /stow before other work.\n' "${SESSION_SOURCE:-unknown}"
return 0
fi
age=$(( $(date +%s) - m ))
[ "$age" -ge 0 ] || age=$STOW_INTERVAL
[ "$age" -ge "$STOW_INTERVAL" ] || return 0
Comment thread
greptile-apps[bot] marked this conversation as resolved.
printf 'STOW DUE: last /stow pass was %ss ago (over the %ss interval, source=%s); run /stow before other work.\n' \
"$age" "$STOW_INTERVAL" "${SESSION_SOURCE:-unknown}"
}

RULE='================================================================================'
SUBRULE='--------------------------------------------------------------------------------'

Expand Down Expand Up @@ -639,6 +678,13 @@ if [ "$LOCK_RC" -ne 0 ]; then
printf '%s\n' "$BAR"
}
fi
# Automatic /stow, trigger 1. Held until the lock verdict above: /stow mutates
# this home's memory files, so a re-emit that could not verify fleet-lock
# ownership must stay silent about it and leave the still-due marker to the
# next session start or re-emit that does own the lock.
if [ "$REEMIT" -eq 1 ] && [ "$READ_ONLY" -eq 0 ] && [ -e "$CONFIG/auto-stow" ]; then
stow_due_line
fi
REBUILDING_SESSION_PID=$(fm_harness_ancestry_pid 2>/dev/null || true)
print_agents_refresh_if_required "$REBUILDING_SESSION_PID"

Expand Down
1 change: 1 addition & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ It is deliberately not a reconciliation of durable records against repository or
Task-scoped notes use `tasks-axi show <id> --full` followed by `tasks-axi update <id> --body-file <path>`, adding `--archive-body` when the prior body should remain recoverable.
The stow pass never writes a skill, but a separately executed, captain-approved migration may move conditional knowledge into a user-owned local skill excluded from the Firstmate clone; changes to Firstmate's tracked skills remain deliberate repository work through the normal PR pipeline.
Invoked in a primary home, `/stow` then cascades the same sweep to every registered secondmate, enumerated through `bin/fm-stow-cascade.sh`: each home is accounted and curated against its own startup-memory allowance, a live secondmate sweeps its own session, and a slow or unreachable home is reported as an exception rather than blocking the primary.
Automatic triggers for the same skill - a lock-owning compact/clear re-emit and a staleness-gated heartbeat check - are owned by [configuration.md](configuration.md#automatic-stow).

## Local clones stay fresh

Expand Down
15 changes: 15 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,20 @@ The flag is per home and is not inherited by secondmate homes, because stow cade
Only the file's presence is read, so its contents are ignored; remove it to return to the default contract on the next pass.
The skill text owns the marker spelling, the tick order, and the reinforcement rule.

## Automatic /stow

Create the home-local, gitignored `config/auto-stow` file to grant automatic memory curation for that home; its contents are ignored, and removing it disables both triggers.
This explicit captain opt-in satisfies `VISION.md`'s authority boundary while letting two existing turns decide whether to run the internal [`/stow` skill](../.agents/skills/stow/SKILL.md) instead of waiting for the captain to type it.

1. An enabled, lock-owning compact/clear session-start re-emit prepends one `STOW DUE:` line when `state/.last-stow-attempt` is missing or older than `FM_AUTO_STOW_INTERVAL_SECS` (default 86400), and stays silent when the marker is current, automatic stow is disabled, or the session could not verify fleet-lock ownership (`bin/fm-session-start.sh`).
2. Enabled heartbeat handling in `AGENTS.md` section 8 rule 4 runs `/stow` first when the same marker is due, using that same larger interval so a pass does not run on every heartbeat wake.

`FM_AUTO_STOW_INTERVAL_SECS` accepts whole seconds from 1 through 31536000 (365 days); empty, non-numeric, zero, or larger values fall back to 86400.
The stow skill touches `state/.last-stow-attempt` at the end of every pass, reset-safe or not, and touches `state/.last-stow` only when the pass is reset-safe.
The automatic triggers read the attempt marker, so a home holding an exception `/stow` cannot clear still waits out the full interval.
Away-mode heartbeats stay bash-only and never run `/stow`.
On a default Pi primary, heartbeat wakes go to the supervision branch, which does not currently run the `AGENTS.md` check; compact/clear re-emit remains the automatic path there ([Pi supervision branch](pi-supervision-branch.md#heartbeat-routing)).

## Secondmate routes (data/secondmates.md)

Persistent secondmate routes live locally in `data/secondmates.md`.
Expand Down Expand Up @@ -649,6 +663,7 @@ FM_ZELLIJ_SESSION=firstmate # zellij-only: named session for normal backend ops
CMUX_SOCKET_PASSWORD= # cmux-only: socket password fallback when config/cmux-socket-password is absent (docs/cmux-backend.md)
FM_SESSION_START_STATUS_TAIL=5 # state/*.status lines printed per task in the session-start digest; each line is capped by bin/fm-line-cap-lib.sh
FM_SESSION_START_QUEUED_LIMIT=20 # plain queued backlog rows in the session-start digest; in-flight, held, and blocked rows are never bounded and done rows are never listed
FM_AUTO_STOW_INTERVAL_SECS=86400 # 1..31536000 seconds; invalid or larger values fall back to 86400; gates automatic /stow against state/.last-stow-attempt's mtime (see "Automatic /stow" above)
FM_BOOTSTRAP_DETECT_ONLY=0 # internal/read-only session-start mode: skip bootstrap's mutating sweeps and print advisory TANGLE wording
FM_BOOTSTRAP_NETWORK=all # internal session-start phase split: all, skip (local steps only), or only (network steps only); see bin/fm-bootstrap.sh
FM_STARTUP_NETWORK_TIMEOUT=120 # seconds bounding the whole deferred network stage; hitting it prints an actionable NETWORK_CHECKS line
Expand Down
5 changes: 5 additions & 0 deletions docs/pi-supervision-branch.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ A review that found literally nothing worth reporting uses verdict `routine`, `t
Only a captain-worthy finding reports verdict `captain` and opens a main turn.
Every other fleet-wide or unresolvable wake - including watcher-failure alarms, which are never offered to the branch - keeps today's wake-to-main path.

The branch's heartbeat review does not currently run automatic `/stow`.
`AGENTS.md` section 8 rule 4's staleness-gated `/stow` instruction lives on main, and default-on branch supervision routes heartbeat wakes away from main.
When the captain has enabled `config/auto-stow`, a lock-owning compact/clear session-start re-emit remains the automatic `/stow` path on a default Pi primary (`bin/fm-session-start.sh`; `FM_AUTO_STOW_INTERVAL_SECS` in [configuration.md](configuration.md)).
Wiring heartbeat `/stow` into the branch is tracked in [issue #2944](https://github.com/kunchenguid/firstmate/issues/2944): `bin/fm-branch-prompt.sh`'s byte-stable-prefix contract forbids per-wake state, so that change has to preserve cache identity rather than appending a live marker age.

## Cost model and the byte-stable prefix

The captain accepted the normal provider prompt-caching strategy: a byte-identical branch prefix generated once per firstmate version, the same tool set in the same order on every request, and one shared `prompt_cache_key` per home for all branch sessions (set in a `before_provider_request` hook, and only for providers whose requests already carry that field); main keeps its own per-session key.
Expand Down
Loading