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
21 changes: 17 additions & 4 deletions .agents/skills/secondmate-provisioning/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,23 @@ Never copy any secondmate `data/captain-shared.md` back into the primary.
Keep each home's `data/captain.md` domain-local.
After first propagation to an existing home, trim that home's local `data/captain.md` by hand to domain-specific content plus pointers to `data/captain-shared.md`; do not automate or silently delete private content.
Keep every `data/learnings.md` fully local by captain decision; route fleet-general machinery facts into tracked documentation through the normal firstmate repo path rather than inventing shared learnings propagation.
No reread nudge is needed at spawn or respawn because the agent reads `AGENTS.md` fresh on launch; only the bootstrap sweep's running-home instruction-surface advance needs one.
Bootstrap reports successful sends as `BOOTSTRAP_INFO:` and only emits `NUDGE_SECONDMATES:` when that send fails and needs retry.
For already-live secondmates, use `bin/fm-config-push.sh` to push a mid-session inherited local-material change without running the tracked-file fast-forward or nudging the agents.
It uses the same live-home discovery and propagation helper as bootstrap and reports each item as `pushed`, `unchanged`, `skipped`, or `error`.
No AGENTS.md reread nudge is needed at spawn or respawn because the agent reads instructions fresh on launch; only the bootstrap sweep's running-home instruction-surface advance needs that AGENTS.md re-read.
Bootstrap reports successful AGENTS.md re-read sends as `BOOTSTRAP_INFO:` and only emits `NUDGE_SECONDMATES:` when that send fails and needs retry.
A separate, literal-content config reread is required whenever inherited `config/*` material changes under an already-running secondmate.
After each successful allowlisted config write, both the locked bootstrap convergence path and mid-session `bin/fm-config-push.sh` use the shared propagation report to build one per-home generation-specific private instruction file from the validated destination post-write bytes for only the allowlisted config items that actually changed for that home (`config/crew-dispatch.json`, `config/crew-harness`, `config/backlog-backend`), in deterministic allowlist order.
Each changed path is printed with clear begin/end delimiters and the destination file's full exact new bytes unparsed, or the explicit token `ABSENT` when propagation removed the destination copy.
The instruction uses only minimal framing that these are defaults/rules and do not remove judgment; it never includes SHA values, selected profiles, parsed summaries, or any other generated interpretation.
`data/captain-shared.md` is not a config file and is never inlined into this instruction file or message.
Homes whose allowlisted config files were all unchanged receive no config-reread message when no retry is pending.
Different homes may receive different changed-file sets based on their pre-push destination bytes.
Delivery uses the existing routed secondmate path (`fm-send`) with only a single-line `CONFIG_REREAD: <absolute generation-specific instruction path>` pointer; a failed instruction publication retains the generated exact bytes in a bounded private retry queue when possible, legacy retry reports remain recoverable, a failed publication or retry-marker write retains the exact generation until it can be delivered, a failed send records a per-generation durable retry marker when possible, and all failures surface a concrete `CONFIG_REREAD:` diagnostic without claiming the live agent already re-read the values.
The propagation, generation publication, and pointer-delivery sequence holds one per-home inheritance lock, so concurrent mid-session pushes cannot deliver an older generation after a newer one.
A newly launched or relaunched secondmate already reads its files at launch, so its pending config-reread generations are discarded or quarantined after cleanup failure and it needs no redundant live-agent config nudge unless propagation changes files after launch.
Quarantined pre-relaunch generations are retained in bounded private history, and cleanup skips creating an empty quarantine generation.
Successfully delivered generations are retained only within a bounded per-home state history, while pending generations remain until delivery succeeds or a launch supersedes them.
These config values remain defaults and rules only; they must not harden `fm-spawn` to reject a deliberate runtime choice that differs from the configured defaults.
For already-live secondmates, use `bin/fm-config-push.sh` to push a mid-session inherited local-material change without running the tracked-file fast-forward.
It uses the same live-home discovery and propagation helper as bootstrap, reports each item as `pushed`, `unchanged`, `skipped`, or `error`, and follows the config-reread contract above for changed or pending generations.
`bin/fm-home-seed.sh` refuses to copy a missing or placeholder charter.

Direct seed without a preexisting brief requires `FM_SECONDMATE_CHARTER`.
Expand Down
62 changes: 59 additions & 3 deletions bin/fm-bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ fleet_sync() {
}

