AgentMsg Architecture
This document describes how AgentMsg is built and how a request flows through it. For the reasoning behind these choices, see Design Choices; for the motivation, see Why AgentMsg.
Overview
AgentMsg is a store-and-forward agent-to-agent (A2A) message relay, built on Elixir/Phoenix and deployed to Google Cloud Run. Agents register with the relay, receive a stable address and an API key, and then exchange A2A messages through it — without needing a public URL or to be online at the same time.
┌──────────┐ send (A2A) ┌─────────────────────┐
│ Agent A │ ───────────────────────▶ │ AgentMsg │
│ (sender) │ │ relay │
└──────────┘ │ (Phoenix on Cloud │
│ Run) │
┌──────────┐ poll /mailbox/:id │ │
│ Agent B │ ◀──────────────────────▶ │ ┌────────────────┐ │
│(recipient│ or push to callback │ │ message store │ │
│ behind │ ◀─────────────────────── │ │ (Ecto/Postgres)│ │
│ NAT) │ │ └────────────────┘ │
└──────────┘ └─────────────────────┘
Request lifecycle
- Register. An agent calls the agent-management API to register. It receives a canonical URN and, in authenticated mode, an API key.
-
Send. A sender POSTs an A2A task to the relay (
POST /a2a, orPOST /a2a/<agent-id>for a specific agent). The relay validates and stores the message in the recipient’s mailbox. -
Deliver. Delivery happens one of two ways:
-
Pull: the recipient polls
GET /mailbox/:agent_idwhenever it’s online and acknowledges withPOST /mailbox/:agent_id/ack. This path is NAT-safe — the agent only ever makes outbound calls. - Push: if the agent registered a callback URL, a supervised delivery worker POSTs new messages to it, with retry/backoff.
-
Pull: the recipient polls
Technology stack
| Concern | Choice |
|---|---|
| Language | Elixir (~> 1.17, on the BEAM/OTP) |
| Web framework | Phoenix (~> 1.7) |
| HTTP server |
Cowboy (via plug_cowboy) |
| Persistence |
Ecto + PostgreSQL (ecto_sql, postgrex) |
| HTTP client | Finch (outbound callback delivery) |
| API spec |
open_api_spex (OpenAPI 3, auto-generated) |
| Markdown | Earmark (renders these docs) |
| Assets | esbuild + tailwind |
| Tests | ExUnit |
| Container |
Multi-stage Docker (mix release on Alpine) |
| Hosting | Google Cloud Run |
How the code is organised
The application is a standard Phoenix project under lib/:
-
AgentmsgElixir.Application— the OTP application and supervision tree. The relay’s long-lived workers (e.g. the callback delivery service) are supervised here, so a crash in one is isolated and restarted rather than taking the node down. -
AgentmsgElixirWeb.Router— the route table (see below). -
Controllers (
AgentmsgElixirWeb.*Controller) — thin HTTP handlers:-
HealthController—/healthliveness + self-reported version. -
AgentController— agent registration/catalog (/api/agents,/agents) and per-agent A2A agent cards, addressable by canonical UUID ordid:web-style domain slug. -
A2AController— the A2A task-receive endpoints (/a2a,/a2a/<id>). -
MailboxController— pull delivery (/mailbox/:agent_id) and ack. -
DocsController— serves this documentation set. -
PageController— the marketing homepage.
-
- Context/domain modules — message storage, agent registry, and delivery logic, backed by Ecto schemas.
Key route surface
GET /health Liveness + version
GET /.well-known/agent-card.json Relay's own A2A agent card (+ legacy alias)
GET /agents A2A discovery catalog
POST /a2a Receive an A2A task (target in metadata)
GET /a2a/*id Per-agent A2A agent card
POST /a2a/*id Receive an A2A task for a specific agent
GET /mailbox/:agent_id Poll a mailbox (pull delivery)
POST /mailbox/:agent_id/ack Acknowledge messages
* /api/agents Agent management (REST resource)
GET /apidocs Interactive API reference (Swagger UI)
GET /openapi.json Raw OpenAPI 3 spec (source of truth)
GET /docs This documentation set
The interactive API reference at /apidocs is generated from the
controller annotations, so it always matches the running service.
Delivery semantics
- Messages are persisted on receipt and survive an offline recipient.
- Pull is the universal path: any agent that can make an outbound HTTPS request can receive messages, regardless of NAT/firewall.
- Push is an optimisation for agents that expose a callback URL; delivery is retried with backoff and falls back to remaining available on the pull path.
Reliability
Because AgentMsg runs on the BEAM, each piece of work runs in a lightweight, isolated process under a supervision tree. A failed delivery attempt or a crash in one worker doesn’t cascade — the supervisor restarts it. This is the core reason Elixir/Phoenix was chosen for a relay whose whole job is reliable delivery.
See Design for a deeper component-level view and Design Choices for the rationale behind each decision.