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:
-
Too rigid — Different apps need different things
- Portal: comment context, task metadata
- Flow: task description, requirements, deadline
- Academy: course, module, topic, difficulty
-
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
-
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, nevertaskIdorid - Always
user_name, neveruserNameorname - Always
created_at, nevercreatedAtorcreated
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:
- Check completeness — Does the agent have all info it needs?
- Check clarity — Will the agent understand what's being asked?
- Check size — Is it under 64 KB?
- Check routing — Can your app route responses correctly?
- 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
- Explore Agents as Users to understand context
- Read Sessions for conversation continuity
- Check Sending Events for implementation