Relay
Relay

Seven roles. One PR. Zero unreviewed diffs.

Relay runs feature work the way a disciplined team would. A PM writes the brief. An Architect plans it. Dev builds it. Review and QA gate it. Retro writes down what was learned. Each handoff is a real artifact; each gate is a deterministic check, not a model's opinion of itself.

01Step through a run

The line

Every feature moves down eight stations, left to right. Click a station, use the arrow keys, or press play. The panel below always shows exactly what that role reads, writes, and is gated on.

Station
02Every role, same shape

Anatomy of a role

PM, Architect, Dev, Review, QA: different jobs, identical machinery. This is what .ai/agents.json and agent-runner.ts assemble for every single call.

Governance
.ai/GOVERNANCE.md and DENIED_ACTIONS.md are injected into every prompt: role boundaries, output rules, hard denials.
Input
The prior role's artifacts, plus .ai/project-memory.md: pitfalls, conventions, and decisions carried over from every past feature.
Output
A single forced tool call, submit_changes, checked against a per-role JSON Schema ({files, artifacts, verdict}). Never free-form prose parsed after the fact.
Permissions
Write access is enforced in agent-runner.ts, regardless of what the prompt says. Only Dev writes source. Only Retro and Memory Compact touch project memory.
Gate
A deterministic check in run-pipeline.sh, typecheck, diagram presence, verdict value, decides retry, halt, or handoff. The model never decides its own control flow.
03What is actually enforced

The safety model

Nine independent guardrails, each enforced in code rather than requested in a prompt. None of them wait on the model to cooperate.

04Solving a real truncation

Dev batching

Found live on a thirteen-file feature: asking Dev for the complete content of every touched file in one call hit the model's real output ceiling. It truncated mid-JSON even at 64,000 tokens, at real cost. Toggle the two behaviors below.

Call 1 of 1
13 files, full content requested
finish_reason: length. Truncated mid-JSON, schema-invalid, the retry burns the same ceiling again.
Batch 1/3
files 1 to 6
Batch 2/3
files 7 to 12, sees batch 1 on disk
Batch 3/3
file 13
Applied per batch. Quality gates and the commit still run once, after batch 3.

Below project.devFileBatchSize impacted files (default 6), Dev still runs exactly one call: the same behavior as before batching existed. run-pipeline.sh never learns how many internal batches it took.

05.ai/config.json

Configuration

Generated once by afp-setup. Full list lives in skills/afp-setup/assets/module.yaml.

06llm.backend

Choosing a backend

Every role calls whichever backend .ai/config.json points at. Same schema validation, same retries, same guardrails either way.

openai‑compatible

the default
  • Needs an API key: OPENROUTER_API_KEY or a custom llm.apiKeyEnv
  • fetch() to any OpenAI-compatible chat-completions endpoint
  • Works with OpenRouter, OpenAI, Azure OpenAI, Groq, Together, Fireworks, or a local Ollama
  • Retries keyed off HTTP status: 429, 500, 502, 503

claude‑cli

subscription only, no key
  • Needs only claude setup-token: a Claude subscription, no API key at all
  • Shells out to claude -p --json-schema instead of an HTTP call
  • llm.maxBudgetUsd optionally caps spend per individual call
  • Retries keyed off exit code and is_error
07Tracked in TODO.md

Roadmap

Known limitations, tracked rather than fixed reactively.

08Get running

Quick start

Node.js 18 or newer, an AI coding tool, and a BMad Method install.

# install the module
$ npx bmad-method install --custom-source https://github.com/arnaudmanaranche/relay
 
# in Claude Code
$ /start afp-setup
 
# start a feature
$ /start afp-pipeline new "Add dark mode toggle"