Skip to content

feat(herdr): add per-project workspaces - #2505

Open
wonder-media wants to merge 7 commits into
kunchenguid:mainfrom
wonder-media:fm/fm-herdr-project-spaces
Open

feat(herdr): add per-project workspaces#2505
wonder-media wants to merge 7 commits into
kunchenguid:mainfrom
wonder-media:fm/fm-herdr-project-spaces

Conversation

@wonder-media

Copy link
Copy Markdown

Intent

The developer was shipping committed changes in the firstmate repository (documentation and sync updates around herdr project-space state and AGENTS inventory) through the no-mistakes validation pipeline, which gates changes with review, tests, lint, docs, push, PR, and CI. The pipeline had passed validation but failed at the push step twice with HTTP 403 because it pushed to the upstream repo kunchenguid/firstmate instead of the developer's fork. The developer diagnosed the root cause as an empty fork_url in the pipeline's repo registry, fixed it by setting fork_url to https://github.com/wonder-media/firstmate.git, and instructed the agent to retry the no-mistakes pipeline from the intact preserved head 5b673e2. Their explicit constraints were to rerun from that preserved, already-validated head (not re-submit an older local head) and to see the run through push, PR creation, and green CI.

What Changed

  • The herdr backend can now group tasks from the same project into one shared workspace: an opt-in herdr-project-spaces config makes spawns from the same project directory land as tabs in a single workspace named after the project, while different projects (including same-basename directories elsewhere) get separate workspaces via path-hashed, durable exact-id binding records under state/.
  • bin/fm-spawn.sh wires the new project-workspace resolution into spawn, and bin/fm-config-inherit-lib.sh plus the secondmate provisioning skill propagate the herdr-project-spaces setting to inherited/secondmate configs; the presentation-journal duplicate-launch recovery guard now runs whenever a journal exists, including under project grouping.
  • Added unit tests (tests/fm-backend-herdr.test.sh), real-herdr e2e presentation tests, and secondmate harness coverage for the new behavior, and documented the feature in docs/herdr-backend.md, docs/configuration.md, docs/architecture.md, and the AGENTS inventory.

Risk Assessment

✅ Low: The feature is opt-in and default-off with exact-id-only adoption, warn-and-fall-back-flat handling on every failure path, single-sourced config inheritance, synced docs, and thorough unit/e2e coverage; the one shared behavior change (running the journal recovery guard whenever a journal exists) is a deliberate, tested durable fix that closes a duplicate-agent launch path.

Testing

Ran the three test files this change touches — the herdr fake-CLI unit suite (180 ok), the secondmate config-inheritance suite (42 ok), and the real-Herdr isolated-lab presentation e2e on herdr 0.7.5 (25 ok, 0 failures, default-session tripwire intact) — then manually drove the production project-workspace functions against a live isolated Herdr session and captured a transcript showing the end-user layout: same-project tasks grouped as tabs in one project-labeled workspace, separate projects and same-named projects in different directories kept in independent exact-id-bound workspaces, with durable binding records on disk. All passed. No screenshot artifact: Herdr lab sessions are headless and the safety contract permits only guarded CLI access, so JSON workspace/tab listings are the reviewer-visible surface.

Evidence: Live-Herdr project-space demo transcript (workspace/tab layout, bindings)

-- 5. What the user sees: herdr workspace list (labels are project names) -- [ {"workspace_id": "w1", "label": "mf-website"}, {"workspace_id": "w2", "label": "soul-site"}, {"workspace_id": "w3", "label": "mf-website"} ] -- 6. Tabs inside the mf-website project workspace (one tab per task) -- [ {"tab_id": "w1:t2", "label": "fm-task-checkout-fix"}, {"tab_id": "w1:t3", "label": "fm-task-seo-audit"} ] -- 7. Durable exact-id bindings persisted in state/ (path-hash keyed) -- # .herdr-project-space-mf-website-701de185cd3a56a5 -> workspace_id=w1 # .herdr-project-space-mf-website-ca87a1dddd2e2d44 -> workspace_id=w3 (same basename, different dir) # .herdr-project-space-soul-site-64e586b861f8e22c -> workspace_id=w2

== Herdr per-project workspace demo (real herdr CLI, isolated lab session fm-lab-evidence-ps) ==
herdr version: herdr 0.7.5

-- 1. Opt-in config parsing: empty config/herdr-project-spaces enables the layout --
enabled: yes (empty file opts in)
off => disabled (correct)
absent => disabled (correct)

-- 2. Two tasks for project 'mf-website' land in ONE project workspace --
task 1 workspace: w4
task 2 workspace: w4 (same id => reused, no duplicate workspace)

-- 3. A different project 'soul-site' gets its own workspace --
soul-site workspace: w5

-- 4. Same basename, different directory => independent workspace --
beta/mf-website workspace: w6 (distinct from w4)

-- 5. What the user sees: herdr workspace list (labels are project names) --
[
  {
    "workspace_id": "w1",
    "label": "mf-website"
  },
  {
    "workspace_id": "w2",
    "label": "soul-site"
  },
  {
    "workspace_id": "w3",
    "label": "mf-website"
  },
  {
    "workspace_id": "w4",
    "label": "mf-website"
  },
  {
    "workspace_id": "w5",
    "label": "soul-site"
  },
  {
    "workspace_id": "w6",
    "label": "mf-website"
  }
]

-- 6. Tabs inside the mf-website project workspace (one tab per task) --
[
  {
    "tab_id": "w4:t2",
    "label": "fm-task-checkout-fix"
  },
  {
    "tab_id": "w4:t3",
    "label": "fm-task-seo-audit"
  }
]

-- 7. Durable exact-id bindings persisted in state/ (path-hash keyed) --
# .herdr-project-space-mf-website-7c841f099332d34f
version=1
project_dir=/var/folders/qb/mzs68z4x5k72692d0yfx2knc0000gn/T//fm-ps-demo.QXJobs/alpha/mf-website
project_name=mf-website
session=fm-lab-evidence-ps
workspace_id=w4

# .herdr-project-space-mf-website-98198907fdb835d0
version=1
project_dir=/var/folders/qb/mzs68z4x5k72692d0yfx2knc0000gn/T//fm-ps-demo.QXJobs/beta/mf-website
project_name=mf-website
session=fm-lab-evidence-ps
workspace_id=w6

# .herdr-project-space-soul-site-741b9fa2e31b712e
version=1
project_dir=/var/folders/qb/mzs68z4x5k72692d0yfx2knc0000gn/T//fm-ps-demo.QXJobs/alpha/soul-site
project_name=soul-site
session=fm-lab-evidence-ps
workspace_id=w5
Evidence: Real-Herdr e2e transcript (25 ok, incl. new project-grouping checks)
ok - real Herdr lab: an opted-out spawn retains the Stage 1 Herdr command sequence with zero ordering calls
ok - real Herdr lab: a home that configured nothing falls back flat on below-floor herdr 0.7.5 with one naming warning
ok - real Herdr lab: every projected create, task-tab create, seeded prune, and move preserves active workspace and tab
warning: herdr presentation cleanup could not verify the exact pane; refusing focus-unsafe pane close
ok - real Herdr lab: active seeded-tab pruning refuses the exact pane and preserves exact focus
ok - real Herdr lab: bounded lock contention warns and falls back flat without projection or focus drift
ok - real Herdr lab: concurrent primary workers form one stable contiguous block without active workspace/tab drift
ok - real Herdr lab: forced workspace.move failure leaves a successful worker in default order with a warning and no cleanup
ok - real Herdr lab: concurrent post-create abort cleanup stays serialized with exact focus restoration
ok - real Herdr lab: Treehouse commands and metadata shape are byte-identical except for endpoint IDs and spawn incarnation
ok - real Herdr lab: exact task-pane close removes the projected workspace with no unrestored wrong-focus interval
ok - real Herdr lab: concurrent projected cleanup is serialized and leaves active workspace/tab unchanged
ok - real Herdr lab: three repeated concurrent create/order/cleanup waves have zero active workspace or tab drift
ok - real Herdr lab: the primary presentation setting inherits into real secondmate homes
ok - real Herdr lab: primary and two secondmate homes each own a top-level contiguous child block
ok - real Herdr lab: concurrent primary/A/B spawns preserve parent order and exact focus
ok - real Herdr lab: session lock contention from a secondmate home falls back flat with no journal
ok - real Herdr lab: Hi Bit and Wheelhouse-style same-identity restarts reclaim one nested space with exact focus and idempotence
ok - real Herdr lab: a pending journal runs the recovery guard under presentation-off project grouping - live refuses, agent-free reclaims exactly
ok - real Herdr lab: fresh grouped spawns share one exact project workspace and skip the presentation projection
ok - real Herdr lab: secondmate restart binding and reclaim stay isolated to the exact child home and parent
ok - real Herdr lab: concurrent cross-home recoveries replace exact husks under one session lock with no focus drift
ok - real Herdr lab: legacy projection labels and flat secondmate tabs are left unmigrated
ok - real Herdr lab: multi-home exact-pane teardowns restore captain focus without workspace close authority
warning: no exact herdr presentation token match for missing1; leaving any stale space untouched and spawning flat
warning: no exact herdr presentation token match for renamed1; leaving any stale space untouched and spawning flat
warning: 2 exact herdr presentation token matches for duplicate1 are quarantined; inspecting only for duplicate-agent risk
warning: quarantined herdr presentation for duplicate1 is dead or agent-free; exact bound reclaim may proceed, otherwise spawning flat
warning: 2 exact herdr presentation token matches for duplicate1 are quarantined; inspecting only for duplicate-agent risk
error: quarantined herdr presentation for duplicate1 has a live pane; refusing duplicate launch
ok - real Herdr lab: missing, renamed, and duplicate tokens trigger zero destructive or adoptive calls, and live duplicate risk refuses launch
ok - real Herdr lab validation completed on Herdr 0.7.5 with the default-session tripwire intact
Evidence: Unit-suite project-space pass lines
ok - herdr project spaces: absence preserves existing placement while empty/on opt in
ok - herdr project spaces: one project reuses one exact workspace while another project gets a separate workspace
ok - herdr project spaces: same-basename projects keep stable independent path-keyed bindings
ok - herdr project spaces: dead and renamed-away exact bindings create and record fresh workspaces
ok - herdr project spaces: duplicate labels are never discovery or adoption authority
ok - herdr presentation: a home that set nothing gets the projection by default at or above the floor
ok - herdr presentation focus: projected seeded pruning refuses the active tab

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 2 infos
  • ℹ️ bin/backends/herdr.sh:1705 - fm_backend_herdr_project_binding_snapshot assigns binding_version/binding_dir/binding_name/binding_session/binding_workspace via read without declaring them local, so they leak into the sourcing shell (fm-spawn.sh). No collision exists today, but a future variable with the same name in the 3000-line sourcing script would be silently clobbered. Add them to the function's local declaration.
  • ℹ️ bin/backends/herdr.sh:1747 - If a spawn fails between fresh project-workspace creation (binding already published atomically) and task-tab creation, the workspace's auto-seeded default tab is never pruned: later spawns reuse the binding with an empty seeded-tab id, and the prune contract is same-call-only. A stray empty tab persists in the project workspace. Cosmetic-only residue in an opt-in visual layout, matching existing failure-mode parity of the flat container path.
✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-backend-herdr.test.sh (fake-CLI unit suite, 180 ok incl. the 5 new project-space tests: opt-in parsing, reuse/separation, same-basename bindings, stale-binding refresh, no label adoption)
  • bash tests/fm-secondmate-harness.test.sh (42 ok; new herdr-project-spaces config propagates to secondmate homes and mirrors absence)
  • bash tests/fm-backend-herdr-presentation-e2e.test.sh (real herdr 0.7.5 isolated lab, 25 ok / 0 failures, incl. both new checks: fresh grouped spawns share one exact project workspace and skip the presentation projection; pending presentation journal still runs the duplicate-launch recovery guard under project grouping; default-session tripwire intact)
  • Manual live-Herdr demo via the guarded fm-herdr-lab.sh helper driving production fm_backend_herdr_project_workspace_ensure/fm_backend_herdr_create_task: two mf-website tasks land as two tabs in one workspace labeled mf-website; soul-site gets a separate workspace; a same-basename project in another directory gets an independent workspace; durable exact-id binding records written under state/ (transcript saved as evidence)
✅ **Document** - passed

✅ No issues found.

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

✅ No issues found.

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