Skip to content
Merged
15 changes: 11 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ config/secondmate-harness harness the PRIMARY uses to launch SECONDMATE agents,
config/backlog-backend backlog backend override; LOCAL, gitignored; absent or "tasks-axi" = default tasks-axi backend, "manual" = force routine backlog updates to hand-editing; inherited by secondmate homes (section 10)
config/backend runtime session-provider backend override for new tasks; LOCAL, gitignored; absent = falls through to runtime auto-detection (the runtime firstmate itself is executing inside), then tmux; tmux is the verified reference backend (docs/tmux-backend.md), while herdr, zellij, orca, and cmux are experimental spawn backends (docs/herdr-backend.md, docs/zellij-backend.md, docs/orca-backend.md, docs/cmux-backend.md) - herdr and cmux can also be selected by runtime auto-detection, zellij and orca never are (always explicit), and codex-app is not accepted; see docs/codex-app-backend.md; inherited by secondmate homes under the primary-authoritative contract in secondmate-provisioning
config/calm Pi Calm presentation preference; LOCAL, gitignored, and not inherited; see docs/configuration.md "Pi Calm preference"
config/concurrency-floor optional target of concurrent live ships per home; LOCAL, gitignored, absent or 0 = off; see docs/configuration.md "Ship concurrency floor"
config/startup-memory-budget primary-authoritative per-home startup-memory budget; LOCAL, gitignored, materialized as 7,500 estimated tokens by locked primary bootstrap and inherited into secondmate homes; see docs/configuration.md "Startup memory budget"
config/herdr-presentation-spaces optional presence flag for Herdr's default-off disposable single-task visual projection; LOCAL, gitignored; inherited by secondmate homes; see docs/herdr-backend.md "Optional presentation spaces"
config/cmux-socket-password optional cmux control-socket password; LOCAL, gitignored; read fresh on every cmux CLI call and passed through without ever overriding an operator's own ambient CMUX_SOCKET_PASSWORD when absent (docs/cmux-backend.md "Setup")
Expand Down Expand Up @@ -110,6 +111,8 @@ state/ volatile runtime signals; gitignored
public-followup/ generated private transport for promised public replies: commitment registrations, typed terminal-result inbox, accepted/rejected ledgers (section 14; bin/fm-public-followup.sh)
x-poll.error x-poll.claim-error generated X-mode relay and offer-claim diagnostic dedupe markers
.wake-queue durable queued wakes: epoch<TAB>seq<TAB>kind<TAB>key<TAB>payload
.refill-completion-<id> crash-recoverable idempotence receipt for one completion-refill wake per task (bin/fm-refill-lib.sh)
.refill-needed-completion .refill-needed-floor durable home-level refill supervision markers (bin/fm-refill-lib.sh)
.afk durable away-mode flag; present = sub-supervisor may inject escalations (set by /afk, cleared on user return)
.watch.lock .wake-queue.lock watcher singleton and queue serialization locks
.claude-autoarm.lock .claude-autoarm-epoch .turnend-claude-blocks Claude Stop auto-arm single-flight, epoch, and guard-budget records; never touch
Expand Down Expand Up @@ -334,6 +337,7 @@ Tear down a ship task only after landing is confirmed.
A teardown refusal for uncommitted or unlanded work is a stop-and-investigate result, never an obstacle to bypass.
Never force teardown without explicit discard authority.
After successful teardown, record completion, retain only the configured recent Done history, and re-evaluate queued work whose blockers and time gates have cleared.
Teardown and merge-poll completion also emit a durable refill wake (`bin/fm-refill-lib.sh`); treat that wake as the claim-next trigger rather than relying on mid-turn memory of a printed reminder.

A secondmate is persistent and an empty queue is healthy.
Retire one only on an explicit captain or main-firstmate decision, after loading `secondmate-provisioning`; its home must contain no work under way, and forced discard still requires explicit captain authority.
Expand All @@ -350,11 +354,11 @@ The promoted worker must inventory scratch state, return to a clean default-bran

