From df8d8f407b506ed52f1cd79e9ce5028946ed3a7f Mon Sep 17 00:00:00 2001 From: YrFnS Date: Sun, 26 Jul 2026 10:18:32 +0300 Subject: [PATCH 01/39] feat: add optional host-root mode --- .agents/skills/harness-adapters/SKILL.md | 20 + .../skills/secondmate-provisioning/SKILL.md | 1 + AGENTS.md | 5 +- README.md | 4 +- bin/backends/cmux.sh | 40 ++ bin/backends/herdr.sh | 26 + bin/backends/orca.sh | 69 +- bin/backends/tmux.sh | 23 + bin/backends/zellij.sh | 42 ++ bin/fm-backend.sh | 39 ++ bin/fm-brief.sh | 113 +++- bin/fm-check-register.sh | 9 + bin/fm-decision-hold.sh | 17 + bin/fm-gate-refuse-lib.sh | 28 +- bin/fm-host-root-lib.sh | 126 ++++ bin/fm-kimi-turnend-hook.sh | 19 +- bin/fm-merge-local.sh | 6 +- bin/fm-pr-check.sh | 5 + bin/fm-pr-merge.sh | 3 + bin/fm-promote.sh | 7 +- bin/fm-review-diff.sh | 7 +- bin/fm-send.sh | 37 +- bin/fm-session-start.sh | 13 +- bin/fm-sessionstart-nudge.sh | 8 +- bin/fm-spawn.sh | 497 +++++++++++--- bin/fm-supervision-instructions.sh | 17 +- bin/fm-teardown.sh | 191 +++--- bin/fm-x-followup.sh | 8 + bin/fm-x-link.sh | 4 + docs/architecture.md | 8 + docs/configuration.md | 43 +- docs/herdr-backend.md | 1 + docs/orca-backend.md | 1 + docs/sessionstart-nudge.md | 5 +- docs/tmux-backend.md | 1 + docs/turnend-guard.md | 2 +- docs/verification/runtime-backends.md | 20 + docs/verification/supervision.md | 27 + tests/fm-backend-cmux.test.sh | 62 ++ tests/fm-backend-orca.test.sh | 172 ++++- tests/fm-backend-tmux-smoke.test.sh | 10 + tests/fm-backend-zellij.test.sh | 10 +- tests/fm-backend.test.sh | 163 ++++- tests/fm-brief.test.sh | 21 + tests/fm-decision-hold-lifecycle.test.sh | 19 +- tests/fm-gate-refuse.test.sh | 39 +- tests/fm-gotmp.test.sh | 6 +- tests/fm-grok-harness.test.sh | 9 +- tests/fm-host-root-mode.test.sh | 634 ++++++++++++++++++ tests/fm-kimi-harness.test.sh | 20 +- tests/fm-pr-check-security.test.sh | 44 +- tests/fm-session-start.test.sh | 29 +- tests/fm-sessionstart-nudge.test.sh | 3 +- tests/fm-tangle-guard.test.sh | 36 +- tests/fm-teardown.test.sh | 85 ++- tests/fm-turnend-guard.test.sh | 1 + tests/fm-watcher-lock.test.sh | 11 +- tests/secondmate-helpers.sh | 19 +- 58 files changed, 2555 insertions(+), 330 deletions(-) create mode 100644 bin/fm-host-root-lib.sh create mode 100644 tests/fm-host-root-mode.test.sh diff --git a/.agents/skills/harness-adapters/SKILL.md b/.agents/skills/harness-adapters/SKILL.md index 0ae4ee05b5..ff80f1c23c 100644 --- a/.agents/skills/harness-adapters/SKILL.md +++ b/.agents/skills/harness-adapters/SKILL.md @@ -103,6 +103,26 @@ OpenCode uses `.opencode/plugins/fm-primary-watch-arm.js`, which coordinates wit Pi and pi-signed use the tracked `.pi/extensions/fm-primary-turnend-guard.ts` plus the tracked `.pi/extensions/fm-primary-pi-watch.ts`, both project-local extensions the Pi engine auto-discovers once trusted. When changing any primary watcher adapter, update `docs/supervision-protocols/`, `docs/turnend-guard.md` if a shared idle or turn-end hook changed, and the relevant concise fact below. +## Host-root task integration + +When `FM_HOST_ROOT` is set for an ordinary ship or scout, the harness starts from that physical host root and receives the isolated target separately as `FM_TARGET_WORKTREE`. +The host's discovered instructions and lifecycle adapters remain authoritative; target instructions are read explicitly under the host-root brief before edits. +FirstMate adds exactly one task turn-end signal without writing host configuration: + +| Harness | Additive task signal in host-root mode | +|---|---| +| claude | An additional state-owned settings file passed with `--settings`; Claude continues loading host project settings. | +| codex | The existing per-launch `notify` command; host `.codex/hooks.json` remains untouched. | +| opencode | A state-owned task plugin named through `OPENCODE_CONFIG_CONTENT`; host project plugins continue to auto-load from the launch cwd. | +| pi | The existing explicit state-owned `-e` task extension; host project extensions continue to auto-load after host trust. | +| grok | The guarded global FirstMate Stop hook reads a per-process `FM_GROK_TURNEND_TOKEN`; host project hooks remain untouched and no pointer is written into the host repository. | +| kimi | The guarded global FirstMate Stop hook reads a per-process `FM_KIMI_TURNEND_TOKEN`; host configuration remains otherwise untouched and no pointer is written into the host repository. | + +Default worktree-root launches retain their existing hook locations and command shapes. +Secondmate launches explicitly clear inherited `FM_HOST_ROOT` and `FM_TARGET_WORKTREE` and retain their isolated-home adapters. + +[`docs/verification/supervision.md`](../../../docs/verification/supervision.md#host-root-task-integration) owns the dated lifecycle evidence and current live-verification limits for these task adapters. + ## Launch profile axes `bin/fm-spawn.sh` accepts concrete `--harness`, `--model`, and `--effort` values chosen by firstmate at intake. diff --git a/.agents/skills/secondmate-provisioning/SKILL.md b/.agents/skills/secondmate-provisioning/SKILL.md index f9e68937ab..1f0d5c6cc9 100644 --- a/.agents/skills/secondmate-provisioning/SKILL.md +++ b/.agents/skills/secondmate-provisioning/SKILL.md @@ -64,6 +64,7 @@ Release happens only on explicit retirement or seed rollback, never on routine r `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. +Secondmates remain outside optional host-root mode: their launch prefix clears any inherited `FM_HOST_ROOT` and `FM_TARGET_WORKTREE`, their metadata has no `host_root=`, and their startup cwd remains the isolated FirstMate home. `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 ` [] []`, with only the first non-empty, non-comment line parsed. A bare `` (today's format, e.g. `claude`) behaves exactly as before - harness only, no model/effort flag - so this is fully backward-compatible. diff --git a/AGENTS.md b/AGENTS.md index ee23c840dc..63406c9264 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,7 +48,8 @@ Never add an agent name as a commit co-author. `docs/configuration.md` is the single owner of the top-level operational-home layout and configuration schemas; each producing script's header and help own exact child fields and mutation mechanics. `FM_HOME` selects an instance's private `data/`, `state/`, `config/`, and `projects/`, while scripts continue to come from their tracked code root. -Each secondmate has a persistent isolated `FM_HOME`, including its own state, backlog, projects, and session lock. +Optional `FM_HOST_ROOT` keeps another repository authoritative for instructions, lifecycle, and cwd while ordinary task edits stay under explicit `FM_TARGET_WORKTREE`; `docs/configuration.md` owns the four-root contract. +Each secondmate has a persistent isolated `FM_HOME`, including its own state, backlog, projects, and session lock, and secondmate launches clear any inherited host-root variables. `bin/fm-send.sh` fails closed unless `FM_HOME` is explicit, so a steer cannot silently resolve against another home. Tracked files hold shared instructions and tooling; `data/` holds durable private fleet records; `state/` holds volatile runtime records and append-only status events; `config/` holds local operating choices; and `projects/` contains clones that are read-only to firstmate except under hard rule 1's concrete captain-approved project operation exception. @@ -89,7 +90,7 @@ state/ volatile runtime signals; gitignored .turn-ended touched by turn-end hooks .grok-turnend-token firstmate-owned grok hook registry token for the task; removed by teardown .kimi-turnend-token firstmate-owned Kimi hook registry token for the task; removed by teardown - .meta written by fm-spawn: window=, endpoint_task_id=, worktree=, project=, harness=, model=, effort=, kind=, mode=, yolo=, tasktmp=; kind=secondmate also records home= and projects=; a non-default runtime backend records further backend-specific fields (docs/configuration.md "Runtime backend"; bin/fm-backend.sh, section 8); fm-pr-check, including through fm-pr-merge, records one canonical pr= and the forge's pr_head= when available (GitHub pull requests and GitLab merge requests; docs/gitlab-merge-watch.md); fm-x-link appends x_request=, x_request_ts=, x_followups=, and optional x_platform=/x_reply_max_chars= for an X-mode-originated task (section 14) + .meta written by fm-spawn: window=, endpoint_task_id=, worktree=, project=, harness=, model=, effort=, kind=, mode=, yolo=, tasktmp=; an ordinary host-root task also records host_root=; kind=secondmate also records home= and projects=; a non-default runtime backend records further backend-specific fields (docs/configuration.md "Runtime backend"; bin/fm-backend.sh, section 8); fm-pr-check, including through fm-pr-merge, records one canonical pr= and the forge's pr_head= when available (GitHub pull requests and GitLab merge requests; docs/gitlab-merge-watch.md); fm-x-link appends x_request=, x_request_ts=, x_followups=, and optional x_platform=/x_reply_max_chars= for an X-mode-originated task (section 14) .herdr-presentation quarantinable attempt and restart-binding journal for Herdr's optional visual projection; never task or endpoint authority; see docs/herdr-backend.md "Optional presentation spaces" .check.sh authenticated slow poll; the watcher dispatches validated PR data and the byte-identified X shim through trusted repository scripts, runs registered custom checks from hash-validated private snapshots, and rejects every other state check without execution .check-trust private content binding created by fm-check-register.sh for an intentional custom check diff --git a/README.md b/README.md index 5a03c4b9b1..cf2fcff819 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ Launching a supported harness inside it instantiates your first mate - and makes - **One liaison** - you talk only to the first mate; it dispatches, supervises, escalates only real decisions, and reports plain outcomes. - **A visible crew** - every crewmate works in its own tmux window, experimental herdr/zellij tab, cmux workspace, or Orca terminal you can watch or type into; the first mate reconciles. - **Disposable worktrees** - each task runs in a clean [treehouse](https://github.com/kunchenguid/treehouse) git worktree, or an Orca-managed worktree when `backend=orca`, so parallel work on one repo never collides. +- **Optional host-root mode** - an explicitly integrated supervisor can keep an existing command-center repository authoritative for instructions, lifecycle hooks, and worker cwd while FirstMate retains its own code/home and every task edits an explicit isolated target worktree; see [configuration](docs/configuration.md#host-root-mode-fm_host_root). - **Two task shapes** - ship tasks deliver authorized changes; scout tasks leave standalone investigation reports when the intake contract warrants separate research. - **Explicit project modes** - each project ships via `no-mistakes`, `direct-PR`, or `local-only`, with an optional `+yolo` autonomy flag. - **Optional secondmates** - opt in to persistent second mates that run from isolated firstmate homes with their own `FM_HOME`, state, projects, and session lock, supervising project clones or a project-less firstmate-repo domain, kept on the primary firstmate version by guarded local fast-forwards and checked for live agent processes at session start. @@ -82,7 +83,8 @@ git clone https://github.com/kunchenguid/firstmate cd firstmate ``` -Then launch one of the co-primary harnesses; AGENTS.md takes over from there: +Then launch one of the co-primary harnesses; AGENTS.md takes over from there. +For the low-level opt-in contract used by an explicit host integration, see [Host-root mode](docs/configuration.md#host-root-mode-fm_host_root). **Claude Code** diff --git a/bin/backends/cmux.sh b/bin/backends/cmux.sh index 12dc7629eb..203fb6b9ca 100644 --- a/bin/backends/cmux.sh +++ b/bin/backends/cmux.sh @@ -350,8 +350,14 @@ fm_backend_cmux_surface_id_for_workspace() { # # workspace/surface/pane create all default focus to false) - no # focus-restore dance is needed, unlike zellij. Echoes " # " on success. +# Creation-state globals are consumed by fm-spawn after direct function calls. +# shellcheck disable=SC2034 fm_backend_cmux_create_task() { #