The Agent2Agent protocol for MoonBit: agent cards, tasks, streaming and push notifications, as client and server.
moon add moonbitstack/moona2a| Package | What it holds |
|---|---|
| moonbitstack/moona2a | The wire model, the JSON-RPC methods, the task lifecycle, and a client over any transport |
| moonbitstack/moona2a/asgi | The HTTP endpoint as an ASGI application: the card, the JSON-RPC path, server-sent events |
| moonbitstack/moona2a/fetch | A client transport over HTTP, and the notifier that posts to a caller's webhook |
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)let message = @moona2a.Message::user("m-1", "summarise this")
match client.send_message(message) {
@moona2a.TaskSnapshot(task) => println("task \{task.id}: \{task.status.state.wire()}")
@moona2a.TaskMessage(reply) => println(reply.text())
_ => ()
}| Method | What it does |
|---|---|
| SendMessage | Send a message; get back a task or a message |
| SendStreamingMessage | The same, with every event as it happens |
| GetTask | Read a task, optionally cutting its history short |
| ListTasks | A page of the caller's tasks, newest first |
| CancelTask | Ask a task to stop |
| SubscribeToTask | Join a running task and follow it to its end |
| CreateTaskPushNotificationConfig | Leave a webhook for a task's updates |
| GetTaskPushNotificationConfig | Read one registration |
| ListTaskPushNotificationConfigs | Read a task's registrations |
| DeleteTaskPushNotificationConfig | Remove a registration, idempotently |
| GetExtendedAgentCard | Read the card shown to authenticated callers |
| Name | What it is |
|---|---|
| Server | Holds the agent, the store and the card; answers bodies and streams |
| Executor | The agent: execute emits events, cancel stops a task |
| EventSink | Where events go — a stream, a webhook, or a test's list |
| TaskStore | Where tasks live between requests; MemoryStore is the default |
| Notifier | Where push notifications go; moona2a/fetch has the HTTP one |
| ReplySink | Where a client's replies go, one per event |
| ServerConfig | History, page sizes, and whether an unknown media type is refused |
data: {"jsonrpc":"2.0","id":1,"result":{"task":{...}}}
data: {"jsonrpc":"2.0","id":1,"result":{"statusUpdate":{"taskId":"t-1",...}}}
data: {"jsonrpc":"2.0","id":1,"result":{"artifactUpdate":{"taskId":"t-1",...}}}let config = @moona2a.TaskPushConfig::new("cfg-1", task_id, "https://caller.example/hook")
.authenticating(@moona2a.AuthenticationInfo::bearer("secret"))
client.create_push_config(config) |> ignore| Run | Result |
|---|---|
| --level must | 85 passed, 0 failed |
| all levels | 97 passed, 0 failed, 166 skipped |
| # | Example | What it shows |
|---|---|---|
| 01 | card | A card as JSON: interfaces, capabilities, skills |
| 02 | client | A client over a transport that answers from a table |
| 03 | server | A server answering a SendMessage body |
| 04 | stream | A streaming answer, event by event |
| 05 | conformance | The server the conformance suite drives, over HTTP |
| 06 | interop | A client calling a live agent: card, message, stream |
moon test --target allpub(open) trait Executor {
async fn execute(Self, RequestContext, &EventSink) -> Unit
async fn cancel(Self, String) -> Unit
}pub(all) suberror Refused {
TaskNotFound(String)
TaskNotCancelable(String)
PushNotificationNotSupported(String)
UnsupportedOperation(String)
ContentTypeNotSupported(String)
InvalidAgentResponse(String)
ExtendedAgentCardNotConfigured(String)
ExtensionSupportRequired(String)
VersionNotSupported(String)
InvalidRequest(String)
InvalidParams(String)
}pub(all) struct AgentCapabilities {
streaming : Bool
push_notifications : Bool
extended_agent_card : Bool
extensions : Array[AgentExtension]
} derive(Eq)impl FromJson for AgentCapabilitiesimpl ToJson for AgentCapabilitiesfn AgentCapabilities::from_json(json : Json, path : JsonPath) -> AgentCapabilities raise JsonDecodeErrorpub(all) struct AgentCard {
name : String
description : String
supported_interfaces : Array[AgentInterface]
provider : AgentProvider?
version : String
documentation_url : String?
capabilities : AgentCapabilities
security_schemes : Json?
security_requirements : Json?
default_input_modes : Array[String]
default_output_modes : Array[String]
skills : Array[AgentSkill]
signatures : Json?
icon_url : String?
} derive(Eq)fn AgentCard::new(name : String, description : String, version : String, url : String, skills : Array[AgentSkill]) -> AgentCardimpl FromJson for AgentExtensionimpl ToJson for AgentExtensionpub(all) struct AgentInterface {
url : String
protocol_binding : String
protocol_version : String
tenant : String?
} derive(Eq)impl FromJson for AgentInterfaceimpl ToJson for AgentInterfaceimpl FromJson for AgentProviderimpl ToJson for AgentProviderimpl FromJson for AgentSkillimpl ToJson for AgentSkillfn AgentSkill::new(id : String, name : String, description : String, tags : Array[String]) -> AgentSkillimpl FromJson for AuthenticationInfoimpl ToJson for AuthenticationInfofn AuthenticationInfo::from_json(json : Json, path : JsonPath) -> AuthenticationInfo raise JsonDecodeErrorpub struct CallableExecutor {
run : async (RequestContext, &EventSink) -> Unit
stop : async (String) -> Unit
}impl Executor for CallableExecutorasync fn CallableExecutor::execute(self : CallableExecutor, ctx : RequestContext, out : &EventSink) -> Unitfn CallableExecutor::new(run~ : async (RequestContext, &EventSink) -> Unit, stop~ : async (String) -> Unit) -> CallableExecutorasync fn Client::send_message(self : Client, message : Message, configuration? : SendMessageConfig, metadata? : Json, tenant? : String) -> StreamEventimpl FromJson for ListTasksQueryimpl ToJson for ListTasksQueryimpl TaskStore for MemoryStorepub(all) enum Method {
SendMessage
SendStreamingMessage
GetTask
ListTasks
CancelTask
SubscribeToTask
CreatePushConfig
GetPushConfig
ListPushConfigs
DeletePushConfig
GetExtendedCard
} derive(Eq)pub(all) struct PushConfigPage {
configs : Array[TaskPushConfig]
next_page_token : String
} derive(Eq)impl FromJson for PushConfigPageimpl ToJson for PushConfigPagefn RequestContext::artifact(self : RequestContext, artifact : Artifact, append? : Bool, last_chunk? : Bool) -> StreamEventpub(all) struct SendMessageConfig {
return_immediately : Bool
history_length : Int?
accepted_output_modes : Array[String]
push : TaskPushConfig?
} derive(Eq)impl FromJson for SendMessageConfigimpl ToJson for SendMessageConfigfn SendMessageConfig::from_json(json : Json, path : JsonPath) -> SendMessageConfig raise JsonDecodeErrorpub(all) struct SendMessageRequest {
message : Message
configuration : SendMessageConfig?
metadata : Json?
tenant : String?
} derive(Eq)impl FromJson for SendMessageRequestimpl ToJson for SendMessageRequestfn SendMessageRequest::from_json(json : Json, path : JsonPath) -> SendMessageRequest raise JsonDecodeErrorpub(all) struct ServerConfig {
history_limit : Int
page_size : Int
max_page_size : Int
refuse_unknown_media : Bool
}type ServerStatepub(all) enum StreamEvent {
TaskSnapshot(Task)
TaskMessage(Message)
StatusChange(TaskStatusUpdate)
ArtifactChunk(TaskArtifactUpdate)
} derive(Eq)impl FromJson for StreamEventimpl ToJson for StreamEventimpl FromJson for TaskArtifactUpdateimpl ToJson for TaskArtifactUpdatefn TaskArtifactUpdate::from_json(json : Json, path : JsonPath) -> TaskArtifactUpdate raise JsonDecodeErrorfn TaskArtifactUpdate::of(task_id : String, context_id : String, artifact : Artifact, append? : Bool, last_chunk? : Bool) -> TaskArtifactUpdatepub(all) struct TaskPushConfig {
id : String
task_id : String
url : String
token : String?
authentication : AuthenticationInfo?
tenant : String?
} derive(Eq)impl FromJson for TaskPushConfigimpl ToJson for TaskPushConfigfn TaskPushConfig::authenticating(self : TaskPushConfig, authentication : AuthenticationInfo) -> TaskPushConfigpub(all) enum TaskState {
Unspecified
Submitted
Working
Completed
Failed
Canceled
InputRequired
Rejected
AuthRequired
} derive(Eq)impl FromJson for TaskStatusimpl ToJson for TaskStatuspub(all) struct TaskStatusUpdate {
task_id : String
context_id : String
status : TaskStatus
metadata : Json?
} derive(Eq)impl FromJson for TaskStatusUpdateimpl ToJson for TaskStatusUpdatefn TaskStatusUpdate::from_json(json : Json, path : JsonPath) -> TaskStatusUpdate raise JsonDecodeErrorfn TaskStatusUpdate::of(task_id : String, context_id : String, state : TaskState) -> TaskStatusUpdatelet default_capacity : Intlet follow_tick_millis : Intfn is_streaming_method(name : String) -> Boollet protocol_version : Stringfn request_method(body : StringView) -> String?fn timestamp_now() -> StringInstall
Download zipThe Agent2Agent protocol for MoonBit: agent cards, tasks, streaming and push notifications, as client and server.