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
4 changes: 3 additions & 1 deletion .agents/skills/secondmate-provisioning/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,9 @@ An SSH transport failure or unreadable remote endpoint remains unknown and must
Respawn re-resolves the secondmate harness from current config, uses the same guarded pre-launch sync, and re-propagates inherited local material, so recovered secondmates converge inherited config items and shared captain preferences whenever their home validates; tracked-file sync remains guarded separately.
If the secondmate is already running and only inherited local material changed, prefer `bin/fm-config-push.sh` over respawning.
To move a live LOCAL secondmate onto a newly pinned harness, model, or effort without a full recovery, set `config/secondmate-harness` and then relaunch it with `bin/fm-control.sh <id> relaunch`, which re-resolves that pin, stops the agent, and launches the replacement in the same home ([`docs/agent-control.md`](../../../docs/agent-control.md)).
That plane refuses a remotely placed secondmate by name, because its agent runs on another host where none of the plane's postconditions can be read; use the remote route's own relaunch path for those.
That plane refuses a remotely placed secondmate by name, because its agent runs on another host where none of the plane's postconditions can be read.
Move a REMOTE one with `bin/fm-on.sh <id> fm-remote-secondmate-control.sh relaunch <id> <harness> <model|default|-> <effort|default|->`, which runs that same control-plane relaunch on its host; pass the profile explicitly and use `default` for an absent pin, because `config/secondmate-harness` is not inherited and the copy on that host belongs to a different home ([`docs/remote-secondmates.md`](../../../docs/remote-secondmates.md)).
An instruction-surface update restarts eligible mates of both placements on its own; the `/updatefirstmate` skill owns that pass, and `bin/fm-secondmate-restart.sh` owns its persist gate and failure vocabulary.

Do not reconstruct a secondmate's whole tree from the main home.
The main firstmate reconciles only direct reports.
Expand Down
55 changes: 44 additions & 11 deletions .agents/skills/updatefirstmate/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: updatefirstmate
description: >-
Self-update a running firstmate and its secondmates to the latest from origin.
Use when the captain invokes /updatefirstmate (e.g. "/updatefirstmate", "update firstmate", "pull the latest firstmate").
Fast-forwards this firstmate repo's default branch and every local or remote secondmate through its guarded update path (never forced, never disruptive), then re-reads AGENTS.md and nudges each updated secondmate to do the same, so the whole tree runs the latest bin/ and instructions.
Fast-forwards this firstmate repo's default branch and every local or remote secondmate through its guarded update path (never forced, never disruptive), then re-reads AGENTS.md and reloads changed second-mate instructions through persist-gated restarts or fallback re-read nudges.
user-invocable: true
metadata:
internal: true
Expand All @@ -16,6 +16,13 @@ Firstmate is its own repo, behind the same no-mistakes gate as any project, so n
Only `AGENTS.md`, `bin/`, and `.agents/skills/` are a running firstmate instruction surface; public `skills/` is installer-facing and is not loaded by firstmate.
This skill performs that pull for the running main firstmate and every secondmate, without disturbing any in-flight work.

Pulling the files is only half of it.
A running agent holds `AGENTS.md` and every skill it has already loaded frozen from the moment it launched, and no verified harness offers a reload, so new bytes on disk change nothing for it until it starts a fresh conversation.
That is why a second mate whose `AGENTS.md` or `.agents/skills/` changed is restarted rather than asked to re-read: a re-read appends a second copy of the mate's own job description with no defined precedence, and cannot reach a skill that is already loaded.
A `bin/` change needs none of this, because every helper is executed fresh on each call.

**One-time rollout note:** the first update that carries this restart design is still executed by the previous release, so eligible second mates receive its re-read message on that pass instead of a restart. After that update completes, run `bin/fm-secondmate-restart.sh <fm-id>...` once with those mate IDs; this change already ships that command, and later updates follow the normal flow below.

