@zerwiz/ymir 0.1.16 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/.agents/config/app-repos.yaml +38 -0
  2. package/.claude/settings.json +31 -0
  3. package/.codex/hooks.json +43 -0
  4. package/.cursor/hooks.json +33 -0
  5. package/.pi/extensions/README.md +39 -0
  6. package/.pi/extensions/gna-pi-watch.ts +15 -0
  7. package/.pi/extensions/lib/ro-assistant-layout.ts +96 -0
  8. package/.pi/extensions/lib/ro-operational-user-layout.ts +145 -0
  9. package/.pi/extensions/lib/ro-visibility.ts +99 -0
  10. package/.pi/extensions/lib/ro-working-ship.ts +243 -0
  11. package/.pi/extensions/lib/rodd-operational-input.ts +62 -0
  12. package/.pi/extensions/lib/skuld-branch-dispatch.ts +252 -0
  13. package/.pi/extensions/lib/skuld-branch-model-picker.ts +77 -0
  14. package/.pi/extensions/lib/vordr-sessionstart-supervisor.mjs +48 -0
  15. package/.pi/extensions/ro.ts +11 -0
  16. package/.pi/extensions/skuld-branch-supervision.ts +11 -0
  17. package/.pi/extensions/syn-turnend-guard.ts +11 -0
  18. package/.pi/mcp.json +16 -0
  19. package/.pi/mcp.json.example +11 -0
  20. package/.pi/npm/.gitignore +2 -0
  21. package/.pi/settings.json +8 -0
  22. package/.pi/shared/extensions/gna-pi-watch.ts +750 -0
  23. package/.pi/shared/extensions/herdr-agent-state.ts +257 -0
  24. package/.pi/shared/extensions/open-editor.ts +509 -0
  25. package/.pi/shared/extensions/ro.ts +504 -0
  26. package/.pi/shared/extensions/skuld-branch-supervision.ts +1535 -0
  27. package/.pi/shared/extensions/syn-turnend-guard.ts +683 -0
  28. package/.pi/shared/extensions/todo.ts +133 -0
  29. package/.pi/shared/extensions/ymir-subagents.ts +287 -0
  30. package/.pi/shared/extensions/ymir-well.ts +130 -0
  31. package/CHANGELOG.md +23 -0
  32. package/assets/Ymir.md +4654 -0
  33. package/assets/healer.html +717 -0
  34. package/assets/icon-family/family-sheet.png +0 -0
  35. package/assets/icon-family/hlidskjalf.svg +1 -0
  36. package/assets/icon-family/sessrumnir.svg +1 -0
  37. package/assets/icon-family/smidja.svg +1 -0
  38. package/assets/icon-family/ymir.svg +1 -0
  39. package/assets/mimirswell.html +880 -0
  40. package/assets/mock/README.md +16 -0
  41. package/assets/mock/feeds.ts +175 -0
  42. package/assets/mock/mock.ts +408 -0
  43. package/assets/reference/docs/agent-control.md +122 -0
  44. package/assets/reference/docs/apodex/Appdex_as_Orchestrator_Planner_Reviewer_Assessment.md +176 -0
  45. package/assets/reference/docs/apodex/Final Report(1).md +198 -0
  46. package/assets/reference/docs/apodex/Final Report(2).md +54 -0
  47. package/assets/reference/docs/apodex/Final Report(3).md +40 -0
  48. package/assets/reference/docs/apodex/Final Report(4).md +234 -0
  49. package/assets/reference/docs/apodex/Final Report(5).md +13 -0
  50. package/assets/reference/docs/apodex/Final Report.md +82 -0
  51. package/assets/reference/docs/architecture.md +392 -0
  52. package/assets/reference/docs/arm-pretool-check.md +247 -0
  53. package/assets/reference/docs/calm-mode-feasibility.md +541 -0
  54. package/assets/reference/docs/calm.md +59 -0
  55. package/assets/reference/docs/captain-hold-lifecycle.md +96 -0
  56. package/assets/reference/docs/cd-guard.md +165 -0
  57. package/assets/reference/docs/cmux-backend.md +133 -0
  58. package/assets/reference/docs/codex-app-backend.md +57 -0
  59. package/assets/reference/docs/configuration.md +924 -0
  60. package/assets/reference/docs/documentation-audiences.json +468 -0
  61. package/assets/reference/docs/documentation-audiences.md +28 -0
  62. package/assets/reference/docs/examples/crew-dispatch.json +26 -0
  63. package/assets/reference/docs/examples/process-event-extension/file-signal.mjs +96 -0
  64. package/assets/reference/docs/examples/process-event-extension/firstmate-extension.json +15 -0
  65. package/assets/reference/docs/examples/watched-tools.json +24 -0
  66. package/assets/reference/docs/examples/wedge-alarm +22 -0
  67. package/assets/reference/docs/extension-bindings.md +237 -0
  68. package/assets/reference/docs/fm-test-isolation-proof.json +37 -0
  69. package/assets/reference/docs/fm-test-isolation-proof.md +149 -0
  70. package/assets/reference/docs/fm-test-portable-shards.md +118 -0
  71. package/assets/reference/docs/gitlab-merge-watch.md +275 -0
  72. package/assets/reference/docs/herdr-backend.md +346 -0
  73. package/assets/reference/docs/orca-backend.md +84 -0
  74. package/assets/reference/docs/pi-supervision-branch-poster.svg +125 -0
  75. package/assets/reference/docs/pi-supervision-branch.md +96 -0
  76. package/assets/reference/docs/plans/agentic-engineering-workflow.md +395 -0
  77. package/assets/reference/docs/plans/agentic-factory-implementation-plan.md +615 -0
  78. package/assets/reference/docs/plans/command-factory-enhancement-plan.md +420 -0
  79. package/assets/reference/docs/plans/phase-1-layer3-program-design.md +134 -0
  80. package/assets/reference/docs/plans/phase-2-vertical-slices.md +100 -0
  81. package/assets/reference/docs/plans/phase-3-measurable-goals.md +106 -0
  82. package/assets/reference/docs/plans/phase-4-context-budget.md +106 -0
  83. package/assets/reference/docs/plans/phase-5-pre-mortem-confidence.md +100 -0
  84. package/assets/reference/docs/plans/phase-6-incident-to-agent.md +109 -0
  85. package/assets/reference/docs/remote-secondmates.md +268 -0
  86. package/assets/reference/docs/scripts.md +146 -0
  87. package/assets/reference/docs/sessionstart-nudge.md +115 -0
  88. package/assets/reference/docs/subagent-guard.md +383 -0
  89. package/assets/reference/docs/supervision-protocols/claude.md +27 -0
  90. package/assets/reference/docs/supervision-protocols/codex.md +16 -0
  91. package/assets/reference/docs/supervision-protocols/cursor.md +31 -0
  92. package/assets/reference/docs/supervision-protocols/grok.md +39 -0
  93. package/assets/reference/docs/supervision-protocols/opencode.md +17 -0
  94. package/assets/reference/docs/supervision-protocols/pi.md +33 -0
  95. package/assets/reference/docs/supervision-protocols/unknown.md +13 -0
  96. package/assets/reference/docs/tmux-backend.md +115 -0
  97. package/assets/reference/docs/trace-context.md +117 -0
  98. package/assets/reference/docs/turnend-guard.md +170 -0
  99. package/assets/reference/docs/verification/dispatch-auth.md +204 -0
  100. package/assets/reference/docs/verification/muse.md +221 -0
  101. package/assets/reference/docs/verification/process-event-sources.md +208 -0
  102. package/assets/reference/docs/verification/public-followup.md +119 -0
  103. package/assets/reference/docs/verification/runtime-backends.md +1022 -0
  104. package/assets/reference/docs/verification/stow-memory.md +53 -0
  105. package/assets/reference/docs/verification/supervision.md +534 -0
  106. package/assets/reference/docs/verification/trace-context.md +31 -0
  107. package/assets/reference/docs/voice-relay.md +295 -0
  108. package/assets/reference/docs/watcher-continuity.md +119 -0
  109. package/assets/reference/docs/wedge-alarm.md +39 -0
  110. package/assets/reference/docs/zellij-backend.md +114 -0
  111. package/assets/reference/index.html +1172 -0
  112. package/assets/reference/skills/afk/SKILL.md +241 -0
  113. package/assets/reference/skills/ahoy/SKILL.md +56 -0
  114. package/assets/reference/skills/ask-user-authority/SKILL.md +56 -0
  115. package/assets/reference/skills/bearings/SKILL.md +161 -0
  116. package/assets/reference/skills/bearings/assets/board-template.html +731 -0
  117. package/assets/reference/skills/bootstrap-diagnostics/SKILL.md +68 -0
  118. package/assets/reference/skills/captain-hold-lifecycle/SKILL.md +54 -0
  119. package/assets/reference/skills/decision-hold-lifecycle/SKILL.md +15 -0
  120. package/assets/reference/skills/diagnostic-reasoning/SKILL.md +53 -0
  121. package/assets/reference/skills/firstmate-codexapp/SKILL.md +110 -0
  122. package/assets/reference/skills/firstmate-codexapp/agents/openai.yaml +4 -0
  123. package/assets/reference/skills/firstmate-coding-guidelines/SKILL.md +127 -0
  124. package/assets/reference/skills/firstmate-orca/SKILL.md +92 -0
  125. package/assets/reference/skills/fmx-respond/SKILL.md +283 -0
  126. package/assets/reference/skills/harness-adapters/SKILL.md +95 -0
  127. package/assets/reference/skills/harness-adapters/references/common/control-and-recovery.md +37 -0
  128. package/assets/reference/skills/harness-adapters/references/common/dispatch.md +32 -0
  129. package/assets/reference/skills/harness-adapters/references/common/model-and-effort.md +42 -0
  130. package/assets/reference/skills/harness-adapters/references/common/primary-hooks.md +40 -0
  131. package/assets/reference/skills/harness-adapters/references/harness/claude.md +55 -0
  132. package/assets/reference/skills/harness-adapters/references/harness/codex.md +43 -0
  133. package/assets/reference/skills/harness-adapters/references/harness/cursor.md +75 -0
  134. package/assets/reference/skills/harness-adapters/references/harness/grok.md +69 -0
  135. package/assets/reference/skills/harness-adapters/references/harness/kimi.md +51 -0
  136. package/assets/reference/skills/harness-adapters/references/harness/muse.md +70 -0
  137. package/assets/reference/skills/harness-adapters/references/harness/opencode.md +42 -0
  138. package/assets/reference/skills/harness-adapters/references/harness/pi.md +56 -0
  139. package/assets/reference/skills/process-event-sources/SKILL.md +137 -0
  140. package/assets/reference/skills/project-management/SKILL.md +92 -0
  141. package/assets/reference/skills/quota-array-dispatch/SKILL.md +131 -0
  142. package/assets/reference/skills/secondmate-provisioning/SKILL.md +254 -0
  143. package/assets/reference/skills/stow/SKILL.md +309 -0
  144. package/assets/reference/skills/stuck-crewmate-recovery/SKILL.md +54 -0
  145. package/assets/reference/skills/updatefirstmate/SKILL.md +64 -0
  146. package/assets/skills/README.md +5 -0
  147. package/assets/yggdrasil.html +649 -0
  148. package/assets/ymir-banner-01.png +0 -0
  149. package/assets/ymir-banner-02.png +0 -0
  150. package/assets/ymir-banner-03.png +0 -0
  151. package/assets/ymir-banner-04.png +0 -0
  152. package/assets/ymir-banner-05.png +0 -0
  153. package/assets/ymir-banner-06.png +0 -0
  154. package/assets/ymir-emblem-darksteel.svg +63 -0
  155. package/assets/ymir-emblem-forged.svg +55 -0
  156. package/assets/ymir-emblem-slate.svg +55 -0
  157. package/assets/ymir-mark-algiz-anvil.svg +9 -0
  158. package/assets/ymir-stave.svg +53 -0
  159. package/assets/ymir.html +563 -0
  160. package/bin/ymir-install.sh +6 -1
  161. package/data/learnings.md +33 -0
  162. package/data/local-models.md +34 -0
  163. package/data/operator.md +15 -0
  164. package/data/projects.md +28 -0
  165. package/data/realm.md +1 -0
  166. package/deploy/Containerfile +27 -0
  167. package/deploy/README.md +104 -0
  168. package/deploy/compose/compose.yaml +45 -0
  169. package/deploy/env.example +30 -0
  170. package/deploy/quadlet/ymir.container +57 -0
  171. package/package.json +10 -8
