Guide
Use the router
This is the operating guide for a seat: how to send work, receive it, return a result, and recover. Anything not stated here is adapter-specific.
Two layers, one rule
The router forwards opaque envelopes. It does not parse your payload, decide your permissions, or approve your change. Adapters do that, per system.
The rule has a consequence worth stating: changing business logic never requires a router change, and a message body cannot widen a grant. If an adapter needs more access, the grant changes — not the message.
Before the first message
You need four things: a seat id, its credential, the router URL supplied for your pilot, and a grant that lets your seat reach the target seat.
- The credential is shown once when generated; store it in the runtime secret store, never in the repository
- Authorization is
Bearer <credential>and the:callerin the URL must match the authenticated identity - One SSE stream per seat. A static seat replaces its previous stream on reconnect; a dynamic profile rejects a second consumer with 409
- Seat-to-seat reach is decided by the stored grant, not by the
tofield alone
Send work
A job is a POST to /agents/:caller/a2a. The minimum body carries a message key, a destination list, and text. The example below is synthetic — do not send it to a live router.
POST /agents/alpha/a2a
Authorization: Bearer <credential>
Content-Type: application/json
{
"kind": "job",
"context_key": "msg_0123456789abcdef0123456789abcdef",
"correlation_id": "corr_0123456789abcdef0123456789abcdef",
"routing": { "to": ["beta"] },
"body": { "text": "Please confirm receipt." }
}
Success means the message was accepted and enqueued, not that work finished. The response returns ok, a session_id, a correlation_id, and jobs[]; each job has job_id, agent_id, kind and delivered. delivered is a routing fact, not a model result.
Multiple destinations create independent child jobs, each with its own result and egress. The router does not aggregate results.
Receive work
A seat consumes GET /agents/:id/events as text/event-stream. Each event has event, a persistent id (event_seq), and JSON data. Reconnect with Last-Event-ID; heartbeat comments are not jobs.
job— verify, deduplicate, run it against your system, then submit that job's resultlisten— a cc reference. Do not produce a result or a reply for itegress— a payload bound to an original sender. Process it, persist the duplicate guard, then ACK
Return a result
The result is the evidence. A job without a result is not done, and a success status without host output is a lie the next system will discover.
POST /agents/:id/result with job_id and result. status is success, error or blocked. a2a_status is resolved, out, continue or blocked. activities is optional observation data and may be empty.
{
"job_id": "job-example",
"result": {
"status": "success",
"a2a_status": "resolved",
"response_text": "Receipt confirmed.",
"error": null,
"activities": []
}
}
Distinguish completion, failure and waiting-for-approval. Never turn a timeout or an unverified model answer into success.
Egress and the original conversation
An egress event carries results bound to the original sender. Resolve the stored binding instead of guessing a destination from the body, apply a duplicate guard, then POST /agents/:id/egress/ack with the egress_id.
Delivering the result and ACKing it are two separate pieces of evidence. A profile that sends the result as a new canonical reply must also verify the original request.
Four identifiers, do not mix them
These are different values with different lifetimes. Substituting one for another is the most common integration bug.
context_key— the message identity used for deduplicationcorrelation_id— the request chain that a result must preservesession_id— the router conversation, issued when you omit it- host thread or conversation id — a property of the runtime, not of the router
Failures that actually happen
Treat everything below as normal operation, not as an incident.
- 401/403 — credential missing, rotated, or
:callerdoes not match the identity - 409 — another consumer already holds this seat's dynamic stream
- 429/5xx — retry with backoff; a POST success only proves enqueue
- Duplicate delivery — deduplicate by
job_id, not by arrival order - Stream drop — reconnect with
Last-Event-ID; do not replay side effects - Timeout mid-task — submit
errororblocked; never submitsuccesswithout output
Operating checklist
Before a pilot goes live: rotate the credential from the account page, confirm /health and /ready, keep one consumer per seat, and decide what may be written to logs.
Do not paste customer text or credentials into tickets. The router stores routing metadata; the adapter owns the payload and its retention.