Fleet supervision is an always-loaded operational contract; `docs/architecture.md`, `docs/turnend-guard.md`, the emitted session-start block, and script help own mechanisms and harness-specific recipes.

Whenever work is under way, keep exactly one live supervision cycle using the emitted protocol for this primary harness.
X mode may require that same live cycle with no fleet work.
Whenever work is under way or refill remains pending, keep exactly one live supervision cycle using the emitted protocol for this primary harness.
X mode or an unmet concurrency floor may require that same live cycle with no fleet metadata.
Do not substitute another harness's wait shape, use shell `&`, or create a second cycle when a healthy one already exists.
For every actionable wake, follow the ordinary-wake continuation in the emitted protocol; use its repair action only when the live cycle is missing or failed.
No turn ends blind while work is under way, including turns described as holding or waiting.
No turn ends blind while supervision remains needed, including work described as holding or waiting.

At the start of every wake-handling turn, drain the durable wake queue before peeking, reading beyond the reason line, steering, or starting work.
Session start is the only exception because its one-shot digest already drained while locked or deliberately left the queue untouched in lock-refused read-only mode.
Expand All @@ -365,7 +369,10 @@ Handle actionable wakes as follows:

1. For `signal:`, read the listed event lines first, then reconcile current state only where action depends on it.
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 and X-mode events.
3. For `check:`, act on the named poll result, including merges, X-mode events, and refill signals.
A `check: refill completion <id>:`, `check: refill floor:`, or `check: refill pending:` wake is claim-next capacity, not a blind spawn: run the normal claim-and-dispatch procedure (verify-at-pickup, atomic claim, exclusions, held and parked items, date gates).
A successful ship spawn clears the handled completion need automatically; after a completed attempt that finds no ready work, no eligible work, or held-only work, run `bin/fm-refill-complete.sh` with the matching `no-ready`, `no-eligible`, or `held-only` outcome.
Until ship worktree leases land, hold pool dispatch unless `fm_refill_has_parked_unpushed` proves every parked ship worktree readable and free of unpushed commits (`bin/fm-refill-lib.sh`).
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 any wake reports a merged PR for a project cloned in this home, refresh that clone through the guarded fleet-sync path.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Launching a supported harness inside it instantiates your first mate - and makes
- **Two task shapes** - ship tasks deliver authorized changes; scout tasks leave standalone investigation reports when the intake contract warrants separate research.
- **Explicit project modes** - each project ships via `no-mistakes`, `direct-PR`, or `local-only`, with an optional `+yolo` autonomy flag.
- **Optional secondmates** - opt in to persistent second mates that run from isolated firstmate homes with their own `FM_HOME`, state, projects, and session lock, supervising project clones or a project-less firstmate-repo domain, kept on the primary firstmate version by guarded local fast-forwards and checked for live agent processes at session start.
- **Event-driven, zero-token supervision** - a bash watcher sleeps on the fleet and wakes the first mate only when something needs you; verified primary harnesses also get a turn-end backstop that blocks or follows up on a blind stop when work is under way and supervision is not live.
- **Event-driven, zero-token supervision** - a bash watcher sleeps on the fleet and wakes the first mate only when something needs you; verified primary harnesses also get a turn-end backstop that blocks or follows up on a blind stop whenever fleet, relay, or refill work needs supervision and no watcher is live.
- **Optional X mode** - opt in with one local `.env` token so firstmate can answer your public `@myfirstmate` mentions, act on normal reversible mention requests through the same lifecycle as chat requests, acknowledge spawned work, and post up to three public-safe completion follow-ups within seven days for genuine milestones and the final outcome without changing non-X behavior; a final reply promised in a thread becomes durable state that is reconciled from disk, so a restart or a compacted conversation cannot lose it; dry-run preview records would-be replies and dismissals locally before go-live.
- **Strict project boundary** - the first mate is read-only over your projects except for the narrow guarded and captain-approved operations authorized by [hard rule 1](AGENTS.md#1-identity-and-prime-directives), including fleet sync's guarded safe branch pruning; crewmates make every other project change behind the configured merge authority.
- **Restart-proof** - all state lives on disk and in the active session backend (tmux by hard default, herdr or cmux when selected or auto-detected, zellij/orca when explicitly selected); kill the session anytime and the next one reconciles, including confirmed-dead secondmate agents, and carries on.
Expand Down
6 changes: 3 additions & 3 deletions bin/fm-claude-stop-autoarm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
# - AFK: while state/.afk exists the away daemon owns the watcher and triage;
# this hook exits 0 and NEVER rewakes the primary (checked again at
# translation time so a mid-cycle AFK transition is honored).
# - Need: arms only while work is in flight (state/*.meta) or X mode has a
# relay poll to run (state/x-watch.check.sh); an idle home exits 0.
# - Need: arms while work is in flight, X mode has a relay poll, or refill
# work remains queued, pending, or below a configured floor.
# - Single-flight: Claude does not dedupe async hooks, so a home-scoped owner
# lock (state/.claude-autoarm.lock) admits exactly one owner; every other
# concurrent firing exits 0 without translating, which keeps one event
Expand Down Expand Up @@ -89,7 +89,7 @@ fi
# --- AFK: the away daemon owns the watcher and triage; never rewake ----------
[ -e "$STATE/.afk" ] && exit 0

# --- need: in-flight work or an X-mode relay poll ----------------------------
# --- need: in-flight work, X-mode relay polling, or refill work --------------
need_supervision() {
fm_supervision_needed "$STATE" "$GRACE"
}
Expand Down
40 changes: 23 additions & 17 deletions bin/fm-guard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# First, always warn if the firstmate primary checkout (FM_ROOT) is on a named
# non-default branch, because that means firstmate-on-itself work landed in the
# primary instead of an isolated worktree.
# Then, if any task is in flight (a state/<id>.meta exists) and the watcher's
# Then, if any task, X-mode relay, or refill work needs supervision and the watcher's
# liveness beacon (state/.last-watcher-beat, touched every poll cycle) is
# missing or older than FM_GUARD_GRACE seconds, prints a loud, clearly delimited
# banner so the agent cannot skim past it in the tool output of whatever it was
Expand Down Expand Up @@ -140,27 +140,26 @@ if [ -n "$tangle_branch" ]; then
} >&2
fi