@@ -0,0 +1,268 @@
1
+ # Remote second mates
2
+
3
+ Remote second mates place a whole persistent Firstmate home on another SSH-reachable host.
4
+ The primary still owns routing and supervision, while the remote home owns its own projects, backlog, and workers.
5
+ Firstmate does not support placing an individual worker remotely or failing a remote route over to a local replacement.
6
+
7
+ The remote second-mate agent itself always runs on the [Herdr backend](herdr-backend.md) in the shared `fm-remote` session, and every path that provisions or launches one refuses a host that is not ready for it.
8
+ `fm-remote` is reserved for remote fleet work and must not be used for personal work.
9
+ The user's interactive Herdr session remains `default` and is not a remote-secondmate prerequisite.
10
+ Herdr's remote-session server belongs to the host's own GUI login session rather than to the SSH connection, so the agent's endpoint survives every disconnection the primary's supervision depends on.
11
+ Local second mates are unaffected and keep their ordinary backend and session selection, as do the workers a remote second mate supervises inside its own home.
12
+
13
+ ## Prerequisites
14
+
15
+ Configure an SSH alias in the primary account's normal OpenSSH configuration.
16
+ Use ordinary public-key authentication, strict host-key verification, and a dedicated remote account where practical.
17
+ Do not enable agent forwarding for Firstmate.
18
+ `fm-on.sh` also disables agent forwarding, forwarding setup, and configured `SendEnv` patterns on every call, and arms bounded SSH dead-peer detection so a vanished host (a reboot, a dropped link) fails within a bounded window instead of hanging indefinitely; its [script header](../bin/fm-on.sh) owns the keepalive defaults and environment overrides.
19
+
20
+ Clone Firstmate on the remote host at an absolute code-root path.
21
+ Expose that clone's fixed entrypoint on the account's non-interactive SSH `PATH`, for example:
22
+
23
+ ```sh
24
+ mkdir -p ~/.local/bin
25
+ ln -s /absolute/path/to/firstmate/bin/fm-remote-entrypoint.sh ~/.local/bin/fm-remote-entrypoint.sh
26
+ ```
27
+
28
+ The entrypoint accepts encoded argv for genuine executable `bin/fm-*.sh` files only.
29
+ It never accepts a shell command string.
30
+ The readiness-owning doctor runs over this plain SSH bootstrap so read-only mode can report worker gaps and `--fix` can install or repair the worker.
31
+ The entrypoint authorizes that bootstrap with normal git tracking when git resolves and with its pinned doctor digest when doctor must report that git itself is missing.
32
+ After setup, every other command verifies Firstmate's account-owned remote job worker, stages the encoded argv and stdin bytes, waits for its result, and relays stdout, stderr, and the exit status separately.
33
+ On macOS the worker is `dev.firstmate.remote-job`, an Aqua-scoped LaunchAgent at `~/Library/LaunchAgents/dev.firstmate.remote-job.plist` with logs under `~/Library/Logs/`.
34
+ After that bootstrap every non-doctor `fm-on.sh` target runs through that worker in the remote account's GUI session, never in the SSH process or a Herdr pane.
35
+ The worker serves one lane per staged home: jobs for the same home follow the staging-order contract owned by [`bin/fm-remote-job-lib.sh`](../bin/fm-remote-job-lib.sh), while different homes' lanes run concurrently so one home's long job never delays another home's commands.
36
+ Within a home's lane the worker preempts a running reply long-poll as soon as any command other than another reply long-poll is queued for that home, so interactive commands and startup checks are never serialized behind a poll window.
37
+ `bin/fm-remote-job-lib.sh` owns that preemption contract and distinguishes preemption from a wait window that closes with no data, so only a genuinely quiet window proves channel freshness while either outcome can re-arm without losing data.
38
+ A caller that disconnects or whose caller-side wait expires before its job completes cancels it instead of abandoning it: cancelled queued work is skipped, cancelled running work is stopped, and the finalized record is cleaned up, so retries never convoy behind abandoned work.
39
+ Linux uses the same queue and worker protocol without the Aqua-session requirement.
40
+ A worker stops itself once its configured code root stops being a Firstmate checkout, so a worker started from a worktree cannot outlive that worktree, and `bin/fm-remote-job-reap-orphans.sh` clears any worker already left behind that way without ever touching one whose checkout still exists.
41
+ The remote account must provide the required toolchain, the selected worker runtime, the selected session backend, and credentials that work on that host.
42
+ The origin URL named for each project must be reachable from the remote account because projects are cloned on that host rather than copied from the primary.
43
+
44
+ ## Non-interactive tool contract
45
+
46
+ No login or interactive shell ever runs on the remote host, so `~/.profile`, `~/.bashrc`, and `~/.zshrc` never contribute to the runtime `PATH`.
47
+ `bin/fm-remote-job-lib.sh` is the single owner of the worker `PATH` and builds it by filesystem discovery rather than by evaluating shell startup files.
48
+ The authorized child sees `<remote-root>/bin` first, then a genuine account `~/.local/bin`, the nvm default version bin, asdf shims and install bins, mise shims and install bins, Nix directories, Homebrew directories, and the system tail `/usr/bin:/bin:/usr/sbin:/sbin`.
49
+ Nvm selection follows the filesystem `alias/default` chain and chooses the highest matching installed semantic version, falling back to the highest installed semantic version when the alias is absent or has no installed match.
50
+ An nvm `system` default adds no nvm version bin, so the later system directories provide Node.
51
+ The Nix and package-manager order after version-manager discovery is `~/.nix-profile/bin`, `/etc/profiles/per-user/<account>/bin`, `/run/current-system/sw/bin`, `/opt/homebrew/bin`, and `/usr/local/bin`.
52
+ Exact repeated entries are omitted.
53
+ For the three Nix locations, a final `bin` symlink is resolved to its physical directory, while a path reached through symlinked ancestors remains in its documented position.
54
+ Other final-component symlink directories, including `~/.local/bin`, are excluded.
55
+ The entrypoint resolves `git` only from the operator portion before prepending `<remote-root>/bin` for the authorized child.
56
+ A checkout-local `bin/git` therefore cannot authorize an untracked command, and a host with no operator `git` receives an install-or-wrapper diagnostic before command execution.
57
+
58
+ The filesystem discovery normally finds tools installed by nvm, asdf, or mise without starting their shell hooks.
59
+ When a required tool remains discoverable only through one of those managers, `fm-remote-doctor.sh --fix` may create a Firstmate-owned wrapper in `~/.local/bin` that executes its selected absolute target.
60
+ It never overwrites a wrapper or other file it does not own, and it never installs a package.
61
+ An operator can use the same wrapper shape when a tool needs a manual selection:
62
+
63
+ ```sh
64
+ mkdir -p ~/.local/bin
65
+ cat > ~/.local/bin/tasks-axi <<'SH'
66
+ #!/usr/bin/env bash
67
+ tool_bin="$HOME/.nvm/versions/node/<selected-version>/bin"
68
+ PATH="$tool_bin:$PATH"
69
+ exec "$tool_bin/tasks-axi" "$@"
70
+ SH
71
+ chmod +x ~/.local/bin/tasks-axi
72
+ ```
73
+
74
+ Replace the placeholder with the remote account's selected nvm version.
75
+ For asdf or mise, use the same shape with the selected version's absolute `bin` directory, one wrapper per tool the remote home actually needs.
76
+ The wrapper must execute that absolute target rather than resolving its own name again through `~/.local/bin`.
77
+
78
+ ## Readiness, repair, and the human steps
79
+
80
+ `bin/fm-remote-doctor.sh` is the single owner of what "ready for a remote second mate" means.
81
+ Check any host against it directly:
82
+
83
+ ```sh
84
+ bin/fm-on.sh <secondmate-id|ssh-alias> fm-remote-doctor.sh
85
+ ```
86
+
87
+ That run is read-only.
88
+ It prints the exact `PATH` its own entrypoint launch produced, executes its required-tool probe through the installed worker when one is available, reports where each required and optional tool resolved, then reports one line per readiness check.
89
+ Each gap is tagged `fixable:` when `--fix` can close it or `human:` when only a person at that machine can, and every gap is followed by an `action:` line naming the exact step.
90
+ Any remaining gap exits non-zero.
91
+ The script's own header owns the full line protocol.
92
+
93
+ `--fix` repairs only the automatable gaps and is safe to rerun:
94
+
95
+ ```sh
96
+ bin/fm-on.sh <secondmate-id|ssh-alias> fm-remote-doctor.sh --fix
97
+ ```
98
+
99
+ Over the plain SSH doctor bootstrap, it writes and reloads the Firstmate-owned `dev.firstmate.remote-job` and `dev.firstmate.herdr.fm-remote` launch agents on macOS, both scoped with `LimitLoadToSessionType=Aqua` and bootstrapped in `gui/<uid>`.
100
+ It starts the same workers directly on Linux, recreates the `~/.local/bin/fm-remote-entrypoint.sh` symlink when it is absent, and creates only Firstmate-owned required-tool wrappers that it can prove resolve to a version-manager target, stopping after one harness satisfies the at-least-one requirement.
101
+ It never installs packages or overwrites a non-Firstmate file at a reserved wrapper path.
102
+ The dedicated Herdr launch agent owns only the remote-secondmate `fm-remote` server and does not inspect, rewrite, start, stop, or require the user's interactive `default` session or its `dev.firstmate.herdr` launch agent.
103
+ It re-derives every check from the host afterwards, so what it prints is the state after the repair rather than the intent of one.
104
+
105
+ These steps are never automated and are always reported rather than silently attempted, because SSH cannot create a GUI session from nothing:
106
+
107
+ - The first console login on that Mac, and automatic login in System Settings > Users & Groups when the machine runs headless and must come back on its own after a reboot.
108
+ - FileVault, which holds a reboot at pre-boot authentication before any login session exists.
109
+ - Installing any missing required tool that no safe wrapper can resolve.
110
+ - The required remote tool set is `git`, `jq`, `herdr`, compatible `tasks-axi`, `treehouse`, and at least one of `claude`, `codex`, `opencode`, `pi`, `pi-signed`, `grok`, or `kimi`.
111
+ - Each worker runtime's own `/login`, and any keychain password prompt that login needs.
112
+
113
+ Firstmate never writes an auto-login password, never changes FileVault, and never stores an account password.
114
+ A file at `~/.local/bin/fm-remote-entrypoint.sh` that is not Firstmate's own symlink is reported for the operator to inspect and is never overwritten.
115
+
116
+ ## Provision a route
117
+
118
+ Create and fill the normal secondmate charter first, then run:
119
+
120
+ ```sh
121
+ bin/fm-remote-home-seed.sh <id> <ssh-alias> <remote-root> <remote-home> {<project>[=<origin-url>]...|--no-projects}
122
+ ```
123
+
124
+ `<remote-root>` is the remote Firstmate code clone that supplies tracked scripts.
125
+ `<remote-home>` is a separate absolute path for the persistent secondmate home and must not overlap the code root.
126
+
127
+ Name each project's origin as `<project>=<origin-url>`.
128
+ Resolve the concrete origin from the captain, the project registry, an existing clone anywhere, the forge, or an explicit paste rather than imposing one URL template.
129
+ Seeding a project this machine has never cloned needs no clone under `projects/`, no `no-mistakes` initialization here, and no fleet sync first.
130
+ A bare `<project>` is still accepted when this machine happens to have `projects/<project>`, whose configured origin is then read instead of being retyped.
131
+ [`bin/fm-project-origin-lib.sh`](../bin/fm-project-origin-lib.sh) owns which URLs are accepted; it decides on structure and safety alone, so no forge, domain, or host is privileged and a self-hosted server works exactly as a hosted one does.
132
+ The primary validates every resolved origin before transport, and the receiving host validates it again before cloning.
133
+ The project's registered delivery mode still comes from this machine's `data/projects.md`, so an unregistered or `local-only` project is refused rather than provisioned.
134
+
135
+ The seed records `host:`, `root:`, and `home:` in `data/secondmates.md`, gates the host on readiness, sends a bounded manifest, and lets the remote host clone its own Firstmate home and project origins.
136
+ In the primary home, its durable registration effects are limited to that route and the charter brief under `data/<id>`; launch records are created only when the secondmate is launched.
137
+ Readiness starts with a read-only check; when that check reports a gap, it runs `--fix` and then a second read-only check whose verdict decides, so the operator never has to run the repair by hand and a repair is never trusted on its own word.
138
+ A host that stays red prints the doctor's remaining gaps and their operator steps, restores the registry, and creates nothing on the remote host.
139
+ It does not copy project trees or the primary process environment.
140
+ A known provisioning failure rolls back the new route, while SSH exit 255 preserves it because remote completion is unknown and must be reconciled on the same host.
141
+
142
+ Seeding also writes a durable `.fm-secondmate-parent` record next to the home's `.fm-secondmate-home` identity marker, naming this home's route to its parent as `local` or `remote`.
143
+ The promised-public-reply subsystem is same-filesystem by construction, so a remote route can never carry a delegated public-reply promise; `bin/fm-teardown.sh`'s cleanup gate reads this record to treat a remote parent as out of scope rather than an unresolved binding.
144
+
145
+ Local secondmates keep the existing route form and need no migration.
146
+ A fleet may contain local and remote routes together.
147
+ Use `bin/fm-home-seed.sh validate` to validate either form.
148
+
149
+ ## Normal operation
150
+
151
+ Launch or recover the remote second mate with the same command used for a local route:
152
+
153
+ ```sh
154
+ bin/fm-spawn.sh <id> --secondmate
155
+ ```
156
+
157
+ The primary resolves the verified secondmate harness and optional model and effort, runs the same readiness gate the seed runs, transfers the inherited-material allowlist, and asks the remote host to launch on Herdr in `fm-remote`.
158
+ All remote secondmates on one host share `fm-remote` and retain separate `2ndmate-<id>` workspaces inside it.
159
+ An explicit request for any other backend is refused rather than honored, and the remote host refuses one too.
160
+ An existing remote endpoint recorded in another Herdr session, including `default`, is classified as unverified and left untouched; launch, liveness recovery, control, and retirement refuse it until an operator explicitly migrates it instead of attempting a live cutover.
161
+ A launch after a host has drifted out of readiness fails with the doctor's own gap text instead of leaving a half-created endpoint.
162
+ Raw launch commands are not accepted for remote secondmates.
163
+ Backends that already refuse secondmate launch, currently Orca and cmux, remain unsupported on the remote host.
164
+
165
+ Startup liveness recovery relaunches a dead or missing remote second mate through this same command, so recovery passes the same readiness gate rather than a weaker one.
166
+
167
+ A persistent remote route's parent metadata intentionally has no local spawn-generation marker and identifies the route by its recorded host instead.
168
+ The Bearings inventory-reconcile hook therefore accepts these markerless routes, revalidates the sampled host at delivery, and refuses a route that changed hosts; [`fm-secondmate-reconcile.sh`](../bin/fm-secondmate-reconcile.sh) owns the exact cooldown, identity, and reporting contract.
169
+
170
+ Send routed requests normally:
171
+
172
+ ```sh
173
+ FM_HOME=<primary-home> bin/fm-send.sh fm-<id> '<request>'
174
+ ```
175
+
176
+ The [`fm-send.sh` header](../bin/fm-send.sh) owns the exact delivery-status contract.
177
+ A routed request is delivered as a durable record in the remote home's steering inbox plus a best-effort doorbell, never by typing the payload into the pane; exit 0 means the record durably exists.
178
+ Every remote transport attempt is bounded by `FM_SEND_REMOTE_BUDGET`; that header owns the setting's default and validation contract.
179
+ An unconfirmed SSH transport (exit 255) is retried identically once, while a budget expiry is not retried because completion is unknown; either outcome preserves this ordinary reply-bearing request's pending-reply expectation for the record that may have landed.
180
+ If delivery remains unconfirmed, only the exact `FM_PENDING_REPLY_EXISTING_CORR=<id>` resend command printed by `fm-send` is safe to run later because it preserves the request body and lets the remote enqueue deduplicate onto the same record; a plain rerun mints a different correlation and is not idempotent.
181
+ When deduplication finds that the worker already moved the matching record into `handled/`, the resend exits successfully without ringing the doorbell again.
182
+ The remote host runs no doorbell re-ring ladder of its own; a swallowed doorbell for an ordinary reply-bearing request surfaces through the parent's pending-reply recovery and escalation, whose recovery request rings the doorbell again when it is enqueued.
183
+ `fm-peek.sh` and `fm-crew-state.sh` route remote-secondmate reads to the endpoint's host instead of consulting local worktree or backend state.
184
+ An unreachable or unreadable remote read is unknown, not evidence that the endpoint is dead.
185
+
186
+ Marked requests keep the existing correlation contract.
187
+ The remote charter appends replies to `state/parent-replies.status` in the remote home.
188
+ A process-event source performs a non-destructive, cursor-anchored delta read, fetches only referenced `data/*.md` documents through the confined reader, mirrors every content-bearing line at most once into the primary status channel, and does not carry blank separators.
189
+ The channel carries the mate's status and decision model: an uncorrelated progress line and a newly raised `needs-decision` travel the same path as a correlated answer, and reach the parent's open-decision fold identically.
190
+ Correlation is a per-line property that settles a pending request; it is never a gate on the stream, so no single line can stop or wedge the relay or hold the cursor back.
191
+ Transport normalization rewrites NUL, every other C0 control except tab and newline, and DEL to `?`, while printable ASCII and all high bytes, including UTF-8, pass through unchanged.
192
+ If the confined remote reader permanently refuses a referenced document, the mate's line is mirrored with its original pointer and the adapter appends one keyed escalation naming the gap instead of stalling the stream.
193
+ An SSH exit status of 255 while fetching a referenced document leaves the delta uncommitted for the process-event runner's normal retry because remote completion is unknown.
194
+ The process-event runner applies each captured delta through this adapter as soon as it is captured, so a mirrored reply reaches the primary status channel without depending on the wake handler running the adapter itself.
195
+ A mirrored line that carries a correlation token settles its pending-reply record and closes that request's own open escalation decision.
196
+ Because a remote reply reaches the primary only through this asynchronous mirror, the primary treats a missing correlated report as a missed report only once the mirror has been read through the end of the remote log after that turn ended.
197
+ A remote mate that did answer is therefore never asked to repost while its answer is still in flight, and a genuinely missing answer still gets exactly one repost once the mirror is known to be current.
198
+ The [process-to-event operating contract](configuration.md#process-to-event-sources-stateprocevent) owns automatic application, one-announcement replay deduplication, and the unhandled fallback path.
199
+ The source log is never truncated or consumed.
200
+ A shortened or changed prefix stops the relay and surfaces a continuity failure instead of silently resetting the cursor.
201
+
202
+ An SSH exit status of 255 always means transport failure or unknown remote completion.
203
+ The underlying `fm-on` transport never retries automatically, but `fm-send` retries its correlation-preserving steering-inbox leg exactly once.
204
+ Semantic callers preserve the route or pending request; an operation that is not idempotent requires same-host reconciliation rather than a blind resend, while an unconfirmed steer may be retried only through the correlation-preserving command described above.
205
+ An unavailable remote home is projected as unknown and is never replaced by a local second mate.
206
+
207
+ ## Backlog handoff
208
+
209
+ Move already-judged queued work with the normal command:
210
+
211
+ ```sh
212
+ bin/fm-backlog-handoff.sh <id> <item-key>...
213
+ ```
214
+
215
+ For a remote route, `tasks-axi mv` first moves the dependency-closed set atomically from the primary backlog into `data/handoff/<id>.outbox.md`.
216
+ The outbox is then copied to the remote handoff scratch directory and `fm-backlog-receive.sh` atomically ingests every destination-absent key under the remote backlog's own lock.
217
+ After receipt, the helper sends a marked routed-work instruction through the recorded remote endpoint and removes the outbox only after that wake is confirmed.
218
+ A failed wake leaves the remote backlog intact and the outbox available for `--resume-pending`; an unresolved send is reported without a blind resend.
219
+ Bootstrap retries pending outboxes and emits `SECONDMATE_HANDOFF:` only when one remains.
220
+ There is no two-phase journal and no additional tasks-axi release requirement.
221
+
222
+ ## Sync, update, and retirement
223
+
224
+ Locked startup convergence and `bin/fm-config-push.sh` transfer only the declared inherited-material allowlist.
225
+ Changed live routes receive a marked instruction to re-read the transferred files.
226
+ The primary records that remote nudge before delivery and retries it during locked startup convergence after a failed send.
227
+ Local secondmates retain their generation-specific local pointer contract; remote transfers do not copy those primary-local instruction paths.
228
+
229
+ `/updatefirstmate` updates each remote code root from its own origin, then guardedly fast-forwards the persistent remote home to that code-root commit.
230
+ Dirty, diverged, unavailable, or otherwise unsafe targets are reported and left untouched.
231
+
232
+ Retire a remote second mate with the normal guarded command:
233
+
234
+ ```sh
235
+ bin/fm-teardown.sh <id>
236
+ ```
237
+
238
+ Retirement is executed on the configured host and refuses while the remote home has child work, while the primary has an unfinished backlog outbox, or while a routed reply remains unresolved.
239
+ It closes only the retiring secondmate's panes or `2ndmate-<id>` workspace in `fm-remote`; it never stops the shared session or removes a sibling secondmate's workspace or panes.
240
+ SSH exit 255 preserves both the route and local records because completion is unknown.
241
+ `--force` remains the explicit discard path and requires the same captain authority as local secondmate discard.
242
+ No generic remote delete or write surface exists: remote writes are confined to inherited allowlist files and backlog handoff scratch files, and remote home removal is reachable only through guarded secondmate retirement.
243
+
244
+ ## Verification
245
+
246
+ The portable tests use the real entrypoint protocol, real git repositories, a deterministic SSH boundary, a stateful host-local Herdr CLI fixture, and a controlled account fixture for the readiness gate.
247
+ The lifecycle test covers seeding a registered project that this machine has never cloned, asserts that the local project tree is unchanged afterwards, and carries Bitbucket, self-hosted, and scp-like origins through to the remote clone:
248
+
249
+ ```sh
250
+ bin/fm-test-run.sh tests/fm-on.test.sh
251
+ bin/fm-test-run.sh tests/fm-send-remote-delivery.test.sh
252
+ bin/fm-test-run.sh tests/fm-secondmate-reconcile.test.sh
253
+ bin/fm-test-run.sh tests/fm-peek-remote.test.sh
254
+ bin/fm-test-run.sh tests/fm-crew-state.test.sh
255
+ bin/fm-test-run.sh tests/fm-remote-job.test.sh
256
+ bin/fm-test-run.sh tests/fm-remote-transport-lanes.test.sh
257
+ bin/fm-test-run.sh tests/fm-remote-doctor.test.sh
258
+ bin/fm-test-run.sh tests/fm-project-origin.test.sh
259
+ bin/fm-test-run.sh tests/fm-remote-reply.test.sh
260
+ bin/fm-test-run.sh tests/fm-remote-backlog-handoff.test.sh
261
+ bin/fm-test-run.sh tests/fm-remote-secondmate-lifecycle-e2e.test.sh
262
+ bin/fm-test-run.sh tests/fm-remote-secondmate-trace-context.test.sh
263
+ ```
264
+
265
+ The account-level checks the doctor performs - a real Aqua login session, a real `launchctl` domain, and a real herdr server - are only ever exercised against fixtures here, so the readiness gate's behavior on a genuine Mac remains an operator-run smoke test.
266
+
267
+ For a real-host smoke test, provision a disposable remote account and project, run the doctor and its repair against that account, launch the second mate, send one marked request, verify its correlated reply and structured fleet projection, simulate an unreachable host to confirm unknown-without-failover behavior, then retire only after the remote queue is empty.
268
+ The deterministic suite is automated; real-host validation is still an operator-run smoke test and is not claimed by the repository tests.
@@ -0,0 +1,146 @@
1
+ # The bin/ toolbelt
2
+
3
+ The first mate drives these; interactive entrypoints work by hand too, while `*-lib.sh` files are sourced helpers.
4
+ Each row is one purpose clause only: the script's own header comment is the authoritative description of its behavior, flags, and contracts, so read the header before first use.
5
+ If you have changed away from the firstmate home in an interactive shell, invoke these scripts by absolute path through the repo's `bin/` directory; the scripts self-locate internally after they start.
6
+ The shared no-mistakes gate refusal for fleet lifecycle entrypoints is summarized in [architecture.md](architecture.md#no-mistakes-gate-authority-boundary), while `docs/sessionstart-nudge.md` covers the silent session-open hook use; `fm-gate-refuse-lib.sh`'s header owns its exact contract.
7
+
8
+ | Script | Purpose |
9
+ | ------------------------ | ------------------------------------------------------------------------------------ |
10
+ | `fm-session-start.sh` | Compose lock, bootstrap, and wake drain into the single ordered session-start digest |
11
+ | `fm-sessionstart-nudge.sh` | Print the native session-start hook nudge when the primary has not already run the digest |
12
+ | `fm-sessionstart-run.sh` | Route a native session-open hook to the full digest, a context re-emit, or the nudge |
13
+ | `fm-operational-input.sh` | Construct and parse the canonical cross-language operational-input protocol |
14
+ | `fm-bootstrap.sh` | Detect toolchain and fleet problems, run the locked session-start sweeps, and install approved tools |
15
+ | `fm-startup-network.sh` | Run session start's network checks off its blocking path, retaining every report while waking only for actionable results |
16
+ | `fm-fleet-sync.sh` | Refresh project clones with safe fast-forwards, self-heals, `STUCK:` reports, branch pruning, and bounded recovery from an orphaned `.git/packed-refs.lock` |
17
+ | `fm-fleet-snapshot.sh` | Print the read-only structured fleet snapshot JSON (schema `fm-fleet-snapshot.v1`) |
18
+ | `fm-home-summary-refresh.sh` | Atomically publish this home's structured summary ledger |
19
+ | `fm-fleet-view.sh` | Render the fleet snapshot as a human Markdown view |
20
+ | `fm-bearings-snapshot.sh` | Project the fleet snapshot to the compact TOON bearings view; local-only unless `--include-prs` |
21
+ | `fm-bearings-board.sh` | Build and arm the stable interactive `/bearings lavish` fleet board |
22
+ | `fm-secondmate-reconcile.sh` | Ask each secondmate to reconcile an inventory mismatch through its durable inbox, limited by a per-home cooldown |
23
+ | `fm-update.sh` | Fast-forward-only self-update of firstmate and local or remote secondmate homes |
24
+ | `fm-on.sh` | Execute one tracked Firstmate command in a configured remote secondmate home, using its job worker except for the doctor bootstrap |
25
+ | `fm-remote-job-lib.sh` | Shared bounded remote job queue, worker readiness, LaunchAgent contract, and filesystem-composed PATH |
26
+ | `fm-remote-job-worker.sh` | Long-lived remote queue worker for tracked `fm-*.sh` commands in the account runtime |
27
+ | `fm-remote-job-reap-orphans.sh` | Stop remote job workers left running by a pruned code root, never one whose checkout still exists |
28
+ | `fm-remote-doctor.sh` | Check, and with `--fix` repair, one remote account's second-mate readiness (remote job worker, Herdr, Aqua launch agents, PATH, and required tools) |
29
+ | `fm-backlog-handoff.sh` | Move queued backlog items into a secondmate home and durably wake its recorded receiver |
30
+ | `fm-backlog-receive.sh` | Idempotently ingest one confined remote handoff outbox through tasks-axi |
31
+ | `fm-captain-hold.sh` | Hold tasks for the captain, record the captain's answers, gate investigation completion, and report record divergence between the status log and the backlog |
32
+ | `fm-decision-hold.sh` | One-release compatibility shim mapping the retired decision commands onto fm-captain-hold.sh |
33
+ | `fm-brief.sh` | Scaffold ship (explicit `--mode`), scout, secondmate-charter, and Herdr-lab briefs |
34
+ | `fm-dod-lib.sh` | One owner of the ship task's mode-specific definition of done, rendered by both the brief scaffold and a scout promotion |
35
+ | `fm-herdr-lab.sh` | Provision and guardedly operate an isolated, never-default Herdr lab session |
36
+ | `fm-install-herdr.sh` | Install CI's exact-version Herdr pin with official asset URL, SHA-256, and protocol checks |
37
+ | `fm-install-treehouse.sh`| Install CI's exact-version Treehouse pin for real-Herdr E2E that needs spawn worktrees |
38
+ | `fm-herdr-ci-cleanup.sh` | Snapshot and tear down only job-owned `fm-lab-*` sessions in the Herdr CI lane |
39
+ | `fm-test-run.sh` | Behavior-test runner: selection, portable lanes, bounded concurrency, budgets, coverage guard, timing/JSON |
40
+ | `fm-test-isolation-proof.sh` | Concurrent isolation harness and portable candidate set owner |
41
+ | `fm-ensure-agents-md.sh` | Ensure a project's real `AGENTS.md`, its `CLAUDE.md` `@AGENTS.md` pointer, and the canonical self-governance section |
42
+ | `fm-guard.sh` | Warn on primary-checkout tangles, pending queued wakes, and unhealthy supervision |
43
+ | `fm-primary-scope-lib.sh` | Shared marker-or-plain-checkout primary-home predicate for tracked hooks |
44
+ | `fm-session-lock-lib.sh` | Shared session-lock harness identity (ancestry walk and holder liveness) for fm-lock.sh and the Claude Stop auto-arm |
45
+ | `fm-claude-stop-autoarm.sh` | Claude Stop `asyncRewake` hook owning tokenless watcher continuity with single-flight exit-2 rewake (docs/watcher-continuity.md) |
46
+ | `fm-turnend-guard.sh` | Shared primary turn-end guard predicate so no turn ends blind (docs/turnend-guard.md) |
47
+ | `fm-turnend-guard-grok.sh` | Grok Stop-hook adapter for the primary turn-end guard |
48
+ | `fm-kimi-turnend-hook.sh` | Surgically install or remove Kimi's guarded global crew turn-end hook |
49
+ | `fm-arm-pretool-check.sh` | Stable PreToolUse transport for the watcher-arm command policy (docs/arm-pretool-check.md) |
50
+ | `fm-arm-command-policy.mjs` | Semantic owner of the watcher-arm PreToolUse policy (docs/arm-pretool-check.md) |
51
+ | `fm-subagent-pretool-check.sh` | Primary-home delegation-shape PreToolUse guard (docs/subagent-guard.md) |
52
+ | `fm-supervision-instructions.sh` | Render the session-start primary-harness supervision block or the one-line repair instruction |
53
+ | `fm-home-seed.sh` | Transactionally provision a local secondmate home and maintain `data/secondmates.md` |
54
+ | `fm-remote-home-seed.sh` | Register and provision a whole secondmate home on an SSH-reachable host |
55
+ | `fm-remote-readiness-lib.sh` | Shared remote second-mate readiness gate: check and, when needed, repair then re-check through `fm-remote-doctor.sh` |
56
+ | [`fm-project-origin-lib.sh`](../bin/fm-project-origin-lib.sh) | Accepted origin-form owner shared by both remote provisioning boundaries |
57
+ | `fm-spawn.sh` | Spawn crewmates, scouts, `id=repo` batches, and secondmates on the resolved harness and runtime backend |
58
+ | `fm-backend.sh` | Runtime-backend selection, meta helpers, selector resolution, and operation dispatch |
59
+ | `fm-backend-hometag-lib.sh` | Shared per-installation home-tag derivation for zellij tab and cmux workspace titles |
60
+ | `fm-composer-lib.sh` | Single fleet-wide owner of composer shapes, capability-aware screen classification, and verdicts |
61
+ | `backends/tmux.sh` | Verified tmux session-provider adapter |
62
+ | `backends/herdr.sh` | Experimental herdr session-provider adapter |
63
+ | `backends/zellij.sh` | Experimental zellij session-provider adapter |
64
+ | `backends/orca.sh` | Experimental Orca backend adapter owning both worktree and terminal |
65
+ | `backends/cmux.sh` | Experimental cmux session-provider adapter |
66
+ | `fm-config-push.sh` | Push declared inherited local material to live local or remote secondmates and send the placement-specific config reread when changed |
67
+ | `fm-project-mode.sh` | Resolve a project's registered delivery posture from `data/projects.md` for fleet sync and home seeding |
68
+ | `fm-merge-local.sh` | Fast-forward a `local-only` project's local default branch after approval |
69
+ | `fm-review-diff.sh` | Review a crewmate branch or resolved PR head against the authoritative base |
70
+ | `fm-marker-lib.sh` | Compatibility entry point for the from-firstmate carrier owned by `fm-operational-input.sh` |
71
+ | `fm-task-inbox-lib.sh` | Single owner of durable steering-inbox records, acknowledgement, doorbells, and the delivery-attempt ladder |
72
+ | `fm-pending-reply-lib.sh` | Parent-owned secondmate pending-reply expectations, recovery, and keyed escalation lifecycle |
73
+ | `fm-secondmate-report.sh` | Optional helper to append a correlated parent status or document-pointer report |
74
+ | `fm-extension.mjs` | Bind, inspect, verify, and strictly invoke trusted external process-event adapter packages |
75
+ | `fm-extension-launch-barrier.mjs` | Publish one exact static core-owned invocation group before package code runs |
76
+ | `fm-extension.sh` | Expose extension binding commands through the tracked shell and remote-home command boundary |
77
+ | `fm-procevent.sh` | Register, supervise, capture, classify, acknowledge, and safely retire built-in or explicitly bound process-event sources |
78
+ | `fm-procevent-remote-reply.sh` | Relay the remote-secondmate status stream through non-destructive process-event deltas |
79
+ | `fm-procevent-quota.sh` | Wake Firstmate when tracked quota drops below a threshold, is exhausted, or cannot be polled |
80
+ | `fm-procevent-when.sh` | Fire a trust-bound deterministic action at most once when its registered condition holds, then wake with the outcome |
81
+ | `fm-gate-refuse-lib.sh` | Shared no-mistakes gate-context refusal for fleet lifecycle entrypoints |
82
+ | `fm-watch-arm.sh` | Verified home-scoped watcher arm wrapper with loud cycle endings and bounded lifecycle ledger |
83
+ | `fm-watch-checkpoint.sh` | Run one bounded foreground watcher checkpoint for Codex-style supervision |
84
+ | `fm-watch.sh` | Singleton-safe watcher: absorb benign wakes, detect stalled local-secondmate wake queues, and exit on actionable ones |
85
+ | `fm-inactive-reconcile.sh` | Reconcile long-inactive direct crewmate terminal outcomes without forge access |
86
+ | `fm-afk-start.sh` | Run the common sourceable away-mode daemon entry in the foreground |
87
+ | `fm-afk-launch.sh` | Own away-mode entry, exit, rollback, and any backend terminal lifecycle |
88
+ | `fm-afk-return.sh` | Own deterministic return shutdown, catch-up evidence, and the firstmate-actionable blocker gate |
89
+ | `fm-supervisor-target-lib.sh` | Resolve the shared supervisor target and backend for the daemon and launcher |
90
+ | `fm-supervise-daemon.sh` | Presence-gated away-mode sub-supervisor: self-handle routine wakes, guard injection by the detected primary harness, escalate batched digests, alert on failed delivery |
91
+ | `fm-crew-state.sh` | Print one deterministic current-state line for a crew |
92
+ | `fm-nm-run-lib.sh` | Single owner of shared no-mistakes run-attribution primitives and rules |
93
+ | `fm-tangle-lib.sh` | Shared default-branch resolution and primary-checkout tangle classification |
94
+ | `fm-timeout-lib.sh` | Single owner of hard-bounded command execution and its fallback watchdog |
95
+ | `fm-timing-lib.sh` | Single owner of the deferred network stage's per-step elapsed-time records, inert unless a run asks for them |
96
+ | `fm-supervision-lib.sh` | Shared in-flight-work-without-fresh-watcher-beacon predicate |
97
+ | `fm-ff-lib.sh` | Shared guarded fast-forward helper for origin pulls and local secondmate syncs |
98
+ | `fm-lock-lib.sh` | Shared "is this git lock provably abandoned?" proof used by teardown and fleet-sync |
99
+ | `fm-config-inherit-lib.sh` | Shared primary-to-secondmate inherited local-material propagation and config-reread delivery |
100
+ | `fm-tasks-axi-lib.sh` | Shared backlog-backend selector and `tasks-axi` compatibility probe |
101
+ | `fm-backlog-transition-lib.sh` | Pair task-record changes with their backlog transitions and replay interrupted closes |
102
+ | `fm-quota-axi-lib.sh` | Shared `quota-axi` compatibility floor and quota snapshot schema validation |
103
+ | `fm-quota-choose.sh` | Choose the first candidate with known positive quota from an ordered harness:model list |
104
+ | `fm-vendor-auth-probe.sh`| Run one hard-bounded, non-destructive authentication probe of a named vendor CLI and report the fact |
105
+ | `fm-wake-drain.sh` | Present and acknowledge the current actor's claimed wake rows alongside status, decision, divergence, recovery, and supervision checks |
106
+ | `fm-wake-grant.sh` | Serialize Pi supervision-branch wake-row claim activation, publication, release, and deactivation |
107
+ | `fm-wake-lib.sh` | Shared durable wake queue, recovery generations, portable locks, and watcher identity/health helpers |
108
+ | `fm-classify-lib.sh` | Shared wake-classification vocabulary, durable keyed-decision folds and scans, and unread informational status-line selection |
109
+ | `fm-send.sh` | Steer a task via a durable inbox record plus doorbell, or send a supported key or typed harness invocation through the recorded backend |
110
+ | `fm-branch-prompt.sh` | Emit the Pi supervision branch's byte-stable system prompt ([pi-supervision-branch.md](pi-supervision-branch.md)) |
111
+ | `fm-branch-outcome.sh` | Own the supervision branch's append-only outcome store, read cursor, and session-start replay |
112
+ | `fm-lease.sh` | Claim, release, inspect, and sweep per-task supervision leases |
113
+ | `fm-lease-lib.sh` | One owner of the supervision lease contract and the main-only role-partition guards |
114
+ | `fm-control.sh` | Agent lifecycle control plane: allowlisted `interrupt`, `exit`, and transactional `relaunch` verbs for an exact task id ([agent-control.md](agent-control.md)) |
115
+ | `fm-control-lib.sh` | One executable owner of the control-plane verb allowlist, per-harness interrupt/exit mechanics, and per-backend capability |
116
+ | `fm-busy-lib.sh` | Single owner of the semantic busy-state contract: verdicts, source attribution, and per-harness sources |
117
+ | `fm-busy-event.sh` | The only writer of a task's semantic busy-state record; arms an incarnation and applies lifecycle events |
118
+ | `fm-tmux-lib.sh` | Shared tmux pane primitives for composer capture, verified submit, and the submit-time busy check |
119
+ | `fm-peek.sh` | Print a bounded tail of a crewmate endpoint |
120
+ | `fm-check-register.sh` | Bind an intentional custom watcher check to its current bytes |
121
+ | `fm-check-unregister.sh` | Retire a custom watcher check and its trust binding by validated task id |
122
+ | `fm-check-lib.sh` | Validate custom-check registrations and prepare private execution snapshots |
123
+ | `fm-tool-update-check.sh` | Report watched tooling with an update available, and updates installed but left inert by PATH order |
124
+ | `fm-pr-lib.sh` | Own canonical task and PR validation plus private atomic PR-poll publication, merge-notification identity, and retirement |
125
+ | `fm-pr-poll.sh` | Provide the byte-static watcher program for validated PR/MR-poll sidecars |
126
+ | `fm-pr-check.sh` | Record validated `pr=` and `pr_head=` values, then atomically arm a static merge poll |
127
+ | `fm-pr-merge.sh` | Record PR metadata, merge a task's canonical full GitHub or GitLab URL, then refuse an outcome it cannot prove landed or queued |
128
+ | `fm-merge-outcome-lib.sh` | Publish a confirmed merge's durable, role-routed supervision outcome |
129
+ | `fm-promote.sh` | Promote a scout task in place to a protected ship task with an explicit delivery mode, and write the ship instructions carrying that mode's definition of done |
130
+ | `fm-teardown.sh` | Fail-closed teardown: return landed ship worktrees, require completed scout deliverables, retire secondmate homes |
131
+ | `fm-harness.sh` | Detect the running harness and resolve crew or secondmate harness, model, and effort |
132
+ | `fm-lock.sh` | Per-home firstmate session lock |
133
+ | `fm-x-lib.sh` | Shared Relay config, relay, and reply-threading helpers |
134
+ | `fm-x-poll.sh` | One bounded Relay poll: stash newly offered mentions and emit their once-only wake |
135
+ | `fm-x-reply.sh` | Post or dry-run preview a composed Relay reply or follow-up |
136
+ | `fm-x-dismiss.sh` | Dismiss a skipped Relay mention at the relay without replying |
137
+ | `fm-x-link.sh` | Link a spawned task to its originating Relay mention in task meta |
138
+ | `fm-x-followup.sh` | Detect, post, and cap completion follow-ups for a Relay-linked task |
139
+ | `fm-public-followup-lib.sh` | Shared Relay gate, open-loop registry state, expiry classification, locking, and private transport paths |
140
+ | `fm-public-followup.sh` | Reconcile and deliver typed public commitments, then rechain or explicitly retire their retained loops |
141
+ | `fm-public-followup-emit.sh` | Report one typed terminal work result into the home that owes the public reply |
142
+ | `fm-inbox.sh` | The captain's out-of-band capture surface: queue a note, dictate one, read status, ask a side question |
143
+ | `fm-voice-relay.py` | Hold the spoken conversation on this host, answer from the records, and hand real work to `fm-inbox.sh` ([voice-relay.md](voice-relay.md)) |
144
+ | `fm-voice-client.py` | The laptop end of the spoken interface: capture, playback, and turn timing over SSH; audio devices unverified |
145
+ | `fm_voice_frame.py` | The wire format both machines share, copied to the laptop beside the client |
146
+ | `fm_voice_records.py` | What a spoken answer may read, and the handover that queues real work |
@@ -0,0 +1,115 @@
1
+ # Native session-start adapters
2
+
3
+ AGENTS.md section 3 is the authoritative behavioral contract for session start.
4
+ This file owns how the tracked native session-open adapters deliver it, and the compatibility limits that force two tiers rather than one.
5
+
6
+ Firstmate ships two session-open tiers, and the tier is a property of the harness surface, not of the home.
7
+
8
+ | Tier | What the adapter does | Used by |
9
+ | --- | --- | --- |
10
+ | Run | Executes `bin/fm-session-start.sh` through the native session-open adapter and gates its ordered digest into model context before the first turn. | Claude, `codex exec`, Pi / pi-signed, Cursor |
11
+ | Nudge | Asks the agent to run the digest through the native adapter or the tracked session-start instruction. | Grok, OpenCode, and run-tier sources routed to the nudge |
12
+
13
+ Codex's interactive TUI has no tracked session-open, compaction, or re-emit channel and is not covered by either tier.
14
+ The run tier exists because the nudge can only ask.
15
+ An agent can defer an instruction, including when a first-command skill has its own read-only path.
16
+ Running the digest through the native adapter removes that discretion, so even a session whose first command is a skill has already taken the helm.
17
+ The nudge tier remains the floor for harnesses that cannot carry hook stdout into model context, and it is never a second contract: both tiers end in the same `bin/fm-session-start.sh`.
18
+
19
+ ## Source routing
20
+
21
+ `bin/fm-sessionstart-run.sh` is the single owner of what a session-open source means, so no harness matcher string has to encode that policy.
22
+ It takes `--source <name>` when the adapter knows the source natively, and otherwise reads the `source` field from a Claude/Codex-shaped JSON hook payload on stdin.
23
+
24
+ | Source | Action | Why |
25
+ | --- | --- | --- |
26
+ | `startup`, `new` | Full digest | This is a true session start that has not taken the helm; Pi CLI continuations are refined to `resume` by the adapter before reaching this boundary. |
27
+ | `clear`, `compact` | `--reemit` after a proven complete startup, otherwise full digest | This process normally has the helm and lost only its context, but an earlier hook may have been truncated after acquiring the lock. |
28
+ | `resume`, `reload`, `fork` | Delegate to the nudge wrapper | Prior context is restored, so re-running is redundant when the lock is still ours and an instruction is enough when a new process resumed an old session. |
29
+ | unreadable or unrecognized | Full digest | Taking the helm redundantly is cheap and idempotent; not taking it is the bug this tier exists to fix. |
30
+
31
+ This deliberately inverts the previous nudge matcher, which fired on `startup|resume|clear` and excluded `compact`.
32
+ Compaction is covered where a tracked adapter delivers that source because a compacted session has lost exactly the digest it needs, and resume is excluded from the run because it restores that digest instead of losing it.
33
+
34
+ Current harness ownership of the lock and its matching `state/.session-start-complete` record together are the idempotency interlock for the whole scheme.
35
+ The full digest clears that completion record after acquiring the lock and republishes the lock owner's pid only after every stage completes, so `clear` or `compact` cannot skip startup sweeps after a truncated run.
36
+ `bin/fm-lock.sh` already treats a lock this session's own harness holds as its own, so a proven `clear` or `compact` re-emit re-verifies ownership and proceeds, while a lock another live session took meanwhile still produces the ordinary read-only digest.
37
+ On a run-tier harness the nudge cannot also fire: `resume`, `reload`, and `fork` are the only sources routed to it, and on those its own ancestry check stays silent whenever this process already holds the lock.
38
+
39
+ `bin/fm-session-start.sh --reemit` owns which work a re-emit skips, its true-start AGENTS.md baseline, and its supported stale-instruction refresh pairs; its header is the single owner of those mechanics.
40
+
41
+ ## Runtime bound
42
+
43
+ The run tier blocks either hook-driven session initialization or Pi's first provider preflight while the digest runs, so `bin/fm-session-start.sh` bounds itself rather than betting on an unbounded prerequisite.
44
+ The digest makes no external-network call at all: every one it owes runs off the blocking path in the separately bounded deferred stage owned by `bin/fm-startup-network.sh`, so an unreachable host can no longer consume this budget.
45
+ What remains is still not individually bounded - tool version probes, the backlog listing, and the per-task endpoint reads are all local but unbounded subprocesses - so the whole digest runs as one bounded child, default 120s via `FM_SESSION_START_TIMEOUT`.
46
+ The shared timeout owner falls back to a pure-Bash process-group watchdog when timeout, gtimeout, and perl are unavailable, so no supported host runs the digest unbounded.
47
+ Because the child streams into the native transport as it runs, everything emitted before the bound was hit is retained for delivery; the parent then prints a `STARTUP TRUNCATED` banner naming the stage that did not finish and the stages that were therefore never emitted, and still exits 0.
48
+ The registered hook timeouts sit above that budget so the harness never preempts the banner.
49
+ The deferred network stage deliberately runs in its own process group under its own deadline, so a truncated digest neither kills work it was not waiting for nor orphans unbounded network work.
50
+
51
+ ## Shared wrapper and safety
52
+
53
+ `bin/fm-sessionstart-run.sh` and `bin/fm-sessionstart-nudge.sh` share the same two eligibility owners.
54
+ They source `bin/fm-gate-refuse-lib.sh` and stay silent for a no-mistakes gate agent identified by `NO_MISTAKES_GATE` or a `.no-mistakes/repos/*.git` git-common-dir.
55
+ They share `bin/fm-primary-scope-lib.sh` with `bin/fm-turnend-guard.sh`, so every hook uses one primary-detection owner.
56
+ The Guard Predicates section of [`turnend-guard.md`](turnend-guard.md#guard-predicates) owns marker validation, plain-checkout detection, and required Firstmate-shaped paths.
57
+
58
+ The nudge payload starts with U+2063 and the stable `FIRSTMATE_OP: ` label, carries the current `session-start` protocol kind, and retains exactly ``Run `bin/fm-session-start.sh` now, exactly once, before executing any other instructions.`` as its body.
59
+ The Ahoy skill owns the rule that this marked operational input is never a captain-authored session boundary, including its narrow legacy compatibility cases, and its own step 0 helm check is the fallback that protects a nudge-tier harness whose first command is a skill.
60
+
61
+ Before printing, the nudge wrapper reads `state/.lock` and walks at most eight parents from its own pid in its own separate, hard-coded loop, independent of `bin/fm-lock.sh`'s ancestry walk (`fm_harness_ancestry_pid()` in `bin/fm-session-lock-lib.sh`, which now walks up to sixteen parents and can extend past a claude-named match to a still-more-ancestral one) and of Pi's `lockOwnership()`.
62
+ If the lock names a live pid in that ancestry, session start already ran in this harness session and the wrapper stays silent.
63
+ Every ordinary transport path in both wrappers exits 0, including malformed state and adapter errors, because a Claude SessionStart exit 2 blocks session initialization.
64
+ The run wrapper's internal `--pi-prerequisite` mode uses silent exit 3 only for an intentional gate or scope stand-down, letting Pi distinguish ineligibility from an eligible empty native result without changing any harness hook's exit contract.
65
+ A lock another session holds and a truncated digest therefore surface as digest text, while broken GitHub auth surfaces through the deferred network result inline or as a wake; none becomes a refusal to open the session.
66
+
67
+ ## Harness transports
68
+
69
+ | Harness | Tier | Tracked transport | Current compatibility |
70
+ | --- | --- | --- | --- |
71
+ | Claude | Run | `.claude/settings.json` registers one unmatched `SessionStart` hook, invoked through `CLAUDE_PROJECT_DIR` with a 180s timeout; the wrapper reads `source` from the hook payload. | Native stdout context injection is supported. |
72
+ | Codex exec | Run | `.codex/hooks.json` anchors to the hook process working directory, verifies a Firstmate-shaped hook-bearing root, and pipes the hook payload into the wrapper with a 180s timeout. | Native stdout context injection is supported under `codex exec`. |
73
+ | Codex interactive TUI | Uncovered | None. | Codex 0.146.0 does not fire the tracked project `SessionStart` hook in its interactive TUI; Firstmate ships no global hook, has no tracked compaction or re-emit channel, and does not claim instruction-refresh delivery for this surface. |
74
+ | Pi / pi-signed | Run | `.pi/extensions/fm-primary-turnend-guard.ts` maps `session_start` reasons `startup`, `new`, `resume`, and `fork` onto wrapper sources, refines a Pi-reported `startup` to `resume` only when a continuation, resume-selection, or explicit-session flag accompanies a session header older than the current process, maps a fork flag to `fork`, and handles `session_compact` as the compaction equivalent; setup-created entries such as `--name` are not restoration evidence. | Each mapped session generation starts one native prerequisite, and `before_agent_start` awaits its matching result and returns one persistent context message before the first provider call; Pi's `reload` reason is deliberately unmapped, as it always was. |
75
+ | OpenCode | Nudge | `.opencode/plugins/fm-primary-sessionstart-nudge.js` listens for `session.created`, runs once per session id, and calls `client.session.promptAsync` only when the wrapper prints a nudge. | Interactive TUI delivery is supported; headless `opencode run` is intentionally fail-open because the process can exit before the queued turn. That early exit is also why OpenCode cannot use the run tier. |
76
+ | Grok | Nudge | `.grok/hooks/fm-primary-sessionstart-nudge.json` registers a project `SessionStart` hook and invokes the wrapper through inline-defaulted `${GROK_WORKSPACE_ROOT:-}`. | The project hook runs when the checkout is trusted, but Grok currently discards hook stdout from model context, so this path is intentionally fail-open and cannot use the run tier. |
77
+ | Cursor | Run | `.cursor/hooks.json` registers `sessionStart`, anchored through `$CURSOR_PROJECT_DIR` with a 180s timeout, invoking `bin/fm-sessionstart-cursor.sh`. | Cursor's payload has no `source` field, so the registration supplies `--source` itself, and the adapter returns the digest as `additional_context`. Project hooks load only when the workspace is launched with `--trust`. |
78
+ | Cursor compaction | Uncovered | None. | Cursor's `preCompact` response can return only `user_message` and is absent from Cursor's `additional_context` step set, so it cannot inject a re-emit digest. Delivering one needs its own design and is deliberately deferred to a follow-up; a Cursor primary does not re-emit its digest after a compaction. |
79
+
80
+ Cursor's `sessionStart` fires at every session open with no source distinction, including a resumed session, so a resume re-runs the full digest; that is redundant and idempotent rather than a lost helm.
81
+ Cursor's compaction surface is uncovered in the same sense as Codex's interactive TUI above: Firstmate registers nothing for `preCompact`, so a compacted Cursor session keeps whatever context survived rather than receiving a fresh digest.
82
+
83
+ Pi is the only adapter that injects a message rather than hook stdout, so whatever it injects must carry operational provenance or the Ahoy skill would have to guess whether it was captain-authored.
84
+ For `session_start`, the extension activates a session-id and monotonic-generation owner synchronously, starts the wrapper once, and makes `before_agent_start` await that same promise before returning Pi's persistent `message` result.
85
+ Replacement or shutdown stops the matching process group, and stale generations cannot deliver into the active session.
86
+ An eligible native failure or empty result settles before the extension returns the existing exact manual instruction, so native and manual startup never run concurrently.
87
+ An intentional gate or non-primary stand-down returns no message, and context-preserving sources retain their existing silent result when the current process already holds the lock.
88
+ Manual and automatic compaction retain the existing persistent delivery path because an automatic retry may have no new `before_agent_start`, but that path shares the same generation cancellation and exactly-once claim.
89
+ The extension encodes an unencoded digest or fallback as `session-start` operational input and leaves an already-encoded nudge alone.
90
+ It streams the hook to completion and retains at most 512 KiB for message delivery; this approved containment keeps the prefix and appends a loud `PI SESSION-START DELIVERY TRUNCATED` marker with direct-inspection guidance whenever the digest is incomplete.
91
+
92
+ The OpenCode nudge runs only on `session.created`.
93
+ The watcher-arm and turn-end plugins run later on `session.idle`, and the guard lets the watcher coordinator act first, so the plugins do not race for one lifecycle event.
94
+
95
+ Grok's guaranteed-loading alternative is a global token-guarded hook like the pattern used by `bin/fm-spawn.sh`.
96
+ That alternative expands trust and writes outside this repository, so Firstmate never installs it or grants folder trust automatically.
97
+
98
+ ## Regression coverage
99
+
100
+ `tests/fm-sessionstart-nudge.test.sh` proves the nudge wrapper's silence for both gate signals, an unmarked linked worktree, a missing state directory, and an already-owned lock, plus its exact U+2063 `FIRSTMATE_OP:`-prefixed, `session-start`-typed one-line output.
101
+ It separately proves the run wrapper's silence for the gate environment and an unmarked linked worktree, including the internal Pi prerequisite's explicit silent stand-down.
102
+ It proves the run wrapper's source routing end to end against a real `fm-session-start.sh`, including completion-gated `--reemit` selection, resume delegation, Pi CLI continuation classification, an unrecognized source falling through to the full digest, and bounded loud delivery of an oversized Pi digest.
103
+ The same portable suite proves provider exclusion until settlement, exactly-one execution and context delivery, interruption, process-tree retirement, two rapid replacements, stale completion, eligible empty output, spawn error, wrapper timeout output, truncation, ineligible stand-down, and compaction cancellation through the extension's public event surface.
104
+ `tests/fm-session-start.test.sh` proves the runtime bound through the forced pure-Bash fallback: a TERM-resistant digest that exceeds its budget is force-killed with its grandchild, still emits its completed stages, names the incomplete stage and every stage it never reached, leaves no completion proof, and exits 0.
105
+ `tests/fm-pi-primary-live-e2e.test.sh` and `tests/fm-opencode-primary-live-e2e.test.sh` exercise native startup paths with first-message and later-message Ahoy regressions.
106
+ `tests/fm-cursor-primary.test.sh` proves the Cursor adapter over real processes: `sessionStart` emits the whole digest as `additional_context` with a caller-supplied `--source`, stays silent in a child worktree, lets the run wrapper stand down on the Cursor-delivered duplicate, and keeps `preCompact` unregistered so the deferred surface cannot be reintroduced unnoticed.
107
+ `FM_CURSOR_PRIMARY_LIVE_E2E=1 tests/fm-cursor-primary-live-e2e.test.sh` proves the injected digest actually reaches model context in a real cursor-agent session.
108
+ `tests/fm-sessionstart-hook-live-e2e.test.sh` is the opt-in live guard for the Claude, Codex exec, and Pi run-tier adapters; it confirms each installed adapter in that suite invokes the run wrapper and delivers its output into context.
109
+ It verifies context-preserving reopen sources for those adapters and context-reset delivery wherever their tracked TUI surface is reachable.
110
+ Its separate `FM_PI_SESSIONSTART_RACE_LIVE_E2E=1` mode uses real Pi with an offline deterministic provider and a barrier-controlled `/new` digest, proving both an immediate prompt and a completed-before-prompt control make their first provider call with exactly one native startup context and no manual execution.
111
+ Cursor uses the separate primary live guard named above because its source-free `sessionStart` and stop-hook park are validated together.
112
+ `tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh` is the separate opt-in real-Pi guard for a post-start AGENTS.md update followed by compaction.
113
+ `tests/fm-turnend-guard.test.sh`, `tests/fm-pi-watch-extension.test.sh`, and `tests/fm-daemon.test.sh` cover marked guard, monitoring, and away-mode delivery.
114
+
115
+ [`verification/supervision.md`](verification/supervision.md#native-session-start-delivery) records the active version-scoped transport evidence.