From 7d9d131cd96ccedf8ba2a45b63cc16c23975498c Mon Sep 17 00:00:00 2001 From: Shane Bracewell Date: Sun, 26 Jul 2026 10:37:34 -0400 Subject: [PATCH 1/6] feat(identity): make a crewmate pane unmistakable for firstmate On 2026-07-26 the captain opened a crewmate pane believing it was firstmate and issued cross-lane instructions there. Nothing was lost only because that crewmate volunteered its identity and asked first, and firstmate's own records still went stale for a day, because a crewmate cannot write another lane's state. Four mechanical contributors, all fixed here. The primary home's herdr crew workspace was labeled "firstmate" - the one workspace firstmate is guaranteed NOT to be in, since firstmate is launched by the captain and never by fm-spawn.sh. It is now "firstmate-crew". The secondmate form stays "2ndmate-" on purpose: a secondmate IS spawned into its own home workspace alongside its children, so that label already names what the workspace contains, and a "-crew" suffix there would introduce the very mislabel this removes. The rename is safe for a home that already has tasks under the old label. No task metadata ever recorded the label - fm-spawn.sh records herdr_workspace_id/herdr_tab_id and a window=: target - so the only label-keyed consumer is fm_backend_herdr_workspace_find, which also feeds fm_backend_herdr_list_live recovery. That function still adopts a legacy-labeled workspace while it holds at least one fm- tab, and the spawn path renames that exact workspace in place, so its id, tabs, and panes never move. A legacy-labeled workspace with no task tab is neither adopted nor renamed, which additionally protects a personal workspace that merely shares the old name. Firstmate's own tab carried no label at all. bin/fm-label-self.sh, run once from the session-start digest, labels it "firstmate" under tmux and herdr. It refuses in a secondmate home, whose fm- endpoint label is the parent's identity handle, and refuses any fm- label, since that prefix is the task-endpoint namespace. fm- labels are untouched, so endpoint identity verification is unchanged. The label sticks in both runtimes. Herdr tracks a pane's terminal_title separately from its tab label (verified read-only against a live Claude pane: terminal_title rewritten by the harness, tab label still the spawn-time fm-). Real tmux 3.6a keeps a renamed window name across an application OSC 2 title change, under default and hostile options alike. Finally, every crewmate scaffold now carries the pane-identity pair: state plainly that you are a worker on this task and not firstmate before acting on anything a human asks outside the brief, then confirm; and record an override actually carried out with a nonterminal "working:" line, so firstmate's records self-heal. That verb is deliberate - a blocked: or needs-decision: line would open a keyed decision that only an explicit resolution closes. --- bin/backends/herdr.sh | 238 +++++++++++-- bin/backends/tmux.sh | 30 ++ bin/fm-backend.sh | 19 + bin/fm-brief.sh | 28 ++ bin/fm-label-self.sh | 90 +++++ bin/fm-session-start.sh | 14 + bin/fm-spawn.sh | 2 +- docs/herdr-backend.md | 17 +- docs/tmux-backend.md | 3 + .../fm-backend-herdr-prune-safety-e2e.test.sh | 16 +- tests/fm-backend-herdr-smoke.test.sh | 6 +- ...ckend-herdr-workspace-per-home-e2e.test.sh | 8 +- tests/fm-backend-herdr.test.sh | 328 ++++++++++++++++-- tests/fm-backend-tmux-smoke.test.sh | 37 ++ tests/fm-backend.test.sh | 18 + tests/fm-brief.test.sh | 54 +++ tests/fm-session-start.test.sh | 91 +++++ tests/lib.sh | 10 + 18 files changed, 940 insertions(+), 69 deletions(-) create mode 100755 bin/fm-label-self.sh diff --git a/bin/backends/herdr.sh b/bin/backends/herdr.sh index 3a28daaa33..9f10a63dda 100644 --- a/bin/backends/herdr.sh +++ b/bin/backends/herdr.sh @@ -56,8 +56,8 @@ # never overrides a real invocation. It exists only so this file's own unit # tests, which source it directly without that preamble, resolve to a sane # default (the firstmate repo root - never a secondmate home, so -# fm_backend_herdr_workspace_label falls through to "firstmate" exactly like -# pre-P3 behavior when a test does not care about home-specific labeling). +# fm_backend_herdr_workspace_label falls through to the primary crew-workspace +# label when a test does not care about home-specific labeling). FM_BACKEND_HERDR_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" FM_ROOT="${FM_ROOT_OVERRIDE:-${FM_ROOT:-$FM_BACKEND_HERDR_ROOT}}" FM_HOME="${FM_HOME:-${FM_ROOT_OVERRIDE:-$FM_ROOT}}" @@ -110,13 +110,42 @@ FM_BACKEND_HERDR_SECONDMATE_MARKER=".fm-secondmate-home" # No send, capture, Treehouse, or general task-ownership path reads it. FM_BACKEND_HERDR_PRESENTATION_JOURNAL_SUFFIX=".herdr-presentation" +# The PRIMARY home's crew-workspace label. It names a CONTAINER OF WORKERS, +# never the supervisor: firstmate itself is launched by the captain, never by +# fm-spawn.sh, so this workspace only ever holds crewmate, scout, and +# secondmate tabs - firstmate's own pane is guaranteed NOT to be in it. The +# pre-existing constant was the bare word "firstmate", which read as +# "firstmate is here" on exactly the one workspace firstmate is never in; on +# 2026-07-26 the captain opened a crewmate pane there believing it was +# firstmate and issued cross-lane instructions to a worker. The `-crew` suffix +# also removes the plain-directory-name label collision that +# fm_backend_herdr_workspace_prune_seeded_default_tab documents below, since a +# workspace whose label is derived from a cwd basename can trivially read +# "firstmate" but essentially never reads "firstmate-crew". +FM_BACKEND_HERDR_PRIMARY_LABEL='firstmate-crew' +# The primary's PRE-MIGRATION crew-workspace label. A home that already has +# tasks recorded under it keeps working unchanged: the label is never stored +# in task metadata (fm-spawn.sh records herdr_workspace_id/herdr_tab_id ids and +# a `window=:` target, never a label), so the only +# label-keyed consumer is fm_backend_herdr_workspace_find - which still adopts +# a legacy-labeled workspace that actually holds task tabs, keeping both spawn +# and fm_backend_herdr_list_live recovery intact, and +# fm_backend_herdr_workspace_migrate_legacy_label renames it in place on the +# next spawn so the id, its tabs, and their panes never move. +FM_BACKEND_HERDR_PRIMARY_LEGACY_LABEL='firstmate' + # fm_backend_herdr_workspace_label: the per-firstmate-HOME herdr workspace # label (docs/herdr-backend.md "Default task container shape"). The PRIMARY home (no -# secondmate marker) resolves to the constant "firstmate", byte-identical to -# every pre-existing task's recorded label - no forced migration. A SECONDMATE +# secondmate marker) resolves to FM_BACKEND_HERDR_PRIMARY_LABEL. A SECONDMATE # home resolves to "2ndmate-", so its tasks land in their own # workspace, obviously distinguishable from the primary's (and from every -# other secondmate's) in herdr's spaces sidebar. Read fresh from FM_HOME on +# other secondmate's) in herdr's spaces sidebar. The secondmate form +# deliberately carries no `-crew` suffix: unlike the primary, a secondmate IS +# spawned by fm-spawn.sh into its own home's workspace (fm-spawn.sh's herdr +# case arm shadows FM_HOME for exactly that call), so that workspace really +# does contain the supervisor it names, and suffixing it would introduce the +# very mislabel this rename removes. The shared rule is that a workspace label +# names what the workspace actually contains. Read fresh from FM_HOME on # every call rather than cached at source time: FM_HOME is the home's own # durable identity, not env plumbing threaded through a call chain, so the # label is automatically stable across every respawn/recovery for the life of @@ -124,15 +153,36 @@ FM_BACKEND_HERDR_PRESENTATION_JOURNAL_SUFFIX=".herdr-presentation" # when the PRIMARY spawns that secondmate (its own process's FM_HOME still # names the primary at that point) - see fm-spawn.sh's herdr case arm. fm_backend_herdr_workspace_label() { + local id + id=$(fm_backend_herdr_secondmate_id) || id="" + if [ -n "$id" ]; then + printf '2ndmate-%s' "$id" + return 0 + fi + printf '%s' "$FM_BACKEND_HERDR_PRIMARY_LABEL" +} + +# fm_backend_herdr_secondmate_id: this home's validated secondmate id, or empty +# for the primary home (and for an empty/unreadable marker, which falls back to +# primary behavior exactly as before). +fm_backend_herdr_secondmate_id() { local marker="$FM_HOME/$FM_BACKEND_HERDR_SECONDMATE_MARKER" id - if [ -f "$marker" ]; then - id=$(tr -d '[:space:]' < "$marker" 2>/dev/null) - if [ -n "$id" ]; then - printf '2ndmate-%s' "$id" - return 0 - fi + [ -f "$marker" ] || return 0 + id=$(tr -d '[:space:]' < "$marker" 2>/dev/null) + printf '%s' "$id" +} + +# fm_backend_herdr_workspace_legacy_label: the pre-migration label this home +# must still ADOPT, or empty when there is nothing to migrate. Only the primary +# home has one; a secondmate home's "2ndmate-" label is unchanged, so it +# prints nothing and every legacy path below becomes a no-op for it. +fm_backend_herdr_workspace_legacy_label() { + local id + id=$(fm_backend_herdr_secondmate_id) || id="" + if [ -n "$id" ]; then + return 0 fi - printf 'firstmate' + printf '%s' "$FM_BACKEND_HERDR_PRIMARY_LEGACY_LABEL" } # fm_backend_herdr_cli: run `herdr ` scoped to , setting @@ -195,6 +245,41 @@ fm_backend_herdr_session() { printf '%s' "${HERDR_SESSION:-default}" } +# fm_backend_herdr_label_self: label the tab the CALLER ITSELF is running in, +# so firstmate's own pane has a standing front door in herdr's sidebar instead +# of the bare positional label ("1") a captain-launched tab carries. Called +# only through bin/fm-label-self.sh, which owns the refusals that make this +# safe (never in a secondmate home, never an fm- label). +# +# The tab is resolved from herdr's own injected HERDR_TAB_ID, falling back to +# a `pane get` on HERDR_PANE_ID for a herdr build that injects only the pane. +# Neither is a lookup by label, so this never touches another tab. +# +# Verified (herdr 0.7.5, real default session, read-only `pane get` on a live +# Claude crewmate pane): herdr tracks a pane's `terminal_title` - the OSC title +# the harness rewrites continuously - as a field SEPARATE from its tab `label`. +# The pane reported terminal_title "⠂ Add crew pane identity guardrails" while +# its tab label was still the spawn-time "fm-crew-pane-identity-guardrails", so +# an explicitly set tab label is not overwritten by the harness. That is the +# same property firstmate's whole label-based recovery +# (fm_backend_herdr_list_live) already depends on. +fm_backend_herdr_label_self() { #