Skip to main content

Payload Design

The payload is the heart of your event. It's the context you send to the agent. Relay never opens it, never parses it, never modifies it. It's sacred.

This guide helps you design payloads that are:

  • Complete — the agent has all the information it needs
  • Clear — the agent understands what you're asking
  • Efficient — no wasted data, respects the 64KB limit
  • Traceable — includes enough metadata to route responses

Core Principle: Payload Is Opaque

Relay never looks inside the payload.

You could send:

{
"type": "event",
"agent_id": "athena",
"thread_id": "task-123",
"payload": "summarize this task"
}

Or:

{
"type": "event",
"agent_id": "athena",
"thread_id": "task-123",
"payload": {
"event": "comment.mention",
"raw_html": "<div>...</div>",
"binary_data": "...",
"custom_format": { ... }
}
}

Relay doesn't care. It just passes the entire payload through to the agent, and echoes it back in the reply.

This means:

  • You define the schema
  • You can evolve it without telling Relay
  • Different apps can send different payload shapes
  • The agent must understand your payload format

Why This Design?

If Relay tried to enforce a schema, it would be:

  1. Too rigid — Different apps need different things

    • Portal: comment context, task metadata
    • Flow: task description, requirements, deadline
    • Academy: course, module, topic, difficulty
  2. Too prescriptive — Relay wouldn't know what agents need

    • Some agents want full conversation history
    • Some agents want just the question
    • Some agents need specialized metadata
  3. Too complex — Relay would need to parse, validate, transform, migrate

    • Instead, you own your schema
    • You can change it at your own pace
    • You can store versions in your own database

Payload Size Limits

Maximum payload size: 64 KB (64,000 bytes)

This is enough for:

  • Lengthy code snippets (~5,000 lines)
  • Full task descriptions with history
  • A document with multiple pages
  • Structured data with nested objects

Examples that fit:

{
"task_title": "Q2 Roadmap Review",
"description": "Long multi-paragraph description...",
"comments": [
{ "author": "John", "text": "..." },
{ "author": "Sarah", "text": "..." },
// ... 100 comments ...
],
"attachments": [ ... ],
"project_context": { ... }
}

This is typically 5-20 KB.

If you exceed 64 KB:

  • Relay rejects the event with an error
  • You need to trim the payload
  • Consider: do you really need all that context?

Payload Best Practices

1. Include Event Type

Always include a field that describes what kind of event this is:

{
"event": "comment.mention",
// OR
"event_type": "task.assign",
// OR
"type": "quiz.generate"
}

This helps the agent understand what it's being asked to do.

2. Include Routing Information

Include anything the app needs to route the response back:

{
"event": "comment.mention",
"task_id": "task-123",
"comment_id": "cmnt-456",
"message_id": "msg-789"
}

When the agent replies, the app needs to know where to post the response. The payload gets echoed back, so the app can use task_id to route.

3. Include Sender/User Information

Tell the agent who made the request:

{
"sender": {
"id": "user-456",
"name": "Sarah Chen",
"email": "sarah@company.com"
}
}

The agent might want to personalize its response or know the context.

4. Include Relevant Context

Send what the agent needs to be useful. Examples:

Portal (comment mention):

{
"event": "comment.mention",
"task_id": "task-123",
"task_title": "Q2 Roadmap Review",
"task_description": "...",
"comments": [
{ "author": "John", "text": "Mobile-first design is key" },
{ "author": "Sarah", "text": "@athena summarize this" }
],
"project": "Smiling Group Platform",
"priority": "high",
"due_date": "2026-04-15"
}

The agent has full context to understand the discussion and give a relevant summary.

Flow (task assignment):

{
"event": "task.assign",
"task_id": "task-789",
"task_title": "Design database schema for comments",
"description": "We need a scalable schema that supports...",
"tech_stack": ["PostgreSQL", "Node.js", "Redis"],
"assigned_to": "klyve",
"deadline": "2026-04-20",
"project_context": {
"name": "Project Atlas",
"current_load": "50K users",
"scale_target": "1M users by Q3"
}
}

Klyve knows the requirements, constraints, and tech stack. It can design appropriately.

Academy (quiz generation):

