Package Exports
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@jcast90/relay) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Relay
Orchestrate coding agents across repos. Local-first, self-hosted, runs inside your existing Claude / Codex CLI.
Relay turns a single request into a plan, decomposes it into tickets, dispatches them to agents across one or more repos, tracks long-running work, and keeps a durable decision log — all on your machine, with no hosted service required.
What Relay is
Relay turns a sentence, a GitHub issue URL, or a Linear ticket into a running plan of AI-coded work — with tickets, verification loops, live PR tracking, and optional human approval gates. Sessions run inside your normal Claude or Codex CLI; Relay wraps them with an MCP server that records everything into Slack-style channels you can query later.
Suitable for individual developers and for teams working inside a company. Relay runs entirely on your machine. There is no hosted Relay service, no telemetry, and no phone-home — all state stays in ~/.relay/ on disk. What it's built for:
- Agent orchestration — classify, plan, decompose, dispatch, and verify. One request turns into a supervised workflow.
- Multi-repo coordination — sessions running in different repos can discover each other, message, and share context via the crosslink MCP tools.
- Long-running work — background PR tracking, plan-approval gates, and a durable decision log so you can step away and come back.
CLI: rly.
Table of contents
- Install
- Quickstart
- How it works
- Key concepts
- Dashboards
- CLI reference
- MCP tools
- Integrations
- 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 welcomeThe 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 welcomePrereq 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 --globalInstall 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-devWindows: 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 attachedThen 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
│
▼
mergedComplexity 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".
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 # diagnosticsDuring 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 rebuildCatppuccin 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 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> |
Chat plumbing used by the TUI/GUI |
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 (8): harness_status, harness_list_runs, harness_get_run_detail, harness_get_artifact, harness_approve_plan, harness_reject_plan, harness_dispatch, project_create
Channels (6): channel_create, channel_get, channel_post, channel_record_decision, channel_task_board, 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 Linear URL (or a bare Linear key like ABC-123) as your first message — the classifier fetches the full issue (title / body / labels / branch hint) before planning. The classifier output carries an optional suggestedBranch so generated PRs match the tracker's native branch name.
Tokens:
GITHUB_TOKEN— GitHub issues + PR watcherLINEAR_API_KEY(orCOMPOSIO_API_KEY) — Linear issues
PR watcher (scm-github)
With GITHUB_TOKEN set, any orchestrator run starts a background poller that uses AO's enrichSessionsPRBatch every 30 s. State transitions — ci: passing → failing, review: pending → changes_requested, prState: merged — post status_update entries into the ticket's channel. CI failures and change-request reviews turn into real follow-up tickets via the scheduler's dynamic enqueue — no manual retriage.
rly pr-watch <url> manually tracks a PR outside the auto-detect loop. rly pr-status shows everything tracked.
AO notifier compatibility
src/channels/ao-notifier.ts exports HarnessChannelNotifier implements Notifier — so you can drop Relay in as a notifier plugin for Composio's ao orchestrator without a rewrite.
Unattended mode
For multi-hour runs where you don't want to click "allow" on every tool call:
export RELAY_AUTO_APPROVE=1 # in ~/.relay/config.env or your shell
rly claudeUnder the hood:
- Claude launches with
--dangerously-skip-permissions - Codex launches with
--sandbox workspace-write+--ask-for-approval never - Internal scheduler-dispatched agents inherit via
RELAY_AUTO_APPROVEpropagated in the child env
One-off: rly claude --yolo or rly claude --auto-approve.
Use this only when you trust the tasks you're dispatching. No per-tool review means rm -rf, git push --force, and unlinked network calls all go through without asking.
Storage & execution backends
Storage
Relay stores everything in ~/.relay/ as JSON/JSONL files (atomic writes via tmp+rename). One backend, no DB required. All state — runs, tickets, decisions, crosslink messages, agent-names, workspace registry, session transcripts — goes through a single HarnessStore interface (src/storage/store.ts) so a future backend can slot in without rewriting handlers.
File backend is all that ships today. A Postgres backend (src/storage/postgres-store.ts) is stubbed in-tree for future multi-agent coordination — LISTEN/NOTIFY decision broadcasts and row-locked writes — but not wired yet; HARNESS_STORE=postgres currently warns and falls back to the file backend. See the Roadmap.
Executor
Verification commands run through an Executor abstraction (src/execution/executor.ts). Today the only shipping impl is LocalChildProcessExecutor — spawns locally. A pod-based executor was prototyped but has been removed until it's wired end-to-end; see the OSS-08 PR for context.
Configuration
Environment flags
| Var / flag | Effect |
|---|---|
HARNESS_LIVE=1 |
Use real Claude/Codex adapters instead of the scripted demo |
RELAY_AUTO_APPROVE=1 / --auto-approve / --yolo |
Unattended mode — no permission prompts. Required for multi-hour runs |
RELAY_USE_DIST=1 |
Run pre-built dist/cli.js instead of live source via tsx. Marginally faster startup; stale until rly rebuild |
GITHUB_TOKEN |
GitHub issues + PR watcher |
LINEAR_API_KEY (or COMPOSIO_API_KEY) |
Linear issues |
CLAUDE_BIN |
Override the claude binary path (default: claude on $PATH) |
RELAY_QUIET=1 / HARNESS_QUIET=1 / --quiet / --silent |
Suppress inline tool-use activity during rly run (both env vars honored) |
--sequential |
Use the v1 sequential orchestrator instead of v2 ticket-based |
--no-harness-mcp |
Launch Claude/Codex without attaching the Relay MCP server |
File layout
~/.relay/
config.json # global config (project dirs, etc.)
config.env.template # copy to config.env and fill in
workspace-registry.json # all registered repos
workspaces/<hash>/
artifacts/
runs-index.json
<runId>/
run.json # full snapshot
events.jsonl # incremental event log
ticket-ledger.json
classification.json
approval.json
channels/<channelId>/
channel.json # name, members, repoAssignments, primaryWorkspaceId
feed.jsonl # append-only feed
tickets.json # unified ticket board
runs.json # linked orchestrator runs
tracked-prs.json # PR-watcher mirror (read by TUI/GUI pr-status surfaces)
decisions/<id>.json # one file per decision (atomic writes)
sessions/<sessionId>.jsonl
spawns.json # spawned-agent tracking (GUI, all platforms)
crosslink/
sessions/<sessionId>.json # live session heartbeats
mailboxes/<sessionId>/ # pending crosslink messages
hooks/ # generated shell hooks for Claude/Codex
agent-names.json # display-name registryArchitecture
src/
cli.ts # entry point (bin/rly.mjs → tsx → here)
index.ts # CLI dispatch (welcome, claude, codex, board, ...)
cli/ # CLI subcommands + launchers (tui, gui, rebuild, welcome)
orchestrator/ # classifier, planner, decomposer, scheduler, approval
agents/ # Claude/Codex CLI adapters, registry, invocation
channels/ # ChannelStore, feed, decisions, ao-notifier
integrations/ # AO plugins — tracker, scm, pr-poller, env-mutex
execution/ # executor abstraction, verification-runner
storage/ # HarnessStore interface + file / postgres backends
domain/ # shared types + zod schemas
mcp/ # MCP server + tool definitions
crosslink/ # session discovery, messaging, hook generation
simulation/ # scripted invoker for the scripted demo mode
tui/ # small TS shim that launches the ratatui binary
tui/ # ratatui dashboard (Rust)
gui/ # Tauri desktop app — React + Vite frontend, Rust backend
crates/harness-data/ # shared Rust crate consumed by tui + gui (reads ~/.relay/)
docs/getting-started.md # canonical reference guide
bin/rly.mjs # CLI launcher (tsx by default, dist with RELAY_USE_DIST=1)
install.sh # one-command installerdocs/— human-facing deeper walkthroughs (getting started, storage injection).agent_docs/— agent-targeted reference (architecture, data model, testing) for coding agents working in the repo.
Development
pnpm install
pnpm test # 380+ vitest cases
pnpm typecheck # tsc --noEmit
pnpm build # tsc → dist/
pnpm demo # scripted simulation, no real API calls
cd gui && pnpm build # Vite bundle
cargo check --workspace # all three Rust cratesPer-area quick loops:
| What | Loop |
|---|---|
| Edit TS source, see CLI change | Just save — rly reads src/ live via tsx (no rebuild) |
| Edit Rust TUI | rly rebuild --tui |
| Edit Tauri GUI | rly gui --dev (hot reload) |
After git pull pulled new deps |
rly rebuild (runs pnpm install first) |
Testing conventions
- Vitest for TS. Tests live in
test/mirroringsrc/. Live-network tests sit indescribe.skipblocks. - Cargo for Rust.
cargo test --workspacecovers the TUI / GUI / shared crate.
Scripted vs. live mode
HARNESS_LIVE=1 switches from the scripted ScriptedInvoker to real Claude/Codex spawns. Leave it off while developing orchestrator logic — the scripted mode is fast and deterministic.
Contributing
Issues and PRs welcome. If you're considering a larger change, open an issue first so we can align on shape before you burn time.
See AGENTS.md for the coding-agent conventions.
- Keep PR scope tight — prefer multiple small PRs over one big one.
- Run
pnpm test && pnpm typecheck && pnpm buildbefore pushing. - Tests for new behavior. No snapshot tests for orchestrator output — assert on shape.
- Formatting: two-space indent, double quotes, semicolons, trailing commas where idiomatic. Run your editor's formatter on touched files.
A CLAUDE.md at the repo root (when present) tells any Claude agent working in this codebase — including rly claude itself — what conventions to follow.
Known limits
- Spawn is cross-platform but lightly tested off macOS. macOS is daily-driven; Linux and Windows branches are compile-checked and unit-tested but real-device integration testing is still the gate before tagging a release.
- Cost guardrails not yet implemented. Token usage isn't tracked or capped. Use
RELAY_AUTO_APPROVE=1with care.
Roadmap
Honest snapshot — most of these haven't started. Order is rough priority, not commitment.
- Postgres backend for multi-agent coordination (exploratory, stubbed) — the file backend serializes writes per-host via tmp+rename atomics. A Postgres-backed
HarnessStorewould let multiple agents (same box or different) share state throughLISTEN/NOTIFYcross-agent decision broadcasts and row-locked decision writes. Postgres here runs locally (brew install postgresql && createdb relay) or remote — this isn't a cloud-only feature. Source stub lives atsrc/storage/postgres-store.ts; not wired into the factory and the integration tests are skipped. - Pod executor (Kubernetes) (exploratory) — verification runs off the dev box in per-ticket pods. Prototype was removed in OSS-08 until it's wired end-to-end again.
- S3 artifacts (exploratory) — moving ticket evidence off the local filesystem so it survives pod/host churn. Pairs with the pod executor.
- Distribution: Homebrew tap + winget manifest (planned) — for one-line
brew install rly/winget install rlyon top of the existingnpm install -g @jcast90/relaypath. - Code signing + notarization (planned) — macOS
.dmg(Developer ID +notarytool) and Windows.msi(Authenticode) so downloads don't need right-click-open / SmartScreen bypass. Requires paid certificates and a secret-management pass in the release workflow. - Cost guardrails (in design) — token usage tracking per run, per ticket, per channel, with a soft cap that pauses scheduling when hit. Prerequisite to making
RELAY_AUTO_APPROVE=1safer for multi-hour runs. - Integration test coverage off macOS (in progress) — Linux and Windows spawn paths are compile-checked but only smoke-tested; promoting them to the fast CI tier is the gate to tagging cross-platform releases.
License
MIT — see LICENSE.