Skip to content

feat(bin): report Herdr fleet role token for sidebar rules - #1

Merged
karanmrn merged 2 commits into
mainfrom
fm/fm-herdr-role-token
Sep 15, 2026
Merged

karanmrn merged 2 commits into
mainfrom
fm/fm-herdr-role-token

Conversation

@karanmrn

Copy link
Copy Markdown
Owner

Intent

Captain (13 Sep 2026, from data/harness-inventory-13sep/report.md part D): Herdr sidebar colour rules are keyed on a $role metadata token. Firstmate must report a role token (firstmate, crewmate, scout, secondmate) to Herdr at spawn so the rules can tell fleet roles apart.

What Changed

  • Add fm_backend_herdr_report_role and fm_backend_herdr_report_own_role to bin/backends/herdr.sh. They run herdr pane report-metadata <pane> --source firstmate --token role=<role> for an exact pane. Valid roles are firstmate, secondmate, crewmate, and scout. The adapter checks both the Herdr client and a running server against a new version floor (Herdr 0.7.4 or protocol 17). Older releases are skipped silently. The existing release-floor classifier now accepts an optional protocol and version floor.
  • bin/fm-spawn.sh reports the role for the pane in the published task record on every Herdr spawn and relaunch (secondmate, scout, or crewmate). bin/fm-session-start.sh reports the session's own role (firstmate, or secondmate for a home with the secondmate marker) during locked bootstrap. It does this only after the pane's injected socket matches the selected Herdr session. A failed report prints a warning and never fails the spawn or the session start.
  • Document the token contract in the new "Fleet role token" section of docs/herdr-backend.md, and add live CLI evidence to docs/verification/runtime-backends.md. Add tests for exact-pane reports, the version floor, warning-only refusals, own-role detection, and the session start report. tests/lib.sh now unsets inherited HERDR_* pane variables, so test suites do not report metadata to the developer's live pane.

🤖 Generated with Claude Code

Risk Assessment

✅ Low: The change is additive and display-only. Every failure warns and never fails a spawn or session start. Pane identity is taken from the published task record or proved through the socket, and the tests check real Herdr state. The only finding is a small duplicated release check.

Testing

The baseline bin/fm-test-run.sh --changed --exclude-family real-herdr-gated exited 1. That selection is wide, and this step did not rerun it. The two changed suites were run on their own instead. The real-Herdr E2E test proved crewmate, scout, and secondmate tokens on exact panes. A new live driver ran the real session start inside a real Herdr lab pane with the lock held. It proved firstmate and secondmate tokens, that a later report replaces the value, that a wrong-session pane is refused with a warning, and that a read-only start reports nothing. Relaunch was not driven live. The one session-start suite failure was a timing test under load, and it passed on rerun. Herdr sidebar colours are a TUI, and this lab has no $role colour rules, so there is no screenshot. herdr pane get JSON is the evidence for the stored token. All lab sessions were torn down, and the worktree is clean.

  • Live validation: ✅ go - 7 of 9 scenarios driven live against the product
Scenario Result Live Evidence
Crewmate spawn on Herdr: real fm-spawn.sh sets pane token role=crewmate on the exact task pane ✅ pass live launcher-workspace-e2e.log: 'a crewmate spawn reports the crewmate fleet role token for its exact pane'
Scout spawn on Herdr: --scout spawn sets role=scout on its pane ✅ pass live launcher-workspace-e2e.log: 'a scout spawn reports the scout fleet role token for its exact pane'
Secondmate spawn on Herdr: --secondmate launch sets role=secondmate, and a secondmate's own worker sets role=crewmate ✅ pass live launcher-workspace-e2e.log: secondmate and secondmate-crewmate role token lines
Primary firstmate session start inside a Herdr pane sets role=firstmate on its own pane ✅ pass live session-start-role-live-locked.log: primary, exit=0, pane tokens {"role":"firstmate"}; session-start-primary.out shows 'lock acquired'
Secondmate home session start inside a Herdr pane sets role=secondmate, replacing the earlier value ✅ pass live session-start-role-live-locked.log: second, exit=0, final herdr pane get tokens {"role":"secondmate"}
Adversarial: HERDR_SESSION names a different session than the pane's socket; session start refuses the report, warns, and still exits 0 ✅ pass live session-start-role-live-locked.log: foreign warning 'could not be proved to belong to herdr session', exit=0, pane tokens {}
Adversarial: read-only session start (lock not held) reports no role token ✅ pass live readonly-session-start-role-live.log: all three runs READ-ONLY, pane tokens stay {} / null
Relaunch through fm-control.sh relaunch reports the role token again on the replacement pane ⏸️ untested no A live relaunch needs a running, authenticated harness agent in a Herdr task pane, which fm-control waits on for the 'alive' state. The spawn E2E test uses a plain shell command, not an agent. To test…
Version floor: Herdr below 0.7.4 skips the report silently ⏸️ untested no This host has only Herdr 0.9.0. A live check needs a Herdr 0.7.3 binary installed next to it, which the workspace rules forbid here.
Evidence: Real Herdr spawn E2E transcript (crewmate/scout/secondmate role tokens)