{
"event": "quiz.generate",
"course_id": "course-101",
"module_id": "module-5",
"topic": "Functions in Python",
"difficulty": "intermediate",
"num_questions": 5,
"learning_objectives": [
"Define and call functions",
"Understand parameters and return values",
"Use default parameters and kwargs"
],
"material_summary": "Functions are reusable blocks of code. They take parameters and return values..."
}

Athena knows what to teach and at what level. It can generate appropriate questions.

5. Don't Over-Send

64 KB is generous, but don't bloat the payload. Remove:

  • Redundant data — if you're sending task_id, don't send the entire task object
  • Large media — don't embed images, videos, or PDFs in the payload
  • Unnecessary history — keep conversation history focused

Example of bloat:

{
// Good
"task_id": "task-123",
"task_title": "...",

// Bad — the full task object
"task": {
"id": "task-123",
"title": "...",
"description": "...",
"created_at": "...",
"updated_at": "...",
"created_by": { ... },
"updated_by": { ... },
// ... 20 more fields ...
}
}

Just send what you need.

6. Include Instructions

If the agent should follow specific guidelines, include them:

{
"event": "quiz.generate",
"topic": "Functions",
"instructions": "Create multiple-choice questions. Each question should have 4 options, exactly one correct answer. Include a brief explanation for each.",
"rules": [
"Do not include questions about advanced topics like decorators",
"Keep language accessible to beginners",
"Vary the types of questions (definition, application, analysis)"
]
}

Or:

{
"event": "comment.mention",
"task_id": "task-123",
"instructions": "Please summarize the main discussion points in bullet form, highlighting any decisions made and action items.",
"format": "markdown"
}

Clear instructions lead to better responses.

7. Use Consistent Naming

Within your app, use consistent field names:

  • Always task_id, never taskId or id
  • Always user_name, never userName or name
  • Always created_at, never createdAt or created

This makes your payloads predictable.


Example Payloads by App

Portal (Comment Mention)

{
"event": "comment.mention",
"source": "portal",
"task_id": "task-abc123",
"comment_id": "cmnt-xyz789",
"task_title": "Design new dashboard component library",
"task_description": "We need a reusable component library for...",
"comments": [
{
"id": "cmnt-001",
"author": "Jordan",
"timestamp": "2026-04-07T10:30:00Z",
"text": "I think we should use Storybook for documentation"
},
{
"id": "cmnt-002",
"author": "Alex",
"timestamp": "2026-04-07T11:15:00Z",
"text": "Agreed, but let's also consider accessibility from the start"
},
{
"id": "cmnt-xyz789",
"author": "Pat",
"timestamp": "2026-04-07T11:45:00Z",
"text": "@athena What are the best practices for building a component library?"
}
],
"project": "Smiling Group",
"priority": "high",
"due_date": "2026-05-01",
"sender": {
"id": "user-pat",
"name": "Pat",
"role": "design-lead"
}
}

Flow (Task Assignment)

{
"event": "task.assign",
"source": "flow",
"task_id": "task-flow-789",
"task_title": "Implement real-time notifications",
"description": "Add WebSocket support for real-time notifications. Should support push notifications on mobile.",
"assigned_agent": "klyve",
"priority": "high",
"deadline": "2026-04-30",
"tech_stack": {
"backend": "Node.js",
"database": "PostgreSQL",
"messaging": "WebSocket + Redis pub/sub"
},
"requirements": [
"Support real-time delivery",
"Queue notifications for offline users",
"Include mobile push support",
"Add configuration for notification types"
],
"acceptance_criteria": [
"All notification types can be sent in real-time",
"Offline users receive queued notifications on reconnect",
"Mobile push tested with iOS and Android",
"Performance: <100ms latency for notifications"
],
"context": {
"project": "Project Atlas",
"current_users": "50K",
"scale_target": "1M by Q3"
},
"created_by": {
"id": "user-alex",
"name": "Alex Chen",
"role": "product-manager"
}
}

Academy (Quiz Generation)

{
"event": "quiz.generate",
"source": "academy",
"course_id": "course-py101",
"module_id": "module-functions",
"lesson": "Functions and Scope",
"difficulty": "intermediate",
"num_questions": 5,
"learning_objectives": [
"Understand function definition and calling syntax",
"Learn about parameters, arguments, and return values",
"Understand scope and variable lifetime",
"Write functions with default arguments and *args/**kwargs"
],
"material_covered": {
"topics": ["def statement", "parameters", "return", "scope", "default args", "kwargs"],
"code_examples": [
"def greet(name): return f'Hello {name}'",
"def add(*args): return sum(args)",
"def config(host='localhost', port=5000): ..."
]
},
"question_format": "multiple_choice",
"answer_options_per_question": 4,
"instructions": "Create 5 questions that test understanding of the covered topics. Include one question on each topic. Each question should have 4 options with exactly one correct answer.",
"constraints": [
"Do not include advanced topics like decorators or closures",
"Keep wording clear and accessible to beginners",
"Vary the question types (definition, application, analysis)",
"Include brief rationale for the correct answer"
],
"created_by": {
"id": "user-instructor",
"name": "Dr. Sarah",
"role": "instructor"
}
}

Common Mistakes

❌ Sending Too Much Data

{
// DON'T: Send full user objects
"created_by": {
"id": "user-123",
"name": "John",
"email": "john@company.com",
"phone": "555-1234",
"timezone": "UTC-5",
"preferences": { ... },
"subscription": { ... },
// ... 50 more fields ...
}
}

// DO: Send only what's relevant
{
"created_by": {
"id": "user-123",
"name": "John"
}
}

❌ Unclear Instruction

{
// DON'T
"payload": "summarize this"

// DO
"payload": {
"event": "comment.mention",
"instruction": "Summarize the discussion above in bullet points, highlighting key decisions and action items."
}
}

❌ Inconsistent Naming

{
// DON'T: Mix naming styles
"task_id": "task-123",
"taskTitle": "...",
"task.author": "...",
"author_email": "..."

// DO: Consistent snake_case
"task_id": "task-123",
"task_title": "...",
"task_author": "...",
"author_email": "..."
}

❌ Missing Routing Information

{
// DON'T: No way to route response
"text": "Please summarize this"

// DO: Include routing info
"task_id": "task-123",
"comment_id": "cmnt-456",
"text": "Please summarize this"
}

Schema Versioning

As your app evolves, your payload schema will change. You have a few options:

Option 1: Include a Version Field

{
"payload_version": "1.0",
"event": "comment.mention",
"task_id": "task-123",
"comments": [...]
}

Later, when you change the schema:

{
"payload_version": "2.0",
"event": "comment.mention",
"task_id": "task-123",
"comments": [...],
"thread_id": "thread-456" // new field in v2.0
}

Agents can check the version and handle accordingly.

Option 2: Keep Backward Compatibility

Keep old fields, add new ones:

// v1.0
{
"event": "comment.mention",
"task_id": "task-123",
"comments": [...]
}

// v1.1 (backward compatible)
{
"event": "comment.mention",
"task_id": "task-123",
"comments": [...],
"new_field": "optional data"
}

Agents that understand v1.0 still work. New agents can use new_field.

Option 3: New Event Type

For major changes, introduce a new event type:

{
"event": "comment.mention.v2",
"task_id": "task-123",
// ... new schema ...
}

Agents handle both old and new types.


Testing Your Payload Design

Before sending events to your agent:

  1. Check completeness — Does the agent have all info it needs?
  2. Check clarity — Will the agent understand what's being asked?
  3. Check size — Is it under 64 KB?
  4. Check routing — Can your app route responses correctly?
  5. Check consistency — Are field names consistent?

Use the Relay dashboard to test events manually, or write a script:

import json

payload = {
"event": "comment.mention",
"task_id": "task-123",
# ... your data ...
}

# Check size
size_bytes = len(json.dumps(payload).encode('utf-8'))
print(f"Payload size: {size_bytes} bytes")

if size_bytes > 64000:
print("❌ Payload too large!")
else:
print("✓ Payload OK")

Summary

Your payload is the contract between your app and the agent:

✓ Define a clear schema ✓ Include all relevant context ✓ Keep it concise (avoid bloat) ✓ Make routing information explicit ✓ Include instructions if needed ✓ Version your schema for evolution

Relay never looks inside. You're in complete control.


Next Steps