Agent Relay
by simozampa
README.md
# Agent Relay
[](https://github.com/simozampa/agent-relay/actions/workflows/ci.yml)
[](LICENSE)
Agent Relay is a small, asynchronous gateway for persistent coding agents.
It lets an MCP client, webhook, or automation submit work to an agent that is already running on a development machine, disconnect, and retrieve the result later. The first working adapter targets a named [Herdr](https://herdr.dev) agent; the adapter boundary is designed for additional agent runtimes.
```text
voice device / phone / MCP client / automation
|
| HTTPS + bearer token
v
Agent Relay
/ | \
policy job queue status/results
|
v
persistent agent session
|
v
repos / terminals / services / agents
```
## Why use it?
Interactive agent CLIs assume that a person is attached to their terminal. Remote devices and automations have different needs: requests should return quickly, long jobs must continue after the caller disconnects, two prompts must not collide in one terminal, and results need to be retrieved separately.
Agent Relay provides that missing boundary:
- A cloud-accessible Streamable HTTP MCP endpoint
- An authenticated webhook that forwards Pebble Index transcriptions without an intermediary agent
- Static bearer-token authentication
- A durable filesystem-backed job queue
- Sequential delivery to one persistent agent
- Asynchronous submission and status tools
- Hot-reloaded Markdown policies
- Short result handoff without exposing a terminal or agent RPC port
- A typed adapter interface for additional runtimes
## What works today
The current adapter sends prompts to a unique, named Herdr agent and waits for Herdr's lifecycle state. That target can be Pi, Codex, Claude Code, OpenCode, or another agent supported by Herdr. A supervisor agent can also coordinate other panes and agents on the same machine.
Agent Relay currently **requires Herdr**. Direct Pi, Codex, Claude Code, OpenCode, HTTP, command, and tmux adapters are planned but not implemented yet. See [Adding adapters](#adding-adapters).
Agent Relay receives text. An audio device works when the device or its companion service transcribes speech and calls the MCP or webhook endpoint. Agent Relay does not currently transcribe raw audio.
## MCP tools
Agent Relay exposes four tools and one optional client prompt:
| Tool | Purpose |
| ----------------------- | ---------------------------------------------------------- |
| `forward_to_supervisor` | Forward a complete owner instruction without rewriting it |
| `submit_task` | Queue a task and return its job ID immediately |
| `submit_dev_task` | Compatibility alias with a development-focused description |
| `task_status` | Retrieve a job by ID, or retrieve the latest job |
Enable the `supervisor_router` MCP prompt in clients that might otherwise solve or paraphrase a request before calling a tool.
Only one task is delivered to the target agent at a time. Jobs queued before a Relay restart resume afterward. A job that was already running is marked failed instead of being replayed, preventing accidental duplicate side effects.
## Quick start
### Requirements
- Linux or macOS
- Node.js 22.19 or newer
- Herdr installed on the same machine
- A supported agent running in Herdr under a unique name such as `supervisor`, or recovery configured so Relay can start it
- A public HTTPS reverse proxy or tunnel if the MCP client is outside the machine
Start a supported agent in an available Herdr shell pane:
```bash
herdr agent start supervisor --kind pi --pane <pane-id>
```
The agent keeps its normal operating-system permissions. Agent Relay does not create a sandbox.
### Recover a closed supervisor automatically
By default, Relay reports an error when its named Herdr target is missing. Optional recovery makes a persistent supervisor self-healing: Relay checks the target before each queued task, reuses a running agent that owns the configured session, or creates a dedicated Herdr workspace and resumes the session.
```bash
AGENT_RELAY_HERDR_RECOVERY=true
AGENT_RELAY_HERDR_RECOVERY_KIND=pi
AGENT_RELAY_HERDR_RECOVERY_CWD=/home/ubuntu/repos
AGENT_RELAY_HERDR_RECOVERY_WORKSPACE=agent-relay
AGENT_RELAY_HERDR_RECOVERY_SESSION=/home/ubuntu/.pi/agent/sessions/project/supervisor.jsonl
AGENT_RELAY_HERDR_RECOVERY_ARGS_JSON='["--session","/home/ubuntu/.pi/agent/sessions/project/supervisor.jsonl","--name","supervisor"]'
```
Recovery is opt-in because starting an agent is a state-changing operation. Agent arguments are passed directly without a shell. Use an absolute path inside the JSON array because shell expansion is not performed.
### Install
```bash
git clone https://github.com/simozampa/agent-relay.git
cd agent-relay
npm install
npm run build
```
Create configuration and choose a policy:
```bash
install -d -m 700 ~/.config/agent-relay
cp examples/env.example ~/.config/agent-relay/env
cp policies/developer.md ~/.config/agent-relay/policy.md
chmod 600 ~/.config/agent-relay/env ~/.config/agent-relay/policy.md
openssl rand -hex 32
```
Copy the generated token into `~/.config/agent-relay/env` as `AGENT_RELAY_TOKEN`, then update the Herdr binary and target if necessary.
Start Relay manually:
```bash
set -a
. ~/.config/agent-relay/env
set +a
npm start
```
It listens on `http://127.0.0.1:8787/mcp` by default. Verify it locally:
```bash
curl http://127.0.0.1:8787/health
```
### Run with systemd
The example unit assumes the repository is cloned to `~/agent-relay`:
```bash
install -d ~/.config/systemd/user
cp examples/systemd/agent-relay.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now agent-relay
```
To start the user service during boot before an interactive login:
```bash
sudo loginctl enable-linger "$USER"
```
Follow logs with:
```bash
journalctl --user -u agent-relay -f
```
### Add HTTPS
Keep Relay on loopback and put a reverse proxy in front of it. A minimal Caddy configuration is included in [`examples/Caddyfile`](examples/Caddyfile):
```caddyfile
relay.example.com {
reverse_proxy 127.0.0.1:8787 {
flush_interval -1
}
}
```
Configure your MCP client with:
```text
URL: https://relay.example.com/mcp
Transport: Streamable HTTP
Authorization: Bearer <AGENT_RELAY_TOKEN>
```
## Pebble Index 01
For direct instructions, use Index's webhook. Pebble sends the `transcription` field directly to Relay as `multipart/form-data`; Relay preserves that field instead of asking an MCP Sandbox model to reinterpret it.
1. In the Pebble app's Index settings, set the webhook URL to `https://relay.example.com/webhook`.
2. Add an `Authorization` header containing the full `Bearer <token>` value.
3. Set **Send** to transcription.
4. Set **Trigger** to the button gesture you want to use for Relay.
5. Set that gesture's behavior to **Webhook only** so MCP Sandbox does not reinterpret or duplicate the instruction.
6. Keep the other gesture assigned to the normal Index agent if you still want its note-taking behavior.
Example requests:
```text
Inspect the current project, run its tests, and summarize any failures.
Send “hello” to the Codex agent working in the API pane.
Check my latest task.
```
### Pebble MCP fallback
If your Pebble app does not deliver physical ring transcriptions through webhook-only mode, use MCP Sandbox with Relay's routing prompt:
1. Add Relay as a Streamable HTTP MCP server at `https://relay.example.com/mcp` with the bearer token.
2. Open that server's **Prompts** setting and enable `supervisor_router`.
3. Assign Relay to the sandbox group used by your chosen ring gesture.
4. Set that gesture to **MCP Sandbox**.
The prompt tells Pebble's cloud agent to call `forward_to_supervisor` exactly once, copy your full instruction verbatim, preserve explicit Codex/Claude/Herdr routing, and never perform the task itself. This makes MCP a narrow router rather than a second development agent.
MCP still has an intermediary model, so prompt adherence cannot provide the same hard fidelity as a direct webhook. Relay stores whatever `forward_to_supervisor` receives without rewriting it.
The same design works with any audio device or companion app that can send Pebble-compatible multipart transcriptions or call MCP.
## Policies
Relay reads the configured Markdown policy before every task. Edit the file and the next job uses the new policy; no restart is required.
Three examples are included:
- [`policies/safe.md`](policies/safe.md): read-only remote assistance
- [`policies/developer.md`](policies/developer.md): normal development and agent coordination
- [`policies/unrestricted.md`](policies/unrestricted.md): the same authority as an interactive owner prompt
Select one by copying it to the configured path:
```bash
cp policies/unrestricted.md ~/.config/agent-relay/policy.md
```
A policy is a model instruction, **not a security boundary**. If the agent must not access something, enforce that with operating-system permissions, a container, or a separate VM.
## Configuration
Configuration uses environment variables so credentials stay outside the repository.
| Variable | Default | Description |
| --------------------------------------- | --------------------------------- | -------------------------------------------------------- |
| `AGENT_RELAY_TOKEN` | required | Bearer token; minimum 32 characters |
| `AGENT_RELAY_BIND` | `127.0.0.1` | HTTP bind address |
| `AGENT_RELAY_PORT` | `8787` | HTTP port |
| `AGENT_RELAY_ADAPTER` | `herdr` | Agent adapter; only `herdr` is currently implemented |
| `AGENT_RELAY_HERDR_BIN` | `herdr` | Herdr executable path |
| `AGENT_RELAY_HERDR_TARGET` | `supervisor` | Unique live Herdr agent name |
| `AGENT_RELAY_HERDR_RECOVERY` | `false` | Recreate a missing named target before its next task |
| `AGENT_RELAY_HERDR_RECOVERY_KIND` | `pi` | Agent kind passed to `herdr agent start` |
| `AGENT_RELAY_HERDR_RECOVERY_CWD` | `~/repos` | Working directory for the recovered workspace |
| `AGENT_RELAY_HERDR_RECOVERY_WORKSPACE` | `agent-relay` | Label for a newly created recovery workspace |
| `AGENT_RELAY_HERDR_RECOVERY_SESSION` | unset | Session identity used to find an already-running agent |
| `AGENT_RELAY_HERDR_RECOVERY_ARGS_JSON` | `[]` | JSON string array passed directly to the recovered agent |
| `AGENT_RELAY_HERDR_RECOVERY_TIMEOUT_MS` | `60000` | Recovered agent startup timeout |
| `AGENT_RELAY_POLICY_FILE` | `~/.config/agent-relay/policy.md` | Hot-reloaded policy file |
| `AGENT_RELAY_STATE_DIR` | `~/.local/state/agent-relay` | Jobs and result files |
| `AGENT_RELAY_TASK_TIMEOUT_MS` | `1800000` | Agent task timeout in milliseconds |
| `AGENT_RELAY_MAX_PROMPT_CHARS` | `10000` | Maximum submitted prompt size |
| `AGENT_RELAY_RESULT_MAX_CHARS` | `1200` | Maximum result returned to the MCP client |
| `AGENT_RELAY_WEBHOOK_MAX_BYTES` | `1000000` | Maximum accepted multipart webhook body size |
## Adding adapters
An adapter translates one queued Relay task into one run on a target runtime:
```ts
export interface AgentAdapter {
readonly kind: string;
readonly target: string;
run(context: AdapterRunContext): Promise<AdapterRunResult>;
}
```
Potential adapters include:
- Persistent Pi RPC sessions
- Codex programmatic sessions
- Claude Code headless sessions
- OpenCode servers
- Generic HTTP agent APIs
- Noninteractive CLI commands
- Existing tmux panes
Adapters should use structured APIs where available and avoid shell interpolation. The MCP, authentication, policy, queue, persistence, and result layers remain runtime-neutral.
## Security
An internet-facing Relay can instruct an agent with all of that agent's permissions. Read [`SECURITY.md`](SECURITY.md) before deployment.
At minimum:
- Generate a long random token and treat it like an SSH credential.
- Keep Relay bound to loopback behind HTTPS.
- Do not commit tokens or instance configuration.
- Use a restricted operating-system user when you need enforceable limits.
- Add reverse-proxy rate limiting for a public endpoint.
## Development
```bash
npm install
npm run validate
```
`validate` checks formatting and types, runs the test suite, and creates a clean production build.
## License
[MIT](LICENSE)
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues