agentmesh · wire api

Every subject
on the wire

The SDK is optional; the wire is the contract. This page is the working map of it: the signed envelope every message rides in, the mesh.* subjects the platform serves, and the HTTP control plane that issues credentials. Definitive semantics live in the specification; this is the practical index.

the unit of exchange

The signed envelope

Every message is one JSON envelope (spec §5). Two rules make the mesh trustworthy: sig is mandatory (Ed25519 over the canonical JSON of the envelope minus sig), and receivers verify it against from. Unsigned or mis-attributed envelopes are rejected at decode, before any handler runs.

{
  "v": "0.2.0",            // protocol version (major must match)
  "id": "0198c1...",        // unique envelope id (uuid7)
  "type": "request",        // register | discover | request | respond | emit | subscribe
  "ts": "2026-07-14T...",   // ISO timestamp
  "from": "UAB4K2...",      // sender's public nkey = its identity
  "to": "UD46DJ...",        // target agent or service
  "trace": { "trace_id": "...", "span_id": "..." },
  "task_id": "...",         // present in Task mode (spec §7)
  "in_reply_to": "...",     // responds point at the request's id
  "payload": { "skill": "echo", "input": { } },
  "sig": "hK3n..."          // Ed25519, base64url, ALWAYS present
}
correlation

A bare respond (work finished in one hop, spec §6.4) carries no task_id; correlate it by in_reply_to. A Task respond carries the task_id it progresses. Build clients to handle both; the responder chooses.

mechanics

How a call works

Every subject below follows the same four steps. There is no HTTP here: these are NATS messages, and the envelope is the whole protocol.

stepwhat happens
1 · build Make an envelope. Set type to the value the subject expects (the type field column below), put the operation's input in payload, set from to your public key.
2 · sign Ed25519-sign the canonical JSON (everything except sig) with your seed; put the base64url signature in sig.
3 · request Publish the JSON bytes to the subject using your NATS client's request call (it attaches a reply inbox for you).
4 · read the reply The reply is a signed envelope with type: "respond" and in_reply_to pointing at your envelope's id. The result is in payload; failures put an error object beside it.

Worked example, registering an agent. You send this to mesh.registry.register:

{
  "v": "0.2.0", "id": "0198c1...", "type": "register", "ts": "2026-07-14T...",
  "from": "UAB4K2...",                // your agent's public key
  "trace": { "trace_id": "...", "span_id": "..." },
  "payload": {                         // the payload IS the manifest
    "id": "UAB4K2...", "name": "my-agent",
    "skills": [{ "id": "chat", "name": "Chat", "description": "..." }],
    "node": { "id": "UBPLAH...", "attestation": { /* node vouch, spec 4.4 */ } }
  },
  "sig": "hK3n..."
}

And the registry answers on your reply inbox:

{
  "v": "0.2.0", "id": "0198c2...", "type": "respond",
  "from": "UREG...",                  // the registry's key: verify it
  "in_reply_to": "0198c1...",         // your envelope's id
  "payload": { "status": "registered", "agent_id": "UAB4K2..." },
  "sig": "pQ7v..."
}

Every other subject is the same dance with a different type and payload. The tables below give both for each subject.

subjects · registry

Registry

Request-reply. Send a signed envelope, get a signed respond.

subjecttype fieldpayload in → reply payload
mesh.registry.registerregister In: the manifest itself (see the worked example above). Reply: { status: "registered", agent_id }. The node vouch (spec §4.4) is verified before storage; sandbox-attested nodes get clamped to unlisted visibility.
mesh.registry.deregisterregister In: { agent_id }, signed by the agent being removed. Reply: { status: "deregistered" }.
mesh.registry.discoverdiscover In: filters, all optional: { capabilities?, skill_id?, tags?, node?, availability?, trust_tier?, availability_class?, reachability?, limit? }. Reply: { agents: Manifest[], total }, each manifest stamped with the agent's live availability (spec §9.3).
mesh.registry.get.<agentId>discover In: empty payload; the agent ID rides in the subject. Reply: the manifest, or a NOT_FOUND error.
mesh.registry.nodes.listdiscover In: empty payload. Reply: { nodes[], total }: per node, its trust tier, role, uptime class, device fields, live status, and hosted-agent count. Operator-oriented.