The update is **fast-forward only** - the same sanctioned self-write as the fleet sync firstmate already runs.
For a remote route, it updates the configured Firstmate code root on that host from its own origin, then guardedly fast-forwards the persistent home to that code-root commit.
It never forces, never creates a merge commit, never stashes, and advances a target only on a clean fast-forward; anything dirty, diverged, offline, or on the wrong branch is skipped and reported.
Expand All @@ -29,27 +36,51 @@ This touches only the firstmate repo and its own worktrees, never anything under
bin/fm-update.sh
```
It fast-forwards this firstmate repo's default branch from origin, then updates every registered local or remote secondmate home through its placement-specific guarded path.
It prints one status line per target (`updated <old>..<new>` / `already current` / `skipped: <reason>`), followed by two action lines that tell you exactly what to do next:
It prints one status line per target (`updated <old>..<new>` / `already current` / `skipped: <reason>`), followed by three action lines that tell you exactly what to do next:
- `reread-firstmate: yes|no`
- `restart-secondmates: fm-<id>...|none`
- `nudge-secondmates: fm-<id>...|none`

The two second-mate sets are disjoint and the script owns the split; do not re-derive it.
A mate reaches neither set because it was skipped, was already current, advanced without changing anything it reads or runs, or had an endpoint positively classified as dead or missing - none of those need any action from you.

2. **Re-read AGENTS.md if your own instructions changed.**
When the updater printed `reread-firstmate: yes`, the tracked instruction surface (`AGENTS.md`, `bin/`, or `.agents/skills/`) just advanced under you.
**Read `AGENTS.md` now** (CLAUDE.md is a real `@AGENTS.md` pointer to it) to refresh your operating instructions before doing anything else, so you are acting on the new instructions rather than the stale ones you were started with.
When it printed `reread-firstmate: no`, nothing changed for you - skip the re-read.

3. **Nudge each updated live secondmate.**
For every target listed on the `nudge-secondmates:` line (do nothing when it says `none`), send a one-line re-read nudge so that secondmate picks up its new instructions too:
3. **Restart every second mate whose own instructions changed.**
Pass the whole `restart-secondmates:` list to one command (skip this step entirely when it says `none`):
```sh
FM_HOME=<this-firstmate-home> bin/fm-send.sh <id> 'firstmate was updated to the latest - please re-read your AGENTS.md to pick up the new instructions.'
FM_HOME=<this-firstmate-home> bin/fm-secondmate-restart.sh <fm-id>...
```
Include `FM_HOME=<this-firstmate-home>` unless `FM_HOME` is already set to the active firstmate home.
This is a gentle steer, not an interruption: the secondmate already got a safe tracked-files fast-forward, and the nudge never forces, tears down, or discards its work.
A secondmate that was skipped, already current, or has no live metadata is not on the list and needs no nudge.
This is automatic and needs no per-mate confirmation from the captain.
Local and remote mates go in the same list; the command owns the transport, the profile each replacement runs on, and the wait.

It asks every listed mate first to write down the open work it holds only in its conversation, and restarts one only after that mate's own answer comes back.
A mate that is mid-turn queues the request behind that turn.
That is the whole point of the step, so do not work around it: it is what keeps a captain call the mate had formed but never registered from being lost with the conversation.
Its header owns the request, the bound, and the two knobs that change them.

Read its per-mate lines and its closing `summary:` line as the outcome:
- `restarted: <id>` - that mate is now genuinely running the new instructions.
- `nudged: <id>: <reason>` - the restart was not safe, so the mate got the older re-read message instead and is still running the previous instructions.
Never report one of these as a clean reload.
- `unreached: <id>: <reason>` - no safe running outcome could be confirmed, including an ambiguous relaunch result.

4. **Send the re-read message to the rest.**
For every target on the `nudge-secondmates:` line (do nothing when it says `none`), send the one-line re-read steer:
```sh
FM_HOME=<this-firstmate-home> bin/fm-send.sh <id> 'firstmate was updated to the latest - please re-read your AGENTS.md to pick up the new instructions.'
```
These are the mates whose advance does not need a fresh conversation, or that could not be restarted provably.
It is a gentle steer, not an interruption: the mate already got a safe tracked-files fast-forward, and the steer never forces, tears down, or discards its work.

4. **Report to the captain in plain outcomes.**
5. **Report to the captain in plain outcomes, in one line where you can.**
Summarize what landed under `AGENTS.md` section 9 without firstmate's internal vocabulary: which parts of the fleet are now on the latest, and which were left as-is and why.
For example: "Captain, firstmate and both second mates are now on the latest."
Say plainly when a mate got the message rather than a clean reload, and why - never let a partial reload read as a full one.
Surface any skipped target whose reason needs the captain's attention - for instance a home with its own un-landed changes (diverged) or local edits (dirty), which were left untouched on purpose.

## Safety
Expand All @@ -59,6 +90,8 @@ This touches only the firstmate repo and its own worktrees, never anything under
Nothing with unlanded work is ever discarded - this is prime directive #3.
- **Only the firstmate repo and its worktrees** are touched, never `projects/`.
It is the same sanctioned self-write as the fleet sync.
- **Secondmates are never disrupted.**
A local or remote secondmate gets a tracked-files fast-forward only when its own checkout is safe to advance, plus a gentle re-read nudge when it changed.
It is never torn down, interrupted, or forced.
- **Nothing with work in it is disrupted.**
A local or remote second mate gets a tracked-files fast-forward only when its own checkout is safe to advance.
A restart replaces that mate's agent in the same home and endpoint after its open work is written down; it is never a teardown and never forced.
Its crewmates keep running in their own endpoints, and every durable record - backlog, held captain calls, unread status, unhandled instructions - is re-presented to the replacement at startup.
A restart refused before it is attempted leaves that mate on the re-read path; once a relaunch is attempted, any failed or ambiguous result is reported as unknown rather than attributed to either incarnation.
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ The scaffold is a safety contract, not a suggestion.
Firstmate's shared instruction surface reaches running homes only after it lands on the default branch and those homes fast-forward.
Only `AGENTS.md`, `bin/`, and `.agents/skills/` are loaded by a running firstmate; public `skills/` is an installer-facing surface.
When the captain invokes `/updatefirstmate` or asks to update firstmate, load the `/updatefirstmate` skill.
It performs guarded fast-forward updates of firstmate and registered secondmate homes, refreshes instructions, and never touches anything under `projects/`.
It performs guarded fast-forward updates of firstmate and registered secondmate homes, refreshes changed second-mate instructions through persist-gated restarts or fallback re-read nudges, and never touches anything under `projects/`.

## 13. Agent-only reference skills

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ Claude and grok use the slash form shown here; codex uses the same names with `$
| `/afk` | Enter away-mode supervision: the sub-supervisor self-handles routine notifications in bash, escalates captain-relevant events and bounded declared-external-wait rechecks as batched digests, and actively alerts if delivery gets stuck while you step away |
| `/ahoy` | Recap visible session events since the prior real captain message plus visibly unanswered captain decisions, then guide the captain through any open decisions one at a time in agent-judged impact order; fall back to Bearings when invoked as the session's first real captain message |
| `/bearings` | Generate a concise four-section chat digest from bounded fleet state, including registered remote-home ledgers; use `/bearings file` to also replace today's dated report in `data/`, and add `include PRs` for live GitHub enrichment |
| `/updatefirstmate` | Self-update the running firstmate and its secondmates to the latest from origin with fast-forward-only pulls, then re-read instructions and nudge secondmates |
| `/updatefirstmate` | Self-update the running firstmate and its secondmates with fast-forward-only pulls, then reload changed second-mate instructions through persist-gated restarts or fallback re-read nudges |
| `/stow` | Sweep the session for uncaptured durable knowledge, persist the open work records this session knows are unfiled or now wrong, curate tiered startup memory with decay and cold archival, enforce each home's budget or surface the required decision, cascade to registered second mates, and report what is safe to reset |

Bearings invocation examples:
Expand Down
14 changes: 8 additions & 6 deletions bin/fm-control.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@
# relaunch Transactionally replace the running agent with a new one, in the
# SAME endpoint and SAME worktree, on the same or a newly chosen
# harness/model/effort - so switching harness is one ordinary use
# of this verb. With no explicit axis, a secondmate re-resolves its
# durable config/secondmate-harness pin (harness plus its optional
# model and effort tokens) exactly as any other respawn does, while
# a ship or scout keeps the exact adapter already recorded for it.
# of this verb. An explicit `default` model or effort clears that
# axis for the replacement. With no explicit axis, a secondmate
# re-resolves its durable config/secondmate-harness pin (harness
# plus its optional model and effort tokens) exactly as any other
# respawn does, while a ship or scout keeps the exact adapter
# already recorded for it.
# A prefixed raw-command basename cannot reconstruct its launch
# command, so relaunch requires an explicit --harness for it.
# --note is required for a ship or scout, whose replacement
Expand Down Expand Up @@ -246,8 +248,8 @@ fi
[ "$MODEL_SET" = 0 ] || [ -n "$NEW_MODEL" ] || die "--model requires a non-empty value"
[ "$EFFORT_SET" = 0 ] || [ -n "$NEW_EFFORT" ] || die "--effort requires a non-empty value"
case "$NEW_EFFORT" in
''|low|medium|high|xhigh|max) ;;
*) die "--effort must be one of low, medium, high, xhigh, max" ;;
''|default|low|medium|high|xhigh|max) ;;
*) die "--effort must be one of default, low, medium, high, xhigh, max" ;;
esac

# --- exact task-id resolution ----------------------------------------------
Expand Down
14 changes: 14 additions & 0 deletions bin/fm-ff-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,20 @@ changed_instr() {
printf '%s' "$out"
}

# Whether a changed_instr list names a surface a RUNNING agent still holds from
# its launch, so picking the new bytes up needs a fresh conversation rather than
# just the next command. AGENTS.md is read once at startup and a loaded skill
# under .agents/skills/ is frozen for the rest of that conversation, while every
# helper under bin/ is executed fresh on each call and therefore reloads itself.
# This is deliberately STRICTER than "changed_instr found something": a bin/-only
# advance changes the tooling without changing anything the agent is holding.
ff_instr_needs_reload() { # <changed_instr-list>
case "$1" in
*AGENTS.md*|*.agents/skills*) return 0 ;;
esac
return 1
}

# Translate one remote home sync leg's failure into an operator-actionable
# reason. The remote leg refuses a command shape it does not recognize with this
# status, which on this leg can only mean that host's Firstmate copy predates the
Expand Down
Loading
Loading