Skip to content
This repository was archived by the owner on Aug 25, 2026. It is now read-only.

feat(herdr): add readable task tab labels - #76

Merged
JTInventory merged 13 commits into
mainfrom
fm/herdr-tab-labels-ship-65b2
Jul 24, 2026
Merged

JTInventory merged 13 commits into
mainfrom
fm/herdr-tab-labels-ship-65b2

Conversation

@JTInventory

Copy link
Copy Markdown
Owner

Intent

Ship Herdr-only display tab labels in the accepted Option C shape ' - · '. Keep workspace identity as firstmate / 2ndmate- and do not change tmux naming. Map ship to Crew, scout to Scout, and secondmate agent to 2nd; derive a deterministic 1-28 character phrase from explicit display title, backlog title, then semantic id fallback; use a stable 4-6 character id-tail or SHA key and extend on collision; cap the full label at 50 characters and enforce the report's safe charset. Set the label once at spawn, persist display_label plus full machine identity and exact Herdr tab/pane ids, journal before create for crash recovery, prefer exact ids then display_label while retaining legacy fm- discovery, and add deterministic intake plumbing, documentation, and hermetic tests. Do not introduce pretty-label-only recovery, LLM titles, phase renames, workspace identity changes, or tmux naming changes.

What Changed

  • Add deterministic, readable Herdr tab labels using the <kind> - <phrase> · <task-key> format, with validated title sources, safe character and length limits, and collision-resistant keys.
  • Persist labels alongside exact Herdr identities, journal labels before tab creation, and recover through exact IDs, validated display labels, or legacy fm-<id> tabs while leaving tmux naming unchanged.
  • Update Herdr documentation and add coverage for intake, label generation, concurrent reservations, backlog parsing, secondmate routing, crash recovery, and malformed inventory data.

Risk Assessment

✅ Low: Captain, the documentation fix now aligns the authoritative operating contract with the implemented Herdr presentation and machine-identity split, and no material regressions remain.

Testing

The supplied full behavior baseline and focused label, intake, spawn, adapter, recovery, secondmate-routing, and unchanged-tmux tests passed. A hermetic end-to-end spawn produced a rendered screenshot plus raw label, metadata, journal, and CLI evidence; a live Herdr screenshot was intentionally avoided because mutating the captain’s real session was outside this gate-worktree validation boundary.

  • Evidence: Rendered Herdr tab and recovery evidence (local file: /tmp/no-mistakes-evidence/01KY8DMPEWVRF5TZG4ZKWFR3WK/herdr-tab-label-evidence.png)
