App Integration Guide
This guide clarifies the boundaries between your app, Relay, and AI agents. Understanding who owns what prevents the most common integration mistakes.
What Your App Manages
- One env var:
RELAY_APP_TOKEN— therlk_token from the Relay Dashboard - Agent ID mapping: Store the
agent_idstring (e.g.,athena_k9p2m3) to route events. Nothing else. - WebSocket lifecycle: Connect on startup, reconnect with exponential backoff on disconnect.
- Message routing: When your app receives
token,reply, orerrormessages, route them to the right handler based onevent_idandagent_id. - Thread management: Your app decides the
thread_idformat. Relay doesn't care what it looks like — it just uses it for session continuity.
What Your App Does NOT Manage
- Agent tokens (
rla_) — These belong to the agent. Your app never sees, stores, or displays them. - Agent registration — Agents are registered in the Relay Dashboard, not via your app.
- Token rotation — Relay Dashboard action only.
- Allowlisting — Which apps can talk to which agents is configured in the Relay Dashboard.
- Connection config — Agent-side config (retry logic, heartbeat) is the agent's responsibility.
Three Participants, Clear Boundaries
┌─────────────────────┐ ┌──────────────────┐ ┌─────────────────────┐
│ YOUR APP │ │ RELAY │ │ AGENT │
│ │ │ (message broker) │ │ (e.g. Athena) │
│ Owns: │ │ Owns: │ │ Owns: │
│ · rlk_ app token │────▶│ · Routing │◀────│ · rla_ agent token │
│ · agent_id mapping │ │ · Delivery │ │ · Session state │
│ · thread_id format │ │ · Error codes │ │ · AI processing │
│ · Message handling │ │ · Heartbeat │ │ · Token streaming │
│ · UI/UX │ │ · Allowlisting │ │ · Reply generation │
│ │ │ │ │ │
│ Does NOT own: │ │ Does NOT own: │ │ Does NOT own: │
│ · Agent tokens │ │ · Payload content│ │ · App tokens │
│ · Agent registration│ │ · Conversation │ │ · Thread format │
│ · Connection config │ │ history │ │ · Routing logic │
└─────────────────────┘ └──────────────────┘ └─────────────────────┘
Key principle: Relay never reads payloads, never decides routing, never filters events. It's a pure pipe. Apps and agents are responsible for everything above the transport layer.
How Your App Discovers Agents
On WebSocket connect, send a discover message:
{ "type": "discover" }
Relay responds with all agents your app is allowed to talk to:
{
"type": "agents",
"agents": [
{
"agent_id": "athena_k9p2m3",
"name": "Athena",
"description": "Content research & writing"
}
]
}
Use this for:
- Populating dropdowns ("assign to agent")
- Showing agent availability in your UI
- Validating that an
agent_idis reachable before sending events
Do NOT poll discover in a loop. Send it once on connect. Update agent status when you receive error messages like AGENT_OFFLINE.
Designing Your Payload
Relay payloads are opaque — Relay never reads them. But here's how to design them well:
Keep payloads slim. Relay has a 64KB limit, but aim for under 2KB. Don't embed large content (documents, images, full conversation history). Instead, include a URL the agent can fetch.
Use signed URLs for context fetching. Agents authenticate to Relay, not to your app. Options for letting agents fetch context from your API:
- HMAC-signed URLs (recommended):
https://yourapp.com/context?sig=hmac&exp=timestamp— self-authenticating, time-limited - Short-lived tokens in the payload: include a one-time-use token the agent passes to your API
- Inline everything: only works for small payloads under 64KB
Include an event field so agents know what to do:
{
"event": "task.assigned",
"task_id": "abc123",
"context_url": "https://yourapp.com/tasks/abc123/context?sig=...&exp=..."
}
Don't duplicate Relay metadata. Relay already provides thread_id, event_id, app_id, and session_key on the delivered message. Don't repeat them in your payload.
What NOT to put in payloads
- Agent tokens or API keys (security risk)
- Callback URLs (use Relay messages for responses — not HTTP callbacks)
- Session keys (Relay provides
thread_idandsession_keyalready) - Large content blobs (use context URLs instead)
- Relay connection config (that's infrastructure, not business data)
event_id Lifecycle
Understanding who generates the event_id and how it flows is critical for correlating messages:
- App sends
eventmessage — noevent_id(the app doesn't generate it) - Relay generates
event_idand returns it in theacceptedresponse - Relay includes
event_idwhen forwarding the event to the agent - Agent must echo
event_idin alltoken,reply, anderrormessages - App uses
event_idto correlate incoming tokens/replies to the original event
App → Relay: { type: "event", agent_id, thread_id, payload }
Relay → App: { type: "accepted", event_id: "evt_abc", agent_id, session_key }
Relay → Agent: { type: "event", event_id: "evt_abc", app_id, thread_id, payload }
Agent → Relay: { type: "token", event_id: "evt_abc", token: "Hello" }
Relay → App: { type: "token", event_id: "evt_abc", agent_id, token: "Hello" }
Agent → Relay: { type: "reply", event_id: "evt_abc", content: "Hello world", metadata }
Relay → App: { type: "reply", event_id: "evt_abc", agent_id, thread_id, reply, metadata }
Store a local map of event_id → your internal context after receiving accepted. This lets you route incoming tokens and replies to the right place in your UI.
Common Mistakes
| Mistake | Why it's wrong |
|---|---|
Storing rla_ agent tokens in your app's database | You don't need them — just the agent_id string |
| Building agent registration UI in your app | Use the Relay Dashboard |
Polling discover repeatedly | Send once on connect, rely on error messages for status |
Generating event_id client-side | Relay generates it — you get it back in the accepted response |
| Storing Relay connection status in your DB | It's ephemeral — use discover for live status |