Observed since 20 Oct, 23:11 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Local-first CLI for handing coding sessions between agents without losing context
Agent Relay is a local-first interactive layer for working with multiple coding agents in one durable project session.
It is built for the moment when one agent needs to stop because of rate limits, tool limits, or a manual handoff. Start inside Relay, talk to Claude, Codex, Gemini, Kimi, or OpenCode from the same shell, and Relay keeps the observable session context, decisions, validation state, and handoff packets on disk so the next agent can continue from the same point.
Built-in agent adapters currently support Claude Code, Codex, Gemini, Kimi Code, and OpenCode.
Python 3.11 or newer is required.
macOS / Linux
curl -fsSL https://agent-relay.dev/install.sh | sh
Homebrew
brew tap bethvourc/tap
brew install agent-relay
relay install
Windows (PowerShell)
irm https://agent-relay.dev/install.ps1 | iex
Already using uv or pipx?
uv tool install agent-relay-tool # or: pipx install agent-relay-tool
Full per-OS walkthroughs and verification steps: agent-relay.dev/installation.
Run Relay inside the repository whose work you want to hand off:
cd /path/to/your/repo
# First-time local wiring: hooks, daemon, and fallback order
relay install
# Start the interactive layer
relay
Inside the REPL, bare text goes to the active agent and slash commands control Relay:
/use claude
Fix the failing tests
/use codex
Continue the release prep from the current Relay session
/handoff-order claude codex gemini kimi opencode
/status
/metrics
Scriptable commands are still available when you need a one-off or automation-friendly flow:
# One-command relay: hand off to another agent from outside the REPL
agent-relay codex --task "Continue the release prep"
# Single-agent managed run
agent-relay run c "Fix the failing tests"
# Turn-based conversation between agents
agent-relay chat c x "Fix the failing tests"
# Concurrent agents working simultaneously (tmux)
agent-relay race c x "Build the auth module"
# Resume the latest unresolved concurrent conflict
agent-relay resolve --latest
# Inspect saved conflict artifacts
agent-relay inspect-conflicts <session-id>
# See what agents are available
agent-relay discover
# View sessions
agent-relay status
# Live view of an in-progress session (auto-picks newest active)
agent-relay watch
# Token / cost / duration rollup
agent-relay metrics
Agent aliases: c = Claude, x = Codex, g = Gemini, k = Kimi, o = OpenCode. Use agent-relay discover to see all available agents and aliases.
Run relay with no arguments to drop into the persistent slash-shell:
relay # interactive REPL (the default when no subcommand is given)
relay --tmux # wrap the REPL in a tmux session for crash-safety
relay --attach # reattach to this project's relay tmux session
relay --no-tmux # skip the first-run tmux prompt for this run
relay --no-onboarding # skip the first-run wizard (useful in CI/scripts)
Inside the shell:
/ to live-filter the slash menu (every CLI subcommand is available as a
slash command). Tab accepts, Enter submits.? on an empty input to open a shortcut overlay; Esc closes overlays./) is forwarded to the active agent. Relay persists
the prompt/output artifacts into the active repo-local session and uses that
same session lineage for later handoffs./use claude, /use codex, or /use gemini. If there is
live context, Relay routes the switch through the handoff machinery so the
target starts with a resume packet./model. The picker lists the models your
signed-in account can actually use, asking the agent's own tooling where it
can (Codex's model cache, agy models, opencode models) and falling back to
Relay's suggestions otherwise. /model <name> sets one directly and
/model default clears it; an unrecognised name is still accepted, since
providers ship models faster than Relay tracks them. Results are cached for a
few minutes, so use /model --refresh after changing your account's models.Enter queues it; what is waiting is listed above, and ↑
on an empty line pulls the newest one back to edit before it is sent. Queued
messages run in order once the turn finishes. Only submitted lines run — a half-typed line carries into the next
prompt instead of being sent, and a multi-line paste stays one message. If a
turn fails or you press Ctrl+C, the queue is discarded and printed back so
nothing is silently lost.! to run it as a shell command in the repo root without
leaving the session (!git status). Each ! is a fresh shell; output lands
in the transcript, so it feeds handoffs. Typed mid-turn, a ! command runs
between turns./help for every command, each with an example, or <command> --help
(also -h, and aliases like /c -h) for one command's arguments and flags.Ctrl+C clears the input on first press; a second press within ~2 s exits.Ctrl+D exits immediately.On first launch Relay runs a brief wizard (detects agents, asks about tmux),
persists your choices to ~/.config/relay/config.toml, and never asks again.
Re-run anytime with /setup. State and logs live under ~/.config/relay/:
| Path | Purpose |
|---|---|
config.toml | User preferences (default agent, tmux_auto, [repl.env]) |
history | Prompt history (mode 0600; secrets redacted before write) |
repl.log | Structured JSONL log (rotating 5×1 MB, redacted) |
agents/ | PID files for orphan-process recovery |
For full details:
docs/repl.md — every slash command (auto-generated from the
registry; CI ensures this never drifts).docs/architecture.md — REPL internals: shell loop,
agent layer, env contract, observability.docs/security.md — input validation, env allowlist,
redaction pipeline.docs/performance.md — keystroke-to-render budgets
and how to run the micro-benches.docs/troubleshooting.md — orphan reaper, log
location, common gotchas.The single most important thing you can do for better handoffs is start the work inside Relay instead of only calling Relay after an agent stops.
When Relay manages the session from the beginning, the REPL owns the live agent process, forwards your bare prompts to the active agent, captures the observable output stream, and persists turn artifacts under .agent-relay/sessions/. This means the handoff packet contains everything Relay can observe or export: recent conversation history, the current plan, blockers, remaining work, intended edits, and any provider-exported session state.
When Relay joins after the fact, it can only hand off what is still observable: the working tree changes, any notes you provide, and whatever the provider can export at that moment. Private reasoning, in-progress drafts, and tool-call history from an unmanaged session are lost.
In simple terms: if Relay was there while the work was happening, handoffs are much stronger. If Relay joins later, it can only hand off what it can still see.
relay as the default starting pointrelay
relay is the recommended way to start day-to-day work. It:
/userelay install has wired hooksrun when you need a scriptable single-agent commandagent-relay run c "Fix the failing tests"
run is still useful for CI, scripts, or one-off single-agent tasks. It:
If the agent finishes the work, great. If it hits a rate limit or needs to hand off, the session already has everything Relay needs to produce a strong resume packet.
chat for multi-agent collaborationagent-relay chat c x "Fix the failing tests"
chat runs agents in alternating turns. Each agent sees the full conversation history — what every other agent said, decided, and proposed. Use this when agents need to build on each other's work iteratively, like one agent investigating and another implementing.
race for parallel work with enforced delegationagent-relay race c x "Build the auth module"
race is a phased concurrent workflow, not just two agents launched side by side. It enforces a structured process:
resolve when human judgment is still needed.agent-relay codex --task "Claude hit its limit; continue from the current state"
If an agent was working outside of Relay and needs to hand off, open a new terminal in the same repo and run a one-command handoff. Relay will capture what it can (git changes, any notes you provide) and generate a packet for the target agent. This is less complete than a managed session but still far better than starting from scratch.
--planning-note-file--proposed-edits-fileopencode export| Command | Description |
|---|---|
agent-relay run <agent> <task> | Single-agent managed session with live capture. Best starting point for any task. |
agent-relay chat <agents...> <task> | Turn-based agent conversation. Agents alternate with full history context. |
agent-relay race <agents...> <task> | Concurrent workflow with planning, isolated worktrees, and conflict recovery (tmux). |
agent-relay <agent> | One-command relay to a target agent. Creates packet and optionally launches. |
| Command | Description |
|---|---|
agent-relay resolve [session-id] | Resume unresolved race conflicts. |
agent-relay resolve --latest | Resume the most recent unresolved conflict. |
agent-relay inspect-conflicts <session-id> | Inspect saved conflict artifacts, versions, and resolution hints. |
| Command | Description |
|---|---|
agent-relay discover | Show available agents, aliases, and CLI paths. |
agent-relay limits | Show real subscription usage for the agent CLIs (Claude's 5-hour/weekly windows, Codex's quota). Read from each CLI's own source, never estimated; agents that expose no quota say so. --refresh re-reads instead of using the short-lived cache. |
agent-relay handoff plan --agent <agent> | Who would take over if <agent> ran out right now, and why not the others ("codex is rate-limited", "gemini is not installed"). Offline. Prefers a successor already chosen by handoff arm; falls back to your handoff.order. --session-id includes the parked pick, --prefer tries one agent first, --limited records <agent> as out before deciding. |
agent-relay handoff arm --agent <agent> --session-id <id> | Pick a successor now, while <agent> still has the quota to answer, and park it on disk. No-ops below handoff.successor_threshold (90%). This is what the REPL and the desktop app call after a turn — running it by hand is mostly for debugging. |
agent-relay status | List all relay sessions in the current repo. |
agent-relay watch [session-id] | Live TUI of an in-progress session. Auto-picks newest active when no id is given. Use root --json / --quiet before the subcommand for alternate output modes. --no-follow prints a single snapshot and exits. Add --metrics for a token / cost / duration panel. |
agent-relay metrics [session-id] | Token / cost / latency rollup for a session. Use --all for cross-session totals, --since YYYY-MM-DD to filter, --agent claude (repeatable) to scope. |
agent-relay metrics-tail [session-id] | Stream metric events as JSONL — one line per turn_completed, plus a final session rollup. Optional --webhook URL POSTs each line. |
agent-relay metrics-serve | Run a metrics exporter (requires at least one of --prometheus / --otlp). --prometheus :9464 exposes /metrics in Prometheus text format; --otlp http://collector:4318/v1/metrics pushes OTLP/HTTP-JSON every 30s. Both can run together. |
agent-relay clean | Remove all sessions. Use --all to remove entire .agent-relay/ directory. |
| Option | Description |
|---|---|
--task, -t | Task for agents (alternative to positional argument) |
--continue | Continue from a prior relay session id |
-n | Max turns for run/chat (default: 10) |
--max-time | Max seconds for race (default: 600) |
--open-terminals | Auto-open terminal windows/tabs for race/resolve on macOS |
--no-open-terminals | Disable auto-open terminal behavior |
--from | Source agent for relay (auto-detected by default) |
--no-launch | Create the handoff packet without launching the target agent |
--yes, -y | Skip confirmation prompt |
--json | Machine-readable JSON output |
--quiet, -q | Minimal output |
# Start inside Relay for the strongest handoff later
agent-relay run c "Fix the failing tests"
# If the agent hits a limit, the session is already captured.
# Hand off to another agent:
agent-relay codex --task "Continue from the saved session"
# Two agents alternating
agent-relay chat c x "Fix the failing tests"
# Three agents, 6 turns max
agent-relay chat c x c "Review and fix" -n 6
While a session is in flight, open a second terminal in the same repo and run:
# Auto-pick the newest active session
agent-relay watch
# Or pin to a specific session
agent-relay watch <session-id>
# Stream events as JSONL — pipe into other tools
agent-relay --json watch | jq -c '{ts: .timestamp, kind: .kind}'
# Print a single snapshot of current state and exit
agent-relay watch --no-follow
The live TUI surfaces journal events, workspace activity, the current turn's
elapsed time and progressive agent output, and the latest turn state — all in
real time. The watcher exits cleanly when the session reaches a terminal
status (completed, ready_for_handoff) or on Ctrl-C.
Add --metrics for a token / cost / duration panel that refreshes after every
turn:
agent-relay watch --metrics
Relay computes metrics on read — no extra files in .agent-relay/, no cache
invalidation. The same data backs four surfaces:
# Per-session table (auto-picks the most recent session)
agent-relay metrics
# Cross-session aggregates: by agent, by day, totals
agent-relay metrics --all
agent-relay metrics --all --since 2026-05-01 --agent claude
# Machine-readable
agent-relay --json metrics
agent-relay --quiet metrics # minimal output
# Live JSONL stream — one line per turn_completed plus a final session rollup
agent-relay metrics-tail
agent-relay metrics-tail --webhook https://hooks.example.com/relay
Cost is best-effort. If an agent output includes an actual total_cost_usd
style field, Relay uses it. Codex exec --json normally emits token usage but
not a billed cost, so Relay estimates Codex cost from the captured model name.
Managed Codex turns save the model from the JSON stream when available, then
fall back to AGENT_RELAY_CODEX_MODEL, CODEX_MODEL, OPENAI_MODEL, or
$CODEX_HOME/config.toml. Uncached input tokens, cached input tokens, and
output tokens are priced separately. Estimates do not include account discounts,
Batch/Flex or priority processing, data residency uplift, long-context uplift,
or separately billed tool fees.
For dashboards and long-running collection, metrics-serve runs an exporter.
At least one of --prometheus or --otlp is required — running it with no
exporter flag exits with an error:
# Prometheus pull-based scrape endpoint (stdlib only)
agent-relay metrics-serve --prometheus :9464
# → curl http://localhost:9464/metrics
# OTLP push to a collector (HTTP/JSON, every 30s by default)
agent-relay metrics-serve --otlp http://localhost:4318/v1/metrics
# Both can run together
agent-relay metrics-serve --prometheus :9464 --otlp http://localhost:4318/v1/metrics
Metrics emitted (Prometheus naming; OTLP uses dotted equivalents):
| Metric | Type | Labels |
|---|---|---|
agent_relay_tokens_total | counter | agent, direction (input, output, cache_read, cache_creation) |
agent_relay_cost_usd_total | counter | agent |
agent_relay_turn_duration_ms_sum / _count | summary | agent |
agent_relay_turns_total | counter | agent, result (success, error) |
agent_relay_session_active | gauge | — |
agent_relay_sessions_total | gauge | status |
Threshold-based alerts ride the metrics-tail channel. They are opt-in —
no config file, no alerts. Drop a .agent-relay/config/alerts.toml:
cost_per_turn_usd = 0.50
cost_per_session_usd = 5.00
duration_per_turn_ms = 300000
tokens_per_turn = 200000
error_rate_threshold = 0.4 # ratio 0..1; gated by error_rate_min_turns
error_rate_min_turns = 5
When a turn breaches a threshold, metrics-tail writes a colored line to
stderr and emits an extra JSONL line on stdout:
{
"kind": "metrics.alert",
"rule": "cost_per_turn",
"severity": "warning",
"session_id": "...",
"turn_number": 3,
"threshold": 0.5,
"observed": 0.71,
"message": "turn 3 cost $0.7100 exceeds threshold $0.5000",
"timestamp": "..."
}
Severity is critical at ≥ 2× threshold, warning otherwise. Webhook
delivery (when --webhook is set) carries alert lines too, so external
systems can react without polling.
# Start a concurrent run
agent-relay race c x "Build the auth module"
# Continue an interrupted or timed-out concurrent session
agent-relay race --continue <session-id> c x "Continue the task"
# Inspect saved conflict artifacts and versions
agent-relay inspect-conflicts <session-id>
# Resume unresolved conflict resolution
agent-relay resolve <session-id>
agent-relay resolve --latest
Claim roles in race:
owner: exclusive editor for that path or directoryshared: multiple agents may edit that scope intentionallyreviewer: review-only overlap; edits in reviewer-only scope are blockedNotes:
--open-terminals or --no-open-terminals to control that behavior explicitly.manual_resolution_required, use inspect-conflicts first to see the saved versions, then resolve to continue the resolution workflow.max_time, interrupted, incomplete, or agent_error, use race --continue <session-id> ... to continue the broader task.Relay now keeps repo-local permission backend config in:
.agent-relay/permissions.toml
From the REPL:
/permissions
/permissions set claude mode bypassPermissions
/permissions set codex approval_policy on-request
/permissions set codex sandbox_mode danger-full-access
Important limitation: this currently controls launch-time permission behavior for turn-mode agents like Claude and Codex. It does not yet provide generic cross-agent per-tool approval unless the underlying CLI exposes pending tool requests in a machine-readable way.
If there are no file changes yet, pass the planning and proposed-edit context explicitly:
agent-relay codex \
--task "Continue from the saved plan" \
--planning-note-file handoff-notes/planning.md \
--proposed-edits-file handoff-notes/proposed.diff \
--no-launch
This is the best fallback when an agent did useful planning but did not write code to disk.
| Variable | Description |
|---|---|
AGENT_RELAY_AUTOSAVE_GIT_TOUCHED_FILES=1 | Auto-save git diff of touched files |
AGENT_RELAY_AUTOSAVE_RESEARCH_NOTE_FILE=<path> | Auto-capture research notes |
AGENT_RELAY_AUTOSAVE_IMPLEMENTATION_NOTE_FILE=<path> | Auto-capture implementation notes |
AGENT_RELAY_AUTOSAVE_VALIDATION_SUMMARY_FILE=<path> | Auto-capture validation summary |
AGENT_RELAY_AUTOSAVE_PLANNING_SNAPSHOT_FILE=<path> | Auto-capture planning snapshot |
AGENT_RELAY_AUTOSAVE_PROPOSED_EDITS_FILE=<path> | Auto-capture proposed edits |
| Variable | Description |
|---|---|
AGENT_RELAY_CLAUDE_LAUNCH_TEMPLATE | Custom launch command for Claude |
AGENT_RELAY_CODEX_LAUNCH_TEMPLATE | Custom launch command for Codex |
AGENT_RELAY_GEMINI_LAUNCH_TEMPLATE | Custom launch command for Gemini |
AGENT_RELAY_KIMI_LAUNCH_TEMPLATE | Custom launch command for Kimi |
AGENT_RELAY_OPENCODE_LAUNCH_TEMPLATE | Custom launch command for OpenCode |
| Variable | Description |
|---|---|
AGENT_RELAY_CLAUDE_PERMISSION_MODE | Override Claude REPL/concurrent permission mode |
AGENT_RELAY_CODEX_APPROVAL_POLICY | Override Codex approval policy (-a) |
AGENT_RELAY_CODEX_SANDBOX_MODE | Override Codex sandbox mode (-s) |
AGENT_RELAY_CAPTURE_PERMISSION_PTY=1 | Capture raw PTY permission prompt output for adapter development |
AGENT_RELAY_PERMISSION_CAPTURE_DIR=<path> | Override the PTY permission capture directory |
| Variable | Description |
|---|---|
AGENT_RELAY_CLAUDE_CAPTURE_TEMPLATE | Custom capture hook for Claude exports |
AGENT_RELAY_CODEX_CAPTURE_TEMPLATE | Custom capture hook for Codex exports |
AGENT_RELAY_OPENCODE_CAPTURE_TEMPLATE | Custom capture hook for OpenCode exports |
Relay-managed OpenCode turns always carry Relay's observable artifacts
(prompts, outputs, and turn state) into handoff packets. When Relay can
determine the native OpenCode session id, it also runs the default sanitized
export command opencode export {session_id} --sanitize and cites the result
as a provider session export. If OpenCode export fails or the native session id
is unavailable, handoff continues with Relay's observable artifacts.
Available in both launch and capture templates:
| Placeholder | Description |
|---|---|
{agent} | Agent key (e.g. claude) |
{agent_name} | Display name (e.g. Claude Code) |
{agent_cli} | Shell-quoted CLI command |
{repo_root} | Shell-quoted repo root path |
{repo_root_path} | Unquoted repo root path |
{resume_path} | Shell-quoted path to resume packet |
{resume_path_path} | Unquoted path to resume packet |
{session_id} | Session identifier |
claude: cd {repo_root} && claude -p "$(cat {resume_path})"
codex: cd {repo_root} && codex "$(cat {resume_path})"
Custom launch templates must include {resume_path} or {resume_path_path}. If a template omits the packet input, launch --execute refuses to run it.
Capture templates are optional. If set, they should print JSON to stdout with any of these fields:
resumable_stateplanning_snapshotproposed_editstranscriptsession_metadatawarningsAgent Relay writes repo-local state under .agent-relay/.
Each session lives under .agent-relay/sessions/<session-id>/ and uses a journal-plus-objects layout:
session.json: immutable session manifestjournal/: append-only event logobjects/checkpoints/<checkpoint-id>/: checkpoint manifests, summaries, repo-state captures, and related artifactsobjects/handoffs/<handoff-id>/: resume packet, packet hash, and launch specificationobjects/launches/<launch-id>/: launch receipts and captured stdout/stderrrefs/head.json: latest derived head pointerderived/view.json: current materialized session viewrecovery/: pending transactions, quarantine, and repair reportsturns/<turn-number>/: turn artifacts from run/chat sessions (prompt, output, stderr, state)This keeps the session state inspectable and vendor-independent.
Do not commit or publish .agent-relay/. Session artifacts can contain:
run/chat sessions)Two practical rules:
.agent-relay/ to .gitignoreAGENT_RELAY_CLAUDE_LAUNCH_TEMPLATE or AGENT_RELAY_CODEX_LAUNCH_TEMPLATE, because the rendered command and template are recorded in handoff metadataDownload counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.7.7 | 12 Aug 2026 | 5 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 23:11 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:01e599e733f7335346caacbcf0d90dbd91fc0d5fc43f1dcd701cbdbc87ed84c9