Cross-agent (Claude Code + Codex) planning/execution workflow — ALIGN, PLAN, PROJECT, IMPLEMENT, SHIP — plus a skill compiler that authors once and emits both host variants.
Project description
Relay
A cross-agent (Claude Code + Codex) planning/execution workflow. Portable operating discipline + continuity across agents: forcefully align on a spec, plan it under a mechanically-bounded agent that can't run off and build, project it onto GitHub (epic → milestones → issues), then implement and ship incrementally.
Local docs are the source of truth; GitHub is the projection (degrades to
local-only with no remote). One workflow, two host bindings — both converge on the
same .workspace/work/<unit>/ files and gh projection.
Status: alpha — pipeline complete (all 5 steps live-tested), published to PyPI as
relay-workflow0.1.0 (2026-06-03). Extracted 2026-05-28 fromfactorywork unit 024 (claude_codex_interop_framework). Successor to the Claude-onlyclaude-code-toolkit(which remains active separately). Design rationale and the empirical host probes are indocs/.See it run:
docs/demo.cast— a recorded end-to-end pipeline against a fresh GitHub repo (applied-artificial-intelligence/relay-demo):relay plan→relay project --execute→relay implement --execute(headless Claude writesgreet.py) →relay ship --execute(PR #8 merged). Play withasciinema play docs/demo.cast.
Pipeline (host-neutral)
Five steps built — pipeline complete:
align/ → spec.md forceful interrogation (skill + Codex prompt; interactive)
relay_plan.py → plan.json/.md bounded plan-only agent run (can't implement)
relay_project.py → GitHub idempotent epic + milestones + issues (+ branches)
relay_implement.py → branch + PR headless agent implements one issue (Closes #)
relay_ship.py → merged PR bubble up issue/milestone/project state
Alongside the pipeline, relay compile is a small adjacent tool that
solves the other cross-agent problem: author a skill once in a canonical
SKILL.md and emit both .claude/skills/<name>/ (full frontmatter) and
.agents/skills/<name>/ (Codex-trimmed, with a SKILL_DIR resolver). See
docs/skill-format.md.
align/ — spec interrogation (interactive)
align/SKILL.md (Claude skill) + align/align.codex.md (Codex /align prompt):
forcefully interrogate the user — one question at a time, challenge vague answers,
force out-of-scope exclusions — then write <unit>/spec.md from spec-template.md.
Front-loads all human clarification so the headless plan step never needs to ask.
Deploy: copy SKILL.md to a skills dir; copy align.codex.md to ~/.codex/prompts/align.md.
relay_plan.py — plan step
relay_plan.py: turns an aligned spec into a structured, milestone/issue
plan without letting the coding agent run off into implementation. Host-neutral.
spec.md → [bounded plan-only agent run] → plan.json + plan.md
- Reads
<unit>/spec.md(produced byalign). - Runs the host's mechanically bounded "plan-but-don't-build" primitive:
- Claude:
claude -p --permission-mode plan—ExitPlanModeis terminal, nothing executes. Plan extracted from--output-format jsonresult (falls back to newest~/.claude/plans/*.md). - Codex:
codex exec --sandbox read-only --output-schema <schema> -o <file>— read-only sandbox can't write; schema enforces the milestone/issue JSON shape.
- Claude:
- Writes
<unit>/plan.json(structured) +<unit>/plan.md(human/checklist). - Emits
<unit>/gh_projection.sh— a flat, non-idempotentghscript. Superseded byrelay_project.pyfor real use; kept only as a quick-look dry-run.
relay_project.py — project step (idempotent GitHub projection)
Mirrors plan.json onto GitHub: a tracking epic issue, milestones, one
issue per work item, with branch + Closes # wiring written back into plan.json.
plan.json → [resolve repo] → epic + milestones + issues → plan.json (numbers+branches) + projection.json
- Idempotent: matches existing milestones/issues by title (
gh api .../milestones,gh issue list) and reuses them — re-running never duplicates. Verified live against a throwaway repo (4 milestones/21 issues; second run created 0 new objects). - Epic: a
[epic] <objective>tracking issue with a child task-list, refreshed (not duplicated) on re-run.--no-epicto skip. - Branch/PR wiring: each issue gets
branch: relay/m<n>-<mslug>/<islug>and aCloses #<n>contract recorded inplan.jsonfor IMPLEMENT to consume. - Local-first: no GitHub remote → exits local-only (add a remote and re-run to promote).
- Missing labels don't abort — the issue is created without them and logged.
relay_implement.py — implement step (headless, one issue → PR)
Consumes the projected plan.json and drives a headless coding agent to
implement ONE issue on its branch, then opens a PR that closes the issue. This is
the step that lets Relay execute its own backlog instead of a human hand-building.
plan.json → [select issue] → branch off base → [headless agent] → push + PR (Closes #N) → plan.json (pr+state)
- One issue at a time (workflow-design.md model). Default target = first
pending issue (state ∉ {implemented, merged, done});
--issue <#n|title>to pick. - Reuses the
branch/closeswiring the PROJECT step wrote intoplan.json(errors if absent — runrelay_project.py --executefirst). - Assembles a brief from objective + milestone + issue body +
spec.md(the durable contract) + mechanical scope rules — host-neutral, byte-identical across hosts — then runs the chosen host headless (Claude:claude -p --output-format json; Codex:codex execin aworkspace-writesandbox). Success is verified bygit(commits on the branch), not by parsing stdout. - Idempotent: reuses an existing branch / existing PR; if the agent leaves no
commits, records
state=no-opand opens no PR. Local-only (no remote) →state=implemented-local, branch left for later promotion. - Dry-run by default: prints target, branch, the commands it would run, and the
full assembled brief.
--executeto branch + spend an agent run + open the PR. - Host:
--host {claude,codex,auto}(defaultauto). Auto-detect mirrors PLAN's precedence —claudeon PATH wins, elsecodex, else a clear error. Both hosts drive the same host-neutral brief and write the samestatevocabulary (open/no-op/implemented-local). - Host equivalence: Codex runs under a
workspace-writesandbox so it can edit and commit the working tree. (PLAN usesread-onlyfor a different reason — to mechanically bound the planner so it can't build; IMPLEMENT must be able to write.) For fully-autonomous runs that must run tests and commit without prompts,codex --dangerously-bypass-approvals-and-sandboxis the analogue of Claude's--permission-mode bypassPermissions(default is the saferacceptEdits).
relay_ship.py — ship step (incremental: PR merge → issue/milestone done)
Consumes the pr/state IMPLEMENT wrote into plan.json and closes the loop
incrementally. There is no terminal "ship" event in workflow-design.md — SHIP
runs (and re-runs) as PRs become mergeable: PR merge → issue done (auto via
Closes #N) → milestone done (when all its issues are merged) → project done
(when every milestone closes).
plan.json → [pick PR] → check mergeable + checks → gh pr merge → bubble-up → plan.json (merged/done)
- One PR per run by default (mirrors IMPLEMENT's one-at-a-time);
--allto drain every mergeable PR in one pass. - Merge gate:
mergeable=MERGEABLEAND checks notFAILING. Pending checks block by default;--autoenables GitHub auto-merge so the PR lands when checks pass. --adminbypasses branch protections on repos without CI.mergeable=UNKNOWN(GitHub's lazy-compute response on first probe) is auto-retried once.- After each pass, bubbles up milestones whose issues are all done and sets
plan.state = "shipped"when every milestone closes. gh pr mergelands on GitHub leaving the local base behind — SHIP fetches + fast-forwards before committing state so the push succeeds the first time.- Already-merged PRs are recognised and reconciled without re-merging (re-running is safe + idempotent).
relay compile — author skills once, emit both host variants
The pipeline is workflow interop; this is artifact interop. Same problem shape (one source, two hosts), different surface.
skills-src/<name>/SKILL.md → .claude/skills/<name>/ (full frontmatter, ${CLAUDE_SKILL_DIR})
→ .agents/skills/<name>/ (name+description only, ${SKILL_DIR} resolver)
- Canonical SKILL.md is the Claude-superset: write at the richer level, the compiler drops Claude-only frontmatter fields when emitting Codex.
- Self-references use
{baseDir}(the form Claude skills already use). The compiler rewrites it to${CLAUDE_SKILL_DIR}for Claude and to${SKILL_DIR}for Codex, and injects a one-line resolver into the first bash block on the Codex side soSKILL_DIRis always defined. @import ./file.mdbecomes an on-demandReadreference for Claude (progressive disclosure) and is inlined verbatim for Codex (which has no include mechanism).- Codex output carries an
AUTO-GENERATED … do not editheader so a future hand-editor doesn't change the wrong file. - Bundled assets (
scripts/,references/, etc.) are copied verbatim to both outputs. - Lints on Codex's caps (
name ≤ 64,description ≤ 1024).
Format details and rationale: docs/skill-format.md.
Functionally verified on real marketplace skills (lightgbm-training,
structured-writing, …).
Install
From PyPI:
uv tool install relay-workflow # recommended (isolated env, like pipx)
# or
pipx install relay-workflow # legacy-compatible alternative
From source (for development or to pin to a working tree):
git clone https://github.com/applied-artificial-intelligence/relay
cd relay
uv tool install --force . # install this checkout
Both paths put a relay binary on your PATH. Requires Python ≥ 3.10 and the
gh CLI for the steps that touch GitHub.
Usage
# align: invoke the Claude skill / Codex /align prompt (interactive), then:
relay plan <work-unit-dir> # auto-detect host, plan only
relay plan <unit> --host codex # force host
relay project <unit> # dry-run GitHub projection
relay project <unit> --execute # create/refresh epic+milestones+issues
relay implement <unit> # dry-run: show target issue + brief
relay implement <unit> --execute # implement first pending issue → PR (auto-detect host)
relay implement <unit> --host codex --execute # force Codex as the headless coding host
relay implement <unit> --issue 7 --execute --permission-mode bypassPermissions
relay ship <unit> # dry-run: which PRs would merge
relay ship <unit> --all --execute # merge every mergeable PR, bubble up
relay ship <unit> --execute --auto # enable auto-merge (wait on checks)
relay compile <skills-src-dir> <out-dir> # one SKILL.md → both .claude/ and .agents/
relay --help lists steps; relay <step> --help lists step-specific options.
python -m relay <step> works too (handy on systems where pipx/uv-installed
scripts aren't on PATH yet).
Verified (2026-05-27)
| Host | Mechanism | Output | Time | Repo touched? |
|---|---|---|---|---|
| Codex (gpt-5.5) | PLAN: read-only + enforced schema | 4 milestones / 21 issues | 39s | no |
| Claude (2.1.152) | PLAN: --permission-mode plan + JSON extract |
2 milestones / 10 issues | 29s | no |
| Claude IMPLEMENT | claude -p in working tree |
2 issues → 2 PRs (Closes #) |
— | yes (commits on branch) |
| Codex IMPLEMENT | codex exec + workspace-write sandbox |
branch + PR (Closes #) — pending live verification |
— | yes (commits on branch) |
Both leave the repo untouched (the anti-runaway guarantee is mechanical, not
behavioral) and converge on the same plan.json + gh_projection.sh.
Known gaps (prototype, not production)
- Codex schema requires
additionalProperties:false+ every property inrequired(OpenAI structured-output rule) — handled inPLAN_SCHEMA. - Claude path is not schema-enforced — relies on the model returning JSON; a structuring/repair pass would harden it.
- No rolling-wave support yet (re-plan a single milestone into issues later).
- GitHub Projects v2 board (Status/Area/Type fields, à la tradesharp) is not
created —
relay_project.pydoes epic issue + milestones only (CLI-friendly, no GraphQL). Project-v2 wiring is the next projection increment. - Issue-level idempotency matches on exact title; renaming an issue in the spec creates a new one rather than updating the old. Acceptable for append-style planning.
- IMPLEMENT
--executelive-tested 2026-05-28 (throwawayrelay-smoketest, 2 issues → 2 PRs viaclaude -p, both in-scope with tests + correctCloses #). Idempotency holds: skips issues that already have a PR (--redoto force), state is committed to the base branch (survives between runs, leaves a clean tree), dirty-guard ignores untracked agent artifacts. Default selection walks pending issues one at a time. - Parallel issues on one file conflict at merge time: each issue branches off base independently, so two issues editing the same file produce mergeable-but-conflicting PRs. That's a SHIP/rebase concern, not a runner bug.
- SHIP
--executelive-tested 2026-05-31 (samerelay-smoketestrepo): merged PR #4 (Closes #1auto-closed issue #1), correctly detected the parallel-PR conflict on PR #5, bubble-up left milestone open (1 of 2 issues done). Already-merged reconciliation verified on a second pass after a hard reset. Fixed two bugs found during the live test: (a)gh pr viewfirst probe returningmergeable=UNKNOWN— auto-retry once; (b) post-merge local base was stale, so state push failed — fetch + fast-forward before committing state.
Repo layout
align/ ALIGN step — Claude skill + Codex /align prompt + spec template
src/relay/ Python package — installs as the `relay` CLI dispatcher
cli.py subcommand dispatcher (relay plan|project|implement|ship)
plan.py PLAN step — bounded plan-only run → plan.json/.md
project.py PROJECT step — idempotent GitHub epic/milestones/issues
implement.py IMPLEMENT step — headless agent implements one issue → PR (Closes #)
ship.py SHIP step — incremental PR merge → issue/milestone bubble-up
pyproject.toml hatchling build; entry-point `relay = "relay.cli:main"`
docs/ workflow-design.md, planmode-probe.md, portal-briefing.md, PROPOSAL.md
.workspace/ agent state (memory/transitions/work), interop convention
Design & provenance
docs/workflow-design.md— canonical workflow spec (ALIGN→PLAN→PROJECT→IMPLEMENT→SHIP).docs/planmode-probe.md— Claude vs Codex plan-mode probe; the empirical basis for the bounded-planner design.docs/portal-briefing.md— briefing for the website Agent Lab portal.- Next steps (from design): self-host Relay (run the full ALIGN→…→SHIP cycle on Relay's own backlog), then rolling-wave re-planning + Projects v2 board.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file relay_workflow-0.2.0.tar.gz.
File metadata
- Download URL: relay_workflow-0.2.0.tar.gz
- Upload date:
- Size: 46.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7162fb2d02b6d14ffab41cc7d1386fbb6c73a8fffc5043b92b30d0b88ea199c4
|
|
| MD5 |
43664729b16fd07e74187b86bf80eb6a
|
|
| BLAKE2b-256 |
d0537b79f9f393bf544c95f4525daca1fa38c2dbbaa4ee95bae0f8ea124b0cf1
|
File details
Details for the file relay_workflow-0.2.0-py3-none-any.whl.
File metadata
- Download URL: relay_workflow-0.2.0-py3-none-any.whl
- Upload date:
- Size: 31.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98044bebfd8a2b94a94a5a5b67482da5b53685bb9e868acb45e750b74e78c04c
|
|
| MD5 |
ce99a0eec640c788b831012af4d20713
|
|
| BLAKE2b-256 |
c236aab3ff3cab81d92a4e5af3c4aebb72afaa3ee484f514fd968148042ea043
|