secondmate_sync() {
# shellcheck source=bin/fm-wake-lib.sh disable=SC1091
. "$SCRIPT_DIR/fm-wake-lib.sh"
# Local-HEAD secondmate sync: fast-forward every LIVE secondmate home
# to the primary checkout's current default-branch commit. Purely LOCAL - no
# fetch, no origin dependency: a linked-worktree home already holds the primary's
Expand Down Expand Up @@ -335,8 +337,13 @@ secondmate_sync() {
# surface into every VALIDATED live secondmate home swept above.
# FF_SEEN_HOMES is exactly that set, and fm-config-inherit-lib.sh owns the
# declared config items plus data/captain-shared.md.
local id home home_real propagated_homes
# After a successful push that changes allowlisted config/* for an already-
# running home, send its literal-content reread instruction pointer so the
# live agent does not keep applying stale defaults. Spawn/respawn already
# re-reads at launch and needs no redundant nudge unless files changed after launch.
local id home home_real home_lock propagated_homes report reread_out reread_skip_pending
propagated_homes=""
SECONDMATE_RESPAWNED_IDS=${SECONDMATE_RESPAWNED_IDS:-}
while IFS='|' read -r id home _window _meta; do
validate_secondmate_home "$id" "$home" || continue
home_real="$VALIDATED_HOME"
Expand All @@ -348,9 +355,56 @@ secondmate_sync() {
*" $home_real "*) continue ;;
esac
propagated_homes="$propagated_homes $home_real"
if ! propagate_secondmate_inheritance "$FM_HOME" "$home_real" "$CONFIG" "$DATA"; then
mkdir -p "$home_real/state" || {
echo "CONFIG_REREAD: secondmate $id: send failed: could not create state directory"
continue
}
home_lock=$(fm_config_inherit_lock_path "$home_real") || {
echo "CONFIG_REREAD: secondmate $id: send failed: could not resolve per-home lock"
continue
}
fm_lock_acquire_wait "$home_lock" || {
echo "CONFIG_REREAD: secondmate $id: send failed: could not acquire per-home lock"
continue
}
reread_skip_pending=0
case " $SECONDMATE_RESPAWNED_IDS " in
*" $id "*) reread_skip_pending=1 ;;
esac
if [ "$reread_skip_pending" -eq 0 ] \
&& fm_config_reread_retry_queue_is_full "$FM_HOME" "$id"; then
fm_config_reread_retry_pending "$id" "$home_real" || true
if fm_config_reread_retry_queue_is_full "$FM_HOME" "$id"; then
echo "CONFIG_REREAD: secondmate $id: send failed: retry instruction queue is full"
fm_lock_release "$home_lock" || true
continue
fi
fi
report=$(mktemp "${TMPDIR:-/tmp}/fm-bootstrap-inherit.XXXXXX" 2>/dev/null) || {
echo "SECONDMATE_SYNC: secondmate $id: skipped: inheritance failed"
fm_lock_release "$home_lock" || true
continue
}
if FM_CONFIG_INHERIT_REPORT="$report" \
propagate_secondmate_inheritance "$FM_HOME" "$home_real" "$CONFIG" "$DATA"; then
:
else
echo "SECONDMATE_SYNC: secondmate $id: skipped: inheritance failed"
fi
if ! reread_out=$(FM_HOME="$FM_HOME" FM_ROOT_OVERRIDE="$FM_ROOT" \
FM_STATE_OVERRIDE="$STATE" \
FM_CONFIG_REREAD_SKIP_PENDING="$reread_skip_pending" \
fm_config_send_reread_nudge "$id" "$home_real" "$report" 2>&1); then
if [ -n "$reread_out" ]; then
printf '%s\n' "$reread_out"
else
echo "CONFIG_REREAD: secondmate $id: send failed: unknown error"
fi
elif [ -n "$reread_out" ]; then
printf '%s\n' "$reread_out"
fi
rm -f "$report"
fm_lock_release "$home_lock" || true
done < <(live_secondmate_meta_records "$STATE" "$DATA/secondmates.md")
return 0
}
Expand Down Expand Up @@ -388,6 +442,7 @@ secondmate_liveness_sweep() {
# explicitly out of scope here.
[ -d "$STATE" ] || return 0
local meta id window harness backend target verdict out
SECONDMATE_RESPAWNED_IDS=""
for meta in "$STATE"/*.meta; do
[ -f "$meta" ] || continue
grep -q '^kind=secondmate$' "$meta" 2>/dev/null || continue
Expand All @@ -409,6 +464,7 @@ secondmate_liveness_sweep() {
dead)
fm_backend_kill "$backend" "$target" 2>/dev/null || true
if out=$(FM_SPAWN_NO_GUARD=1 "$FM_ROOT/bin/fm-spawn.sh" "$id" --secondmate 2>&1); then
SECONDMATE_RESPAWNED_IDS="$SECONDMATE_RESPAWNED_IDS $id"
:
else
echo "SECONDMATE_LIVENESS: secondmate $id: respawn failed: $(first_line "$out")"
Expand Down Expand Up @@ -800,8 +856,8 @@ if [ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" = 1 ] \
echo "BOOTSTRAP_INFO: tasks-axi available"
fi
if [ "${FM_BOOTSTRAP_DETECT_ONLY:-0}" != 1 ]; then
secondmate_sync
secondmate_liveness_sweep
secondmate_sync
x_mode_setup
fleet_sync
fi
Expand Down
Loading