Skip to main content

Event Patterns

Relay doesn't care what triggered your event

This is the most important thing to understand: Relay has no opinion about what type of event you're sending. The payload field is completely opaque — Relay forwards it to the agent untouched.

Your app decides:

  • When to send an event to Relay (what condition triggered it)
  • What goes in the payload (any JSON up to 64KB)

Your agent decides:

  • How to handle different event types (read the event field in the payload and branch logic)

Relay just bridges them.


The one required field in payload

There is no required shape for payload. However, we strongly recommend including an event field as the first key — this is the string your agent uses to know what kind of thing just happened:

{
"event": "task.assigned",
... the rest is up to you
}

This is a convention, not enforced by Relay.


Common event patterns

1. Mention / @ping

The classic pattern. User @mentions an agent in a comment, chat, or thread.

{
"type": "event",
"agent_id": "athena",
"thread_id": "task-123",
"payload": {
"event": "comment.mention",
"message": "@athena can you summarize this task?",
"sender": { "id": "user-456", "name": "Christian Garcia" },
"context_url": "https://yourapp.com/tasks/123"
}
}

App-side trigger: User types @agentname in a comment box. App detects the mention, sends this event to Relay instead of a normal comment post.


2. Task assigned to agent

Agent is treated as a user. When a task is assigned to the agent's user account, the app sends an event.

{
"type": "event",
"agent_id": "athena",
"thread_id": "task-456",
"payload": {
"event": "task.assigned",
"task_id": "task-456",
"task_title": "Reconcile Q1 expenses",
"task_description": "Pull all receipts from Drive and categorize by department.",
"due_date": "2026-04-15",
"assigned_by": { "id": "user-789", "name": "Katrina" },
"priority": "high",
"attachments": [
{ "name": "Q1_receipts.pdf", "url": "https://..." }
]
}
}

App-side trigger: Assignment dropdown → agent selected. App sends to Relay. Agent starts working immediately and replies with a status update or asks a clarifying question.

Key insight: The agent doesn't need to poll. The assignment IS the trigger. This is the operational automation pattern.


3. Recurring / scheduled task

Your app has an internal scheduler (cron job, task queue, etc.). When it fires, the app sends an event to Relay on the agent's behalf.

{
"type": "event",
"agent_id": "athena",
"thread_id": "recurring-daily-report",
"payload": {
"event": "schedule.fired",
"schedule_name": "Daily standup summary",
"schedule_id": "sched-001",
"fired_at": "2026-04-07T09:00:00Z",
"context": {
"team": "engineering",
"report_type": "standup",
"period": "yesterday"
}
}
}

App-side trigger: Cron job fires → app sends event. Relay has no scheduler — the app owns the schedule. Relay just delivers the event when it arrives.

Thread design tip: Use the schedule ID or a stable ID as the thread_id so the agent always continues the same conversation context for recurring tasks.


4. Form submitted / document ready

AI review triggered by a user action — submitting a form, uploading a document, completing a stage.

{
"type": "event",
"agent_id": "klyve",
"thread_id": "form-submit-8821",
"payload": {
"event": "form.submitted",
"form_type": "expense_report",
"submitted_by": { "id": "user-101", "name": "Marcus" },
"fields": {
"total_amount": 2340.00,
"currency": "USD",
"description": "Team offsite Q1",
"receipts_attached": 4
},
"review_instructions": "Flag anything over $500 per item. Check if receipts match totals."
}
}

App-side trigger: Form submit button → backend validates → sends to Relay → agent reviews and replies with approval/rejection + notes.


5. Webhook relay (external trigger)

Your app receives a webhook from a third-party service (Stripe, GitHub, Jira). The app decides the AI should be aware of it and forwards it.

{
"type": "event",
"agent_id": "athena",
"thread_id": "github-pr-9918",
"payload": {
"event": "github.pr_opened",
"pr_number": 9918,
"pr_title": "Add WebSocket reconnection logic",
"pr_url": "https://github.com/ckgworks/relay/pull/9918",
"author": "christian",
"diff_summary": "...",
"review_checklist": ["security", "performance", "tests"]
}
}

App-side trigger: Your app receives a GitHub webhook → decides to route it to Relay → agent reviews and posts a comment back to the PR (via your app's GitHub integration).

Note: Relay doesn't receive GitHub webhooks directly — your app does, then decides to forward to Relay. Relay is always the bridge between your app and your agent, not the entry point from external services.


6. User-initiated but complex / long-running

User clicks "Analyze" or "Generate Report." The agent will take time. The app sends the event, agent starts working asynchronously, streams progress tokens, sends final reply.

{
"type": "event",
"agent_id": "athena",
"thread_id": "analysis-session-7741",
"payload": {
"event": "analysis.requested",
"analysis_type": "competitor_landscape",
"instructions": "Analyze the top 5 competitors in the AI messaging space. Focus on pricing, API design, and developer experience.",
"output_format": "markdown_report",
"requested_by": { "id": "user-200", "name": "Katrina" }
}
}

App-side trigger: User clicks a button. App shows "Athena is working..." while streaming tokens arrive via the WebSocket. Final reply is displayed when type: "reply" arrives.


What the agent sees

Regardless of event type, the agent always receives the same envelope from Relay:

{
"type": "event",
"event_id": "evt_k9p2m",
"app_id": "portal",
"thread_id": "task-456",
"session_key": "relay:portal:task-456",
"payload": {
"event": "task.assigned",
... everything your app sent
}
}

The agent reads payload.event and branches its logic accordingly. This is standard agent design — one entry point, many event types.


Use dot-notation with resource.action format:

PatternExamples
resource.actiontask.assigned, task.completed, comment.mention
resource.actionform.submitted, document.uploaded
schedule.actionschedule.fired, schedule.skipped
external_service.actiongithub.pr_opened, stripe.payment_failed
user.actionuser.requested_analysis

This convention is not enforced by Relay — it's just a recommendation for consistent agent logic.


What Relay does not do

  • No event routing based on type. Relay does not read payload.event. Routing is based on agent_id only.
  • No scheduling. Relay has no cron scheduler. Your app fires recurring events.
  • No event transformation. Relay forwards the payload byte-for-byte. Shape it before sending.
  • No retry on agent failure. If the agent doesn't reply, Relay logs a timeout. Retry logic is in your app.
  • No event queue (v1). Events are delivered immediately if the agent is connected. If not connected, the event is logged as undelivered. (Redis queue planned for v2.)

App-side filtering is your responsibility

Relay never sees human-to-human interactions. Your app is responsible for deciding which events to send to Relay. A good pattern:

# App backend — comment creation handler
def on_comment_created(comment):
# Only send to Relay if an agent is mentioned
mentioned_agents = detect_agent_mentions(comment.text) # your logic
if mentioned_agents:
for agent_id in mentioned_agents:
relay.send_event(
agent_id=agent_id,
thread_id=comment.thread_id,
payload={
"event": "comment.mention",
"message": comment.text,
"sender": comment.author,
}
)
# Otherwise: normal comment, don't touch Relay
save_comment(comment)

This keeps Relay clean — it only ever sees events that are actually intended for an AI agent.


Summary

Your triggerevent fieldPattern
User @mentions agentcomment.mentionInteractive
Task assigned to agenttask.assignedOperational
Cron job firesschedule.firedScheduled
Form submitted for reviewform.submittedAsync review
External webhook receivedgithub.pr_openedWebhook relay
User requests analysisanalysis.requestedLong-running

One protocol. Any trigger. Your app decides when — Relay delivers.