# Compute in-flight count and watcher-beacon freshness via the shared
# grace-based predicate (bin/fm-supervision-lib.sh). Only act with tasks in
# flight; count them so the banner can say how much is riding on an absent
# watcher.
# Compute fleet, relay, and refill supervision need plus watcher-beacon freshness
# through the shared grace-based predicate (bin/fm-supervision-lib.sh). Keep the
# in-flight count so the banner can say how much is riding on an absent watcher.
fm_supervision_status "$STATE" "$GRACE"
in_flight=$FM_SUP_IN_FLIGHT
watcher_fresh=$FM_SUP_WATCHER_FRESH
beacon_desc=$FM_SUP_BEACON_DESC
if [ "$in_flight" -eq 0 ]; then
# Leave the unhealthy state (no work riding on the watcher): clear so a later
# in-flight + stale combination is a fresh episode even if the beacon is still
# absent with the same key string.
if [ "$FM_SUP_NEEDED" = false ]; then
# Leave the unhealthy state (no supervision need): clear so a later need plus
# stale watcher is a fresh episode even if the beacon is still absent with the
# same key string.
[ "$READ_ONLY" -eq 1 ] || fm_guard_clear_stale_banner
exit 0
fi

[ -s "$FM_WAKE_QUEUE" ] && queue_pending=true

