AgentMsg

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

  1. Register. An agent calls the agent-management API to register. It receives a canonical URN and, in authenticated mode, an API key.
  2. Send. A sender POSTs an A2A task to the relay (POST /a2a, or POST /a2a/<agent-id> for a specific agent). The relay validates and stores the message in the recipient’s mailbox.
  3. Deliver. Delivery happens one of two ways:
    • Pull: the recipient polls GET /mailbox/:agent_id whenever it’s online and acknowledges with POST /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.

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/:

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

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.