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.
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.
{ "jsonrpc": "2.0", "id": 1, "method": "SendStreamingMessage", … }
data: { "jsonrpc": "2.0", "id": 1,
"result": { "task": {
"id": "t-1", "contextId": "c-1",
"status": { "state": "TASK_STATE_SUBMITTED" } } } }
data: { "jsonrpc": "2.0", "id": 1,
"result": { "statusUpdate": {
"taskId": "t-1", "contextId": "c-1",
"status": { "state": "TASK_STATE_WORKING" } } } }
data: { "jsonrpc": "2.0", "id": 1,
"result": { "artifactUpdate": {
"taskId": "t-1", "contextId": "c-1",
"artifact": { "artifactId": "a-1",
"parts": [ { "text": "a short paragraph" } ] } } } }
data: { "jsonrpc": "2.0", "id": 1,
"result": { "statusUpdate": {
"taskId": "t-1", "contextId": "c-1",
"status": { "state": "TASK_STATE_COMPLETED" } } } }
Serve it, or call it. One library, both ends of the same conversation.
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.
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.
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.
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.
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.
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.
| Method | Answer | What it does |
|---|---|---|
| SendMessage | reply | Send a message; get back the task it opened, or the message the agent answered with |
| SendStreamingMessage | stream | The same, with every event as it happens |
| GetTask | reply | Read a task, with as much of its history as the caller asks for |
| ListTasks | reply | A page of the caller's tasks, newest first |
| CancelTask | reply | Ask a task to stop; a finished one refuses with a code |
| SubscribeToTask | stream | Join a running task, then follow what happens after |
| CreateTaskPushNotificationConfig | reply | Leave a webhook for a task's updates |
| GetTaskPushNotificationConfig | reply | Read one registration |
| ListTaskPushNotificationConfigs | reply | Every registration a task carries |
| DeleteTaskPushNotificationConfig | reply | Remove a registration, idempotently |
| GetExtendedAgentCard | reply | The card shown to a caller that authenticated |
Every event is a frame.
Every frame is a whole reply.
data: line per event
closed at a terminal state
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.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.
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.
Three packages arrive together: the wire model and the client, the ASGI endpoint, and the client's HTTP transport with the webhook notifier.
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)
moon add moonbitstack/moona2a
# moonbitstack/moona2a model · server · client
# moonbitstack/moona2a/asgi the HTTP endpoint
# moonbitstack/moona2a/fetch transport · notifier