Skip to content

feat: lease secondmate homes with treehouse - #43

Merged
kunchenguid merged 3 commits into
mainfrom
fm/fm-lease-adopt-l5
Jun 23, 2026
Merged

feat: lease secondmate homes with treehouse#43
kunchenguid merged 3 commits into
mainfrom
fm/fm-lease-adopt-l5

Conversation

@kunchenguid

Copy link
Copy Markdown
Owner

Intent

Adopt treehouse v1.8.0's durable worktree lease in firstmate's secondmate-home lifecycle, replacing the fragile process-based hold plus path-capture hack.

Background: a secondmate's home is a treehouse worktree of the firstmate repo acquired at seeding. The old acquire_treehouse_home() ran 'treehouse get' with a throwaway custom SHELL runner ONLY to capture the worktree path from the subshell's PWD, then let the subshell exit - which returned the worktree to the pool, so the home only persisted because a live process happened to keep it in use. treehouse v1.8.0 adds a real durable lease.

Changes made:

  1. bin/fm-home-seed.sh acquire_treehouse_home(): replaced the mktemp SHELL-runner path-capture with 'treehouse get --lease --lease-holder ', reading the path from stdout (treehouse prints only the path to stdout, banners to stderr, so command substitution captures a clean path). Threaded the secondmate id from seed_home through ensure_home into acquire so the lease holder label is the secondmate id.
  2. Lease release on retirement: seed rollback (seed_return_treehouse_home) and secondmate teardown (remove_firstmate_home in fm-teardown.sh) already call 'treehouse return --force', which releases the lease in v1.8.0. Confirmed both and added clarifying comments documenting that this now frees the leased pool slot. Deliberately did NOT release the lease on a normal restart/recovery - the home must stay leased across restarts, which is the whole point; release happens only on explicit retirement or rollback.
  3. AGENTS.md: updated section 6 (seeding) and section 7 (secondmate teardown) prose to describe the lease semantics, each full sentence on its own line per repo convention.
  4. tests/fm-secondmate.test.sh: updated the treehouse mock to support 'get --lease [--lease-holder]' (prints path to stdout, records holder in an optional lease file) and 'return ' (clears the lease file, removes the slot). Added assertions that the lease holder is set to the secondmate id on acquire and that retirement releases the lease via treehouse return. Kept all existing security/path-escape and --force child-safety guards green.

Deliberate scope choice: existing process-held homes (e.g. live secondmate homemux-h7, acquired before v1.8.0) are intentionally NOT retroactively leased - only new provisioning uses the lease; they keep the old hold until reprovisioned. This is noted for the PR.

Validation done locally: all 5 test scripts pass (110 assertions) and shellcheck bin/.sh tests/.sh is clean, matching CI.

What Changed

  • Adopted durable treehouse get --lease --lease-holder provisioning for secondmate homes, with retirement and rollback paths releasing leased slots through treehouse return --force.
  • Added bootstrap detection for the required treehouse lease capability and hardened teardown/rollback behavior so lease release failures are surfaced instead of silently removing homes.
  • Updated secondmate lifecycle docs and expanded shell tests around lease acquisition, holder tracking, retirement cleanup, and bootstrap compatibility.

Risk Assessment

✅ Low: Captain, the changes are narrowly scoped to the secondmate lease lifecycle, add bootstrap coverage for the required treehouse feature, and preserve failure paths without introducing a material merge risk.

Testing

Inspected the lease-related diff and docs, ran all six shell behavior test scripts, then captured a focused end-to-end CLI transcript proving seed acquires a durable secondmate lease and teardown releases it. All checks passed, the worktree stayed clean, and I did not run linters or static analysis per the prompt.

Evidence: Focused CLI lease seed and teardown transcript
E2E: secondmate home durable treehouse lease
workspace=/Users/kunchen/.no-mistakes/worktrees/016d88035d58/01KVRW18PP6HKJ5779PP3318PE
tmp=/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T//fm-lease-e2e.6arc6r

$ FM_HOME=<main-home> fm-home-seed.sh dash - alpha
leased worktree for dash
home=/private/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/fm-lease-e2e.6arc6r/leased-home
lease-holder-after-seed=dash
seeded-marker=dash
seeded-project-origin=file:///var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T//fm-lease-e2e.6arc6r/remotes/alpha.git
registry-route=- dash - dash acquired scope (home: /private/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/fm-lease-e2e.6arc6r/leased-home; scope: dash acquired scope; projects: alpha; added 2026-06-22)
treehouse-log-after-seed:
treehouse get --lease --lease-holder dash

