Skip to main content

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 — the rlk_ token from the Relay Dashboard
  • Agent ID mapping: Store the agent_id string (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, or error messages, route them to the right handler based on event_id and agent_id.
  • Thread management: Your app decides the thread_id format. 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_id is reachable before sending events
caution

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_id and session_key already)
  • 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:

  1. App sends event message — no event_id (the app doesn't generate it)
  2. Relay generates event_id and returns it in the accepted response
  3. Relay includes event_id when forwarding the event to the agent
  4. Agent must echo event_id in all token, reply, and error messages
  5. App uses event_id to 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 }
tip

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

MistakeWhy it's wrong
Storing rla_ agent tokens in your app's databaseYou don't need them — just the agent_id string
Building agent registration UI in your appUse the Relay Dashboard
Polling discover repeatedlySend once on connect, rely on error messages for status
Generating event_id client-sideRelay generates it — you get it back in the accepted response
Storing Relay connection status in your DBIt's ephemeral — use discover for live status