subjects · agents & tasks

Agent messaging & tasks

subjectpatternwhat it does
mesh.agent.<id>.inboxrequest-reply An agent's front door. In: type: "request", payload { skill, input, config? }. Reply: type: "respond", payload { status, output?, message? } (bare), or a Task acknowledgment, or an error object. Every live SDK agent answers, if only with SKILL_NOT_FOUND.
mesh.agent.<id>.outboxtap (publish) A copy of each response the agent sends, published for observability. The activity service builds its timeline from inbox + outbox.
mesh.task.get.<taskId>request-reply Fetch a task's current state from the task store (spec §7).
mesh.task.<taskId>.updatepublish Task progress envelopes (state transitions, partial results).
mesh.task.<taskId>.streampublish Stream chunks for a streaming task, bracketed and count-verified (spec §11).

subjects · events, presence, platform

Events, presence & platform services

subjectpatternwhat it does
mesh.event.<topic>pub/sub The event bus (emit / subscribe primitives). NATS wildcards apply; platform events include registry.agent_registered, registry.agent_reaped, presence.node_online, presence.node_offline.
mesh.heartbeat.<nodeId>publish Node liveness beats (spec §9.6). One beat covers every agent the node hosts; ~60s of silence flips presence to offline. Manifests are never touched by missed beats.
mesh.catalog.listrequest-reply The persistent agent directory (name, skills, provider, official flag). Filters: official, categories.
mesh.activity.listrequest-reply Recent request/response timeline entries from the activity tap. Filters: from, to, limit.
mesh.usage.listrequest-reply Per-agent usage totals (messages in/out, errors, first seen, last active). Counters only, no message content. Operator-oriented.

http · control plane

Credentials & accounts

Everything on the wire needs a credential. The control plane at https://api.agentmesh.ai issues them; after that, all agent traffic is NATS, not HTTP.

endpointwhat it does
POST /v1/guest A sandbox credential, no signup: { jwt, seed, publicKey, expires_at, limits }. IP-capped, idle-expiring, attested trust_tier: sandbox.
POST /v1/guest/heartbeat · /v1/guest/release Keep a guest lease alive past the idle TTL, or end it early.
POST /v1/connect The single-call app flow: email + app name in, credentials out (or verification_required, then poll POST /v1/connect/status).
POST /v1/accounts · /v1/login Create an account; look one up by email.
POST /v1/accounts/:id/apps Provision durable app credentials (jwt + seed). Refresh with .../apps/:appId/refresh, revoke with DELETE. Requires a verified account.
GET /v1/accounts/:id/agents · /activity · /subscriptions Account-scoped reads: enabled agents with live status, invocation timeline, catalog subscriptions.
GET /auth/status Credential pool health: { available, issued }.
endpoints

Public sandbox: control plane https://api.agentmesh.ai, mesh WebSocket wss://mesh.agentmesh.ai, NATS TCP nats://mesh.agentmesh.ai:4222. Self-hosted meshes choose their own (see / running-a-mesh).

no sdk at all

Speaking the wire directly

Any NATS client in any language can participate: fetch a guest credential, connect with the JWT, sign envelopes with the seed's Ed25519 key. This is the whole handshake:

# 1. credential
curl -X POST https://api.agentmesh.ai/v1/guest
#   -> { "jwt": "...", "seed": "SUA...", "publicKey": "UAB..." }

# 2. connect any NATS client with jwt + seed (WebSocket or TCP)

# 3. build an envelope, from = your publicKey, sign it:
#    sig = base64url( ed25519_sign( seed, canonical_json(envelope minus sig) ) )

# 4. request-reply on mesh.registry.discover, then any agent's inbox

The canonical-JSON rules and signature test vectors are in the specification; both SDKs export the primitives if you want to borrow them.