Source: Real Herdr spawn E2E transcript (crewmate/scout/secondmate role tokens)

ok - real herdr E2E: with one 'firstmate' workspace and no herdr parent, a crewmate still lands in this home's own workspace without stealing focus
ok - real herdr E2E: a crewmate spawn reports the crewmate fleet role token for its exact pane
ok - real herdr E2E: a scout spawn reports the scout fleet role token for its exact pane
ok - real herdr E2E: the normal unique-label path is unchanged when the launcher's own pane identifies the workspace
ok - real herdr E2E: presentation spaces still create the isolated child workspace and bind it under the launcher's exact parent, without stealing focus
ok - real herdr E2E: with two 'firstmate' workspaces, a worker spawned from inside the second one lands in that exact workspace
ok - real herdr E2E: the duplicate-labeled sibling workspace is left entirely untouched and focus is preserved
ok - real herdr E2E: with a duplicated home label, a projected worker still hangs off the launcher's exact workspace and the sibling stays untouched
ok - real herdr E2E: an ambiguous home label with no launcher identity refuses before any worker endpoint exists
ok - real herdr E2E: a launcher pane that no longer exists refuses before any worker endpoint exists
ok - real herdr E2E: a secondmate launching its own worker gets the same exact-workspace guarantee, and its same-labeled sibling is untouched
ok - real herdr E2E: a secondmate's own crewmate reports the crewmate fleet role token
ok - real herdr E2E: a --secondmate launch still stands up that secondmate's own workspace instead of inheriting the launcher's
ok - real herdr E2E: a --secondmate launch reports the secondmate fleet role token for its exact pane
ok - real herdr E2E: teardown closes only the worker's own pane and leaves the launcher, its workspace, and the same-labeled sibling intact
ok - real herdr E2E: isolated lab session removed and default fleet session unchanged
exit=0
Evidence: Live session start in real Herdr pane, lock held (wrong-session refusal, firstmate, secondmate)

Source: Live session start in real Herdr pane, lock held (wrong-session refusal, firstmate, secondmate)

tokens before any session start: {} == foreign: warning: herdr role token 'firstmate' not reported: pane 'w1:p1' could not be proved to belong to herdr session 'fm-lab-notthisone'; pane tokens after: {} == primary: session start exit=0; pane tokens after: {"role":"firstmate"} == second: session start exit=0; pane tokens after: {"role":"secondmate"}

lab session: fm-lab-role-live-57716-7172
pane: w1:p1
tokens before any session start: {}
== foreign: session start exit=0
   injected env: HERDR_BIN_PATH=/opt/homebrew/bin/herdr HERDR_ENV=1 HERDR_PANE_ID=w1:p1 HERDR_SESSION=fm-lab-role-live-57716-7172 HERDR_SOCKET_PATH=~/.config/herdr/sessions/fm-lab-role-live-57716-7172/herdr.sock HERDR_TAB_ID=w1:t1 HERDR_WORKSPACE_ID=w1 
   role warnings in session start output:
     warning: herdr role token 'firstmate' not reported: pane 'w1:p1' could not be proved to belong to herdr session 'fm-lab-notthisone'
   pane tokens after: {}
== primary: session start exit=0
   injected env: HERDR_BIN_PATH=/opt/homebrew/bin/herdr HERDR_ENV=1 HERDR_PANE_ID=w1:p1 HERDR_SESSION=fm-lab-role-live-57716-7172 HERDR_SOCKET_PATH=~/.config/herdr/sessions/fm-lab-role-live-57716-7172/herdr.sock HERDR_TAB_ID=w1:t1 HERDR_WORKSPACE_ID=w1 
   role warnings in session start output:
   pane tokens after: {"role":"firstmate"}
== second: session start exit=0
   injected env: HERDR_BIN_PATH=/opt/homebrew/bin/herdr HERDR_ENV=1 HERDR_PANE_ID=w1:p1 HERDR_SESSION=fm-lab-role-live-57716-7172 HERDR_SOCKET_PATH=~/.config/herdr/sessions/fm-lab-role-live-57716-7172/herdr.sock HERDR_TAB_ID=w1:t1 HERDR_WORKSPACE_ID=w1 
   role warnings in session start output:
   pane tokens after: {"role":"secondmate"}
== herdr pane get w1:p1 (final):
{
  "pane_id": "w1:p1",
  "workspace_id": "w1",
  "tokens": {
    "role": "secondmate"
  }
}
driver exit=0
Evidence: Session start digest, primary home (lock acquired)

Source: Session start digest, primary home (lock acquired)


