⚠️ Beta — pre-v1. Relay is actively developed and hasn't cut a 1.0 yet. APIs, CLI flags, file layouts under
~/.relay/, and GUI surfaces can change between releases. Expect bugs. If you hit one, please open an issue with a reproduction — or send a PR. Newcomer-friendly work is taggedgood first issue; meatier tickets live underhelp wanted. Pick something up and comment "I'll take this."
What makes Relay different
Most agent harnesses assume the work lives in one repo. Real product surfaces don't — UI, backend, ML, infra, SDKs each sit in their own codebase. Relay is built around that shape: one orchestrator agent you talk to, reaching into every repo you own, delegating down a tree.
- 🌳 Cross-repo agent-to-agent delegation. You talk to one orchestrator. It reaches into your other repos and delegates to live agents there, who can spin up their own sub-teams. A request like "ship OAuth across UI, backend, and the SDK" fans out as a delegation tree — orchestrator → repo agents → sub-agents — not three uncorrelated chat sessions you babysit. Sessions in different repos discover each other through MCP crosslink tools and exchange messages directly. Parallel agent runners and single-repo orchestrators don't do this; Relay does.
- 📋 One ticket board spans many repos. Tickets carry a dependency DAG and
assignedAliasrouting — a feature touching 3 repos is one plan with 3 ticket streams, scheduled in order, not 3 disconnected runs. Single-repo orchestrators can't express this. - 💬 An audit trail falls out of the coordination. Because every cross-repo decision is routed through Relay, you get rationale + alternatives recorded automatically — who chose what, why, what else was considered. Step away for 4 hours, come back to a log that reads like Slack threads for architectural choices. Most harnesses don't persist anything beyond the raw transcript.
- 🗂 GitHub Projects v2 integration (v0.2). Channels project to a GH Projects v2 board automatically — channels become epics, tickets become draft items, with
Type/Status/Prioritycustom fields kept in sync. Relay stays authoritative; drift detected on the GitHub side is logged to the channel feed and overwritten. Paste a Projects item URL into chat and the classifier resolves the project + epic + creates the ticket. Seedocs/trackers.md. - 🎛 Three dashboards, one source of truth — no cloud. CLI (
rly), ratatui TUI, and Tauri desktop GUI all read the same~/.relay/files. No sync layer, no split brain, no hosted service, no telemetry. The chat feed is Slack-shaped, but the GUI is a window onto the delegation tree, not the headline.
What Relay is
Relay turns a sentence, a GitHub issue URL, or a Linear ticket into a running plan of AI-coded work: classify → plan → decompose into a ticket DAG → dispatch to Claude or Codex agents → verify → open PR → track until merged. Every event — tool calls, state changes, decisions — lands in a Slack-style channel feed you can query later.
Suitable for individual developers and teams. CLI: rly.
Use cases
Where Relay earns its keep today:
1. Multi-repo feature rollouts. You change an API contract in backend, the frontend needs the new payload shape, and the client SDK needs a version bump. One channel, three attached repos, dependency DAG ensures the backend ticket completes before the frontend / SDK work starts. Agents crosslink-message each other as shapes stabilize.
2. Dependency upgrades that ripple. Bumping a shared library across a dozen service repos. One ticket per repo, agents run in parallel (capped by max-concurrency), status on one board. Agents that hit a migration wall post a decision with alternatives before continuing — so the fifth repo's agent inherits the first repo's answer instead of re-solving it.
3. Incident response + post-mortems. Fixes that span services — the api hotfix, the worker retry change, the infra timeout bump. Relay's decision log, with rationale and alternatives per decision, doubles as the post-mortem artifact. Named agents ("Saturn reviewed the migration") give the writeup a coherent narrative instead of agent-3.
4. Long-form refactors. "Migrate every axios call to fetch across 40 repos." One autonomous channel, tickets per repo, approval gates at milestones. Run it with rly run --autonomous <channelId> --budget-tokens 500000 --max-hours 8, step away, come back to a stack of PRs and a decision log showing what each agent chose.
5. Overnight autonomous runs. Queue a backlog Friday evening, run with a budget cap. Monday you have merged PRs plus a decision log showing each architectural choice. Bounded by wall-clock, token budget, and a STOP-file kill switch — not "trust the agent and hope."
Table of contents
- Install
- Quickstart
- How it works
- Key concepts
- Dashboards
- CLI reference
- MCP tools
- Integrations
- Multi-provider
- Unattended mode
- Storage & execution backends
- Configuration
- Architecture
- Development
- Contributing
- Known limits
- Roadmap
- License
Install
Quick start
npm install -g @jcast90/relay
rly welcome
…or without globally installing:
npx @jcast90/relay welcome
The npm package is published under the @jcast90 scope because both
unscoped relay and rly were already taken on npm when Relay shipped.
The binary exposed on $PATH is still rly.
From source
git clone https://github.com/jcast90/relay
cd relay
./install.sh
rly welcome
Prereq checks (node >= 20, pnpm, git; plus cargo if you add --with-tui or --with-gui; plus Linux Tauri system libs if you add --with-gui on Linux), pnpm install && pnpm build, links the rly binary on your $PATH, scaffolds ~/.relay/config.env.template. Safe to re-run.
--with-tuialso builds the Rust dashboard.--with-guialso builds the Tauri desktop app. On Linux, the preflight will offer toapt-get installthe required system libraries if they're missing.--skip-linkskips the global link (useful in CI).
GUI app
Download the .dmg (macOS) / .AppImage + .deb (Linux) / .msi (Windows) from the latest release.
Note: pre-release builds are unsigned. macOS will show a Gatekeeper warning on first open — right-click → Open the first time. Windows SmartScreen will ask for a click-through. Code signing + notarization is on the Roadmap.
Manual
pnpm install && pnpm build && pnpm link --global
Install prerequisites (platform notes)
macOS: Xcode Command Line Tools (xcode-select --install). Nothing else required.
Ubuntu / Debian (needed for --with-gui): install the Tauri system deps first —
sudo apt-get update
sudo apt-get install -y \
libglib2.0-dev \
libgtk-3-dev \
libwebkit2gtk-4.1-dev \
libsoup-3.0-dev \
libjavascriptcoregtk-4.1-dev \
libayatana-appindicator3-dev \
librsvg2-dev
Windows: no extra system deps for the CLI. The GUI needs WebView2 (usually pre-installed on Windows 11).
How rly finds the source
The launcher (bin/rly.mjs) runs the current src/cli.ts via tsx by default — so a git pull reflects immediately, no rebuild required. RELAY_USE_DIST=1 switches to the pre-built dist/ for slightly faster startup.
Quickstart
rly welcome # 6-step interactive tour (recommended)
# Or manually:
cd /path/to/your/repo
rly up # register this repo
rly doctor # sanity-check tokens + MCP wiring
rly claude # launch Claude with Relay MCP attached
Then paste any of these as your first message:
- A plain sentence —
"Add OAuth2 to /api/users" - A GitHub issue URL —
https://github.com/owner/repo/issues/42 - A Linear URL or key —
linear.app/acme/issue/ABC-123or justABC-123
Relay's classifier picks it up, plans the work, and either executes or asks for approval depending on complexity.
How it works
your request tracker URL detected?
│ │
▼ ▼
┌───────────┐ GitHub / Linear ┌─────────────────┐
│classifier │ ◄───────────────────► │ resolve issue │
└─────┬─────┘ └─────────────────┘
│ complexity tier
▼
┌───────────┐
│ planner │ ──► design doc (if architectural)
└─────┬─────┘
│ phased plan
▼
┌────────────┐
│ decomposer │ ──► tickets with dependency DAG
└─────┬──────┘
│
▼
┌────────────────┐ parallel, max-concurrency capped
│ scheduler │ ──► [T-1] [T-2] [T-3]
└────────────────┘ │ │ │
▼ ▼ ▼
implement → verify → retry
│
▼
PR opens
│
┌─────────┴─────────┐
│ PR watcher │ GITHUB_TOKEN required
│ every 30 s │
└─────────┬─────────┘
│
CI fail / changes_requested ──► follow-up tickets
│
▼
merged
Complexity tiers
| Tier | Behavior |
|---|---|
trivial |
Heuristic match (typo, rename, lint) — single ticket, skip planning |
bugfix |
Heuristic match — debug-first flow |
feature_small |
LLM classification — lightweight plan, no approval |
feature_large |
Full plan + user approval required via MCP |
architectural |
Design-doc phase → plan → approval |
multi_repo |
Like feature_large + crosslink coordination across repos |
Key concepts
Channels
Slack-style workspaces for one piece of work. Each channel carries:
- feed — messages, tool calls, PR state transitions
- tickets — unified ticket board (
tickets.json) shared by chat and the orchestrator - runs — linked orchestrator runs with full event history
- decisions — recorded choices with
title / description / rationale / alternatives / linkedArtifacts, queryable per channel - sessions — persisted chat transcripts (
sessions/<id>.jsonl)
Channels sort by most-recent activity in the sidebar — the one you're working in stays at the top.
Primary + associated repos
A channel can attach multiple repos. Exactly one is primary — that's where the GUI's main chat agent works and where its cwd defaults. Every other attached repo is associated — visible to the primary as alias + path + AGENTS.md summary (first ~40 lines), not full context.
Cross-repo work happens through the primary's tools, not by reading:
- Quick question →
crosslink_sendto another repo's live agent - Long task → write a ticket with
assignedAlias: "<repo-alias>"; the associated agent pollstickets.jsonand picks it up - Primary is explicitly told not to grep or edit associated repo files directly
Crosslink
Live agents in different repos (each a rly claude session) discover each other via heartbeat files in ~/.relay/crosslink/sessions/ and exchange messages. MCP tools: crosslink_discover / crosslink_send / crosslink_poll.
Spawning associated agents
Opt-in per associated repo at channel creation, or on demand when the primary hits a no_session error. The GUI opens a terminal tab running rly claude in the repo. Per platform: macOS uses Terminal.app via osascript (window/tab ids tracked for targeted close); Linux probes $TERMINAL then a chain (x-terminal-emulator, gnome-terminal, konsole, xterm, alacritty, kitty, wezterm); Windows prefers wt.exe, falls back to powershell.exe / cmd.exe. Tracked per channel in spawns.json; kill from the GUI closes the tab on macOS and SIGTERMs (or taskkill /T /F) the crosslink session on Linux/Windows. Self-heals against dead crosslink heartbeats.
If no supported terminal is detected, spawn surfaces an error and posts a system entry to the channel feed — run rly claude in the repo manually and crosslink will pick it up.
Tickets
Parallelisable work units with:
- dependency DAG (
dependsOn) - retry budget (
maxAgentAttempts,maxTestFixLoops) - specialty tag (
general | ui | business_logic | api_crud | devops | testing) - optional
assignedAliasfor routing to a specific associated-repo agent - verification commands (run against an allowlist, never shelled blindly)
Statuses: pending | blocked | ready | executing | verifying | retry | completed | failed.
Decisions
First-class records with rationale + alternatives, written to channels/<id>/decisions/<id>.json. Each write is atomic (temp-rename) — readers (TUI, GUI, other CLI invocations) see a consistent file or the previous version, never a torn one.
Named agents
Every agent has a display name (src/domain/agent-names.ts), so channel feeds show "Saturn reviewed the migration", not "agent-3".
Token-usage telemetry
Per-session context-window telemetry — TUI, GUI, and rly status each render a % of context window consumed indicator for live Claude or Codex chat sessions, populated by adapter token-usage parsing and persisted at ~/.relay/sessions/<sessionId>/budget.jsonl with a kind: "chat" | "run" | "admin" discriminator. Threshold events fire on the channel feed at 75 / 90 / 95 % — the 90 % event is the on-disk signal rly handoff subscribes to for its handoff prompt. The contract is documented in docs/design/context-threshold-events.md.
Handoff briefs
When a session approaches its context limit (or you want to switch providers mid-task), rly handoff <channelId> --to <dest> synthesizes a structured markdown brief from the channel's feed, decisions, ticket DAG, and git log-derived files-touched, captures the departing agent's working memory through the channel_handoff_finalize MCP tool, and seeds a fresh session in the destination provider with the brief as its first turn. --save archives without dispatching; --resume <briefId|latest> re-seeds days later. See docs/cli/rly-handoff.md and docs/design/handoff-brief.md.
Dashboards
All three read the same ~/.relay/ files — no synchronization, no split brain.
CLI
rly channels # list channels (sorted by latest activity)
rly channel <id> # show channel details + feed
rly board <channelId> # kanban view of the ticket board
rly decisions <channelId> # decision history with rationale
rly running # active tasks across every workspace
rly status # workspace paths + recent runs
rly list-runs [--workspace <id>]
rly doctor # diagnostics
During HARNESS_LIVE=1 rly run with the Claude provider, tool-use events stream inline on stderr — each tool call appears as ⚙ [HH:MM:SS] [agent] Reading foo.ts. Pass --quiet (or set RELAY_QUIET=1) to silence the feed without affecting stdout.
TUI (ratatui)
rly tui # auto-builds on first run (~1 min)
Vertical channel sidebar · feed · task board · decisions · agents. Keyboard-driven, fast. While a Claude session is streaming, the chat pane shows a live tool-use stack (newest tool call, recent history, and a last update … timestamp) — matching the GUI's activity card.
GUI (Tauri desktop app)
rly gui # auto-builds the .app on first run (~2–3 min), then opens it
rly gui --dev # hot-reload Vite + Tauri window
rly gui --rebuild # force rebuild
Catppuccin Mocha theme. Three tabs per channel:
- Chat — live streaming with thinking previews + tool-call rail + pulsing accent indicator
- Board — kanban with empty columns hidden, per-column scroll, click-any-ticket detail modal with dependency tree
- Decisions — chronological decision list with rationale
Right pane shows repo assignments (with PRIMARY badge), pinned refs, and — when present — a Spawned agents section with kill buttons.
CLI reference
| Command | What it does |
|---|---|
rly welcome |
6-step interactive tour (pass --reset to replay) |
rly up |
Register the current repo in the global workspace |
rly status |
Workspace paths + recent runs |
rly list-runs |
Recent persisted runs across workspaces |
rly list-workspaces |
All registered workspaces |
rly claude |
Launch Claude with Relay MCP attached |
rly codex |
Launch Codex with Relay MCP attached |
rly channels |
List channels (most-recently-active first) |
rly channel create <name> [--repos alias:wsId:path,...] [--primary <alias>] |
Create a channel |
rly channel update <id> [--repos ...] [--primary <alias>] |
Update repos / primary |
rly channel archive <id> |
Archive a channel |
rly channel unarchive <id> |
Restore an archived channel |
rly channel <id> |
Show channel details + recent feed |
rly channel feed <id> [--limit N] |
Raw feed entries |
rly channel post <id> <content> [--from <name>] [--type <type>] |
Post to the feed |
rly channel link-linear <id> <linearProjectId> |
Bind a Linear project to the channel + do a first read-only mirror of its issues onto the ticket board (requires LINEAR_API_KEY) |
rly channel linear-sync <id> |
Re-run the Linear → channel-board mirror for a channel already linked |
rly running |
Active tasks across every workspace |
rly board <channelId> |
Kanban view of the ticket board |
rly decisions <channelId> |
Decision history |
rly pr-watch <url-or-#> [--branch <b>] [--ticket <id>] [--channel <id>] |
Manually track a PR |
rly pr-status [--channel <id>] [--json] |
List tracked PRs with CI + review state (reads the on-disk mirror when no orchestrator is running) |
rly approve <runId> |
Approve a pending plan (same code path as harness_approve_plan MCP tool) |
rly reject <runId> [--feedback "…"] |
Reject a pending plan |
rly pending-plans [--json] |
List runs awaiting plan-approval decisions |
rly run --autonomous <channelId> --budget-tokens <N> [--max-hours N] [--trust supervised|god] [--allow-repo <alias>]... [--json] |
Start an autonomous session against a channel's ticket board. Records a tagged decision entry; driver (AL-4) executes until budget / wall-clock / queue exhausts |
rly chat rewind --channel <id> --session <id> [--to <iso> | --interactive] |
Roll repos + session transcript back to a rewindable user turn |
rly handoff <channelId> [--to <profile|adapter|alias>] [--save] [--resume <briefId|latest>] |
Synthesize a handoff brief from channel artifacts and (optionally) seed a fresh session in the destination provider — see docs/cli/rly-handoff.md |
rly cost [--json] |
Cost-per-task report by complexity tier, aggregated from the per-task cost ledger at ~/.relay/task-costs.jsonl (mean / median / p90 USD, tokens, avg retries) |
rly crosslink status |
Active cross-session chatter |
rly tui |
Terminal dashboard (auto-builds on first run) |
rly gui [--dev] [--rebuild] |
Desktop dashboard |
rly rebuild [--all] [--dist] [--tui] [--gui] [--skip-install] |
Rebuild artifacts (runs pnpm install first unless skipped) |
rly doctor |
Diagnostics: paths, MCP wiring, token presence |
rly session <create|list|get|delete|...> |
Session-transcript management |
rly chat <system-prompt|resolve-refs|mcp-config|record-usage> |
Chat plumbing used by the TUI/GUI. record-usage --session <id> --input <n> --output <n> [--kind chat|run|admin] [--channel <id>] [--model <name>] records adapter-reported token usage into ~/.relay/sessions/<id>/budget.jsonl (called by the GUI + TUI chat workers; see Token-usage telemetry) |
rly config <add-project-dir|remove-project-dir> |
Global config |
rly mcp-server --workspace <path> |
Run the MCP server (invoked by Claude/Codex automatically) |
rly inspect-mcp |
Show the live MCP tool catalogue |
Two-axis routing on the project board
The autonomous-loop tickets are mirrored to the public Relay project board, which carries the (role, repo) routing model the loop uses to dispatch work. Each issue has a Status, Effort, Target Repo, Admin, and Depends on field — Target Repo is the repo alias the work lands in and Admin names the repo-admin-<alias> that owns the ticket. Pick up work by filtering Status: Todo and making sure Depends on is empty or has all dependencies closed. Issues carrying the relay-seeded label were generated by Relay's seeder; others are human-authored. The board is resynced idempotently by scripts/push-tickets-to-github.ts — re-running it updates field values in place rather than creating duplicates.
MCP tools
Exposed to Claude and Codex via the Relay MCP server:
Harness (9): harness_status, harness_list_runs, harness_get_run_detail, harness_get_artifact, harness_approve_plan, harness_reject_plan, harness_dispatch, project_create, pr_review_start
Channels (7): channel_create, channel_get, channel_post, channel_record_decision, channel_task_board, channel_handoff_finalize, harness_running_tasks
Crosslink (3): crosslink_discover, crosslink_send, crosslink_poll
Run rly inspect-mcp for the authoritative live list.
Integrations
Issue trackers (tracker-github, tracker-linear)
Built on Composio's @aoagents/ao-core leaf plugins. Paste a GitHub or Linea
No comments yet
Be the first to share your take.