MoonA2A
Agent2Agent Protocol 1.0

Two agents, onetranscript

A card says what an agent is. One endpoint answers the protocol's eleven methods, and a stream reports the task while it runs. Serve that from MoonBit, or call an agent someone else serves.

serve
moon add moonbitstack/moona2a
call
POST / · JSON-RPC 2.0
stream
text/event-stream
POST / 200 · text/event-stream
call SendStreamingMessage
{ "jsonrpc": "2.0", "id": 1, "method": "SendStreamingMessage", … }
frame 1 task
data: { "jsonrpc": "2.0", "id": 1,
  "result": { "task": {
    "id": "t-1", "contextId": "c-1",
    "status": { "state": "TASK_STATE_SUBMITTED" } } } }
frame 2 working
data: { "jsonrpc": "2.0", "id": 1,
  "result": { "statusUpdate": {
    "taskId": "t-1", "contextId": "c-1",
    "status": { "state": "TASK_STATE_WORKING" } } } }
frame 3 artifact
data: { "jsonrpc": "2.0", "id": 1,
  "result": { "artifactUpdate": {
    "taskId": "t-1", "contextId": "c-1",
    "artifact": { "artifactId": "a-1",
      "parts": [ { "text": "a short paragraph" } ] } } } }
frame 4 completed
data: { "jsonrpc": "2.0", "id": 1,
  "result": { "statusUpdate": {
    "taskId": "t-1", "contextId": "c-1",
    "status": { "state": "TASK_STATE_COMPLETED" } } } }
stream closed · one request, four frames
endpoint1
methods11
states8
of them final4

Serve it, or call it. One library, both ends of the same conversation.

01
What it is

A protocol, and both of its ends.

An agent that other agents can talk to needs a document saying what it is, one endpoint answering a fixed set of methods, and a way to report work that takes longer than a request.

01

One address per agent

The card is served at a well-known path and lists the interfaces the agent answers on. Every method is a POST to that one address, so a caller needs nothing else to begin.

02

A task, not a request

The first thing an agent says about a piece of work opens its task. History, artifacts and state live in the task, so a caller that arrives late can read what it missed.

03

Streams that can be joined

A streaming call reports every event as it happens. A caller that arrives later subscribes to the running task, is given the task as it stands, then follows what comes next.

04

No socket of its own

The server is an ASGI application, so the HTTP server stays a choice the host makes. The client talks through a transport trait, so a test and a live agent carry the same calls.

02
The methods

Eleven calls, one path.

Everything an agent can be asked is one of these eleven. Six answer once, two answer with a stream, three keep the bookkeeping for a task someone else is watching.

MethodAnswerWhat it does
SendMessagereplySend a message; get back the task it opened, or the message the agent answered with
SendStreamingMessagestreamThe same, with every event as it happens
GetTaskreplyRead a task, with as much of its history as the caller asks for
ListTasksreplyA page of the caller's tasks, newest first
CancelTaskreplyAsk a task to stop; a finished one refuses with a code
SubscribeToTaskstreamJoin a running task, then follow what happens after
CreateTaskPushNotificationConfigreplyLeave a webhook for a task's updates
GetTaskPushNotificationConfigreplyRead one registration
ListTaskPushNotificationConfigsreplyEvery registration a task carries
DeleteTaskPushNotificationConfigreplyRemove a registration, idempotently
GetExtendedAgentCardreplyThe card shown to a caller that authenticated

Every event is a frame.
Every frame is a whole reply.

text/event-stream one data: line per event closed at a terminal state
03
The task

Eight states, four of them final.

Once a task is completed, failed, canceled or rejected, no message, cancellation or transition applies to it again. Up to that point it may wait on the caller as readily as it works.

SUBMITTEDAccepted, not yet picked up.
WORKINGThe agent is running it.
INPUT_REQUIREDWaiting on the caller.
AUTH_REQUIREDWaiting on credentials.
COMPLETEDFinished, with a result.
FAILEDThe run raised.
CANCELEDStopped on request.
REJECTEDRefused before it ran.
may still move terminal
04
Both ends

Write an executor,
or call someone else's.

An agent says what happens; the server turns that into a task, keeps its history, answers the methods around it, and hands the same events to every watcher. A client is the same protocol from the other side.

Serve an agent

  • Executor — emits status, artifacts, messages
  • Server — tasks, history, methods, watchers
  • to_asgi — the card and the JSON-RPC path
json-rpc 2.0

Call an agent

  • Client — the protocol, method by method
  • Transport — a trait, not a socket
  • HttpTransport — a live agent over HTTP
05
Refusals

Nine ways to be told no.

Each refusal carries the code the specification assigns it and an ErrorInfo detail, so a client branches on the reason rather than on the sentence.

-32001TaskNotFoundNo task under that id.
-32002TaskNotCancelableIt is finished, or it cannot stop.
-32003PushNotificationNotSupportedThe agent takes no webhooks.
-32004UnsupportedOperationThe method, or a task that is over.
-32005ContentTypeNotSupportedA part the agent cannot read.
-32006InvalidAgentResponseThe agent answered with something else.
-32007ExtendedAgentCardNotConfiguredThere is no second card.
-32008ExtensionSupportRequiredAn extension the caller must honour.
-32009VersionNotSupportedA version this interface does not speak.
06
Ten lines

An agent, and a server.

Three packages arrive together: the wire model and the client, the ASGI endpoint, and the client's HTTP transport with the webhook notifier.

  • A streaming answer is one JSON-RPC reply per event, written as the event happens.
  • A task that raises still lands in FAILED, so no watcher is left waiting on a task that will not move.
  • The next step is the guide: a card, a server, a client, a stream.
  • Every public type and function is in the reference, generated from the sources.
hello.mbt
impl @moona2a.Executor for Echo with fn execute(self, ctx, out) {
  ignore(self)
  out.emit(ctx.status(@moona2a.Working))
  out.emit(ctx.artifact(@moona2a.Artifact::text("a-1", "done")))
  out.emit(ctx.status(@moona2a.Completed))
}

let server = @moona2a.Server::new(Echo::{ }, card)
let app = @asgi.to_asgi(server)
install
moon add moonbitstack/moona2a

#  moonbitstack/moona2a        model · server · client
#  moonbitstack/moona2a/asgi   the HTTP endpoint
#  moonbitstack/moona2a/fetch  transport · notifier