Read the runtime adapter before following this workflow. Its runtime mappings apply to all referenced playbooks and scripts.
Pstack pair
Two agents, one Herdr or Rex session, one repository. The master owns judgment and the sidekick owns the working tree. Every message between them is a file in the pair store plus a one-line prompt that names the file. The terminal is never the record, because agents on the alternate screen leave no scrollback.
Preconditions
- Inside Herdr (
test "${HERDR_ENV:-}" = 1) or Rex (REX_BLOCKset). Outside both, say so and stop. - Load the multiplexer's skill for pane and agent control: Herdr's
~/.agents/skills/herdr/SKILL.md(orherdr --skillwhen that file is absent), or Rex'srex skill show. Its safety rules apply throughout. In Rex, read Running in Rex. herdrorrex, and Python 3.11 or later (python3), on PATH.scripts/pair.sh helplists every store and channel operation; use it instead of hand-builtherdrorrexcalls for the pair.- Start the master in auto mode. Devin defaults to bypass permissions with
--permission-mode dangerous. Other kinds inherit the master's mode.pair.sh permissionprints the detected master mode. Use--permission <mode>or native permission flags after--to override the default;--permission autoselects Devin smart mode. Kinds without a translation take native flags after--.spawnalso skips each kind's folder-trust prompt, which would hang an unattended agent in a folder it has not seen: pi gets--approve, Devin--respect-workspace-trust false. A trust flag after--overrides it. Claude and Codex instead ask to trust a folder A spawned Claude also starts with no MCP servers (--strict-mcp-configand an empty configuration), since each server is a process per session; a--mcp-configafter--keeps yours. they have not seen; that decision is the human's, sospawndeclines the dialog and stops with what to do: open that agent in the repo once and trust it.
Roles
| Master | Sidekick | |
|---|---|---|
| Owns | plan, briefs, review, decisions, the human | the working tree, tests, diagnosis, evidence |
| Runs pstack as | poteto-mode framing, how, architect, review skills | the playbook each brief names, with its todolist |
| Writes | the pair store only | files inside the brief's Scope, plus its progress log and report |
| Speaks through | briefs, steers, reviews, standing orders, gates | progress lines, reports |
Enter your role. Master: references/master.md. Sidekick: references/sidekick.md.
Pair store
${XDG_STATE_HOME:-$HOME/.local/state}/pstack/pair/<slug>/, created by
pair.sh init. One writer per file.
| Path | Writer | Content |
|---|---|---|
pair.json | master via pair.sh | agent names, pane ids, cwd, git root, the sidekick's fallback and failovers, pane placement (split or tab) |
standing-orders.md | master | numbered constraints pasted by path into every brief |
plans/NNN-<slug>.md | master | one plan round, from the plan template |
briefs/NNN-<slug>.md | master | one unit, from the brief template |
progress/NNN-<slug>.md | sidekick via pair.sh progress | one timestamped line per completed step or change of approach; a check-in shows only the lines the master has not seen |
steers/NNN-<slug>-s<k>.md | master | mid-brief direction k for unit NNN, from the steer template; supersedes: names the objected steer it answers |
reports/NNN-<slug>-s<k>.md | sidekick | objection to steer k, from the steer response template |
reports/NNN-<slug>.md | sidekick | evidence for brief NNN, from the report template, or the agree/object response to plan NNN, from the plan response template |
reviews/NNN-<slug>.md | master | verdict on report NNN, from the review template; agreed on a plan unlocks its briefs |
scratch/<id> | master via pair.sh scratch | link to a throwaway worktree at a unit's commit for rerunning its checks, kept outside the repo under ${XDG_STATE_HOME:-~/.local/state}/pstack/scratch/ (PSTACK_SCRATCH_ROOT moves it), which Claude and Codex must trust once per host (kitchen.py doctor checks); removed after the review |
gates.md | master | open questions for the human |
decisions.tsv | master via pair.sh log | the show-me-your-work trail |
status.md | pair.sh status | derived table; never hand-edited |
queue | master via pair.sh queue and dispatch | the next brief's path, one slot |
steps/NNN-<slug>.tsv | sidekick via pair.sh step | one row per committed step: number, commit, time, notes it resolves, summary |
notes/NNN-<slug>-n<k>.md | master via pair.sh new-note and note | review of the unit's steps through step k, from the note template: blocking items and follow-ups |
followups.md | master via pair.sh note | follow-ups from every note, for a later brief or an issue |
session-handoff.md | master | compact recovery and next-task context, read at bootstrap |
sessions/ | pair.sh spawn / rotate | prior ready reports; retained session exports and timing evidence |
events.tsv | every pair.sh command | one row per message, report, wake, and log entry, read by pair.sh metrics |
NNN is a zero-padded sequence shared by plan or brief, report, and review, so
the files for one unit sort together.
Messages
| Prompt text | Direction | Sender's helper | Receiver's move |
|---|---|---|---|
| bootstrap (names the skill and the store) | master to sidekick | pair.sh spawn | bootstrap steps, reports/000-ready.md, reply READY |
pstack-pair PLAN <plan-path> | master to sidekick | pair.sh discuss | ground the plan in the code, write an agree or object response, end the turn |
pstack-pair BRIEF <brief-path> | master to sidekick | pair.sh dispatch | run the brief, write its report, run pair.sh finish, then end the turn with the REPORT line |
pstack-pair BRIEF <brief-path>, queued | master to a working sidekick | pair.sh queue | the master dispatches it after a fresh session; pair.sh next refuses |
pstack-pair STEER <steer-path> | master to a working sidekick, or to one paused on an objection | pair.sh steer | read it on arrival, between tool calls; agree and continue, or object with evidence and end the turn |
pstack-pair REPORT <report-path> | sidekick to master | pair.sh finish (or notify) | read the report, review |
pstack-pair STOP <store> | master to sidekick | pair.sh stop | pause safely, write a stop report |
The master's dispatch and wait return when the sidekick's reply lands:
the report written since the message, or a blocked sidekick. A settle from
the multiplexer alone is not trusted: a Devin sidekick shows idle as a prompt arrives
and between steps, and in a narrow pane can show done for a whole run, so a
settle is checked against the pane's "esc to interrupt" line; a settle with
no report that holds for a minute ends the wait with report: missing.
They also return when the check-in interval passes with the sidekick still
working, printing a check-in digest instead of a report path. wait also
returns the moment a dispatched brief's report lands, even when the sidekick
has already taken the queued brief; running: then names the brief it is on.
notify prompts the master only when the master is idle, so a waiting master
is never interrupted. A message you receive after compaction still names this
skill and the file to read, so reload the skill and continue from that file.
Plan first
Implementation starts only from a plan both agents agreed to. The master
drives the implementation, architecture, and system design: it drafts
plans/NNN-<slug>.md with the chosen design, the alternatives it weighed, and
their pros and cons, then sends it with pair.sh discuss. The sidekick
brainstorms against those trade-offs, checks every step against the code, and
answers agree or object with evidence.
The master revises in a new plan round or answers the objections, until a
response says agree and the master writes a review with verdict agreed.
pair.sh dispatch refuses a feature, bug-fix, refactoring, perf-issue, or
pstack-tdd brief whose plan: line does not name an agreed plan. Read-only
and forensic briefs need no plan; a bug's diagnosis brief runs before its plan,
so the plan rests on runtime evidence. Three rounds without agreement become a
gate for the human, with both positions in gates.md.
Check-ins and steers
The master drives the way a pairing driver does: it looks up on a cadence, not continuously, and corrects direction, not keystrokes.
- The sidekick appends one line per completed todolist step and per change
of approach to
progress/NNN-<slug>.mdthroughpair.sh progress. Before it writes outside Scope or departs from the plan, it writes the line first, so a check-in can catch it. dispatchandwaitreturn at the check-in interval (--every MIN, nine minutes by default) when the sidekick is still working, with a digest: elapsed against the timebox, the progress lines not yet seen, files touched and which fall outside Scope, commits since dispatch, and steer counts. When the sidekick switched branch or rebased, so HEAD no longer descends from the dispatch head, only the commits made since dispatch count as touched. That digest is all the master reads between reports; it reads the pane only when the digest marks the logSTALE.- A steer is a mid-brief correction from the master:
pair.sh new-steer, thenpair.sh steer. It lands in the sidekick's input queue while it works; the harness hands it over between tool calls and the sidekick acts on it there, not at the end of the step. A Devin sidekick gets it as aSTEER:line from its nextpair.sh step,notes, orprogressinstead, since the Enter that delivers a queued message to Devin cancels its running command.--interruptcancels the running tool call first, for a direction that cannot wait for it. Steers carry direction: an approach the agreed plan did not choose, scope drift, a step to skip, a Forbidden line about to be crossed, or news from the human. Anything the review can catch at the end waits for the review. - The sidekick agrees or objects, as with a plan. Agree: apply, acknowledge
in the progress log, continue. Object: write the steer response with
evidence and an alternative, notify, end the turn. The master answers with
a steer that supersedes the objected one, revised or withdrawn, and
pair.sh steerresumes the sidekick. Two rounds per steer; the decision stays with the master, and a standing disagreement goes to a plan round. - Two fresh steers per brief. A third means the brief was wrong: stop the unit and re-brief.
Task sessions of every kind
A sidekick of any kind (Claude, Codex, Devin, pi, Prime, Muse) uses a fresh conversation for each completed brief. Before
spawning, queueing, rotating, or cleaning its storage, read
the Devin session lifecycle for Devin or Pi,
or the Prime adapter for Prime. Partial and blocked work stays in
its session; a completed brief leaves the queue for the master to dispatch.
dispatch and discuss rotate the sidekick themselves first (rotated: in
their output); pair.sh rotate stays for the master to run by hand. The
pstack store persists across sessions.
Muse Code
Use --kind muse for any role. Read the Muse adapter
for its permission, trust, preflight and exit handling. Muse keeps one
conversation per pane, so it is not rotated per brief.
Prime Agent
Use --kind prime-agent for the sidekick. Read the Prime
Agent adapter for native model arguments, preflight,
steering, exact daemon-session cleanup, and provider failover. Prime uses the
shared store protocol and requires rotation after each completed brief.
Pi sidekick and fallback
A pi sidekick reports its state to Herdr (and, with the pstack extension, to Rex) through its own events and queues a
steer without cancelling the running command. When its model sits behind a
route that can fail, spawn it with --fallback devin: spawn checks the
model first, and pair.sh failover swaps in the fallback mid-run. Read
the Pi sidekick reference before spawning one.
Keeping the sidekick busy
The sidekick is the critical path: the master turns a report around in minutes, while a unit takes tens of minutes to hours. Three habits keep the sidekick from waiting on the master.
- Queue the next brief. While a unit runs, the master drafts the next one
and holds it with
pair.sh queue. After adonereport the sidekick starts a fresh session and the master dispatches that brief;pair.sh nextrefuses. The queue has one slot and holds only a unit that does not hinge on the running unit's review, so arevisebecomes a follow-up brief, not a rework of the queued one. - Review at the commit. A brief that writes says
commit: yes, and the report'shead:names that commit. The master reads it withgit showwhile the sidekick works on the next unit, and reruns checks inpair.sh scratch <store> NNN-<slug>-review --at <head>, never in the shared tree. - Amend instead of re-planning. An objection the master adopts becomes a
numbered amendment in the
agreedreview. A new plan round is for a changed design, not a narrowed step.
pair.sh metrics <store> reads events.tsv and shows where the time went:
sidekick busy and idle, master wakes, review latency, verdicts. Sidekick idle
time is the number to drive down.
Draft-PR review
A brief is a draft PR that the master reads as it grows, not a diff it meets at the end. Review that arrives after hours of work returns long finding lists, and each fix round widens the change and reruns every check; review of each step catches the same blockers while they are one commit old.
- The brief's Steps are commit-sized, twenty to forty-five minutes each. The
sidekick commits each step, records it with
pair.sh step, and goes straight on. It never waits for review. - The master's
waitreturnssteps:when a step lands that no note covers. The master reads that range, writes a note withpair.sh new-note, and publishes it withpair.sh note, while the sidekick works on. - A note, like a review, splits its findings. Blocking is only what would
make the unit wrong to land: wrong behaviour, a missed consumer of a
changed value, data or schema safety, a rollback hazard, a broken
contract, a test that does not test its claim. Everything else is a
follow-up:
notecopies those tofollowups.md, and they never widen the unit or cause a revise. - At each step boundary the sidekick runs
pair.sh notes, fixes the open blocking items first as fixup commits, and records them withpair.sh step --resolves.finishrefuses adonereport while one is open. A blocking note on a unit already done is fixed by the next brief, whose step names it qualified:--resolves 020-n1. - A problem that would waste the next steps, a wrong design rather than a wrong line, still goes out at once as a steer.
- Each step runs the checks it names, and a brief's Verify runs the targeted tests plus the landing gate's fast checks. The full test battery runs once: in the landing brief, or, where the gate is slow, in the issue's last implementation unit.
- The final review reads only the report's
review-delta, the diff since the last note, and accepts unless it finds a new blocking item.
A brief without Steps runs as before, with one review at the end.
Pausing
pair.sh pause <store> --reason <why> stops the work at the next safe point,
for a skill upgrade or anything else that needs all agents quiet. Anyone may
run it, including the human or another session.
- The sidekick sees
PAUSE:in the output of its nextpair.sh step,notes, orprogress. It commits what is verified, writes the report aspartialwith where it stopped and the next step, runspair.sh finish, and ends the turn.finishhands out no queued brief while paused. - The master's
dispatch,queue, anddiscussrefuse while paused, and so doconsultandanswerwhere the skill has them. A wait printspaused:. The master finishes the step it is in, records where the work stands and the exact next step ingates.md, and ends its turn. pair.sh resume <store>lifts the pause. The master re-reads the skill if it changed, then re-dispatches the paused brief; the sidekick's step log says where to continue.
pair.sh stop is the immediate variant, for a sidekick that must stop
mid-step.
Shared rules
- Facts about panes, agents, and states come from the multiplexer through
pair.sh, never from memory or sidebar order. - Approval dialogs belong to the human unless the standing orders delegate a class of them to the master. Neither agent answers the other's dialogs.
- Both agents share one working tree. The sidekick writes it. The master
reads it and its history at any time and edits nothing under the
repository; it runs builds and tests only in a scratch worktree from
pair.sh scratch, so they never race the sidekick's edits. - Close only panes you created, and only when the human asked.