# No fresh watcher with tasks in flight is the dangerous state: emit a prominent,
# bordered banner FIRST so it reads as an alarm, not a buried stderr line. Later
# calls in the same episode get a one-line reminder only.
# A supervision need with no fresh watcher is the dangerous state: emit a
# prominent, bordered banner FIRST so it reads as an alarm, not a buried stderr
# line. Later calls in the same episode get a one-line reminder only.
if [ "$watcher_fresh" = false ]; then
episode_key=$(fm_guard_stale_episode_key "$STATE")
episode_key=${episode_key%$'\n'}
Expand All @@ -187,7 +186,13 @@ if [ "$watcher_fresh" = false ]; then
{
printf '●%s\n' "$rule"
printf '● WATCHER DOWN - SUPERVISION IS OFF\n'
printf '● %s task(s) in flight, but no watcher has a fresh beacon (last beat: %s, grace %ss).\n' "$in_flight" "$beacon_desc" "$GRACE"
if [ "$in_flight" -gt 0 ]; then
printf '● %s task(s) in flight, but no watcher has a fresh beacon (last beat: %s, grace %ss).\n' "$in_flight" "$beacon_desc" "$GRACE"
elif [ "$FM_SUP_REFILL_NEEDED" = true ]; then
printf '● Refill work is pending, but no watcher has a fresh beacon (last beat: %s, grace %ss).\n' "$beacon_desc" "$GRACE"
else
printf '● X-mode relay polling is active, but no watcher has a fresh beacon (last beat: %s, grace %ss).\n' "$beacon_desc" "$GRACE"
fi
if [ "$READ_ONLY" -eq 1 ]; then
printf '● This read-only session should report the lapse, not repair it.\n'
else
Expand All @@ -202,14 +207,15 @@ if [ "$watcher_fresh" = false ]; then
"$beacon_desc" "$GRACE" >&2
fi
else
# Healthy again while work is still in flight: end the episode so a later
# Healthy again while supervision remains needed: end the episode so a later
# restale re-prints the full banner.
[ "$READ_ONLY" -eq 1 ] || fm_guard_clear_stale_banner
fi

# Queued wakes are an independent hazard; warn whenever they are pending, even if
# a watcher is alive. Kept after the banner so the no-watcher alarm reads first.
# Dedup of the watcher-down banner never suppresses this warning.
# Queued wakes are independent of watcher freshness; while supervision remains
# needed, warn whenever they are pending even if a watcher is alive. Keep this
# after the banner so the no-watcher alarm reads first. Dedup of the watcher-down
# banner never suppresses this warning.
if "$queue_pending"; then
if [ "$READ_ONLY" -eq 1 ]; then
echo "WARNING: queued wakes pending - left untouched because this session lacks verified fleet-lock ownership." >&2
Expand Down
27 changes: 27 additions & 0 deletions bin/fm-refill-complete.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# Record a handled refill claim-and-dispatch cycle that spawned no ship.
set -u

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
FM_ROOT="${FM_ROOT_OVERRIDE:-$(cd "$SCRIPT_DIR/.." && pwd)}"
FM_HOME="${FM_HOME:-${FM_ROOT_OVERRIDE:-$FM_ROOT}}"
STATE="${FM_STATE_OVERRIDE:-$FM_HOME/state}"
CONFIG="${FM_CONFIG_OVERRIDE:-$FM_HOME/config}"

# shellcheck source=bin/fm-refill-lib.sh
. "$SCRIPT_DIR/fm-refill-lib.sh"

case "${1:-}" in
no-ready|no-eligible|held-only) outcome=$1 ;;
*)
echo "usage: fm-refill-complete.sh <no-ready|no-eligible|held-only>" >&2
exit 2
;;
esac
[ "$#" -eq 1 ] || {
echo "usage: fm-refill-complete.sh <no-ready|no-eligible|held-only>" >&2
exit 2
}

fm_refill_dispatch_cycle_completed "$STATE" "$CONFIG"
printf 'refill claim-and-dispatch cycle completed: %s\n' "$outcome"
Loading
Loading