Evidence: Interactive evidence source
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Herdr display-label end-to-end evidence</title>
<style>
  :root { color-scheme: dark; font-family: Inter, ui-sans-serif, system-ui, sans-serif; }
  * { box-sizing: border-box; }
  body { margin: 0; background: #090d14; color: #e9eef8; }
  main { width: 1200px; min-height: 760px; margin: 0 auto; padding: 44px; }
  h1 { margin: 0 0 8px; font-size: 28px; letter-spacing: -.03em; }
  .sub { margin: 0 0 28px; color: #95a4bc; font-size: 14px; }
  .window { overflow: hidden; border: 1px solid #2c3b52; border-radius: 14px; background: #101722; box-shadow: 0 24px 60px #0008; }
  .chrome { height: 48px; display: flex; align-items: center; gap: 8px; padding: 0 16px; background: #151e2b; border-bottom: 1px solid #26354a; }
  .dot { width: 11px; height: 11px; border-radius: 50%; background: #50617a; }
  .workspace { margin-left: 10px; color: #7f90aa; font: 12px ui-monospace, monospace; }
  .tabbar { display: flex; align-items: end; gap: 7px; padding: 14px 18px 0; background: #0d131d; }
  .tab { padding: 11px 18px 12px; border: 1px solid #375171; border-bottom: 0; border-radius: 9px 9px 0 0; background: #1a2636; color: #f7fbff; font: 600 15px ui-monospace, SFMono-Regular, Menlo, monospace; }
  .terminal { min-height: 290px; padding: 25px; background: #071019; font: 14px/1.65 ui-monospace, SFMono-Regular, Menlo, monospace; }
  .prompt { color: #68d391; }
  .muted { color: #74849c; }
  .label { color: #ffd166; }
  .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 18px; }
  .card { border: 1px solid #26364c; border-radius: 12px; padding: 18px; background: #101722; }
  .card h2 { margin: 0 0 12px; color: #b9c7db; font-size: 13px; text-transform: uppercase; letter-spacing: .09em; }
  dl { display: grid; grid-template-columns: 150px 1fr; gap: 8px 12px; margin: 0; font: 13px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; }
  dt { color: #71829a; }
  dd { margin: 0; color: #dce8f7; }
  .ok { color: #70e1a1; }
  footer { margin-top: 16px; color: #71829a; font-size: 12px; }
</style>
<main>
  <h1>Herdr tab label — hermetic end-to-end spawn</h1>
  <p class="sub">Rendered from the preserved fake-Herdr inventory and Firstmate metadata produced by the focused spawn test. No live Herdr session was mutated.</p>

  <section class="window" aria-label="Rendered Herdr terminal surface">
    <div class="chrome">
      <span class="dot"></span><span class="dot"></span><span class="dot"></span>
      <span class="workspace">session=fmtest · workspace=firstmate · workspace_id=w1</span>
    </div>
    <div class="tabbar">
      <div class="tab">Scout - Herdr labels · c1db</div>
    </div>
    <div class="terminal">
      <div><span class="prompt">herdr</span> tab list --workspace w1 --session fmtest</div>
      <div>{ "tab_id": "w1:t1", "label": <span class="label">"Scout - Herdr labels · c1db"</span>, "workspace_id": "w1" }</div>
      <br>
      <div class="muted"># create command captured by the fake Herdr CLI</div>
      <div>tab create --workspace w1 --label <span class="label">Scout - Herdr labels · c1db</span> --no-focus --session fmtest</div>
      <br>
      <div class="ok">✓ display label set once at spawn</div>
      <div class="ok">✓ exact tab and pane IDs persisted for recovery</div>
      <div class="ok">✓ pre-create journal observed before tab creation</div>
    </div>
  </section>

  <div class="grid">
    <section class="card">
      <h2>Published task metadata</h2>
      <dl>
        <dt>kind</dt><dd>scout</dd>
        <dt>backend</dt><dd>herdr</dd>
        <dt>display_label</dt><dd>Scout - Herdr labels · c1db</dd>
        <dt>task_key</dt><dd>c1db</dd>
        <dt>herdr_session</dt><dd>fmtest</dd>
        <dt>herdr_workspace_id</dt><dd>w1</dd>
        <dt>herdr_tab_id</dt><dd>w1:t1</dd>
        <dt>herdr_pane_id</dt><dd>w1:p1</dd>
      </dl>
    </section>
    <section class="card">
      <h2>Journal snapshot seen at create</h2>
      <dl>
        <dt>task_id</dt><dd>herdr-label-c1db</dd>
        <dt>display_label</dt><dd>Scout - Herdr labels · c1db</dd>
        <dt>task_key</dt><dd>c1db</dd>
        <dt>herdr_session</dt><dd>fmtest</dd>
        <dt>workspace identity</dt><dd>firstmate</dd>
        <dt>herdr_workspace_id</dt><dd>w1</dd>
        <dt>journal after success</dt><dd class="ok">retired after metadata publish</dd>
      </dl>
    </section>
  </div>

  <footer>Source run: tests/fm-spawn-herdr-label.test.sh at target commit 940762d76becd67c10fb13e1143e1ae23ce996aa</footer>
</main>
</html>
Evidence: Hermetic Herdr CLI transcript
status --json --session fmtest
status --json --session fmtest
status --json --session fmtest
workspace list --session fmtest
workspace list --session fmtest
workspace create --cwd /tmp/no-mistakes-evidence/01KY8DMPEWVRF5TZG4ZKWFR3WK/cases/fm-spawn-herdr-label.JKo2lw/success/project --label firstmate --no-focus --session fmtest
workspace report-metadata w1 --source firstmate --token firstmate_home=99a9f90868969d709601f017eed19ae573df795ae412dc15a59fe011bd840bee --session fmtest
tab list --workspace w1 --session fmtest
tab list --workspace w1 --session fmtest
tab create --workspace w1 --cwd /tmp/no-mistakes-evidence/01KY8DMPEWVRF5TZG4ZKWFR3WK/cases/fm-spawn-herdr-label.JKo2lw/success/project --label Scout - Herdr labels · c1db --no-focus --session fmtest
status --json --session fmtest
status --json --session fmtest
pane run w1:p1 treehouse get --session fmtest
status --json --session fmtest
pane get w1:p1 --session fmtest
status --json --session fmtest
pane run w1:p1 export GOTMPDIR='/tmp/fm-herdr-label-c1db/gotmp' --session fmtest
status --json --session fmtest
pane send-text w1:p1 codex --model 'gpt-5.6-sol' -c 'model_reasoning_effort="medium"' --dangerously-bypass-approvals-and-sandbox -c "notify=[\"bash\",\"-c\",\"touch '/tmp/no-mistakes-evidence/01KY8DMPEWVRF5TZG4ZKWFR3WK/cases/fm-spawn-herdr-label.JKo2lw/success/home/state/herdr-label-c1db.turn-ended'\"]" "$(cat '/tmp/no-mistakes-evidence/01KY8DMPEWVRF5TZG4ZKWFR3WK/cases/fm-spawn-herdr-label.JKo2lw/success/home/data/herdr-label-c1db/brief.md')" --session fmtest
status --json --session fmtest
pane send-keys w1:p1 enter --session fmtest
Evidence: Persisted task metadata
window=fmtest:w1:p1
worktree=/tmp/no-mistakes-evidence/01KY8DMPEWVRF5TZG4ZKWFR3WK/cases/fm-spawn-herdr-label.JKo2lw/success/wt
project=/tmp/no-mistakes-evidence/01KY8DMPEWVRF5TZG4ZKWFR3WK/cases/fm-spawn-herdr-label.JKo2lw/success/project
harness=codex
kind=scout
mode=no-mistakes
yolo=off
route_profile=critical
route_harness=codex
route_model=gpt-5.6-sol
route_effort=medium
route_reason=task touches Firstmate core safety
route_override=none
route_risk_flags=firstmate-core
tasktmp=/tmp/fm-herdr-label-c1db
model=gpt-5.6-sol
effort=medium
backend=herdr
display_label=Scout - Herdr labels · c1db
task_key=c1db
herdr_session=fmtest
herdr_workspace_id=w1
herdr_tab_id=w1:t1
herdr_pane_id=w1:p1
Evidence: Pre-create recovery journal
version=1
task_id=herdr-label-c1db
display_label=Scout - Herdr labels · c1db
task_key=c1db
herdr_home=/tmp/no-mistakes-evidence/01KY8DMPEWVRF5TZG4ZKWFR3WK/cases/fm-spawn-herdr-label.JKo2lw/success/home
herdr_session=fmtest
herdr_workspace_id=w1
Evidence: Visible tab label

Scout - Herdr labels · c1db

Scout - Herdr labels · c1db

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

🔧 **Rebase** - 10 issues found → auto-fixed ✅
  • ⚠️ .agents/skills/fmx-respond/SKILL.md - merge conflict rebasing onto origin/main
  • ⚠️ AGENTS.md - merge conflict rebasing onto origin/main
  • ⚠️ README.md - merge conflict rebasing onto origin/main
  • ⚠️ bin/fm-x-followup.sh - merge conflict rebasing onto origin/main
  • ⚠️ bin/fm-x-lib.sh - merge conflict rebasing onto origin/main
  • ⚠️ bin/fm-x-reply.sh - merge conflict rebasing onto origin/main
  • ⚠️ docs/architecture.md - merge conflict rebasing onto origin/main
  • ⚠️ docs/configuration.md - merge conflict rebasing onto origin/main
  • ⚠️ docs/scripts.md - merge conflict rebasing onto origin/main
  • ⚠️ tests/fm-x-mode.test.sh - merge conflict rebasing onto origin/main

🔧 Fix applied.
✅ Re-checked - no issues remain.

🔧 **Review** - 6 issues found → auto-fixed (10) ✅
  • 🚨 bin/backends/herdr.sh:921 - Required recovery is not operational: the criterion says “prefer exact ids then display_label while retaining legacy fm-<id> discovery,” but this new helper has no production caller or generic backend wrapper. Its implementation also maps readable tabs by label at line 934 without first comparing persisted session/workspace/tab/pane IDs. Wire recovery into the lifecycle and resolve exact IDs before unique label fallback.
  • 🚨 bin/fm-task-label-lib.sh:140 - The required safe charset is not enforced when reusing persisted records. The * phrase glob accepts empty phrases, /, :, quotes, and arbitrary Unicode, contradicting “enforce the report's safe charset.” Validate the 1–28 character phrase against the same ASCII grammar used during generation.
  • 🚨 bin/fm-task-label-lib.sh:222 - The required collision extension is racy. Collision scanning occurs before journal publication and before Herdr’s workspace lock, so concurrent spawns can both reserve the same short key; identical labels may also cause one new pane to be mistaken for a replaceable husk. Serialize collision selection, journal reservation, and creation.
  • ⚠️ bin/backends/herdr.sh:923 - Recovery converts workspace and tab-list failures into a successful empty inventory and skips pane lookup failures. A transient Herdr or JSON error therefore looks like no live tasks. Propagate these failures and validate responses before emitting inventory.
  • ⚠️ bin/fm-task-label-lib.sh:232 - The 50-character check is locale-dependent: under LC_ALL=C, the UTF-8 middot counts as three bytes, so a valid 49-character Scout label with a 28-character phrase and extended key is rejected as 51 bytes. Use an explicit character-counting rule and cover C and UTF-8 locales.
  • ⚠️ bin/fm-brief.sh:74 - The display-title record is written before task-shape and project validation. A failed brief command can leave durable intake without a brief, and a later spawn may consume that stale title. Publish it only after validation or clean it up on failure.

🔧 Fix: Harden Herdr label recovery and spawn serialization
6 errors still open:

  • 🚨 bin/fm-backend.sh:199 - Journal-only crash recovery returns a Herdr target, but no .meta record exists yet, so fm_backend_of_selector later defaults that target to tmux. This contradicts “journal before create for crash recovery” because fm-send and fm-peek will operate through the wrong backend. Preserve or return the recovered backend alongside the target.
  • 🚨 bin/backends/herdr.sh:997 - Display-label recovery only recognizes labels with exactly four key characters. Valid 5–6 character keys and collision-extended keys are excluded, contradicting “use a stable 4-6 character id-tail or SHA key and extend on collision” and “prefer exact ids then display_label.” Reuse the full label-record validator instead of this fixed-width pattern.
  • 🚨 bin/fm-spawn.sh:880 - Secondmate creation selects its Herdr workspace from PROJ_ABS but writes the recovery journal into the primary $STATE; the journal does not retain the secondmate home/workspace needed to inventory that workspace after a pre-meta crash. This leaves the required crash recovery unavailable for secondmate tasks. Persist and use the workspace identity during recovery.
  • 🚨 bin/backends/herdr.sh:670 - Collision reservation is protected by a workspace-specific lock while primary and secondmate spawns scan and update the same primary label state. Concurrent spawns in different Herdr workspaces can therefore reserve the same short key despite the required collision extension. Use one lock for the shared reservation state or separate the state by workspace.
  • 🚨 tests/fm-backend-herdr.test.sh:707 - The concurrency test relies on a 50 ms sleep to assume the first process acquired the lock. A delayed scheduler can let the second process win and reverse the expected keys, contradicting the requirement for hermetic tests. Replace the timing assumption with an explicit barrier indicating lock acquisition.
  • 🚨 tests/fm-task-label-lib.test.sh:127 - The character-count test fails when none of four hardcoded UTF-8 locale names is installed, even though other valid UTF-8 locales may exist. This makes the required hermetic test suite depend on host locale provisioning; create a locale-independent assertion or skip only the locale-specific comparison.

🔧 Fix: Harden Herdr crash recovery and label reservations
2 errors still open:

  • 🚨 bin/fm-backend.sh:201 - The criterion requires “journal before create for crash recovery” and display-label fallback, but secondmate recovery overrides FM_HOME without retaining the primary $state as FM_STATE_OVERRIDE. Label correlation consequently searches &lt;secondmate-home&gt;/state instead of the directory containing the journal, so journal-only secondmate selectors still cannot recover. Pass FM_STATE_OVERRIDE=&#34;$state&#34; into the inventory call.
  • 🚨 bin/fm-fleet-snapshot.sh:51 - The criterion requires deriving the phrase from the backlog title, but this regex omits the closing \) and never strips normal metadata suffixes such as (repo: demo, kind: ship). Short backlog titles therefore produce labels containing routing metadata. Correct the suffix regex and cover a short title that does not hide the leak through truncation.

🔧 Fix: Fix secondmate recovery and backlog title parsing
5 errors still open:

  • 🚨 bin/fm-backend.sh:182 - The required “prefer exact ids then display_label” recovery is still incomplete: an existing Herdr meta record returns its persisted pane immediately, without checking that endpoint or falling back through its persisted label. fm-send and fm-peek therefore fail instead of recovering when exact IDs are stale. Route failed exact-ID resolution through the safe label inventory.
  • 🚨 bin/fm-backend.sh:209 - The required “retaining legacy fm-<id> discovery” is not wired into production resolution. With no meta or journal, a canonical fm-&lt;id&gt; selector errors before Herdr inventory is queried, so a surviving legacy-labeled tab cannot be discovered. Add a Herdr legacy-inventory fallback before this error.
  • 🚨 bin/fm-backend.sh:175 - The new raw-meta lookup runs before documented fm-&lt;id&gt; interpretation. Because valid task IDs may begin with fm-, fm-send fm-foo can select state/fm-foo.meta instead of the canonical state/foo.meta, potentially sending to the wrong task and contradicting retained legacy selector behavior. Give prefixed selectors their established stripped-ID meaning and reserve exact lookup for bare inputs.
  • 🚨 bin/fm-fleet-snapshot.sh:44 - The required backlog-title fallback handles only checkbox rows with trailing metadata. It misses the repository-supported bold in-flight form - **&lt;id&gt;** - ..., and its end-anchored suffix removal leaves metadata in canonical queued rows followed by blocked-by:. Parse every supported backlog form and isolate the title before routing metadata.
  • 🚨 tests/fm-task-label-lib.test.sh:46 - The required hermetic tests directly require sha256sum, although production intentionally falls back to shasum. Hosts providing only the supported fallback fail this test for environmental reasons. Compare against a fixed digest or call the production hash helper.

🔧 Fix: Complete Herdr recovery and backlog title parsing
2 errors still open:

  • 🚨 bin/fm-backend.sh:216 - The required order “prefer exact ids then display_label while retaining legacy fm-<id> discovery” is still collapsed into one fm-$id alias. Inventory emits that alias for both a persisted display-label match and a raw legacy tab, so stale-ID recovery can select an old legacy pane or fail as ambiguous instead of preferring the recorded display label. Match the persisted label distinctly and use legacy discovery only afterward.
  • 🚨 bin/fm-fleet-snapshot.sh:53 - The required backlog-title fallback still includes completion metadata for supported Done rows. The parser accepts - [x] &lt;id&gt; - ... but strips only repository and blocked-by: suffixes, so Fix UI - local main (merged ...) becomes the phrase instead of Fix UI. Parse the PR, local-main, and report completion suffixes and cover short Done rows.

🔧 Fix: Prioritize label recovery and clean Done titles
1 error still open:

  • 🚨 bin/backends/herdr.sh:1005 - The tab-delimited inventory is injectable because jq -r emits raw labels and fm-* labels bypass validation. A label containing tabs, such as fm-victim&lt;TAB&gt;fm-victim, can satisfy the legacy matcher and route sends or peeks to the wrong pane. Encode inventory structurally or validate the complete legacy label against the canonical safe fm-&lt;id&gt; grammar before recovery.

🔧 Fix: Validate legacy Herdr recovery labels
1 error still open:

  • 🚨 bin/backends/herdr.sh:1005 - Recovery remains record-separator injectable: jq -r decodes newlines before validation, so a label like junk\nw1:t4\tfm-victim becomes a forged valid inventory row and can route recovery to the wrong pane. Validate each complete JSON label before serialization or use structural encoding, then add a newline-injection regression alongside the tab case.

🔧 Fix: Block newline-forged Herdr recovery rows
2 issues (1 error, 1 warning) still open:

  • 🚨 bin/backends/herdr.sh:1008 - Raw jq -r extraction still enters Bash command substitution before validation. Bash drops NUL bytes, so fm-vic\u0000tim becomes fm-victim, passes legacy validation, and can forge recovery routing. Reject controls while values remain JSON-encoded or preserve an encoded representation through validation; add a NUL regression.
  • ⚠️ bin/backends/herdr.sh:1006 - A valid empty tabs array produces rows=&#39;&#39;, but the here-string executes the loop once with an empty row and fails the sentinel check. Empty workspaces are therefore reported as inventory failures. Return success before the loop when rows is empty, or avoid the synthetic here-string row.

🔧 Fix: Harden encoded Herdr inventory parsing
1 warning still open:

  • ⚠️ tests/fm-backend-herdr.test.sh:698 - The empty-tabs regression checks only empty output. Because the suite uses set -u, a nonzero command that emits nothing still passes, so the test does not catch the exact failure it targets. Guard the assignment with || fail or explicitly assert a zero status.

🔧 Fix: Assert empty Herdr inventory success
1 error still open:

  • 🚨 AGENTS.md:127 - The criterion requires “Ship Herdr-only display tab labels” and “add ... documentation,” and the changed spawn hunk now calls fm_backend_create_labeled_task; however, this authoritative contract still says Herdr uses an fm-&lt;id&gt; tab. Update it—and clarify docs/operating-map.md:48—to describe the Option C label while retaining exact machine identity and unchanged tmux naming.

🔧 Fix: Document Herdr display and machine identity
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • bash bin/fm-run-behavior-tests.sh
  • Pre-supplied baseline: bash bin/fm-run-behavior-tests.sh (reported successful)
  • bash tests/fm-task-label-lib.test.sh
  • bash tests/fm-brief-display-title.test.sh
  • bash tests/fm-backend-herdr.test.sh
  • Evidence-copy run: env -u NO_MISTAKES_GATE bash tests/fm-spawn-herdr-label.test.sh
  • Evidence-copy run: env -u NO_MISTAKES_GATE bash tests/fm-backend.test.sh
  • Evidence-copy run: env -u NO_MISTAKES_GATE bash tests/fm-send-herdr-secondmate-marker.test.sh
  • Evidence-copy run: env -u NO_MISTAKES_GATE bash tests/fm-spawn-route.test.sh
  • Rendered herdr-tab-label-evidence.html and captured herdr-tab-label-evidence.png with agent-browser
  • Visually inspected the captured screenshot and verified the final worktree with git status --porcelain=v1
✅ **Document** - passed

✅ No issues found.

🔧 **Lint** - 1 issue found → auto-fixed ✅
  • ⚠️ linter found issues (exit code 1)

🔧 Fix: Captain, fix ShellCheck local variable declaration
✅ Re-checked - no issues remain.

✅ **Push** - passed

✅ No issues found.

@JTInventory
JTInventory merged commit a81dbfc into main Jul 24, 2026
4 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant