Repository navigation
✨ feat(skills): add public Herdr fleet workflow #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 3 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
2d34528
✨ feat(skills): add public Herdr fleet workflow
AojdevStudio 4ab08d1
Merge remote-tracking branch 'origin/main' into pi/add-herdr-fleet-skill
AojdevStudio 30bb886
🐛 fix(skills): harden Herdr fleet orchestration
AojdevStudio eb7300d
fix(herdr-fleet): harden roster orchestration
AojdevStudio f201764
Merge remote-tracking branch 'origin/main' into pi/add-herdr-fleet-skill
AojdevStudio 47fcb4f
fix(herdr-fleet): serialize watcher startup
AojdevStudio 358d74a
fix(herdr-fleet): bind watcher process ownership
AojdevStudio 5e2bd0b
ci: run verification with Bun
AojdevStudio 7415ffd
fix(herdr-fleet): close final review gaps
AojdevStudio 7818785
fix(herdr-fleet): bind release readiness state
AojdevStudio 78b09ec
fix(herdr-fleet): clarify skill trigger
AojdevStudio cce85d4
fix(herdr-fleet): close final stream edge cases
AojdevStudio c520924
fix(herdr-fleet): skip blank watcher records
AojdevStudio File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| --- | ||
| name: herdr-fleet | ||
| description: Launch a Herdr fleet, start implementer and reviewer panes, rebuild surviving worker panes, or control a project-scoped issue and pull-request queue. Requires HERDR_ENV=1. | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| --- | ||
|
|
||
| # Herdr Fleet | ||
|
|
||
| Act as the fleet's control pane. The principal talks to this pane; the control pane dispatches work, consumes review verdicts, and reports progress. Workers implement or review. Only the control pane operates Herdr. Merge authority is established explicitly during launch and defaults to report-only. | ||
|
|
||
| Choose the active branch: | ||
|
|
||
| - **Cold start** — no fleet exists or panes were lost: follow [launch-fleet.md](launch-fleet.md), then enter the live loop. | ||
| - **Live loop** — the fleet exists: follow [protocols.md](protocols.md). | ||
|
|
||
| ## Standing rules | ||
|
|
||
| - Derive a collision-checked control label from the current repository as `<project-key>-control-pane`; never copy a label from another project. [Launch Step 2](launch-fleet.md#step-2-derive-a-unique-project-key) defines the derivation. | ||
| - Scope every inventory, watcher, split, discovery, and event action to both the injected workspace and tab IDs. | ||
| - Reconcile surviving project-owned panes before creating anything. Reuse healthy panes, retire only proven stale owned panes, and create only missing roles. | ||
| - Reserve Herdr remote control for the control pane. Broadcast this boundary before assigning work. | ||
| - Default to report-only. Merge only when explicit launch-time authorization covers the repository, base branch, and strategy, and a fresh verdict names the current head with green required checks. If policy or tooling requires approval, ask the principal directly; routing it through a worker is permission laundering. | ||
| - Delegate parallel work and retain only findings in the control context. Read worker transcripts with `herdr pane read`; dispatch with `herdr pane run`. | ||
| - Report events in proportion to their importance. Use the environment's notification mechanism for unattended merges, blockers, and decisions. Ask the principal only for genuine product, scope, cost, or irreversible decisions, with a recommendation first. | ||
| - Resolve reversible, PR-gated worker questions in the control pane. Verify every claim that the principal "approved" a choice. | ||
|
|
||
| ## Default roster | ||
|
|
||
| Adapt commands to installed agents and the principal's preference; preserve the role split. | ||
|
|
||
| | Label suffix | Default command | Role | | ||
| |---|---|---| | ||
| | `claude-impl` | `claude --model sonnet` | implementer | | ||
| | `codex-impl` | `codex` | implementer | | ||
| | `pi-impl` | `pi` | implementer | | ||
| | `codex-review` | `codex` | pull-request reviewer | | ||
| | `claude-review` | `claude --model opus` | pull-request reviewer | | ||
|
|
||
| Prefix each label with the project key, for example `${PROJECT_KEY}-pi-impl`. Place the control pane left at full height, implementers in the middle column, and reviewers in the right column. Reviewers use the repository's review-queue skill when one exists; otherwise use the available code-review workflow. | ||
|
|
||
| ## Operational gotchas | ||
|
|
||
| 1. **Dialogs can swallow dispatches.** A message sent while a worker has a blocking permission or question dialog can become the dialog's answer. After dispatching to a blocked pane, read the transcript and confirm the message became a prompt. Audit any resulting approval claim. | ||
| 2. **Hooks parse dispatch command text.** Repository hooks may regex-match the shell command containing `herdr pane run`, including words inside the worker message. When a hook trips, use its documented satisfaction path in separate commands rather than bypass environment variables. | ||
| 3. **Capacity failures need bounded failover.** Retry a model-capacity error once. On a second failure, hand the task to another compatible worker and identify any orphaned review claim that must be superseded. | ||
| 4. **Review claims race.** Two reviewers can claim after checking the same head. Duplicate reviews are acceptable, but only a review newer than the pull-request head is fresh. Explicitly override orphaned claims. | ||
| 5. **Verdicts expire on every head change.** Content pushes require full review. Mechanical base syncs may use a focused delta review, but that review must issue a new verdict naming the post-sync SHA; source-file resolutions receive semantic scrutiny. | ||
| 6. **Union merge drivers do not apply on GitHub.** A shared append-only file can make sibling pull requests dirty after every merge. Merge the base branch locally in each branch worktree, resolve with the repository's intended union behavior, and push. | ||
| 7. **Context wedges masquerade as work.** A full context and a large nested-agent swarm can leave a pane reporting `working` while its worktree is frozen. Prevent this with incremental commits, bounded delegation, and compaction near 75%. Confirm liveness through worktree modification times. Salvage verified work, retire the pane, and start fresh. | ||
| 8. **Long-lived sessions drift.** Recycle workers that accumulate very large contexts, use stale directories, repeat obsolete recaps, or leave dispatches unsubmitted. A fresh pane with a self-contained brief is safer. | ||
| 9. **Runner starvation resembles test failure.** Classify failures before rerunning. A local pass, the same failure on unrelated runs, and high concurrent load point to infrastructure. Repeated failures of the same class warrant a structural CI fix. | ||
| 10. **Workers exceed roles unless corrected.** Reviewers review; implementers implement; the control pane operates Herdr and applies the recorded merge policy. Watch queued intents and correct role drift before execution. | ||
| 11. **Background deliverables may duplicate or disappear.** Request missing output explicitly. Act only on delivered content and deduplicate retries. | ||
| 12. **Nested agents can finish files but fail to report.** If a worker waits on a silent child, inspect output modification times. When writes have stopped, tell the worker to verify disk state and finish inline. | ||
| 13. **Long dispatches may paste without submitting.** Confirm every dispatch changes the pane to `working`. If a paste placeholder remains at the prompt, submit it with an empty `herdr pane run <id> ""`, then verify again. | ||
| 14. **Repository policy is runtime input.** Before assigning work, derive lane labels, branch prefixes, provenance variables, hook requirements, issue-link rules, and worktree cleanup commands from the repository's checked-in instructions. Include only the applicable rules in each brief. | ||
| 15. **Context footer formats vary.** The scoped watcher recognizes forms such as `[79% ...]`, `90% context used`, and `9.3%/372k`. It re-arms compaction after usage drops below 60%; a permanent "already compacted" set would disable protection for the rest of the session. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,182 @@ | ||
| # Launch or rebuild the fleet | ||
|
|
||
| Build or reconcile a worker fleet inside the current Herdr tab. Complete the steps in order. A rebuild reuses a healthy partial fleet; it never creates a second copy. | ||
|
|
||
| ## Step 1: Verify the environment and scope | ||
|
|
||
| ```bash | ||
| test "${HERDR_ENV:-}" = 1 || exit 1 | ||
| test -n "${HERDR_WORKSPACE_ID:-}" || exit 1 | ||
| test -n "${HERDR_TAB_ID:-}" || exit 1 | ||
| test -n "${HERDR_PANE_ID:-}" || exit 1 | ||
| herdr pane | ||
| printf '%s\n' "$HERDR_WORKSPACE_ID" "$HERDR_TAB_ID" "$HERDR_PANE_ID" | ||
| ``` | ||
|
|
||
| The installed `herdr pane` output is the syntax authority. Confirm each roster command exists with `command -v`. Stop before mutation if the session IDs or required agents are unavailable. | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| `herdr pane list` can filter by workspace but not tab. Every inventory in this workflow must therefore filter returned pane records by both `workspace_id` and `tab_id`. Never treat a workspace-only result as the fleet. | ||
|
|
||
| ## Step 2: Derive a unique project key | ||
|
|
||
| Derive the base key from the current repository. Multiword names use each word's first letter; a single word uses its first three characters. | ||
|
|
||
| ```bash | ||
| REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" | ||
| project="$(basename "$REPO_ROOT")" | ||
| BASE_KEY="$(printf '%s' "$project" | tr '[:upper:]' '[:lower:]' | awk -F'[-_ ]+' '{s=""; for (i=1;i<=NF;i++) s=s substr($i,1,1); if (length(s)<2) s=substr($1,1,3); print s}')" | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| ALL_PANES_JSON="$(herdr pane list --workspace "$HERDR_WORKSPACE_ID")" | ||
| TAB_PANES_JSON="$(printf '%s' "$ALL_PANES_JSON" | TAB_ID="$HERDR_TAB_ID" WORKSPACE_ID="$HERDR_WORKSPACE_ID" bun -e ' | ||
| const payload = JSON.parse(await Bun.stdin.text()); | ||
| const panes = payload?.result?.panes ?? payload?.panes ?? payload?.result ?? payload; | ||
| const scoped = (Array.isArray(panes) ? panes : []).filter( | ||
| (pane) => pane.workspace_id === process.env.WORKSPACE_ID && pane.tab_id === process.env.TAB_ID, | ||
| ); | ||
| process.stdout.write(JSON.stringify(scoped)); | ||
| ')" | ||
| ``` | ||
|
|
||
| Detect an existing foreign use of the base key in this tab. Ownership requires both the key prefix and a pane `cwd`/`foreground_cwd` inside `$REPO_ROOT`; a matching label alone is not ownership. | ||
|
|
||
| ```bash | ||
| KEY_COLLISION="$(printf '%s' "$TAB_PANES_JSON" | BASE_KEY="$BASE_KEY" REPO_ROOT="$REPO_ROOT" bun -e ' | ||
| import path from "node:path"; | ||
| const panes = JSON.parse(await Bun.stdin.text()); | ||
| const root = path.resolve(process.env.REPO_ROOT); | ||
| const ownsPath = (value) => value && (path.resolve(value) === root || path.resolve(value).startsWith(`${root}${path.sep}`)); | ||
| const collision = panes.some((pane) => { | ||
| if (typeof pane.label !== "string" || !pane.label.startsWith(`${process.env.BASE_KEY}-`)) return false; | ||
| return !ownsPath(pane.foreground_cwd ?? pane.cwd); | ||
| }); | ||
| process.stdout.write(collision ? "yes" : "no"); | ||
| ')" | ||
| PROJECT_KEY="$BASE_KEY" | ||
| if [ "$KEY_COLLISION" = yes ]; then | ||
| suffix="$(printf '%s' "$REPO_ROOT" | git hash-object --stdin | cut -c1-4)" | ||
| PROJECT_KEY="${BASE_KEY}-${suffix}" | ||
| fi | ||
| CONTROL_LABEL="${PROJECT_KEY}-control-pane" | ||
| ``` | ||
|
|
||
| Examples: `billing-api` normally becomes `ba-control-pane`; `widget` becomes `wid-control-pane`. A colliding key gains a deterministic repository-derived suffix. Re-run the scoped inventory with the resolved prefix before continuing. | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| ## Step 3: Establish merge policy | ||
|
|
||
| Ask the principal which merge policy this fleet has. Default to report-only and record the answer in the control transcript. | ||
|
|
||
| | Policy | Behavior | | ||
| |---|---| | ||
| | `report-only` | Stop at a fresh `MERGE_READY` verdict and report it. This is the default. | | ||
| | `authorized-merge` | Merge only on explicitly allowed base branches, with the explicitly selected strategy, after a fresh verdict and green required checks. | | ||
|
|
||
| Authorization must name: | ||
|
|
||
| 1. allowed repository and base branch or branches; | ||
| 2. merge strategy; | ||
| 3. whether branch deletion is allowed. | ||
|
|
||
| Authorization does not bypass tool or repository permissions. If a merge command still requires approval, ask the principal directly. Never route it through a worker. | ||
|
|
||
| ## Step 4: Reconcile the current-tab fleet | ||
|
|
||
| Before renaming, splitting, or launching anything, inventory the resolved `${PROJECT_KEY}-` labels from `$TAB_PANES_JSON` and inspect each candidate with `herdr pane get`, `herdr pane process-info`, and a recent transcript read. | ||
|
|
||
| Classify each expected role: | ||
|
|
||
| - **Healthy owned pane:** project-prefixed label, current workspace and tab, repository cwd, and an expected agent in `idle`, `working`, `blocked`, or `done`. Reuse it. | ||
| - **Stale owned pane:** the same ownership evidence, but the process exited, returned to a plain shell, or is otherwise proven abandoned. Retire only this pane. | ||
| - **Foreign or ambiguous pane:** ownership evidence is missing or points outside the repository. Leave it untouched. | ||
| - **Duplicate healthy role:** stop and ask which pane to keep. Do not close or create another. | ||
| - **Missing role:** record it for Step 5. | ||
|
|
||
| Close a stale pane only after recording its ownership evidence and pane ID, then refresh and re-filter the current-tab inventory before any other mutation: | ||
|
|
||
| ```bash | ||
| herdr pane close <proven-stale-owned-pane-id> | ||
| ``` | ||
|
|
||
| If another healthy `${CONTROL_LABEL}` exists and is not the current pane, stop and direct the principal to that control pane. Replace it only when it is proven stale or the principal explicitly authorizes replacement. Then claim the current pane: | ||
|
|
||
| ```bash | ||
| herdr pane rename "$HERDR_PANE_ID" "$CONTROL_LABEL" | ||
| herdr pane layout --pane "$HERDR_PANE_ID" | ||
| ``` | ||
|
|
||
| Re-read the pane and confirm its `workspace_id` and `tab_id` match the injected IDs. Reconciliation is complete when every surviving pane has one role, every missing role is known, and no foreign pane was mutated. | ||
|
|
||
| ## Step 5: Create only missing roles | ||
|
|
||
| For the default five-worker roster, preserve the intended layout: control pane left at full height, implementers in the middle column, reviewers in the right column. | ||
|
|
||
| Use explicit source pane IDs from the current-tab inventory for every split. Before each split, verify the source pane still belongs to `$HERDR_WORKSPACE_ID` and `$HERDR_TAB_ID`. Always use `--no-focus`, read `result.pane.pane_id` from JSON, verify the returned pane's workspace and tab, then rename it `${PROJECT_KEY}-<role>`. Never predict pane IDs. | ||
|
|
||
| Create only roles marked missing in Step 4. A partial surviving fleet must remain partial-plus-new, never duplicated. | ||
|
|
||
| ## Step 6: Launch only new agents | ||
|
|
||
| Start each newly created worker with only its normal interactive command: | ||
|
|
||
| ```bash | ||
| herdr pane run <pane-id> "<agent-command>" | ||
| herdr wait agent-status <pane-id> --status idle --timeout 60000 | ||
| ``` | ||
|
|
||
| Wait for every new agent to reach `idle`. Reused healthy panes keep their sessions. Replace unavailable default commands only with principal-approved installed agents while preserving the implementer/reviewer split. | ||
|
|
||
| ## Step 7: Broadcast standing constraints | ||
|
|
||
| Send this to every new or reused worker before assigning work: | ||
|
|
||
| > STANDING CONSTRAINT: you are a worker pane. Remote control of this Herdr session is reserved for the control pane (`$CONTROL_LABEL`). Work only in your assigned role. Never run Herdr commands or merge pull requests. Never switch branches in the canonical checkout; use short-lived worktrees from the required remote base. Commit incrementally. Report results only in your own transcript. | ||
|
|
||
| For workers prone to nested delegation, add: | ||
|
|
||
| > Keep delegation bounded; do not spawn large parallel subagent swarms. The parent context is the scarce resource. | ||
|
|
||
| Read each pane transcript and verify delivery. Re-send when a pane was blocked by a dialog or retained an unsubmitted paste. | ||
|
|
||
| ## Step 8: Dispatch initial work | ||
|
|
||
| Inspect the repository's checked-in agent instructions, contribution guide, pull-request template, issue labels, open pull requests, and worktree conventions first. | ||
|
|
||
| - **Reviewers:** include the repository review workflow, claim mutex, fresh-head verdict requirement, peer reviewer label, and current open pull requests. | ||
| - **Implementers:** split ready issues by repository-derived ownership. Every brief includes existing-branch/PR detection, claim procedure, base branch, worktree and branch naming, verification commands, provenance/hook rules, and reporting requirements. | ||
| - **Thin queue:** assign open-PR advancement work or hold workers idle, then report the shortage. Do not invent work merely to occupy panes. | ||
|
|
||
| The step is complete when every active worker has one self-contained assignment or an explicit idle state and no item has conflicting owners. | ||
|
|
||
| ## Step 9: Arm the scoped watcher | ||
|
|
||
| Resolve `scripts/watch-fleet.mjs` relative to this skill directory. Start exactly one persistent watcher and record its PID and output paths: | ||
|
|
||
| ```bash | ||
| MONITOR_DIR="${TMPDIR:-/tmp}/herdr-fleet-${HERDR_WORKSPACE_ID//:/_}-${HERDR_TAB_ID//:/_}" | ||
| mkdir -p "$MONITOR_DIR" | ||
| bun <skill-directory>/scripts/watch-fleet.mjs --project-key "$PROJECT_KEY" \ | ||
| >"$MONITOR_DIR/events.ndjson" 2>"$MONITOR_DIR/errors.ndjson" & | ||
| FLEET_WATCHER_PID=$! | ||
| printf '%s\n' "$FLEET_WATCHER_PID" >"$MONITOR_DIR/pid" | ||
| ``` | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| The watcher: | ||
|
|
||
| - inventories with `pane list --workspace`, then rejects records outside the current `workspace_id`, `tab_id`, and project-label prefix; | ||
| - polls status every 20 seconds and emits only state changes as NDJSON; | ||
| - polls visible context every five minutes, recognizes the supported footer formats, requests `/compact` at 75%, defers blocked dialogs, and re-arms after usage falls to 60%; | ||
| - exits nonzero after three consecutive inventory failures and emits per-pane monitor failures after three context-read failures. | ||
|
|
||
| Treat watcher exit as a fleet blocker; restore socket health and restart one watcher rather than falling back to unscoped polling. Consume only this watcher's NDJSON. Do not consume global Herdr events. On `compaction_requested`, read that pane and verify `/compact` was accepted or visibly queued; apply Gotcha 13 from [SKILL.md](SKILL.md) when it remained as an unsubmitted paste. | ||
|
|
||
| For shutdown: | ||
|
|
||
| ```bash | ||
| kill "$(cat "$MONITOR_DIR/pid")" | ||
| wait "$(cat "$MONITOR_DIR/pid")" 2>/dev/null || true | ||
| rm -f "$MONITOR_DIR/pid" | ||
| ``` | ||
|
|
||
| Create bounded CI waits per pull request that handle success, failure, cancellation, and timeout. | ||
|
|
||
| ## Step 10: Report and enter the loop | ||
|
|
||
| Report merge policy, workspace/tab IDs, project key, pane IDs, reused/created/retired roles, assignments, watcher PID, substitutions, and queue shortages. Then follow [protocols.md](protocols.md). | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.