================================================================================
SESSION START - /private/var/folders/j_/wjtv52sn0vx4tgwpwzmq50vw0000gn/T/fm-role-live.cXjkai/primary
================================================================================

LOCK
--------------------------------------------------------------------------------
lock acquired: harness pid 60084

BOOTSTRAP
--------------------------------------------------------------------------------
NOTICE: auto-detected herdr runtime (HERDR_ENV=1) - spawning into the EXPERIMENTAL herdr backend. Set config/backend or pass --backend tmux to opt out.

WAKE QUEUE
--------------------------------------------------------------------------------
(no queued wakes)
================================================================================
SUPERVISION OPERATING INSTRUCTIONS - primary harness: claude
================================================================================
Current state:
- Lock: held by this session; this session owns normal supervision unless away mode says otherwise.
- Away mode: inactive.
- X mode: inactive; use the default watcher cadence.
- Ordinary wake: the Stop-owned auto-arm (bin/fm-claude-stop-autoarm.sh) already owns watcher continuity; drain and handle the wake, and do not arm another cycle yourself.

Mode: Claude Stop-hook-owned supervision.

When this session owns supervision and away mode is not active:
1. Drain first with `bin/fm-wake-drain.sh`.
   After handling all emitted wakes and reconciling open decisions and unread status lines, run the exact `--ack-through` command printed as `WAKE_ACK_REQUIRED`; until then the work remains durable for idempotent re-handling after interruption.
2. Routine watcher arm and re-arm are owned by the Stop `asyncRewake` hook (`bin/fm-claude-stop-autoarm.sh`), never by you.
   Every turn end while supervision is needed launches or attaches one home-scoped watcher cycle with no model command and no model tokens.
   An actionable close wakes you through the hook's exit-2 rewake, delivered as a `Stop hook feedback` message.
3. On a `Stop hook feedback` wake (`signal:`, `stale:`, `check:`, or `heartbeat`), run `bin/fm-wake-drain.sh` first and handle the wake.
   Do not run `bin/fm-watch-arm.sh` after an ordinary wake; the next turn end re-arms automatically when supervision is still needed.
   Do not invent a wake from an attach-status line alone; drain and act only on real wake records, the drain's `OPEN DECISIONS` and `UNREAD STATUS` entries, or a real watcher reason line.
4. On the one `Stop hook feedback` automatic-mechanism failure notice (`firstmate watcher auto-arm FAILED ...`), drain, inspect the automatic mechanism failure, and do not turn the notice into a repeating manual-arm loop.
5. If the Stop hook does not claim the home or reports an exhausted failure, inspect its registration and watcher startup path before ending blind.
   Keep the Stop-owned automatic mechanism as the only Claude arm owner.
6. Treat `watcher: started ...` and `watcher: attached ...` inside automatic arm output as proof that one live cycle exists.
   On attach, the arm follows verified identity-matched successors instead of exiting when the first cycle ends.
7. The durable wake queue preserves actionable events between a rewake and the next Stop-launched arm, while the bounded turn-end guard prevents a blind Stop when recovery did not start.
   No PreToolUse hook denies fleet commands based on watcher status.
   [`watcher-continuity.md`](../watcher-continuity.md) owns the exact session-lock recovery boundary.
8. The turn-end guard (`bin/fm-turnend-guard.sh --claude`) remains the final backstop.
   It requires the PID-strict live-watcher and fresh-beacon predicate at the Stop boundary, while the mid-turn pull guard accepts a fresh beacon without a live process under Claude's between-turns auto-arm model.
   It allows the stop when a watcher is healthy or an open auto-arm generation claim owns recovery, while fresh failure epochs advance the bounded one-time attended fail-open progression described in [`turnend-guard.md`](../turnend-guard.md).
9. Waiting on the hook-owned cycle is silent: do not send idle progress while the watcher is parked.

The watcher itself remains `bin/fm-watch.sh`, and `bin/fm-watch-arm.sh` remains the verified arm wrapper that the Stop hook foregrounds.
Re-arm attaches to an existing healthy cycle when one is already present and follows its verified successor chain.
See [`watcher-continuity.md`](../watcher-continuity.md) for the arm-layer successor and clean-close failure contract and the Claude ownership model.


