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
2 changes: 2 additions & 0 deletions .agents/skills/secondmate-provisioning/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ The slot stays reserved across restarts until the lease is released.
Release happens only on explicit retirement or seed rollback, never on routine restart or recovery.

`bin/fm-home-seed.sh` copies the charter into the secondmate home as `data/charter.md`.
It also writes the required `.fm-secondmate-home` identity marker, which is gitignored and must remain in place for home validation.
`bin/fm-spawn.sh --secondmate` launches it through the secondmate harness path, resolving `config/secondmate-harness` -> `config/crew-harness` -> the primary's own harness unless an explicit per-spawn harness override is passed.

`config/secondmate-harness` may also pin a concrete model and effort for the secondmate agent, in the SAME file rather than a new one: the format is a single whitespace-separated line `<harness> [<model>] [<effort>]`, with only the first non-empty, non-comment line parsed.
Expand All @@ -71,6 +72,7 @@ Because this resolves from the file on every spawn, the pin is durable across ev
This is secondmate-only: crewmate/scout model resolution is untouched by this file.

Before launch, `fm-spawn.sh --secondmate` locally fast-forwards the home to the primary firstmate checkout's current default-branch commit when it is safe; dirty, diverged, or in-flight homes launch unchanged with a warning.
That no-fetch path advances a linked worktree immediately; a standalone clone that lacks the target receives firstmate updates through `/updatefirstmate`'s origin refresh.
The same launch also propagates the primary's declared inheritable local config, currently `config/crew-dispatch.json`, `config/crew-harness`, and `config/backlog-backend`, into the secondmate home's `config/`.
`config/secondmate-harness` is not inherited because it is only the primary's knob for launching secondmate agents.
For already-live secondmates, use `bin/fm-config-push.sh` to push a mid-session inherited-config change without running the tracked-file fast-forward or nudging the agents.
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ state/
data/
.no-mistakes/
.lavish/
.fm-secondmate-home
.DS_Store
.env
config/crew-harness
Expand Down
7 changes: 4 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,10 @@ Tell the captain another active session is already managing the work and operate
Bootstrap is detect, then consent, then install.
Never install anything the captain has not approved in this session.
The locked fleet-sync sweep runs via `bin/fm-fleet-sync.sh`, best-effort and non-fatal, under the hard-rule exception in section 1 (set `FM_FLEET_PRUNE=0` to temporarily disable that branch pruning).
The locked local secondmate sync sweep fast-forwards every live secondmate home's worktree to firstmate's own current default-branch commit so the fleet stays converged on whatever version firstmate is on.
The locked local secondmate sync sweep fast-forwards every live secondmate home to firstmate's own current default-branch commit so the fleet stays converged on whatever version firstmate is on.
The live set comes from `state/<id>.meta` records with `kind=secondmate`; `data/secondmates.md` only backfills `home=` for older or incomplete meta records.
This is a purely local fast-forward (every secondmate home is a worktree of this same repo, sharing one object store), never a fetch from origin and never a surprise pull: the version followed is simply whatever the primary is currently on, which only the captain changes deliberately via `git pull` or `/updatefirstmate`.
This is a purely local fast-forward for linked-worktree homes, which share the primary's object store, never a fetch from origin or a surprise pull.
A standalone clone that lacks the primary target is skipped untouched by this local sweep and advances through `/updatefirstmate`'s origin refresh instead.
A tracked-files fast-forward never touches the gitignored operational dirs, so a secondmate's backlog, projects, and in-flight work are never disturbed; a dirty, diverged, or in-flight home is skipped untouched.
The same sweep also propagates the primary's declared inheritable config (`config/crew-dispatch.json`, `config/crew-harness`, and `config/backlog-backend`; sections 4 and 10) into each live secondmate home's `config/`, so every secondmate's own crewmates, dispatch profiles, and backlog backend stay on the primary's settings.
Because `config/` is gitignored this is a separate, primary-authoritative copy independent of the tracked-files fast-forward: it re-converges every live home whether or not its tracked files advanced, and it touches only the declared inheritable items (never `config/secondmate-harness`).
Expand Down Expand Up @@ -749,7 +750,7 @@ If a guard warning says queued wakes are pending, drain them before doing anythi
If a guard warning says watcher liveness is stale, drain any queued wakes and then resume the emitted supervision protocol.

`fm-guard.sh` carries a second, independent alarm in the same bordered ●-marked style: the **worktree-tangle** guard.
Firstmate is a treehouse-pooled git repo of itself - the primary checkout (the repo root, `FM_ROOT`) and every crewmate worktree and secondmate home are linked worktrees of one repo - and the primary must stay on its default branch.
Firstmate's primary checkout (the repo root, `FM_ROOT`) and crewmate worktrees share the treehouse pool, while a secondmate home may be a linked worktree or a standalone clone, and the primary must stay on its default branch.
If a crewmate sent to work firstmate-on-itself branches or commits in the primary instead of its own isolated worktree, the primary is stranded on a feature branch (the failure this guards against); the guard names the offending branch and prints the non-destructive restore (`git -C <root> checkout <default>`), so the tangle surfaces on the very next fleet action.
The check is scoped precisely to the primary: detached HEAD (the legitimate resting state of crewmate worktrees and secondmate homes on the default branch) and the default branch itself never alarm; only a named non-default branch checked out in the primary does.
The same assertion runs at session start as the bootstrap `TANGLE:` line inside the `bin/fm-session-start.sh` digest (section 3), with read-only wording when this session does not hold the fleet lock.
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ tests/fm-dispatch-select.test.sh # deterministic crew-dispatch profile
tests/fm-spawn-batch.test.sh # batch dispatch and FM_HOME project-path scoping tests
tests/fm-spawn-dispatch-profile.test.sh # concrete dispatch profile flags: active-profile backstop, harness/model/effort meta, launch templates, batch forwarding, and secondmate exemption
tests/fm-update.test.sh # fast-forward-only self-update, reread, nudge, dedup, and skip-safety tests
tests/fm-secondmate-sync.test.sh # local-HEAD secondmate sync, no-fetch, bootstrap nudge gating, stable nudge selectors after respawn, and spawn hook tests
tests/fm-secondmate-sync.test.sh # local-HEAD secondmate sync, no-fetch, ignored seed-marker migration and real-dirt protection, bootstrap nudge gating, stable nudge selectors after respawn, and spawn hook tests
tests/fm-secondmate-liveness.test.sh # session-start secondmate agent-liveness probe and respawn sweep tests
tests/fm-secondmate-harness.test.sh # secondmate-vs-crewmate harness resolution, optional secondmate model/effort pins, primary-to-secondmate config inheritance, and config-push tests
tests/fm-secondmate-lifecycle-e2e.test.sh # persistent secondmate routing, seeding, backlog handoff, spawn, recovery, teardown, and FM_HOME flow tests
Expand Down
7 changes: 4 additions & 3 deletions bin/fm-bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,11 @@ fleet_sync() {
}

secondmate_sync() {
# Local-HEAD secondmate sync: fast-forward every LIVE secondmate home's worktree
# 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 secondmate home is a worktree of this same repo
# and already holds the primary's commit (fm-ff-lib.sh). Emits NUDGE_SECONDMATES:
# fetch, no origin dependency: a linked-worktree home already holds the primary's
# commit (fm-ff-lib.sh), while a standalone clone without it is skipped until
# /updatefirstmate refreshes it from origin. Emits NUDGE_SECONDMATES:
# only for RUNNING secondmates whose instruction surface (AGENTS.md, bin/, or
# .agents/skills/) actually changed, so a secondmate already on the primary's
# version is never disturbed (AGENTS.md bootstrap + supervision). Mirrors
Expand Down
17 changes: 11 additions & 6 deletions bin/fm-ff-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@
# on startup) follows the PRIMARY checkout's current default-branch commit:
# base_mode is that local commit, with NO fetch and no origin dependency.
#
# Every secondmate home is a worktree of this same repo, so it already holds the
# primary's commit in the shared object store; the local-HEAD sync is therefore a
# purely local fast-forward that never touches the network. A tracked-files
# fast-forward never touches the gitignored operational dirs (data/, state/,
# config/, projects/, .no-mistakes/), so a secondmate's backlog, projects, and
# in-flight work are never disturbed. Homes are leased at a detached HEAD on the
# A linked-worktree secondmate home already holds the primary's commit in the
# shared object store, so its local-HEAD sync is a purely local fast-forward that
# never touches the network. A standalone clone moves through that path only when
# it already has the target; otherwise it is skipped until the origin path updates it.
# A tracked-files fast-forward never touches the gitignored operational dirs
# (data/, state/, config/, projects/, .no-mistakes/), so it cannot disturb a
# secondmate's backlog, projects, or in-flight work.
# The seeded .fm-secondmate-home identity marker is gitignored too; the local
# sync tolerates only that marker during the one-time upgrade of pre-ignore
# linked-worktree homes.
# Homes are leased at a detached HEAD on the
# default branch, so the fast-forward advances HEAD only and never moves the
# shared default branch or any other worktree's checkout.

Expand Down
2 changes: 1 addition & 1 deletion bin/fm-home-seed.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# refuses a home with project clones or project-registry entries, so it
# never converts populated homes in place. The charter brief
# is copied to data/charter.md, newly cloned no-mistakes projects are
# initialized, a .fm-secondmate-home marker is written, and
# initialized, an ignored .fm-secondmate-home identity marker is written, and
# data/secondmates.md is updated.
# Seeding is transactional: on validation, clone, init, or registry failure,
# generated briefs, new homes, new project clones, and registry edits are
Expand Down
3 changes: 3 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ Idle secondmate panes are healthy; teardown is explicit and refuses while the se

Secondmate homes stay on the same firstmate version as the primary checkout.
On locked session start, `fm-bootstrap.sh` fast-forwards each live secondmate home recorded in `state/*.meta` to the primary default-branch commit with no origin fetch.
Linked-worktree homes share the primary's object store, so that local target is available immediately.
A standalone clone that does not contain the target remains unchanged in the local sweep and receives firstmate updates through `/updatefirstmate`'s origin refresh instead.
The seeded-home identity marker's ignored state and upgrade path are documented in [configuration.md](configuration.md#secondmate-routes-datasecondmatesmd).
The live signal is a `state/<id>.meta` record with `kind=secondmate`; `data/secondmates.md` only backfills `home=` for older or incomplete meta records.
A tracked-files fast-forward leaves the home's gitignored `data/`, `state/`, `config/`, `projects/`, and `.no-mistakes/` directories untouched.
The same locked session start probes each live secondmate endpoint for a real agent process and respawns only a confidently dead endpoint; inconclusive probes are reported and never acted on.
Expand Down
5 changes: 5 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ Secondmate routes cover `no-mistakes` and `direct-PR` projects; `local-only` pro
For `no-mistakes` projects, seeding initializes only projects newly cloned into a secondmate home and refuses to mutate a preexisting clone that is not already initialized.
After creating a secondmate, move existing main-backlog queued items that you have judged in-scope with `fm-backlog-handoff.sh <secondmate-id> <item-key>...`; it is idempotent and refuses In flight, Done, or non-secondmate homes.
Set `FM_SECONDMATE_CHARTER` to seed from inline charter text when no filled charter brief exists; set `FM_SECONDMATE_SCOPE` when the routing scope should differ from the charter text.
Each seed writes an `.fm-secondmate-home` identity marker at the home root.
The tracked root `.gitignore` ignores that marker, so validation can read it without making a freshly seeded home appear dirty to porcelain-based safety checks.
This does not relax protection for any other untracked file.
An existing linked-worktree home that predates this rule advances through its marker-only state during its next bootstrap or spawn local sync, after which Git ignores the marker normally.
A standalone-clone home cannot receive a primary-local commit through that no-fetch sync, so it receives the rule through `/updatefirstmate`'s origin refresh instead.

## FM_HOME

Expand Down
2 changes: 1 addition & 1 deletion docs/scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ If you have changed away from the firstmate home in an interactive shell, invoke
| `fm-arm-pretool-check.sh` | Stable PreToolUse transport for the command-position watcher policy owned by `fm-arm-command-policy.mjs`; see `docs/arm-pretool-check.md` for the blessed tree, reason codes, adapter outputs, and validation record |
| `fm-arm-command-policy.mjs` | The single semantic owner of the watcher-arm PreToolUse policy: a quote-aware, execution-position classifier that only parses executed positions and never executes, sources, evals, or expands any byte of the submitted command; resolves protected watcher-script identities, recognizes the blessed setup-plus-final tree and broad watcher kills, emits stable deny reason codes, and fails closed on unsupported or malformed syntax that contains a protected execution (docs/arm-pretool-check.md) |
| `fm-supervision-instructions.sh` | Render the session-start primary-harness supervision block from `docs/supervision-protocols/`, or a one-line harness-aware repair instruction for guards and turn-end hooks |
| `fm-home-seed.sh` | Lease/provision a secondmate home transactionally, clone projects or deliberately seed a project-less firstmate-repo domain with `--no-projects`, initialize gates, and maintain `data/secondmates.md` |
| `fm-home-seed.sh` | Lease/provision a secondmate home transactionally, clone projects or deliberately seed a project-less firstmate-repo domain with `--no-projects`, initialize gates, write its ignored identity marker, and maintain `data/secondmates.md` |
| `fm-spawn.sh` | Spawn one task, several `id=repo` pairs, or a persistent secondmate with `--secondmate`; accepts concrete `--harness`, `--model`, `--effort`, and `--backend` axes; rejects `backend=codex-app`; ship/scout spawns require an explicit resolved harness when dispatch profiles are active and an isolated worktree, install per-harness turn-end signaling, and secondmate spawns resolve the secondmate harness plus optional `config/secondmate-harness` model/effort tokens, locally sync the home, propagate declared inheritable config, land herdr tabs in the target home's workspace, land home-scoped zellij tabs in the selected shared zellij session, land cmux workspaces in the shared cmux app, or create Orca worktrees/terminals before launch |
| `fm-dispatch-select.sh` | Resolve one already-matched crew-dispatch rule to a concrete JSON profile; owns deterministic `quota-balanced` selection and quota-axi fallback behavior |
| `fm-backend.sh` | Runtime session-provider backend selector with explicit/env/config/runtime auto-detection precedence, meta helper, exact-id-first selector resolver, spawn-capability validation, operation dispatcher, and shell-portable backend-name membership for bash-sourced scripts or zsh-sourced diagnostics; deliberately keeps `codex-app` out of known/spawn-capable backends; defaults absent `backend=` meta to `tmux`; `fm_backend_target_exists` is a cheap read-only alive/dead endpoint check that never starts a server or session; `fm_backend_agent_alive` is the deeper agent-process liveness probe used by the session-start secondmate sweep; `fm_backend_composer_state` exposes backend composer checks for pending-input guards and submit fallbacks |
Expand Down
Loading