Relay

Relay

Agent harnesses assume your work lives in one repo. It doesn't.
Relay lets one agent talk to agents in your other repos — delegate, coordinate, spin up sub-teams — across every codebase you own.

Works natively with cmux. Runs inside your existing Claude or Codex CLI via MCP. Local-first, self-hosted, all state in ~/.relay/ on your machine. No hosted service, no telemetry.

CI Release npm npm downloads

GitHub stars open issues last commit MIT

--- > ⚠️ **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](https://github.com/jcast90/relay/issues/new) with a reproduction — or send a PR. Newcomer-friendly work is tagged [`good first issue`](https://github.com/jcast90/relay/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22); meatier tickets live under [`help wanted`](https://github.com/jcast90/relay/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22). 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 `assignedAlias` routing — 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` / `Priority` custom 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. See [`docs/trackers.md`](./docs/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 --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](#install) - [Install prerequisites (platform notes)](#install-prerequisites-platform-notes) - [Quickstart](#quickstart) - [How it works](#how-it-works) - [Key concepts](#key-concepts) - [Dashboards](#dashboards) - [CLI reference](#cli-reference) - [MCP tools](#mcp-tools) - [Integrations](#integrations) - [Multi-provider](#multi-provider) - [Unattended mode](#unattended-mode) - [Storage & execution backends](#storage--execution-backends) - [Configuration](#configuration) - [Architecture](#architecture) - [Development](#development) - [Contributing](#contributing) - [Known limits](#known-limits) - [Roadmap](#roadmap) - [License](#license) ## Install ### Quick start ```bash npm install -g @jcast90/relay rly welcome ``` …or without globally installing: ```bash 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 ```bash 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-tui` also builds the Rust dashboard. - `--with-gui` also builds the Tauri desktop app. On Linux, the preflight will offer to `apt-get install` the required system libraries if they're missing. - `--skip-link` skips the global link (useful in CI). ### GUI app Download the `.dmg` (macOS) / `.AppImage` + `.deb` (Linux) / `.msi` (Windows) from the [latest release](https://github.com/jcast90/relay/releases/latest). > **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](#roadmap). ### Manual ```bash 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 — ```bash 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](https://developer.microsoft.com/en-us/microsoft-edge/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 ```bash 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-123` or just `ABC-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/.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_send` to another repo's live agent - **Long task** → write a ticket with `assignedAlias: ""`; the associated agent polls `tickets.json` and 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 `assignedAlias` for 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//decisions/.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//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`](./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 --to ` 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 ` re-seeds days later. See [`docs/cli/rly-handoff.md`](./docs/cli/rly-handoff.md) and [`docs/design/handoff-brief.md`](./docs/design/handoff-brief.md). ## Dashboards All three read the same `~/.relay/` files — no synchronization, no split brain. ### CLI ```bash rly channels # list channels (sorted by latest activity) rly channel # show channel details + feed rly board # kanban view of the ticket board rly decisions # decision history with rationale rly running # active tasks across every workspace rly status # workspace paths + recent runs rly list-runs [--workspace ] 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) ```bash 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) ```bash 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 [--repos alias:wsId:path,...] [--primary ]` | Create a channel | | `rly channel update [--repos ...] [--primary ]` | Update repos / primary | | `rly channel archive ` | Archive a channel | | `rly channel unarchive ` | Restore an archived channel | | `rly channel ` | Show channel details + recent feed | | `rly channel feed [--limit N]` | Raw feed entries | | `rly channel post [--from ] [--type ]` | Post to the feed | | `rly channel link-linear ` | 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 ` | Re-run the Linear → channel-board mirror for a channel already linked | | `rly running` | Active tasks across every workspace | | `rly board ` | Kanban view of the ticket board | | `rly decisions ` | Decision history | | `rly pr-watch [--branch ] [--ticket ] [--channel ]` | Manually track a PR | | `rly pr-status [--channel ] [--json]` | List tracked PRs with CI + review state (reads the on-disk mirror when no orchestrator is running) | | `rly approve ` | Approve a pending plan (same code path as `harness_approve_plan` MCP tool) | | `rly reject [--feedback "…"]` | Reject a pending plan | | `rly pending-plans [--json]` | List runs awaiting plan-approval decisions | | `rly run --autonomous --budget-tokens [--max-hours N] [--trust supervised\|god] [--allow-repo ]... [--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 --session [--to \| --interactive]` | Roll repos + session transcript back to a rewindable user turn | | `rly handoff [--to ] [--save] [--resume ]` | Synthesize a handoff brief from channel artifacts and (optionally) seed a fresh session in the destination provider — see [`docs/cli/rly-handoff.md`](./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 ` | Session-transcript management | | `rly chat ` | Chat plumbing used by the TUI/GUI. `record-usage --session --input --output [--kind chat\|run\|admin] [--channel ] [--model ]` records adapter-reported token usage into `~/.relay/sessions//budget.jsonl` (called by the GUI + TUI chat workers; see [Token-usage telemetry](#token-usage-telemetry)) | | `rly config ` | Global config | | `rly mcp-server --workspace ` | 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](https://github.com/users/jcast90/projects/3), 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-` 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`](https://www.npmjs.com/package/@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 watcher + GitHub Projects v2 sync (needs `project` scope; `read:org` for org-owned projects) - `LINEAR_API_KEY` (or `COMPOSIO_API_KEY`) — Linear issues ### GitHub Projects v2 (v0.2, new) Relay channels project onto a GH Projects v2 board: channel → epic draft item, ticket → child draft item, with `Type` / `Status` / `Priority` custom fields kept in sync by a one-way Relay-authoritative sync worker. URL-paste a Projects v2 item into chat and the classifier resolves the project + epic + creates the ticket. Reuses `GITHUB_TOKEN`. Full reference: [`docs/trackers.md`](./docs/trackers.md). ### 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 ` 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. ## Multi-provider Relay's two CLI adapters (`claude`, `codex`) accept any endpoint that speaks their wire protocol, so **any provider exposing an OpenAI-compatible or Anthropic-compatible HTTP API works today with zero code changes** — just set the adapter's base-URL + key env vars. Known-good targets: MiniMax, OpenRouter, DeepSeek, Groq, Together, LiteLLM, vLLM. ```bash # Route Codex through MiniMax. export HARNESS_PROVIDER=codex export OPENAI_BASE_URL=https://api.minimax.io/v1 export OPENAI_API_KEY=$MINIMAX_API_KEY export HARNESS_AGENT_ATLAS_MODEL=MiniMax-M2 rly run "Add a health endpoint" ``` For smoke-testing + per-agent overrides + named profiles, see [`docs/providers.md`](./docs/providers.md). ```bash rly providers profiles list # named profiles rly providers profiles add ... # save a profile for reuse rly providers default # pick a default profile rly channel set-provider # pin a channel to a profile ``` Profiles never store secrets — they reference env-var names (e.g. `apiKeyEnvRef: "MINIMAX_API_KEY"`), and `rly providers profiles add` rejects any `--env KEY=VAL` whose value looks like a raw key. The GUI has a Provider dropdown per channel (Settings drawer → About tab) and a Providers tab in the global Settings page for full CRUD. **Not yet shipped:** native adapters for coding CLIs that aren't Claude- or Codex-compatible (Cursor, Gemini, Aider). See the Roadmap for status. ## Unattended mode For multi-hour runs where you don't want to click "allow" on every tool call: ```bash export RELAY_AUTO_APPROVE=1 # in ~/.relay/config.env or your shell rly claude ``` Under 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_APPROVE` propagated 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](#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.) # `tracker` config block — see docs/trackers.md config.env.template # copy to config.env and fill in workspace-registry.json # all registered repos workspaces// artifacts/ runs-index.json / run.json # full snapshot events.jsonl # incremental event log ticket-ledger.json classification.json approval.json channels// channel.json # name, members, repoAssignments, primaryWorkspaceId # .trackerLinks.githubProjects → projectId, projectNumber, projectUrl, epicItemId, epicDraftIssueId # (populated when the channel is provisioned against GH Projects v2) feed.jsonl # append-only feed tickets.json # unified ticket board (each ticket may carry .externalIds) runs.json # linked orchestrator runs tracked-prs.json # PR-watcher mirror (read by TUI/GUI pr-status surfaces) decisions/.json # one file per decision (atomic writes) handoffs/ # `rly handoff` brief artifacts (schemaVersion: 1) .md # rendered markdown brief .gap.json # working-memory slots from the departing agent sessions/.jsonl spawns.json # spawned-agent tracking (GUI, all platforms) sessions// budget.jsonl # per-session token-usage telemetry # one line per record() call; kind: "chat" | "run" | "admin" # fueled by orchestrator dispatch + `rly chat record-usage` # read by TUI + GUI bars + `rly status` task-costs.jsonl # global per-task cost ledger — one costed line per # agent call, tagged with ticketId + complexity tier; # aggregated by `rly cost` and the cost-aware router crosslink/ sessions/.json # live session heartbeats mailboxes// # pending crosslink messages hooks/ # generated shell hooks for Claude/Codex agent-names.json # display-name registry ``` ## Architecture ``` 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 installer ``` - `docs/` — 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 ```bash 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 crates ``` Per-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/` mirroring `src/`. Live-network tests sit in `describe.skip` blocks. - **Cargo** for Rust. `cargo test --workspace` covers 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 Relay is pre-v1 and issues/PRs are genuinely welcome — including from folks who've never contributed before. If you hit a bug, file it with a reproduction. If you want to help: - **First-time contributors** → [`good first issue`](https://github.com/jcast90/relay/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) — small, scoped, concrete. Comment "I'll take this" before you start so two people don't collide. - **Repeat contributors** → [`help wanted`](https://github.com/jcast90/relay/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) — bigger efforts like distribution channels, cross-platform CI, and cost guardrails. - **For larger changes**, open an issue first so we can align on shape before you burn time. See [`AGENTS.md`](./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 build` before 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.** Per-session context-window telemetry ships in v0.7.x (see [Token-usage telemetry](#token-usage-telemetry)); cost tracking and per-run dollar / token budget caps are still a follow-up. Use `RELAY_AUTO_APPROVE=1` with 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 `HarnessStore` would let multiple agents (same box or different) share state through `LISTEN/NOTIFY` cross-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 at `src/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 rly` on top of the existing `npm install -g @jcast90/relay` path. - **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=1` safer 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`](./LICENSE). ## Acknowledgements - [Composio](https://github.com/ComposioHQ/agent-orchestrator) for `@aoagents/ao-core` and the tracker / SCM plugin surface. - [Catppuccin Mocha](https://github.com/catppuccin/catppuccin) palette. - [Tauri](https://tauri.app/), [Ratatui](https://ratatui.rs/), [vitest](https://vitest.dev/), [tsx](https://tsx.is/) — the foundations everything rests on.