Privacy
Agent Relay is local-first. Every byte written by the always-on layer
lands in ~/.relay/ on your machine. Nothing uploaded unless you
explicitly opt into the proxy mode or telemetry — and even then, the
defaults stay conservative.
What gets captured
| Adapter | Captures |
|---|---|
| Claude Code hook | Tool name on each invocation, stop reasons, notification text. |
| PTY wrapper | Process lifecycle + line-level rate-limit detection from stdout. The agent's full stdout is mirrored to your terminal, not copied to disk. |
| Codex Stop hook | Last assistant message, error/output fields, and transcript tail only when needed to detect a rate limit. |
| Gemini Notification hook | Notification payload fields used only to detect Google quota/rate-limit signals. |
| OpenCode plugin | Selected session.error, message.updated, and session.status event payload fields used only to detect quota/rate-limit signals. |
| OpenCode export | Optional sanitized opencode export output for Relay-managed OpenCode sessions when Relay can determine the native OpenCode session id. Export failure is non-fatal. |
| Warp MCP | Whatever an MCP-aware client sends to relay mcp serve; relay install --warp only writes a snippet and paste instructions. |
| Proxy mode (opt-in) | LLM API response headers (rate-limit counters, retry-after). Request/response bodies are seen in flight but never written to disk. |
What gets redacted
By default (privacy.redact_secrets = true), every adapter applies the
same redaction rules before writing:
- Object keys matching
(secret|key|token|password|credential|bearer|api[_-]?key)→ value replaced with[redacted]. - Inline values matching well-known token formats → replaced inline:
sk-…(Anthropic / OpenAI)ghp_…(GitHub personal access tokens)xox[abp]-…(Slack)
- Tool argument values are not captured by default
(
privacy.capture_tool_args = false) — we keep the structure ("agent calledEditwith these arg names") but drop the values. Flip the flag if you want lossless capture and trust your redaction rules.
Same rules apply across the Python daemon and MCP server. Source:
src/agent_relay/daemon/redact.py.
Where it lives on disk
~/.relay/
├── VERSION # layout version
├── config.toml # your settings
├── sessions/ # per-session manifests
├── events/<session>.jsonl # append-only event log
├── snapshots/<id>.md # handoff primers
├── logs/daemon.log # daemon stdout/stderr
├── relay.sock # unix socket (POSIX)
└── telemetry-id # random UUID, only present if you opt in
To inspect everything captured for a session:
cat ~/.relay/events/<session>.jsonl # raw events
relay snapshots # the resulting primers
relay daemon status # in-memory viewTo wipe everything captured:
relay uninstall # remove adapters + launch agent
rm -rf ~/.relay # delete the dataTelemetry
Off by default. Toggle on in ~/.relay/config.toml:
[telemetry]
enabled = true
endpoint = "https://telemetry.agent-relay.dev/v1/ingest"When enabled we send aggregate-only counters: which adapters are in use,
handoff success rate, the daemon version. No event content, no file
paths, no environment variables, no per-session anything. The install id
is a random UUID stored at ~/.relay/telemetry-id; deleting it rotates
the id.
What gets sent (full schema):
{
"schema": "agent-relay.telemetry/v1",
"event": "handoff.fired",
"install_id": "01HRZ…",
"sdk_version": "0.1.0",
"properties": { … aggregate counters … }
}The full source is at
src/agent_relay/daemon/telemetry.py.
Sends are background-threaded with a 3-second timeout and silently
dropped on failure — telemetry can never block the daemon.
What we never store
- Your source code.
- LLM prompts or responses.
- Hidden OpenCode state that is not exposed by
opencode export. - Environment variables.
.envfile contents.- Tool argument values (with the default config).
- Anything resembling an API key — even when the proxy is on.
Reporting concerns
If you find a redaction miss or a place where data leaves the machine unexpectedly, please open an issue at github.com/bethvourc/agent--relay. This is the area we treat as a hair-trigger bug class.