relayapp
Message your local coding agent from your phone. relayapp bridges a Relay
agent conversation to Claude Code, Codex, or Hermes Agent running on this
machine. It also installs native Relay channels for Claude Code and OpenClaw.
Texts become prompts, replies come back as messages, and tool approvals arrive
as Allow/Deny cards you answer with a tap.
Relay is messaging for agents — https://relayapp.im. API reference: https://docs.relayapp.im.
Quickstart
npm install -g relayapp
# 1. Pair this machine with the Relay app (QR + short code, ~30 s)
relayapp pair
# 2. Start the bridge in the repo you want the agent to work in
cd ~/code/my-project
relayapp start --engine claude # or: --engine codex | --engine hermes
Now text the agent from the Relay app. Each message (or quick burst of messages) becomes one engine turn; the bridge shows a typing indicator while the engine works and posts one finalized reply per turn.
Commands
| Command | What it does |
|---|---|
relayapp pair |
POST /v1/pairings, shows a terminal QR + code, long-polls until you claim it in the app, stores the Agent Token in ~/.relayapp/config.json (chmod 600) and pins your user id as the bridge owner (from GET /v1/agents/me; override with RELAY_OWNER_USER_ID). If owner lookup is interrupted after the token is saved, running the command again resumes that saved token without creating another agent. The token never appears on the phone. |
relayapp start |
Receive loop: long-polls GET /v1/events, drives the engine over ACP, replies via POST /v1/messages with an Idempotency-Key. Flags: --engine claude|codex|hermes, --dir <path>. Claude and Codex adapters are bundled; Hermes must already be installed and pass hermes acp --check. |
relayapp install-codex |
Run from a project root to opt in that project only. Merges — never clobbers — [mcp_servers.relay] + notify into ~/.codex/config.toml (comments preserved; a .bak of the original is kept) and a PermissionRequest hook into ~/.codex/hooks.json. Other projects are suppressed until installed separately. Codex gates untrusted hook handlers: the first run may ask you to trust the relayapp handler. |
relayapp install-claude |
After pairing, strictly validates the Claude plugin bundled in the installed npm package, persists its local marketplace under the paired account's private runtime directory, installs relay@relayapp-bundled, and writes the token/API origin/owner pin to ~/.claude/channels/relay/.env with mode 600 without printing the token. It refuses to overwrite a different configured identity. |
relayapp install-openclaw |
After pairing, persists and installs the OpenClaw plugin archive bundled in the npm package, adds only Relay's plugin/channel fields to ~/.openclaw/openclaw.json, and writes the paired token to an owner-only file. Existing unrelated config is preserved and a different configured identity is refused. |
relayapp doctor |
Checks Node, pairing, token file permissions, API reachability, installed adapter pins, and durable-state health. |
How the wire works
Everything rides Relay's public agent API — the same surface you can drive with curl:
# what the bridge polls (agent bearer auth; long-poll, cursor acks ≤ N)
curl -H "Authorization: Bearer $AGENT_TOKEN" \
"https://api.relayapp.im/v1/events?cursor=0&timeout=25"
# what the bridge sends per finished turn
curl -X POST https://api.relayapp.im/v1/messages \
-H "Authorization: Bearer $AGENT_TOKEN" \
-H "Idempotency-Key: relay-turn-<sha256(conversation,event-id-batch)>" \
-H "Content-Type: application/json" \
-d '{"conversation_id":"cnv_…","parts":[{"type":"text","text":"done"}]}'
- Engines: Claude and Codex are spawned as ACP adapters over
stdio (
@agentclientprotocol/claude-agent-acp,@agentclientprotocol/codex-acp). Both adapters are exact runtime dependencies resolved from the installed package; the bridge never runs mutable registrylatestcode. Adapter subprocesses receive platform and engine/provider variables, not the complete parent environment. Hermes is launched shell-free through its installedhermes acpstdio server and is checked withhermes acp --check. Conversation → session bindings persist in the paired account's runtime directory, so a conversation keeps its engine context. OpenClaw is deliberately separate:install-openclawinstalls a native channel plugin into OpenClaw rather than pretending it is an ACP coding-engine preset. - Approvals: an engine
session/request_permissionbecomes a Relay message with a text part plus anagent_permission_requestdata part (origin-tagged Allow/Deny options and quick-reply chips). Tap a chip or textyes <id>/no <id>. The full security-relevant tool input and affected paths must fit in the card; an operation that cannot be represented in full is denied instead of shown partially. No answer within 10 minutes → deny. - Owner gate: only the user pinned at pair time can prompt the engine or answer an approval card; messages from anyone else are ignored before their content is interpreted.
- Reliability: the receive cursor advances only in the same atomic
(fsync + rename) write that persists the event queue
(
~/.relayapp/accounts/<origin-agent-hash>/state.json), event ids are deduped, rapid messages debounce ~800 ms into one turn, and the poll loop restarts with capped exponential backoff + jitter. Each pending approval is its own create-once file under that account'sapprovals/, so a bridge restart cannot lose one and no two processes ever rewrite a shared snapshot. Engine/tool turns are at-most-once: an attempt marker is durable before execution, so a crash never silently repeats a deploy, deletion, command, or external send. Completed replies use a durable outbox and stable idempotency key, so delivery can retry without rerunning those tools. An interrupted turn is reported and must be retried explicitly by the owner. - Long-poll is exclusive: an enabled webhook endpoint or a second poller gets
409(Telegram semantics). One consumer per token. A401stops the loop with re-pair guidance instead of retrying. - Codex notification privacy:
install-codexstores an explicit local allowlist entry for the current project root in~/.relayapp/codex-notify.json. A completed turn from any other project is suppressed. For an allowed project, Relay receives the project directory's basename plus Codex's completelast-assistant-message; input messages and the absolute working-directory path are not sent. That text is retained in Relay message history. There is no global-all-projects opt-in; runinstall-codexin each project you choose to disclose. - Codex MCP sends:
relay_send_messagerequires a caller-chosen stablesend_id. Reuse the samesend_id, conversation, and text only after an unknown outcome; a changed payload is rejected. The mapping and idempotency key live in the paired account's private runtime directory, so a process restart cannot turn one logical send into two messages.
Files
~/.relayapp/config.json agent token, API origin, pinned owner (chmod 600)
~/.relayapp/codex-notify.json locally allowed Codex project roots (not sent)
~/.relayapp/accounts/<hash>/state.json cursor, queued events/replies,
owner conversation (start-only)
~/.relayapp/accounts/<hash>/approvals/ one file per pending approval
~/.relayapp/accounts/<hash>/sessions.json conversation → session bindings
~/.relayapp/accounts/<hash>/mcp-sends/ durable Codex MCP logical sends
~/.relayapp/accounts/<hash>/installed-plugins/ stable bundled plugin sources
Requires Node >= 22.18.