$ FM_HOME=<main-home> fm-teardown.sh dash
teardown dash complete (window firstmate:fm-dash, worktree /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T//fm-lease-e2e.6arc6r/leased-home)
🌱 Backlog: dash just finished. Update data/backlog.md - move dash to Done (keep Done to the 10 most recent), then re-scan Queued for items now unblocked (a "blocked-by: dash" may have just cleared) or now time-due, and dispatch what's ready.
treehouse-log-after-teardown:
treehouse get --lease --lease-holder dash
treehouse return --force /private/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/fm-lease-e2e.6arc6r/leased-home
lease-file-after-teardown=absent
home-after-teardown=absent
registry-route-after-teardown=absent
meta-after-teardown=absent
Evidence: Secondmate lifecycle behavior test log
ok - FM_HOME parameterizes data and state paths
ok - fm-lock status is scoped per home
ok - secondmates registry records scopes and allows overlapping project clone lists
ok - home seeding records routing scope from filled charter briefs
ok - home seed validation rejects duplicate home routes
ok - home seed validation rejects duplicate id routes
ok - home seed validation rejects nested home routes
leased worktree for dash
ok - home seeding durably leases treehouse-acquired dash homes under the secondmate id
ok - home seeding returns rejected acquired homes through treehouse
ok - home seed rollback warns when treehouse-acquired return fails
ok - home seeding leaves unsafe acquired active homes untouched
ok - home seeding rolls back failed clone attempts without residue
ok - home seeding refuses direct seed without filled charter text
ok - home seeding refuses unfilled placeholder charters
ok - home seeding refuses empty normalized charter fields
ok - home seeding refuses local-only projects
ok - home seeding refuses registry delimiter home paths
ok - home seeding refuses active home and repo root
ok - home seeding refuses homes marked for another id
ok - home seeding refuses homes registered to another id
ok - home seeding refuses same-id reassignment to a different home
ok - home seeding refuses registered home overlaps
ok - remote-backed subhome seeding requires a source origin
ok - remote-backed subhome seeding validates existing destination origins
ok - home seeding resolves relative source origins against the source project
ok - home seeding skips initialized existing no-mistakes clones
ok - home seeding refuses uninitialized existing no-mistakes clones
ok - home seeding refuses project destinations outside the subhome
ok - home seeding refuses operational directories outside the subhome
ok - home seeding refuses symlinked leaf files
ok - kind=secondmate spawn launches in the home and records routing meta
ok - secondmate spawn validates homes before launch
ok - secondmate spawn refuses operational directories outside the subhome
ok - fm-send resolves bare firstmate windows through this home
ok - restart recovery can respawn a secondmate from durable registry and charter
ok - secondmate teardown retires empty homes and releases routing
ok - secondmate teardown refuses to hide failed leased-home return
ok - secondmate teardown raw-removes plain-clone homes
ok - secondmate force teardown discards child work
ok - force teardown allows operational directory symlinks inside the subhome
ok - force teardown refuses operational directory symlinks outside the subhome
ok - secondmate teardown requires seeded home marker
ok - secondmate teardown refuses homes containing registered nested homes
ok - secondmate teardown refuses nested homes from the child registry
ok - force teardown validates subhome before child cleanup
ok - force teardown refuses child worktrees inside the active home
ok - force teardown refuses child worktrees inside the firstmate repo
ok - force teardown refuses unregistered child worktree paths
ok - secondmate teardown refuses ancestor homes
ok - secondmate teardown refuses descendant homes
ok - idle kind=secondmate pane is healthy and not stale
ok - secondmate charter brief is idle by default and does not self-initiate work
ok - fm-backlog-handoff moves in-scope items, is idempotent, and aborts safely
ok - fm-backlog-handoff creates absent sections and refuses unsafe homes
Evidence: Bootstrap feature-probe behavior test log
ok - bootstrap accepts treehouse get --lease support
ok - bootstrap reports treehouse without get --lease support
Evidence: Teardown regression behavior test log
ok - local-only worktree with HEAD on a fork remote is torn down (fix holds)
ok - local-only worktree with truly unpushed work is refused (safety preserved)
ok - local-only worktree with work merged into local main is torn down (no regression)
ok - no-mistakes worktree with HEAD on origin is torn down (no regression)
ok - no-mistakes worktree with truly unpushed work is refused (no regression)
ok - local-only worktree with unpushed work is torn down under --force (escape hatch)
- Evidence: Remaining shell behavior test logs (local file: /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVRW18PP6HKJ5779PP3318PE)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 3 issues found → auto-fixed ✅
  • ⚠️ bin/fm-home-seed.sh:468 - acquire_treehouse_home now requires the v1.8.0 treehouse get --lease flag, but bootstrap still only checks that some treehouse binary exists. A pre-1.8 install will pass startup and fail later during secondmate seeding, so add a bootstrap feature/version probe for get --lease and surface the normal install/upgrade instruction there.
  • ⚠️ bin/fm-teardown.sh:281 - On secondmate retirement, falling back to safe_rm_rf after treehouse return fails can remove a leased worktree directory while leaving the durable lease recorded in treehouse state, then the script clears meta and registry as if release succeeded. For homes registered as firstmate treehouse worktrees, teardown should fail unless treehouse return --force succeeds; reserve raw removal for plain-clone homes with no treehouse pool slot.
  • ⚠️ bin/fm-home-seed.sh:638 - Rollback suppresses treehouse return failures for newly leased homes, so a failed seed can leave the durable lease held with no visible cleanup warning. Since the registry is never written in that path, make release failure visible at least, and preferably preserve enough evidence for manual cleanup instead of silently swallowing it.

🔧 Fix: Captain, harden treehouse lease lifecycle
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • git status --short --branch
  • git diff --stat ef1107d41c7ef710bdb2e27aa64bebdf94f2e027..bf195ee8ab8dbd8fe3e7e8e553c0776ab50e7062
  • git diff --unified=80 ef1107d41c7ef710bdb2e27aa64bebdf94f2e027..bf195ee8ab8dbd8fe3e7e8e553c0776ab50e7062 -- bin/fm-home-seed.sh
  • git diff --unified=80 ef1107d41c7ef710bdb2e27aa64bebdf94f2e027..bf195ee8ab8dbd8fe3e7e8e553c0776ab50e7062 -- bin/fm-teardown.sh
  • git diff --unified=60 ef1107d41c7ef710bdb2e27aa64bebdf94f2e027..bf195ee8ab8dbd8fe3e7e8e553c0776ab50e7062 -- tests/fm-secondmate.test.sh
  • git diff --unified=60 ef1107d41c7ef710bdb2e27aa64bebdf94f2e027..bf195ee8ab8dbd8fe3e7e8e553c0776ab50e7062 -- tests/fm-bootstrap.test.sh README.md AGENTS.md
  • tests/fm-bootstrap.test.sh > /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVRW18PP6HKJ5779PP3318PE/fm-bootstrap.test.log 2>&1
  • tests/fm-secondmate.test.sh > /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVRW18PP6HKJ5779PP3318PE/fm-secondmate.test.log 2>&1
  • tests/fm-teardown.test.sh > /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVRW18PP6HKJ5779PP3318PE/fm-teardown.test.log 2>&1
  • tests/fm-spawn-batch.test.sh > /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVRW18PP6HKJ5779PP3318PE/fm-spawn-batch.test.log 2>&1
  • tests/fm-wake-queue.test.sh > /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVRW18PP6HKJ5779PP3318PE/fm-wake-queue.test.log 2>&1
  • tests/fm-afk-inject-e2e.test.sh > /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVRW18PP6HKJ5779PP3318PE/fm-afk-inject-e2e.test.log 2>&1
  • Focused manual CLI check captured in lease-e2e.transcript: seeded a secondmate with home=-, verified the lease holder, marker, project clone, and registry route, then retired it and verified treehouse return --force, lease removal, home removal, registry cleanup, and meta cleanup.
  • git status --short
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Replace the SHELL-runner path-capture hack in acquire_treehouse_home with
treehouse get --lease --lease-holder <id> (v1.8.0), which durably leases the
worktree so the home survives with no live process and is never recycled by a
later get/prune until released. Thread the secondmate id through ensure_home
into the lease holder. Teardown and seed rollback already release via treehouse
return, which clears the lease in v1.8.0; document that intent.

Existing process-held homes (e.g. homemux-h7) keep their old hold until
reprovisioned; only new provisioning leases.

Update the secondmate treehouse mock to support get --lease/--lease-holder and
return <path>, and assert the lease holder is set on acquire and released on
retirement.
@kunchenguid
kunchenguid merged commit 3cc5e49 into main Jun 23, 2026
4 checks passed
@kunchenguid
kunchenguid deleted the fm/fm-lease-adopt-l5 branch June 23, 2026 00:27
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.
vipentti pushed a commit to vipentti/firstmate that referenced this pull request Aug 5, 2026
* feat(secondmate): adopt treehouse durable lease for secondmate homes

Replace the SHELL-runner path-capture hack in acquire_treehouse_home with
treehouse get --lease --lease-holder <id> (v1.8.0), which durably leases the
worktree so the home survives with no live process and is never recycled by a
later get/prune until released. Thread the secondmate id through ensure_home
into the lease holder. Teardown and seed rollback already release via treehouse
return, which clears the lease in v1.8.0; document that intent.

Existing process-held homes (e.g. homemux-h7) keep their old hold until
reprovisioned; only new provisioning leases.

Update the secondmate treehouse mock to support get --lease/--lease-holder and
return <path>, and assert the lease holder is set on acquire and released on
retirement.

* no-mistakes(review): Captain, harden treehouse lease lifecycle

* no-mistakes(document): Document lease lifecycle
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant