feat: support persistent secondmates - #37
Merged
Merged
Conversation
kunchenguid
force-pushed
the
fm/sub-firstmate-q3
branch
from
June 22, 2026 18:54
102e5ff to
3725d6b
Compare
Mechanical rename of the subordinate domain-supervisor entity from "sub-firstmate" to "secondmate" (captain -> first mate -> second mate). Renamed: the entity name, the routing registry (firstmates.md -> secondmates.md), the seed marker (.fm-sub-firstmate-home -> .fm-secondmate-home), the kind= and mode= meta values, the --secondmate spawn/brief flag, the FM_SECONDMATE_CHARTER / FM_SECONDMATE_SCOPE env vars, and the registry-specific identifiers, plus the test file. Preserved (the main role): the firstmate playbook/repo, "the main firstmate", a firstmate home, the active firstmate home, the firstmate session name, and fm-send's generic bare-firstmate-window resolution. No behavior change; all behavior tests pass.
e-jung
added a commit
to e-jung/firstmate
that referenced
this pull request
Jun 22, 2026
…, not just local main A local-only-registered project whose task pushes its work to a fork (upstream-contribution PRs) was falsely refused by fm-teardown.sh: the local-only branch short-circuited to a strict local-main check before the 'on a remote' check ran, so fork-pushed work that IS safely on a remote looked unlanded. This bit us 3x this session (no-mistakes x2, treehouse orphan). The fix unifies the safety check across modes: HEAD --not --remotes (empty == on any remote, fork included) is the primary gate for every mode. The local-main check now runs only as a fallback for local-only projects whose work is genuinely on no remote at all. The real safety property - refuse truly unpushed work - is preserved, and --force is unchanged. Re-applied onto the kunchenguid#37 teardown rewrite, which already added FM_ROOT_OVERRIDE / FM_STATE_OVERRIDE support (so the new tests can drive the script in isolation); this change is now purely the local-only fork-remote eligibility fix. Motivating incidents: - no-mistakes teardown false-refused twice during this session's 8-PR upstream-contribution batch - a treehouse orphan stuck behind the same false refuse Tests (tests/fm-teardown.test.sh): - local-only + HEAD on a fork remote-tracking branch -> ALLOW (the fix) - local-only + truly unpushed work (no remote) -> REFUSE (safety) - local-only + work merged into local main -> ALLOW (no regression) - no-mistakes + HEAD on origin -> ALLOW (no regression) - no-mistakes + truly unpushed -> REFUSE (no regression) - local-only + unpushed + --force -> ALLOW (escape hatch) AI disclosure: Human-reviewed.
kunchenguid
pushed a commit
that referenced
this pull request
Jun 22, 2026
…, not just local main (#31) * fix(teardown): allow teardown when work is on any remote (incl. fork), not just local main A local-only-registered project whose task pushes its work to a fork (upstream-contribution PRs) was falsely refused by fm-teardown.sh: the local-only branch short-circuited to a strict local-main check before the 'on a remote' check ran, so fork-pushed work that IS safely on a remote looked unlanded. This bit us 3x this session (no-mistakes x2, treehouse orphan). The fix unifies the safety check across modes: HEAD --not --remotes (empty == on any remote, fork included) is the primary gate for every mode. The local-main check now runs only as a fallback for local-only projects whose work is genuinely on no remote at all. The real safety property - refuse truly unpushed work - is preserved, and --force is unchanged. Re-applied onto the #37 teardown rewrite, which already added FM_ROOT_OVERRIDE / FM_STATE_OVERRIDE support (so the new tests can drive the script in isolation); this change is now purely the local-only fork-remote eligibility fix. Motivating incidents: - no-mistakes teardown false-refused twice during this session's 8-PR upstream-contribution batch - a treehouse orphan stuck behind the same false refuse Tests (tests/fm-teardown.test.sh): - local-only + HEAD on a fork remote-tracking branch -> ALLOW (the fix) - local-only + truly unpushed work (no remote) -> REFUSE (safety) - local-only + work merged into local main -> ALLOW (no regression) - no-mistakes + HEAD on origin -> ALLOW (no regression) - no-mistakes + truly unpushed -> REFUSE (no regression) - local-only + unpushed + --force -> ALLOW (escape hatch) AI disclosure: Human-reviewed. * no-mistakes(test): Wire fm-teardown.test.sh into CI and README --------- Co-authored-by: e-jung <8334081+e-jung@users.noreply.github.com>
leo1oel
referenced
this pull request
in leo1oel/nemo
Jun 23, 2026
Bring the upstream secondmate feature (PRs #37/#42/#43/#31/kunchenguid#45) to herdr-backend, rewriting every treehouse/tmux mechanism into the herdr equivalents this branch already uses. A secondmate is a crewmate whose workspace is an isolated firstmate home (its own FM_HOME) and whose brief is a charter; it runs the same spawn/brief/status/watch/teardown/recovery lifecycle and is idle by default. treehouse -> herdr mapping: - Home leasing -> a herdr worktree of the firstmate repo. fm-home-seed.sh `-` runs `herdr worktree create --cwd $FM_ROOT --branch secondmate-<id>` and records the worktree path + open_workspace_id; herdr never recycles a worktree, so persistence is automatic and all lease/return bookkeeping is dropped. The workspace id is stored beside the home marker (.fm-secondmate-home.workspace) so retirement can find it. - Retirement / seed rollback -> `herdr worktree remove --workspace <ws> --force`. An explicit <home> path stays a plain directory home (a git clone, no herdr worktree); spawn opens a workspace for it on the fly via `herdr workspace create` and teardown closes it with `herdr workspace close` then deletes the dir. - firstmate_home_has_treehouse_slot -> "does the home record a workspace marker"; if yes remove via herdr, else plain rm. New: bin/fm-home-seed.sh (charter fill, project cloning, no-mistakes init, registry edits, transactional rollback, validate subcommand all ported as-is), bin/fm-backlog-handoff.sh (no treehouse/tmux; near-verbatim). Edits (re-implemented on the diverged herdr-backend files, not applied as upstream hunks): fm-spawn.sh (--secondmate path, FM_HOME indirection, registry/home resolution, launch in home, meta records home=/home_workspace=/projects=), fm-teardown.sh (kind=secondmate retirement via herdr, child-work refusal + --force discard, plus #31: allow teardown when work is on any remote incl. a fork), fm-brief.sh (--secondmate charter scaffold), fm-watch.sh (skip stale-pane wakes for kind=secondmate), fm-project-mode.sh (honor FM_HOME/FM_DATA_OVERRIDE so a secondmate resolves its own projects.md). kunchenguid#45: dropped the ':' from the afk skill description. Skipped fm-bootstrap.sh / fm-harness.sh (removed on this branch). Docs: AGENTS.md + README.md gain herdr-flavored secondmate sections (concept, secondmates.md routing table, fm-home-seed usage, idle-by-default contract, backlog handoff, kind=secondmate recovery, watch idle) with no treehouse/tmux wording. Tests: new herdr-stubbed tests/fm-secondmate.test.sh drives the real scripts against a stub `herdr` over the full lifecycle (seed `-`, register, validate dup ids/homes/overlap, spawn, backlog handoff, retire); tests/fm-teardown.test.sh ports the #31 any-remote/fork matrix to herdr. shellcheck bin/*.sh tests/*.sh clean; 5/5 test scripts pass.
This was referenced Jun 24, 2026
Merged
vipentti
pushed a commit
to vipentti/firstmate
that referenced
this pull request
Aug 5, 2026
* Add sub-firstmate lifecycle support * no-mistakes(review): Captain, fix sub-firstmate home isolation * no-mistakes(review): Captain, isolate sub-firstmate supervision * no-mistakes(review): Captain, fix sub-firstmate recovery and charter precedence * no-mistakes(review): Captain, retire sub-firstmates safely * no-mistakes(review): Captain, seed local-only clones without origin * no-mistakes(review): Captain, validate remote-backed subhome origins * no-mistakes(review): Captain, harden sub-firstmate home safety * no-mistakes(review): Captain, harden sub-firstmate teardown and seeding * no-mistakes(document): document sub-firstmate operations * no-mistakes(lint): Shell lint clean * no-mistakes: apply CI fixes * Revise sub-firstmate routing scopes * no-mistakes(review): Scope fm-send to current home * no-mistakes(review): Captain, validate sub-firstmate homes and scoped peeks * no-mistakes(review): Captain, harden forced child teardown * no-mistakes(review): Captain, block local-only sub-firstmate seeding * no-mistakes(document): Sync sub-firstmate scope docs * Refine sub-firstmate seeding * no-mistakes(review): Harden sub-firstmate home rollback * no-mistakes(review): Harden sub-firstmate home rollback * no-mistakes(review): Harden sub-firstmate home boundaries * no-mistakes(review): Captain, harden sub-firstmate project destinations * no-mistakes(review): Harden sub-firstmate seeding * no-mistakes(review): Reject nested sub-firstmate homes * no-mistakes(review): Captain, harden sub-firstmate home isolation * no-mistakes(review): Guard sub-firstmate id reseeding * Require filled sub-firstmate charters * Derive sub-firstmate registry scope from charter * Harden sub-firstmate registry parsing * Reject symlinked sub-firstmate seed files * Harden sub-firstmate launch and teardown paths * no-mistakes(review): Fix sub-firstmate supervision validation * no-mistakes(review): Captain, align teardown symlink validation * no-mistakes(document): Sync sub-firstmate documentation * no-mistakes(lint): Shell lint clean * refactor: rename sub-firstmate to secondmate Mechanical rename of the subordinate domain-supervisor entity from "sub-firstmate" to "secondmate" (captain -> first mate -> second mate). Renamed: the entity name, the routing registry (firstmates.md -> secondmates.md), the seed marker (.fm-sub-firstmate-home -> .fm-secondmate-home), the kind= and mode= meta values, the --secondmate spawn/brief flag, the FM_SECONDMATE_CHARTER / FM_SECONDMATE_SCOPE env vars, and the registry-specific identifiers, plus the test file. Preserved (the main role): the firstmate playbook/repo, "the main firstmate", a firstmate home, the active firstmate home, the firstmate session name, and fm-send's generic bare-firstmate-window resolution. No behavior change; all behavior tests pass. * no-mistakes(review): Captain, quote generated status paths * no-mistakes(review): Captain, resolve FM_HOME project paths * no-mistakes(document): Sync secondmate documentation * no-mistakes: apply CI fixes
vipentti
pushed a commit
to vipentti/firstmate
that referenced
this pull request
Aug 5, 2026
…, not just local main (kunchenguid#31) * fix(teardown): allow teardown when work is on any remote (incl. fork), not just local main A local-only-registered project whose task pushes its work to a fork (upstream-contribution PRs) was falsely refused by fm-teardown.sh: the local-only branch short-circuited to a strict local-main check before the 'on a remote' check ran, so fork-pushed work that IS safely on a remote looked unlanded. This bit us 3x this session (no-mistakes x2, treehouse orphan). The fix unifies the safety check across modes: HEAD --not --remotes (empty == on any remote, fork included) is the primary gate for every mode. The local-main check now runs only as a fallback for local-only projects whose work is genuinely on no remote at all. The real safety property - refuse truly unpushed work - is preserved, and --force is unchanged. Re-applied onto the kunchenguid#37 teardown rewrite, which already added FM_ROOT_OVERRIDE / FM_STATE_OVERRIDE support (so the new tests can drive the script in isolation); this change is now purely the local-only fork-remote eligibility fix. Motivating incidents: - no-mistakes teardown false-refused twice during this session's 8-PR upstream-contribution batch - a treehouse orphan stuck behind the same false refuse Tests (tests/fm-teardown.test.sh): - local-only + HEAD on a fork remote-tracking branch -> ALLOW (the fix) - local-only + truly unpushed work (no remote) -> REFUSE (safety) - local-only + work merged into local main -> ALLOW (no regression) - no-mistakes + HEAD on origin -> ALLOW (no regression) - no-mistakes + truly unpushed -> REFUSE (no regression) - local-only + unpushed + --force -> ALLOW (escape hatch) AI disclosure: Human-reviewed. * no-mistakes(test): Wire fm-teardown.test.sh into CI and README --------- Co-authored-by: e-jung <8334081+e-jung@users.noreply.github.com>
This was referenced Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Pure mechanical rename of the subordinate domain-supervisor entity from 'sub-firstmate' to 'secondmate' on the existing PR #37 branch, per the captain's decision (nautical: captain -> first mate -> second mate). This is a RENAME ONLY: no behavior change, no logic change, no test additions/removals beyond renaming. The diff is intentionally balanced (487 insertions / 487 deletions) and all 48 behavior tests pass.
A deliberate, load-bearing DISAMBIGUATION governs every change, so apparent inconsistencies are intentional, not mistakes. ONLY the subordinate entity is renamed: the entity name (sub-firstmate -> secondmate), the routing registry file (firstmates.md -> secondmates.md, including parent-firstmates.md backup and all path references), the seed marker (.fm-sub-firstmate-home -> .fm-secondmate-home), the meta values kind=firstmate -> kind=secondmate and mode=firstmate -> mode=secondmate (plus every KIND/kind/child_kind bash comparison and window_kind check), the spawn/brief flag --firstmate -> --secondmate, the env vars FM_FIRSTMATE_CHARTER/SCOPE -> FM_SECONDMATE_CHARTER/SCOPE, the registry-specific identifiers (FIRSTMATE_REG -> SECONDMATE_REG, firstmate_registry_value -> secondmate_registry_value, remove_firstmate_registry_entry -> remove_secondmate_registry_entry, and the local FIRSTMATE_CHARTER/SCOPE/PROJECTS mirrors), and the test file (tests/fm-sub-firstmate.test.sh -> tests/fm-secondmate.test.sh, its test_secondmate_* functions, scaffold_secondmate_charter helper, and subordinate-lifecycle pass/fail labels like 'secondmate spawn/teardown/launch').
The MAIN firstmate role is intentionally PRESERVED and must NOT be renamed: the firstmate playbook/repo, 'the main firstmate', the generic concept of a 'firstmate home' / 'the active firstmate home' / 'the firstmate repo' (a secondmate's workspace genuinely IS a firstmate home because it runs the firstmate playbook), the generic home-validator/operation function names (validate_firstmate_home_for_spawn/removal, validate_firstmate_operational_dirs(_for_removal), verify_firstmate_home, remove_firstmate_home, cleanup_firstmate_home_children, mark_firstmate_home) and the FIRSTMATE_HOME variable and arg placeholder, the tmux session name 'firstmate' (e.g. window=firstmate:fm-domain), and fm-send/fm-peek's generic 'bare firstmate window' resolution (so test_fm_send_resolves_bare_firstmate_window_from_home_meta keeps its name). The 'child firstmate home' teardown label was preserved exactly as the original author wrote it (the original already used generic 'firstmate home' there, distinct from the top-level 'sub-firstmate home' -> 'secondmate home'); preserving that asymmetry is correct for a rename-only change.
Unrelated PR #30 content (the /afk skill, fm-supervise-daemon.sh, the AGENTS.md Sub-supervisor section, state/.afk and .subsuper-*) was intentionally left untouched - its uses of 'firstmate' mean the main role. CLAUDE.md remains a symlink to AGENTS.md; executable bits on the scripts/test are preserved (755).
What Changed
FM_HOMEboundaries and secondmate metadata.Risk Assessment
Testing
Captain, I inspected the supplied base-to-target diff and commit shape, ran the focused secondmate and spawn-path behavior tests, broadened to the remaining behavior scripts, and captured a manual CLI transcript showing secondmate seeding and spawn metadata working end to end. The worktree was clean after testing; evidence is in the dedicated no-mistakes evidence directory.
Evidence: secondmate behavior transcript
Evidence: spawn batch behavior transcript
Evidence: wake queue behavior transcript
Evidence: AFK injection E2E transcript
Evidence: manual secondmate CLI demo
CLI demo showsdata/secondmates.mdroute creation,.fm-secondmate-homemarker valuedesign, and spawn outputkind=secondmate mode=secondmatewith persisted metaprojects=alpha.Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 1 issue found → auto-fixed (2) ✅
bin/fm-brief.sh:83- The generated status command leaves the redirected path unquoted, so anyFM_HOME/FM_STATE_OVERRIDEcontaining spaces makes crewmates append to the wrong path or fail; render the generated command with a quoted or shell-escaped status-file path in all brief variants.🔧 Fix: Captain, quote generated status paths
1 warning still open:
bin/fm-spawn.sh:291-FM_HOMEmakesprojects/an operational directory under the active home, but ordinary and batch spawns still resolveprojects/foorelative to the caller's cwd. When firstmate is run from the shared repo withFM_HOMEpointing at a secondmate or external home, this will either fail to spawn or target the wrong clone while writing meta into the active home; resolveprojects/*through$FM_PROJECTS_OVERRIDE/$FM_HOME/projectsbeforecd.🔧 Fix: Captain, resolve FM_HOME project paths
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
git status --short --branchgit diff --stat b7c933e795f2fee3e744e0a44f32c5038378d58a..a84850ccb01462c9cdb54aef35c9829eb3a7a28agit diff --name-status b7c933e795f2fee3e744e0a44f32c5038378d58a..a84850ccb01462c9cdb54aef35c9829eb3a7a28agit log --oneline --decorate --max-count=12tests/fm-secondmate.test.sh 2>&1 | tee /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVRF3YFM70QS6FYR19KAXZSN/fm-secondmate.test.logtests/fm-spawn-batch.test.sh 2>&1 | tee /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVRF3YFM70QS6FYR19KAXZSN/fm-spawn-batch.test.logtests/fm-wake-queue.test.sh 2>&1 | tee /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVRF3YFM70QS6FYR19KAXZSN/fm-wake-queue.test.logtests/fm-afk-inject-e2e.test.sh 2>&1 | tee /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVRF3YFM70QS6FYR19KAXZSN/fm-afk-inject-e2e.test.logManual CLI demo: seeded a secondmate route withfm-home-seed.sh, spawned it withfm-spawn.sh --secondmatethrough fake tmux, and captured registry, marker, meta, and launch transcript insecondmate-cli-demo.log.git status --short✅ **Document** - passed
✅ No issues found.
.github/workflows/ci.yml:18- Configured CI lintershellcheck bin/*.sh tests/*.shcould not be run because noshellcheckexecutable is available on PATH or in standard local tool paths.✅ **Push** - passed
✅ No issues found.