| name | relay |
| description | Generate and run a relay thread file in `relay-system/<date>/<slug>.md` — a turn-based, file-based review loop between two Claude Code agents (a Producer who builds, a Reviewer who critiques and proposes fixes the author applies) so a human stops copy-pasting output between two windows. Use this whenever the user wants to "set up a relay", run a "producer/reviewer" or "worker/reviewer" loop, have "two agents" review each other's work in a shared file, "hand off" between agents without pasting, or cut copy-paste and stray artifacts across AI sessions. Also use it to START a relay (scaffold the dated file) or to TAKE A TURN in an existing one (read the file, act only on your turn, append your block, flip the pointer). Trigger even if the user only describes two Claude windows shuttling text back and forth. |
Relay
Two agents, one file, no copy-paste.
A relay is a turn-based review loop carried entirely inside a single Markdown file that both agents can read and append. One agent builds (Producer), the other reviews and proposes (Reviewer). They never message each other — they read the file, take their turn, and flip a pointer. The human serializes turns by nudging the next window — or, with CLI-driven mode, a single Claude session calls agy or codex as a subprocess and the human's role collapses to zero. The file is the shared bus, the change-log, and the decision record all at once.
Three handoff modes, each a first-class option: manual nudge (portable, works with any tool), hands-free poll (all-Claude, two sessions), and CLI-driven (one session orchestrates, calls agy/Codex as subprocess). Manual is the default — the other two are opt-in.
This skill does two things: start a relay (scaffold the dated file from the template in the Appendix) and take a turn in an existing one. The Producer always starts it — the same step that creates the dated folder and file also writes turn 1. The Reviewer never creates the file; it only reads and appends.
Honest caveat — independence is only as good as the second agent. Two Claude sessions share a model and much of the same repo context, so they share blind spots: a same-model relay catches what a fresh pass with fresh framing catches, not what a truly independent reviewer would. For genuinely independent eyes, run a different model in the Reviewer window (e.g. Codex, or a different Claude tier). The file-based protocol is model-agnostic by design — any agent that can read and append the file can take a turn.
Composes with phase-qa for plan/spec-doc reviews. When the artifact is a planning, spec, or roadmap doc (not code), build the Reviewer's checklist with the phase-qa skill first, and seed the relay's Definition of Done from it. Critically, give completeness its own dedicated turn with an explicit omission-diff instruction — "list every claim in the source doc that has no equivalent in the target; report the gap, don't summarize." A reviewer asked the easy direction (find overclaims / errors) will satisfice and miss silent drops — the classic failure when one doc is a compression or merge of another. The relay is the transport; phase-qa owns the rubric — keep them composed, not coupled.
When to use
- The user says "set up a relay", "start a relay", "run a producer/reviewer loop", "two-agent review", "have one agent review the other".
- The user is shuttling output between two Claude windows by hand and wants to stop.
- A piece of work needs an independent review pass and the user has a second Claude Code session available in the same repo.
- The user asks to take the next turn / continue a relay that already exists.
The loop
Producer (build + request) → Reviewer (review + propose + verdict)
↑ │
└────── Producer (decide + implement) ←──┘
One round = a Producer turn + a Reviewer turn. The relay ends when the Reviewer's verdict is Approved, or escalates to the human at the max round if it is still not Approved.
File location
relay-system/<YYYY-MM-DD>/<slug>.md
- One folder per day; multiple relays per day live side by side, each with its own slug.
- Slug = short, lowercase, hyphenated topic — derive it from the artifact filename when obvious (
detect_abuse.py → detect-abuse), otherwise ask the user for a 2–4 word topic.
- If today's target path already exists, derive a unique sibling (
<slug>-2, <slug>-3, ...) instead of overwriting the earlier relay.
relay-system/ is local working scratch, not the artifact. Whether to track or .gitignore it is the operator's call: gitignore it to keep history quiet (recommended for public repos), or track it in a private repo if you want the relay thread in history. Either way, both agents always read it on disk in the same worktree — only the artifact must stay git-tracked, since ground rule 8's git diff handoff runs against the artifact, not this log.
Mode 1 — Start a relay (Producer only)
- Gather three things (pull from context first; ask only what's missing): the artifact under review (a local path — or a PR, in which case check the branch out locally first, since the direct-edit and
git diff mechanics assume the artifact is in the shared working tree), a one-line Definition of Done, and a slug.
- Get today's date and create
relay-system/<date>/ if it doesn't exist (this also creates relay-system/ on first ever use).
- If
relay-system/<date>/<slug>.md already exists, pick the next unused -2, -3, ... suffix instead of overwriting it.
- Write
relay-system/<date>/<slug>.md using the template in the Appendix below. Fill <TITLE>, the Setup fields, and Started. Leave NEXT: Producer, STATUS: Open, ROUND: 1 / 5.
- Take Round 1 immediately (Mode 2) — you're the Producer and you have the request. Folder, file, and turn 1 are all one step.
- Commit, then tell the user the path and to carry it to window B with: "take the Reviewer turn on
<path>."
If the user asks to take a turn but the file doesn't exist yet, you're in this mode — scaffold first.
Mode 2 — Take a turn
- Read the whole file. Setup, ground rules, and every prior turn.
- Check it's your turn. The user tells you your role ("act as the Reviewer"). If
NEXT ≠ your role, reply Not my turn — NEXT is <role>. and stop. Do not write anything.
- Do your role's work:
- Producer: build or update the artifact, then write your block. On later rounds, decide every Reviewer proposal with the operator — implement, modify, or decline — and log each disposition before adding new work.
- Reviewer: review against the Definition of Done. Do not edit the artifact — it isn't yours to change. Write each issue as a graded finding, attaching a concrete suggested fix wherever you can (so the Producer can apply it in one step). Set a verdict.
- Append your block at the bottom, directly above the marker line. Never edit earlier turns.
- Update the header: flip
NEXT; bump ROUND when a Producer opens a new cycle; set STATUS (Approved ends the relay; Escalated if the max ROUND ends without Approved).
- Commit your turn:
relay(<slug>): <role> r<N>, then fill the hash into your block's Commit: line. This is what lets the operator git diff exactly which proposals the Producer implemented — the safety net behind every applied change. A Reviewer turn never changes the artifact: if the relay log is gitignored (the common case) it writes Commit: none (comments only); if the log is tracked, the Reviewer still commits the log (rule 9 — no uncommitted state across a handoff) and records that hash. The Producer's turn carries the actual artifact diff — or Commit: none (comments only) if it too touched no tracked files.
- Report to the human using the format in Reporting to the human below — TLDR first, sorted findings last, then the hand-off. The human nudges; they never paste.
Turn block formats
Append exactly one of these per turn.
Reviewer:
### Round N · Reviewer · <timestamp>
**Verdict:** Approved | Changes requested | Blocked
**Basis:** behaviorally proven (<what I ran / observed>) | textual only (read, not run) | N/A — non-executable artifact
**Prior fixes:** (when re-reviewing) <ref> — behaviorally proven | textually fixed | unverified
**Findings & proposals:** (I propose; I do not edit the artifact)
- [Blocker] <finding @ file:line> — Proposed fix: <concrete suggested edit, or "author's call">
- [Should] <finding> — Proposed fix: <…>
- [Nit] <finding> — Proposed fix: <…>
- [Pass] <thing I checked and found sound — evidence it's checked, not assumed>
(or "none — approved as-is")
**Answers:** (respond to the Producer's "Re-review this" / open questions, point by point — or "none asked")
- <their question> → <direct answer>
**Commit:** <log hash if this log is tracked, else "none (comments only)"> — the Reviewer never edits the artifact
Basis is mandatory on every verdict. An Approved can never silently mean "I read the diff and it looked fine." If correctness rides on runtime behavior and nothing was run, the strongest honest verdict is textual only — a flagged, weaker approval the operator sees. Prior fixes: is where the Reviewer says whether the Producer's claimed fixes are textually fixed (the code now reads correctly) or behaviorally proven (confirmed to actually run / pass) — the distinction that stops doc approval from outrunning implementation reality.
Verdict semantics. Changes requested and Blocked keep the relay open for a Producer turn to dispose of the proposals; Approved closes it. So a Reviewer that wants its proposals actioned in-thread must set Changes requested, not Approved — any [Nit] left on an Approved verdict is the author's discretion, handled out-of-band after the relay closes.
Producer (rounds 2+):
### Round N · Producer · <timestamp>
**Decisions on proposals:** (operator-approved)
- [Blocker] <quote/ref> — Implemented → <what I changed @ file:line> · Proof: <cmd→result | "textual only" | "none"> | Modified → <what & why> · Proof: <…> | Declined → <one-line rationale>
- [Should] <quote/ref> — Implemented | Modified | Declined + why · Proof: <…>
**Did:** <further changes>
**Verification:** ran <cmd> → <result> · skipped <X> (why) · impossible <Y> (why) — or "N/A — non-executable artifact"
**Re-review this:** <what changed / where to look>
**Commit:** <hash or "none (comments only)">
(The Round 1 Producer block ships pre-stubbed in the template.)
The Producer's Verification: line is mandatory and one line. skipped and impossible are first-class — "tests pass" with no command is theater, but "skipped the race test (no harness yet)" is honest and reviewable, and an omitted line is a visible blank. Per-finding Proof: says how each implemented fix was confirmed. The contract scales to the artifact: a pure doc or design answers N/A — non-executable artifact in one token and the overhead disappears exactly where the relay already excels.
Guardrails
- Never act out of turn, and never edit a prior turn. The header pointer and the marker are load-bearing — respect them. (The one exception: immediately after committing, you may fill in your own just-written turn's
Commit: line with the hash. Nothing else in a prior block is ever touched.)
- Only one window acts at a time — the pointer is honor-system, not a lock. Both windows share one working tree. The human serializes by nudging one window at a time; never start a turn while the other window may still be mid-edit, or the shared file and tree can be clobbered.
- Clean tree at every handoff. Before you flip
NEXT, commit (or stash) all your changes — never hand off with uncommitted edits sitting in the working tree. A dirty tree means the next agent reads your half-finished state as if it were the artifact.
- Smallest change that satisfies the finding. A proposal — and the fix that implements it — is the narrowest change that resolves the finding; don't rewrite the artifact wholesale.
- Only the author writes. The Reviewer never edits the artifact — it proposes, and the Producer (the original author), with the operator, implements. Every change flows through one consistent hand, and the independent check stays independent: the reviewer never grades its own edits.
- No proposal left undecided. On its turn the Producer logs a disposition — Implemented / Modified / Declined (+ reason) — for every proposal before adding new work. A Declined Blocker is contested, not skipped (see below).
- Verify the finding before disposing of it. The Producer independently checks each proposal against the source before implementing — a finding can be wrong, or worse than flagged. Real relays open the turn with "verified every claim against the repo before deciding"; that check is what makes a disposition trustworthy, not just polite.
- No ignored Blockers. The Producer resolves or explicitly contests each one — never skips it.
- Evidence contract — state your proof every turn. The Producer logs
Verification: (what it ran / skipped / couldn't run); the Reviewer logs a verdict Basis: — behaviorally proven or textual only — and classes prior fixes textually fixed vs behaviorally proven. One line each, mandatory. It keeps a clean Approved from outrunning what was actually executed, and deflates to N/A — non-executable artifact for pure docs.
- Reconcile every "Implemented" against the artifact before you hand off. A disposition is a until the file proves it. Before flipping , the Producer re-reads the artifact (or ) and confirms each actually resolves to the changed line — a claimed edit that silently didn't land is caught here, one step before it reaches the Reviewer. Cite the line , so an with no matching hunk is false on its face.
Hands-free handoff (opt-in, all-Claude only)
By default the human serializes the relay with a one-line "your turn" nudge — and that nudge is the lock: it guarantees the previous turn is committed before the next begins. You can automate the nudge away only when both windows are Claude Code sessions, by replacing it with a guarded poll — each window watches the file and takes its turn the moment it's genuinely ready, never on a clock.
Why not a fixed timer. A "fire in N minutes" timer swaps a readiness condition for a guess: too short and the next turn fires on a half-finished, uncommitted tree (the clobber rule 9 exists to prevent); too long and you waited for nothing. The trigger you want is "it's my turn and the tree is clean," not "N minutes elapsed."
The guard (non-negotiable). A polling window takes its turn only when both hold:
NEXT names its role, and
- the working tree is clean — the other window has already committed (
git status --porcelain shows nothing for the artifact).
If either is false it does nothing and waits for the next tick. The condition is now the lock, so "one window at a time" still holds — and the order is unchanged: do the work → commit → flip NEXT, so a poller never sees NEXT flip before the commit lands.
Setup. Opt in at relay start by running a guarded /loop in each Claude window:
# Reviewer window
/loop 60s take the Reviewer turn on relay-system/<date>/<slug>.md ONLY if NEXT is Reviewer and the tree is clean; otherwise do nothing and wait
# Producer window
/loop 60s take the Producer turn on relay-system/<date>/<slug>.md ONLY if NEXT is Producer and the tree is clean; otherwise do nothing and wait
Record the mode in the file so each window knows it's live — set Setup's Handoff: to hands-free poll (all-Claude). A short interval (≈60s) keeps the prompt cache warm; the other window's edits aren't harness-tracked, so polling is the correct way to notice them.
Stop conditions. A polling window stops when STATUS is Approved or Escalated, or after a bounded number of idle ticks with no change — then it escalates to the human rather than spinning forever. Honor the max ROUND exactly as in manual mode.
Stays manual when you have no CLI runner for the Reviewer tool, or want the human as an explicit checkpoint between turns. For non-Claude tools that have a -p / --print mode (agy, Codex), see CLI-driven handoff below — that section covers fully automated single-session relays with no second window needed.
Self-closing loops (avoid stray cron housekeeping)
A /loop-created cron job is per-session, in-memory, and auto-expires after 7 days — far too long for a short review relay, and easy to forget. Make each loop self-closing so no one has to remember to clean it up:
- Bake a deadline / tick-budget into the loop, not just a stop-on-
Approved. The loop should end on the first of: STATUS terminal (Approved/Closed/Escalated), a wall-clock deadline (e.g. 30 min), or N idle ticks. A deadline also kills the "idles forever because the peer window died" failure mode.
- The loop deletes its own job on stop. On a stop decision the polling turn runs
CronList → finds its job (match it by the relay-file path in the prompt, since the turn doesn't know its own ID at creation) → CronDeletes it, then ends. Without this the cron keeps firing (harmlessly idling) until the 7-day expiry or the session closes.
- If a tick-driven runner is involved (e.g. a
poll.sh-style guard), give it the deadline so it emits the stop decision itself (--deadline <epoch> / --max-idle-ticks N); then the one stop path (DECISION: stop → CronDelete self) covers Approved, expiry, and stall uniformly.
Cross-session caveat (load-bearing). Cron jobs live in the session that created them. You cannot stop another window's loop from yours — CronList/CronDelete only see the current session. So every window's loop must self-close (deadline + self-delete), or be stopped in its own window; there is no central "kill all loops." This is why self-expiry matters more here than for a single-window loop.
Use cases: short hands-free review relays (set a 30-min deadline — most relays finish in 2–4 rounds well under that); unattended overnight polls (longer deadline, but still bounded); any multi-window all-Claude relay where several loops run at once and manual cleanup of each would be error-prone.
CLI-driven handoff (single-session, agy / Codex)
A single Claude Code session can drive both roles — it takes the Producer turn itself, then calls agy -p or codex as a subprocess for the Reviewer turn, parses the output, and appends it to the relay file. No second window, no human nudge between rounds.
When to use. You have agy or codex installed, want fully automated review from one session, and trust the output enough to proceed without a human checkpoint between turns. Manual is still the default — this is opt-in. Set Handoff: cli-driven (agy) or cli-driven (codex) in Setup so any agent reading the file knows which tool is driving the Reviewer.
How it works.
- Producer takes its turn and writes its block normally.
- Producer reads the relay file + artifact and calls the CLI tool, passing both as the prompt:
RELAY=$(cat relay-system/<date>/<slug>.md)
ARTIFACT=$(cat <artifact-path>)
RESULT=$(agy -p "You are the REVIEWER in a relay review. Read the relay thread and artifact, then take your Reviewer turn. Output ONLY the block to append (starting with '### Round N · Reviewer …'), nothing else.
=== RELAY THREAD ===
$RELAY
=== ARTIFACT ===
$ARTIFACT")
- Assert non-empty output (see gotchas below), then append the result directly above the
<!-- ↓↓↓ NEXT TURN --> marker.
- Update
NEXT and STATUS in the header.
- Loop until
STATUS: Approved or max round.
The embedded ▶ TAKE YOUR TURN block in the relay file is the Reviewer's full instruction set — pass the file verbatim and the tool knows what to do without extra scaffolding.
agy-specific gotchas (load-bearing)
- Silent failure under Claude Code's Bash sandbox.
agy -p exits 0 with empty output when its backend network is blocked — it does NOT error. A sandboxed call reads as a successful empty turn. Always run agy with dangerouslyDisableSandbox: true and assert non-empty output before appending:
if [ -z "$RESULT" ]; then echo "ERROR: agy empty — sandbox or auth issue"; exit 1; fi
- Timeout is a Go duration string, not seconds. Use
--print-timeout 2m, not --print-timeout 120.
- No JSON or token output.
agy returns plain text only — no usage numbers, no structured output flag. The relay is cost-blind when agy is the Reviewer.
Codex-specific gotchas
- Needs sandbox disabled. Codex CLI requires keychain access and
chatgpt.com network — both blocked under Claude Code's sandbox. Run with dangerouslyDisableSandbox: true.
- Auth mode. When using
auth_mode=chatgpt, turns are billed via the ChatGPT subscription, not API credits.
Reporting to the human (chat reply, not the file)
The relay log (the turn block you appended) and your chat reply are two different documents with two different jobs — the log is the durable record, the chat reply is what the human actually reads to decide what to do next. Keep them separate, and shape the chat reply like this:
- TLDR — one line, at the very top. Round, role, verdict/status, and what happens next: "Round 2 Reviewer done — Changes requested, 1 Blocker. Producer's turn next." This is the hand-off nudge, just promoted to the first line instead of the last — the human should never have to read past line one to know the result.
- One short framing sentence, in your own voice — "Reviewing
evidence.py against the DoD…" — only if it adds something the TLDR didn't already say. Skip it when it would just restate the TLDR.
- If there are findings to report, close with them sorted by grade —
[Blocker] → [Should] → [Nit] → [Pass], in that fixed order, never chronologically or interleaved with prose. One line each, with a file:line and the proposed fix inline:
[Blocker] auth.py:42 — token isn't validated before use → Proposed fix: check `token.valid` before the call.
[Should] evidence.py:88 — magic number, extract a constant.
- Skip step 3 entirely for 0–2 trivial findings, or a clean
Approved with nothing to report — plain prose is fine. Don't force a sorted list where there's nothing to sort (same threshold linear uses for its own numbered-step format).
This doesn't change what goes in the log — the log still uses the turn block formats above, verdict semantics are unchanged, and the Reviewer's Approved/Changes requested/Blocked distinction still governs whether the relay is open or closed. This section only governs how you talk about that log to the human.
What success looks like
The human's entire role collapses to two actions: "start a relay" and "your turn." No text shuttled between windows, no extra notes outside the relay thread, no lost context — just one dated, git-diffable Markdown file holding the full review thread and every decision, ending cleanly on Approved or a clear escalation.
Appendix — relay thread template
Write this verbatim to relay-system/<date>/<slug>.md when starting a relay, filling the <…> fields. Newest turns append at the bottom, above the marker; the header and ground rules stay pinned at the top.
The ▶ TAKE YOUR TURN block is embedded in the template so a non-Claude agent (Codex, Gemini) that never loaded this skill can act correctly from the file alone — the operator's whole job stays a one-line nudge ("take your turn on <file>"), never a pasted wall of instructions. This base skill stays partially manual (a human nudges each turn); automating the nudge itself is the job of the xyz/relay-automation add-on, not this portable skill.
# RELAY · <TITLE>
<!--
Single source of truth for this two-agent relay.
Read this ENTIRE file before doing anything. Act only on your turn.
-->
NEXT: Producer
STATUS: Open
ROUND: 1 / 5
## ▶ TAKE YOUR TURN — read this first (works for ANY agent: Claude, Codex, Gemini)
The operator just said "take your turn on this file." Everything you need is **in this file** — don't wait for pasted instructions.
1. **Read this whole file** (header, Setup, Ground rules, every turn in the Log).
2. **Check it's your turn:** `NEXT` (top) names the role to act. Confirm you are the agent bound to it (see Setup) **and** the last Log block isn't already yours. If not → STOP and reply "wrong window — nudge the <other> window."
3. **Do your role's work** on the artifact named in Setup (read the real files / the latest `git show <last commit>` diff; cite `file:line`):
- **Reviewer:** review vs the Definition of Done → graded findings (`[Blocker]`/`[Should]`/`[Nit]`/`[Pass]`), each with a concrete proposed fix → set a **Verdict** (Approved | Changes requested | Blocked). Do **not** edit the artifact; you only append findings here. **Before you set `Approved`, re-read the artifact file itself** (not this log) and confirm every prior `Implemented` fix is actually present and complete — any that is missing or partial → set `Changes requested` with a `[Blocker] claimed-implemented-but-absent @ file:line` instead. For a doc artifact this file check is the only backstop there is.
- **Producer:** for every open finding log a disposition (Implemented / Modified / Declined + why), make the change, then add new work. **Before you flip `NEXT`, re-read the artifact and confirm each `Implemented → @ file:line` actually landed in the file** — cite the line as it appears in your commit diff. A claim you can't point to in the file is not done.
at the very bottom, directly the marker line (). Never edit earlier turns. Header it ; a Reviewer block carries + (graded bullets) + ; a Producer block carries + + + . (Need the exact shape? Mirror the most recent block of the other role above.)
flip to the other role; set ( closes the relay — Reviewer only; else leave ); the Producer bumps when opening a new cycle.
(artifact + this log): , then put the short hash in your block's line and . Push if the team shares a remote.
Tell the operator your one-line result (e.g. "Changes requested, 1 Blocker — Producer's turn").
Artifact under review:
Definition of Done:
Producer: · Reviewer:
Handoff: manual nudge <!-- options: "manual nudge" · "hands-free poll (all-Claude)" · "cli-driven (agy)" · "cli-driven (codex)" — see skill -->
Started:
This file is the single source of truth. If it isn't written here, assume the other agent doesn't know it. The two agents may be different tools (e.g. Claude and Codex) and never share memory.
Read the whole file. Take a turn only if names your role — otherwise reply "not my turn" and stop.
One turn = one block appended at the very bottom, above the marker. Never edit earlier turns. Then update , , at the top. (Only exception: right after committing, fill the hash into your own just-written turn's line.)
Stay tight. Requests and findings are bullets, not essays.
It proposes graded findings, each with a concrete suggested fix where possible. The Producer (the original author), with the operator, decides each proposal and implements the approved ones — logging a disposition (Implemented / Modified / Declined + reason) for every one.
Grade every finding: must fix to ship · strong recommendation · optional · checked and sound (records what was verified, not assumed). Answer the Producer's "Re-review this" questions in an block.
The Reviewer posts a Verdict every turn. The relay ends on — so to get proposals actioned in-thread the Reviewer sets , not ; a left on an verdict is the author's discretion, handled out-of-band. If the max ends without , set and hand back to the human.
End your turn by committing it: , then fill the hash into your line — so the other agent can exactly what changed. If your turn touched no tracked files (comments-only, or this log is gitignored), write .
Both agents share one working tree; the pointer is honor-system, not a lock. Never start a turn while the other window may still be editing, and never flip with uncommitted changes left in the tree — commit or stash first, so the next agent never inherits half-finished state.
The Producer logs a one-line (what it ran / skipped / couldn't run); the Reviewer logs a verdict — (ran/observed) or (read, not run) — and classes any prior fix vs . An can't silently mean "looked fine on read": if correctness rides on runtime behavior and nothing ran, is the strongest honest verdict. Scales to the artifact — a pure doc answers .
A disposition is a claim until the artifact proves it. The Producer, before flipping , re-reads the artifact (or ) and confirms each actually landed — citing the line as it appears in the commit diff. The Reviewer, before it may set , re-reads the and confirms every finding is present and complete; any missing or partial one flips the verdict to with a . For a non-executable artifact (design-spec / plan doc) this file check is the only backstop — so the closing approval is never granted on the log's word alone.
— the only writer of the artifact: builds it, requests review, decides and implements proposals (with the operator), updates.
— reviews against the DoD, proposes graded findings with suggested fixes, sets a verdict. Never edits the artifact.
---
ran → · skipped (why) · impossible (why) — or "N/A — non-executable artifact"
<!-- ↓↓↓ NEXT TURN GOES ABOVE THIS LINE — keep this marker last ↓↓↓ -->