================================================================================
READ-ONCE CONTRACT
================================================================================
Everything below is printed in full for this session start: every state/*.meta,
a compact data/backlog.md listing, a bounded tail of every state/*.status,
data/projects.md, data/secondmates.md, data/captain.md, data/captain-shared.md,
and data/learnings.md.
Do NOT re-read any of them after reading this digest, and do NOT bulk-read
data/backlog.md or state/*.status: re-reading everything defeats the entire
point of this command.

Go to a source directly only when:
  - this digest flagged it ABSENT (then rebuild or create it per AGENTS.md),
  - its contents looked unparseable or corrupt,
  - an individual full status log is needed for older wake-event history, or a
    status line was capped and its tail matters (each task's full log path is
    printed with its tail),
  - a full task body is needed (tasks-axi show <id> --full, or data/backlog.md),
  - the backlog listing disclosed omitted queued items and this turn needs them,
  - the NETWORK CHECKS section reported its checks still IN PROGRESS and this
    turn needs their verdict (bin/fm-startup-network.sh report),
  - or a STARTUP TRUNCATED banner named the stage that would have printed it, in
    which case that stage's sources were never emitted and must be reconciled.

================================================================================
FLEET STATE
================================================================================

data/backlog.md
--------------------------------------------------------------------------------
ABSENT

Work under way (state/*.meta)
--------------------------------------------------------------------------------
(none)

Orphan status logs (state/*.status without matching .meta)
--------------------------------------------------------------------------------
(none)

AFK
--------------------------------------------------------------------------------
absent

================================================================================
NETWORK CHECKS
================================================================================
completed off the startup path in 1s: GitHub authentication, dead-secondmate relaunch, secondmate convergence, pending handoff delivery, project clone refresh with its drift reporting, and inactive terminal-outcome reconciliation.
NOTICE: auto-detected herdr runtime (HERDR_ENV=1) - spawning into the EXPERIMENTAL herdr backend. Set config/backend or pass --backend tmux to opt out.
These ran AFTER the sections above were composed, so re-read any record a line here names.

================================================================================
CONTEXT
================================================================================

data/projects.md
--------------------------------------------------------------------------------
ABSENT

data/secondmates.md
--------------------------------------------------------------------------------
ABSENT

data/captain.md
--------------------------------------------------------------------------------
ABSENT

data/captain-shared.md (shared, main-authoritative, read-only in secondmate homes)
--------------------------------------------------------------------------------
ABSENT

data/learnings.md
--------------------------------------------------------------------------------
ABSENT

================================================================================
NEXT STEP
================================================================================
Follow the supervision operating instructions block above for harness 'claude'.
This script never starts supervision itself.

The digest above is complete for this session start. The READ-ONCE CONTRACT
section near the top of it governs what may still be read from disk.
Evidence: Session start digest, secondmate home (lock acquired)

Source: Session start digest, secondmate home (lock acquired)


================================================================================
SESSION START - /private/var/folders/j_/wjtv52sn0vx4tgwpwzmq50vw0000gn/T/fm-role-live.cXjkai/second
================================================================================

LOCK
--------------------------------------------------------------------------------
lock acquired: harness pid 62618

BOOTSTRAP
--------------------------------------------------------------------------------
NOTICE: auto-detected herdr runtime (HERDR_ENV=1) - spawning into the EXPERIMENTAL herdr backend. Set config/backend or pass --backend tmux to opt out.

WAKE QUEUE
--------------------------------------------------------------------------------
(no queued wakes)
================================================================================
SUPERVISION OPERATING INSTRUCTIONS - primary harness: claude
================================================================================
Current state:
- Lock: held by this session; this session owns normal supervision unless away mode says otherwise.
- Away mode: inactive.
- X mode: inactive; use the default watcher cadence.
- Ordinary wake: the Stop-owned auto-arm (bin/fm-claude-stop-autoarm.sh) already owns watcher continuity; drain and handle the wake, and do not arm another cycle yourself.

Mode: Claude Stop-hook-owned supervision.

When this session owns supervision and away mode is not active:
1. Drain first with `bin/fm-wake-drain.sh`.
   After handling all emitted wakes and reconciling open decisions and unread status lines, run the exact `--ack-through` command printed as `WAKE_ACK_REQUIRED`; until then the work remains durable for idempotent re-handling after interruption.
2. Routine watcher arm and re-arm are owned by the Stop `asyncRewake` hook (`bin/fm-claude-stop-autoarm.sh`), never by you.
   Every turn end while supervision is needed launches or attaches one home-scoped watcher cycle with no model command and no model tokens.
   An actionable close wakes you through the hook's exit-2 rewake, delivered as a `Stop hook feedback` message.
3. On a `Stop hook feedback` wake (`signal:`, `stale:`, `check:`, or `heartbeat`), run `bin/fm-wake-drain.sh` first and handle the wake.
   Do not run `bin/fm-watch-arm.sh` after an ordinary wake; the next turn end re-arms automatically when supervision is still needed.
   Do not invent a wake from an attach-status line alone; drain and act only on real wake records, the drain's `OPEN DECISIONS` and `UNREAD STATUS` entries, or a real watcher reason line.
4. On the one `Stop hook feedback` automatic-mechanism failure notice (`firstmate watcher auto-arm FAILED ...`), drain, inspect the automatic mechanism failure, and do not turn the notice into a repeating manual-arm loop.
5. If the Stop hook does not claim the home or reports an exhausted failure, inspect its registration and watcher startup path before ending blind.
   Keep the Stop-owned automatic mechanism as the only Claude arm owner.
6. Treat `watcher: started ...` and `watcher: attached ...` inside automatic arm output as proof that one live cycle exists.
   On attach, the arm follows verified identity-matched successors instead of exiting when the first cycle ends.
7. The durable wake queue preserves actionable events between a rewake and the next Stop-launched arm, while the bounded turn-end guard prevents a blind Stop when recovery did not start.
   No PreToolUse hook denies fleet commands based on watcher status.
   [`watcher-continuity.md`](../watcher-continuity.md) owns the exact session-lock recovery boundary.
8. The turn-end guard (`bin/fm-turnend-guard.sh --claude`) remains the final backstop.
   It requires the PID-strict live-watcher and fresh-beacon predicate at the Stop boundary, while the mid-turn pull guard accepts a fresh beacon without a live process under Claude's between-turns auto-arm model.
   It allows the stop when a watcher is healthy or an open auto-arm generation claim owns recovery, while fresh failure epochs advance the bounded one-time attended fail-open progression described in [`turnend-guard.md`](../turnend-guard.md).
9. Waiting on the hook-owned cycle is silent: do not send idle progress while the watcher is parked.

The watcher itself remains `bin/fm-watch.sh`, and `bin/fm-watch-arm.sh` remains the verified arm wrapper that the Stop hook foregrounds.
Re-arm attaches to an existing healthy cycle when one is already present and follows its verified successor chain.
See [`watcher-continuity.md`](../watcher-continuity.md) for the arm-layer successor and clean-close failure contract and the Claude ownership model.


================================================================================
READ-ONCE CONTRACT
================================================================================
Everything below is printed in full for this session start: every state/*.meta,
a compact data/backlog.md listing, a bounded tail of every state/*.status,
data/projects.md, data/secondmates.md, data/captain.md, data/captain-shared.md,
and data/learnings.md.
Do NOT re-read any of them after reading this digest, and do NOT bulk-read
data/backlog.md or state/*.status: re-reading everything defeats the entire
point of this command.

Go to a source directly only when:
  - this digest flagged it ABSENT (then rebuild or create it per AGENTS.md),
  - its contents looked unparseable or corrupt,
  - an individual full status log is needed for older wake-event history, or a
    status line was capped and its tail matters (each task's full log path is
    printed with its tail),
  - a full task body is needed (tasks-axi show <id> --full, or data/backlog.md),
  - the backlog listing disclosed omitted queued items and this turn needs them,
  - the NETWORK CHECKS section reported its checks still IN PROGRESS and this
    turn needs their verdict (bin/fm-startup-network.sh report),
  - or a STARTUP TRUNCATED banner named the stage that would have printed it, in
    which case that stage's sources were never emitted and must be reconciled.

================================================================================
FLEET STATE
================================================================================

data/backlog.md
--------------------------------------------------------------------------------
ABSENT

Work under way (state/*.meta)
--------------------------------------------------------------------------------
(none)

Orphan status logs (state/*.status without matching .meta)
--------------------------------------------------------------------------------
(none)

AFK
--------------------------------------------------------------------------------
absent

================================================================================
NETWORK CHECKS
================================================================================
completed off the startup path in 1s: GitHub authentication, dead-secondmate relaunch, secondmate convergence, pending handoff delivery, project clone refresh with its drift reporting, and inactive terminal-outcome reconciliation.
NOTICE: auto-detected herdr runtime (HERDR_ENV=1) - spawning into the EXPERIMENTAL herdr backend. Set config/backend or pass --backend tmux to opt out.
These ran AFTER the sections above were composed, so re-read any record a line here names.

================================================================================
CONTEXT
================================================================================

data/projects.md
--------------------------------------------------------------------------------
ABSENT

data/secondmates.md
--------------------------------------------------------------------------------
ABSENT

data/captain.md
--------------------------------------------------------------------------------
ABSENT

data/captain-shared.md (shared, main-authoritative, read-only in secondmate homes)
--------------------------------------------------------------------------------
ABSENT

data/learnings.md
--------------------------------------------------------------------------------
ABSENT

================================================================================
NEXT STEP
================================================================================
Follow the supervision operating instructions block above for harness 'claude'.
This script never starts supervision itself.

The digest above is complete for this session start. The READ-ONCE CONTRACT
section near the top of it governs what may still be read from disk.
Evidence: Session start digest, wrong-session pane (lock acquired, report refused)

Source: Session start digest, wrong-session pane (lock acquired, report refused)


================================================================================
SESSION START - /private/var/folders/j_/wjtv52sn0vx4tgwpwzmq50vw0000gn/T/fm-role-live.cXjkai/foreign
================================================================================

LOCK
--------------------------------------------------------------------------------
lock acquired: harness pid 58150

BOOTSTRAP
--------------------------------------------------------------------------------
warning: herdr role token 'firstmate' not reported: pane 'w1:p1' could not be proved to belong to herdr session 'fm-lab-notthisone'
NOTICE: auto-detected herdr runtime (HERDR_ENV=1) - spawning into the EXPERIMENTAL herdr backend. Set config/backend or pass --backend tmux to opt out.

WAKE QUEUE
--------------------------------------------------------------------------------
(no queued wakes)
================================================================================
SUPERVISION OPERATING INSTRUCTIONS - primary harness: claude
================================================================================
Current state:
- Lock: held by this session; this session owns normal supervision unless away mode says otherwise.
- Away mode: inactive.
- X mode: inactive; use the default watcher cadence.
- Ordinary wake: the Stop-owned auto-arm (bin/fm-claude-stop-autoarm.sh) already owns watcher continuity; drain and handle the wake, and do not arm another cycle yourself.

Mode: Claude Stop-hook-owned supervision.

When this session owns supervision and away mode is not active:
1. Drain first with `bin/fm-wake-drain.sh`.
   After handling all emitted wakes and reconciling open decisions and unread status lines, run the exact `--ack-through` command printed as `WAKE_ACK_REQUIRED`; until then the work remains durable for idempotent re-handling after interruption.
2. Routine watcher arm and re-arm are owned by the Stop `asyncRewake` hook (`bin/fm-claude-stop-autoarm.sh`), never by you.
   Every turn end while supervision is needed launches or attaches one home-scoped watcher cycle with no model command and no model tokens.
   An actionable close wakes you through the hook's exit-2 rewake, delivered as a `Stop hook feedback` message.
3. On a `Stop hook feedback` wake (`signal:`, `stale:`, `check:`, or `heartbeat`), run `bin/fm-wake-drain.sh` first and handle the wake.
   Do not run `bin/fm-watch-arm.sh` after an ordinary wake; the next turn end re-arms automatically when supervision is still needed.
   Do not invent a wake from an attach-status line alone; drain and act only on real wake records, the drain's `OPEN DECISIONS` and `UNREAD STATUS` entries, or a real watcher reason line.
4. On the one `Stop hook feedback` automatic-mechanism failure notice (`firstmate watcher auto-arm FAILED ...`), drain, inspect the automatic mechanism failure, and do not turn the notice into a repeating manual-arm loop.
5. If the Stop hook does not claim the home or reports an exhausted failure, inspect its registration and watcher startup path before ending blind.
   Keep the Stop-owned automatic mechanism as the only Claude arm owner.
6. Treat `watcher: started ...` and `watcher: attached ...` inside automatic arm output as proof that one live cycle exists.
   On attach, the arm follows verified identity-matched successors instead of exiting when the first cycle ends.
7. The durable wake queue preserves actionable events between a rewake and the next Stop-launched arm, while the bounded turn-end guard prevents a blind Stop when recovery did not start.
   No PreToolUse hook denies fleet commands based on watcher status.
   [`watcher-continuity.md`](../watcher-continuity.md) owns the exact session-lock recovery boundary.
8. The turn-end guard (`bin/fm-turnend-guard.sh --claude`) remains the final backstop.
   It requires the PID-strict live-watcher and fresh-beacon predicate at the Stop boundary, while the mid-turn pull guard accepts a fresh beacon without a live process under Claude's between-turns auto-arm model.
   It allows the stop when a watcher is healthy or an open auto-arm generation claim owns recovery, while fresh failure epochs advance the bounded one-time attended fail-open progression described in [`turnend-guard.md`](../turnend-guard.md).
9. Waiting on the hook-owned cycle is silent: do not send idle progress while the watcher is parked.

The watcher itself remains `bin/fm-watch.sh`, and `bin/fm-watch-arm.sh` remains the verified arm wrapper that the Stop hook foregrounds.
Re-arm attaches to an existing healthy cycle when one is already present and follows its verified successor chain.
See [`watcher-continuity.md`](../watcher-continuity.md) for the arm-layer successor and clean-close failure contract and the Claude ownership model.


================================================================================
READ-ONCE CONTRACT
================================================================================
Everything below is printed in full for this session start: every state/*.meta,
a compact data/backlog.md listing, a bounded tail of every state/*.status,
data/projects.md, data/secondmates.md, data/captain.md, data/captain-shared.md,
and data/learnings.md.
Do NOT re-read any of them after reading this digest, and do NOT bulk-read
data/backlog.md or state/*.status: re-reading everything defeats the entire
point of this command.

Go to a source directly only when:
  - this digest flagged it ABSENT (then rebuild or create it per AGENTS.md),
  - its contents looked unparseable or corrupt,
  - an individual full status log is needed for older wake-event history, or a
    status line was capped and its tail matters (each task's full log path is
    printed with its tail),
  - a full task body is needed (tasks-axi show <id> --full, or data/backlog.md),
  - the backlog listing disclosed omitted queued items and this turn needs them,
  - the NETWORK CHECKS section reported its checks still IN PROGRESS and this
    turn needs their verdict (bin/fm-startup-network.sh report),
  - or a STARTUP TRUNCATED banner named the stage that would have printed it, in
    which case that stage's sources were never emitted and must be reconciled.

================================================================================
FLEET STATE
================================================================================

data/backlog.md
--------------------------------------------------------------------------------
ABSENT

Work under way (state/*.meta)
--------------------------------------------------------------------------------
(none)

Orphan status logs (state/*.status without matching .meta)
--------------------------------------------------------------------------------
(none)

AFK
--------------------------------------------------------------------------------
absent

================================================================================
NETWORK CHECKS
================================================================================
completed off the startup path in 1s: GitHub authentication, dead-secondmate relaunch, secondmate convergence, pending handoff delivery, project clone refresh with its drift reporting, and inactive terminal-outcome reconciliation.
NOTICE: auto-detected herdr runtime (HERDR_ENV=1) - spawning into the EXPERIMENTAL herdr backend. Set config/backend or pass --backend tmux to opt out.
These ran AFTER the sections above were composed, so re-read any record a line here names.

================================================================================
CONTEXT
================================================================================

data/projects.md
--------------------------------------------------------------------------------
ABSENT

data/secondmates.md
--------------------------------------------------------------------------------
ABSENT

data/captain.md
--------------------------------------------------------------------------------
ABSENT

data/captain-shared.md (shared, main-authoritative, read-only in secondmate homes)
--------------------------------------------------------------------------------
ABSENT

data/learnings.md
--------------------------------------------------------------------------------
ABSENT

================================================================================
NEXT STEP
================================================================================
Follow the supervision operating instructions block above for harness 'claude'.
This script never starts supervision itself.

The digest above is complete for this session start. The READ-ONCE CONTRACT
section near the top of it governs what may still be read from disk.
Evidence: Read-only session start in real Herdr pane: no token reported

Source: Read-only session start in real Herdr pane: no token reported

lab session: fm-lab-role-live-8670-26289
pane: w1:p1
tokens before any session start: {}
== foreign: session start exit=0
   injected env: HERDR_BIN_PATH=/opt/homebrew/bin/herdr HERDR_ENV=1 HERDR_PANE_ID=w1:p1 HERDR_SESSION=fm-lab-role-live-8670-26289 HERDR_SOCKET_PATH=~/.config/herdr/sessions/fm-lab-role-live-8670-26289/herdr.sock HERDR_TAB_ID=w1:t1 HERDR_WORKSPACE_ID=w1 
   role warnings in session start output:
   pane tokens after: {}
== primary: session start exit=0
   injected env: HERDR_BIN_PATH=/opt/homebrew/bin/herdr HERDR_ENV=1 HERDR_PANE_ID=w1:p1 HERDR_SESSION=fm-lab-role-live-8670-26289 HERDR_SOCKET_PATH=~/.config/herdr/sessions/fm-lab-role-live-8670-26289/herdr.sock HERDR_TAB_ID=w1:t1 HERDR_WORKSPACE_ID=w1 
   role warnings in session start output:
   pane tokens after: {}
== second: session start exit=0
   injected env: HERDR_BIN_PATH=/opt/homebrew/bin/herdr HERDR_ENV=1 HERDR_PANE_ID=w1:p1 HERDR_SESSION=fm-lab-role-live-8670-26289 HERDR_SOCKET_PATH=~/.config/herdr/sessions/fm-lab-role-live-8670-26289/herdr.sock HERDR_TAB_ID=w1:t1 HERDR_WORKSPACE_ID=w1 
   role warnings in session start output:
   pane tokens after: {}
== herdr pane get w1:p1 (final):
{
  "pane_id": "w1:p1",
  "workspace_id": "w1",
  "tokens": null
}
  • Evidence: Live driver script
  • Outcome: ⚠️ 2 issues (1 error, 1 warning) across 1 run (2h7m51s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ℹ️ bin/backends/herdr.sh:359 - fm_backend_herdr_role_token_supported re-implements the client/server release composition that fm_backend_herdr_presentation_release_supported already owns, and the copy handles one case differently. It reads .server.running // empty and treats any non-true value as 'no running server', returning the client-only verdict. The presentation composer returns 2 (indeterminate) when .server.running is neither true nor false. Concrete input: {&#34;client&#34;:{&#34;version&#34;:&#34;0.9.0&#34;,&#34;protocol&#34;:22},&#34;server&#34;:{&#34;version&#34;:&#34;0.7.3&#34;,&#34;protocol&#34;:16}}, with running missing. The role path returns 0 and sends report-metadata to a below-floor server. The presentation path reports indeterminate. The impact is small, because the token is display-only and a failed report only warns. Remedy: pass the min protocol and min version into the existing composer, the same way fm_backend_herdr_release_floor_verdict now takes them, and call it from the role path. That leaves one definition of the client/server floor rule.
⚠️ **Test** - 2 issues (1 error, 1 warning)
  • 🚨 tests failed with exit code 1
  • ⚠️ tests/fm-session-start.test.sh - tests/fm-session-start.test.sh failed with 'not ok - the digest waited 10s for inactive reconciliation's 8s state read'. It ran at the same time as the real-Herdr E2E test. The suite stops at that failure, so later tests did not run. A rerun alone passed all 51 cases. This timing test is flaky under host load. The configured baseline --changed run also exited 1 for a cause this step did not isolate.
  • Live validation: ✅ go - 7 of 9 scenarios driven live against the product
Scenario Result Live Evidence
Crewmate spawn on Herdr: real fm-spawn.sh sets pane token role=crewmate on the exact task pane ✅ pass live launcher-workspace-e2e.log: 'a crewmate spawn reports the crewmate fleet role token for its exact pane'
Scout spawn on Herdr: --scout spawn sets role=scout on its pane ✅ pass live launcher-workspace-e2e.log: 'a scout spawn reports the scout fleet role token for its exact pane'
Secondmate spawn on Herdr: --secondmate launch sets role=secondmate, and a secondmate's own worker sets role=crewmate ✅ pass live launcher-workspace-e2e.log: secondmate and secondmate-crewmate role token lines
Primary firstmate session start inside a Herdr pane sets role=firstmate on its own pane ✅ pass live session-start-role-live-locked.log: primary, exit=0, pane tokens {"role":"firstmate"}; session-start-primary.out shows 'lock acquired'
Secondmate home session start inside a Herdr pane sets role=secondmate, replacing the earlier value ✅ pass live session-start-role-live-locked.log: second, exit=0, final herdr pane get tokens {"role":"secondmate"}
Adversarial: HERDR_SESSION names a different session than the pane's socket; session start refuses the report, warns, and still exits 0 ✅ pass live session-start-role-live-locked.log: foreign warning 'could not be proved to belong to herdr session', exit=0, pane tokens {}
Adversarial: read-only session start (lock not held) reports no role token ✅ pass live readonly-session-start-role-live.log: all three runs READ-ONLY, pane tokens stay {} / null
Relaunch through fm-control.sh relaunch reports the role token again on the replacement pane ⏸️ untested no A live relaunch needs a running, authenticated harness agent in a Herdr task pane, which fm-control waits on for the 'alive' state. The spawn E2E test uses a plain shell command, not an agent. To test…
Version floor: Herdr below 0.7.4 skips the report silently ⏸️ untested no This host has only Herdr 0.9.0. A live check needs a Herdr 0.7.3 binary installed next to it, which the workspace rules forbid here.
  • bin/fm-test-run.sh --changed --exclude-family real-herdr-gated
  • bash tests/fm-backend-herdr-launcher-workspace-e2e.test.sh (real Herdr 0.9.0 in an isolated lab session: real bin/fm-spawn.sh sets role=crewmate, scout, secondmate, and a secondmate's own crewmate; pane tokens read with herdr pane get)
  • ROOT=$PWD bash &lt;evidence&gt;/session-start-role-live.sh (real bin/fm-session-start.sh run inside a real Herdr lab pane under a shell named claude, so the session lock is really held: wrong-session case, primary home, secondmate home; herdr pane get read after each run)
  • Same driver without a harness in the process tree (lock refused, read-only session start): pane tokens stay empty
  • bash tests/fm-backend-herdr.test.sh (report_role / report_own_role unit cases: version floor, unknown role, missing pane, failed report, wrong-session socket)
  • bash tests/fm-session-start.test.sh (first run failed one timing test while the real E2E ran at the same time; passed on rerun alone, including test_herdr_role_token_reported_for_own_pane)
  • herdr session list after teardown: no fm-lab sessions left, default session still running
✅ **Document** - passed

✅ No issues found.

⚠️ **Lint** - 1 warning
  • ⚠️ linter found issues (exit code 1)
✅ **Push** - passed

✅ No issues found.

Karan Manoharan added 2 commits September 14, 2026 18:50
Herdr sidebar colour rules key on a $role metadata token. Firstmate now
reports role=<firstmate|secondmate|crewmate|scout> with
`herdr pane report-metadata --source firstmate --token role=<role>`.

- Spawn and relaunch report crewmate, scout, or secondmate for the exact
  pane in the published task record.
- A locked session start reports firstmate, or secondmate in a marked
  secondmate home, for its own pane after its socket identity matches
  the named session.
- Reports need Herdr 0.7.4 or newer and skip silently below it. A failed
  report warns and never fails the spawn or the session start.
- tests/lib.sh drops inherited Herdr pane identity so suites run from a
  Herdr pane cannot report metadata to the developer's live pane.

Claude-Session: https://claude.ai/code/session_01XUEUUVFLABdU3vL4NoAe2f
@karanmrn
karanmrn merged commit d8dd03d into main Sep 15, 2026
0 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant