Skip to main content

Agent Messaging

Fyso agents can send and receive messages to each other using the _agent_messages system entity. This enables multi-agent pipelines where agents coordinate work asynchronously.

How it works

  1. Agent A sends a message to Agent B using fyso_agents({ action: "send_message", ... }).
  2. The message lands in Agent B's inbox with status pending.
  3. If auto_run: true, the server runs Agent B automatically with the message as its input.
  4. Agent B can reply by sending a message back with in_reply_to set to the original message ID, forming a thread.

Chains are limited to 5 hops. If a chain would exceed the limit, the message is left as pending and a message.chain_limit SSE event is emitted.

Sending a message

MCP:

fyso_agents({
action: "send_message",
to_agent: "inventory-agent",
subject: "Low stock alert",
payload: { product_id: "uuid", current_stock: 3 },
priority: "high",
auto_run: true
})

REST:

curl -X POST https://api.fyso.dev/api/tenants/<slug>/agent-messages/send \
-H "Authorization: Bearer <api_key>" \
-H "Content-Type: application/json" \
-d '{
"from_agent": "warehouse-monitor",
"to_agent": "inventory-agent",
"subject": "Low stock alert",
"payload": { "product_id": "uuid", "current_stock": 3 },
"priority": "high",
"auto_run": true
}'

Response:

{
"success": true,
"data": {
"message_id": "uuid",
"from_agent": "warehouse-monitor",
"to_agent": "inventory-agent-a3f2c1",
"subject": "Low stock alert",
"priority": "high",
"status": "pending",
"created_at": "2026-03-23T10:00:00Z"
}
}

Agent name resolution

The to_agent field supports fuzzy matching:

  • Exact slug match → resolved immediately.
  • Single prefix match: "cero" resolves to "cero-b4e5d6" if that is the only agent whose slug starts with cero-.
  • Multiple prefix matches → error with a list of candidates.

Message fields

FieldTypeRequiredDescription
to_agentstringYesRecipient agent name or slug (fuzzy matching)
from_agentstringNoSender name. Defaults to mcp-caller when using MCP
subjectstringNoOptional subject line
payloadobjectNoArbitrary JSON data (max 64 KB)
prioritynormal | high | urgentNoDefault: normal
in_reply_tostringNoUUID of the parent message (enables threading)
auto_runbooleanNoRun recipient agent automatically on delivery

Reading the inbox

fyso_agents({
action: "inbox",
agent_name: "inventory-agent",
inbox_status: "pending"
})
inbox_statusReturns
pendingUnread messages (default)
readAlready-read messages
allAll messages

REST:

GET /api/tenants/<slug>/agent-messages/inbox/<agentName>?status=pending&limit=50

Reading a single message

Fetching a message marks it as read.

fyso_agents({ action: "read_message", message_id: "<uuid>" })

REST:

GET /api/tenants/<slug>/agent-messages/<messageId>/read

Archiving a message

fyso_agents({ action: "archive_message", message_id: "<uuid>" })

REST:

POST /api/tenants/<slug>/agent-messages/<messageId>/archive

Counting unread messages

fyso_agents({ action: "count_unread", agent_name: "inventory-agent" })

REST:

GET /api/tenants/<slug>/agent-messages/count/<agentName>

Response:

{ "agent": "inventory-agent-a3f2c1", "unread": 4 }

Auto-run and chain depth

When auto_run: true is set and the recipient is a Fyso agent, the server runs the agent immediately in the background. The HTTP response returns before the agent finishes — the run is fire-and-forget.

The agent receives the message as its user input with this context:

trigger=message
message_id=<uuid>
from_agent=<sender>
subject=<subject>
payload=<json>
thread_depth=<depth>
max_depth=5

Chain depth limit: If following the in_reply_to chain back to the root produces 5 or more hops, the auto-run is skipped. The message is left as pending and a message.chain_limit SSE event is emitted on the event stream. You can monitor this event to detect runaway loops.

SSE notifications

External agents can subscribe to incoming messages via the SSE event stream. Pass your agent_id (from .fyso-agent) as a query parameter:

curl -N "https://api.fyso.dev/api/v1/tenants/<slug>/events/stream?agent_id=<agent_id>" \
-H "Authorization: Bearer <api_key>"

Incoming messages arrive as message.received events. Chain-limit notifications arrive as message.chain_limit. See SSE Event Stream for full details.

Access control

Caller typeCan read/send
Admin token / API keyAny agent inbox
Bot JWT (participant)Own inbox only, own from_agent only

Inbox limits

Each agent inbox holds at most 1000 pending messages. Sending to a full inbox returns an error.

REST API endpoints

MethodPathDescription
POST/api/tenants/:slug/agent-messages/sendSend a message
GET/api/tenants/:slug/agent-messages/inbox/:agentNameList inbox
GET/api/tenants/:slug/agent-messages/:messageId/readFetch + mark read
POST/api/tenants/:slug/agent-messages/:messageId/archiveArchive
GET/api/tenants/:slug/agent-messages/count/:agentNameCount unread