moona2a

    The Agent2Agent protocol for MoonBit: agent cards, tasks, streaming and push notifications, as client and server.

    a2a
    agent2agent
    agent
    agents
    protocol
    json-rpc
    jsonrpc
    sse
    server-sent-events
    agent-card
    well-known
    task
    artifact
    streaming
    push-notification
    webhook
    multi-agent
    interop
    moonbit
    Download zip
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    10 hours ago
    Downloads
    7

    #moona2a

    The Agent2Agent protocol for MoonBit. Agent cards, tasks, streaming and push notifications — one JSON-RPC endpoint, served or called.

     home     guide     api     MoonCakes 


    An agent that other agents can talk to needs three things: a document saying what it is, one endpoint that answers a fixed set of methods, and a way to report progress on work that takes longer than a request. moona2a is that protocol in MoonBit, in both directions: a server you plug an agent into, and a client you point at someone else's agent.

    Nothing here owns a socket. 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, HTTP and anything else can carry the same calls.

    #Install

    moon add moonbitstack/moona2a

    The module has three packages:

    PackageWhat it holds
    moonbitstack/moona2aThe wire model, the JSON-RPC methods, the task lifecycle, and a client over any transport
    moonbitstack/moona2a/asgiThe HTTP endpoint as an ASGI application: the card, the JSON-RPC path, server-sent events
    moonbitstack/moona2a/fetchA client transport over HTTP, and the notifier that posts to a caller's webhook

    #A server in ten lines

    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))
    }

    An agent says what happens; the server turns that into a task, keeps its history and artifacts, answers the protocol's methods around it, and hands the same events to every watcher. Cancelling, listing, push registrations and task lookup are the server's work, not the agent's.

    let server = @moona2a.Server::new(Echo::{}, card)
    let app = @asgi.to_asgi(server)

    to_asgi answers two addresses: the card at /.well-known/agent-card.json, and every method on the JSON-RPC path. A streaming method answers with text/event-stream and one data: frame per event, written as the event happens.

    #A client

    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())
    _ => ()
    }

    Client is the protocol; Transport is how the messages travel. moona2a/fetch supplies an HTTP transport, and a test supplies a table.

    #The methods

    MethodWhat it does
    SendMessageSend a message; get back a task or a message
    SendStreamingMessageThe same, with every event as it happens
    GetTaskRead a task, optionally cutting its history short
    ListTasksA page of the caller's tasks, newest first
    CancelTaskAsk a task to stop
    SubscribeToTaskJoin a running task and follow it to its end
    CreateTaskPushNotificationConfigLeave a webhook for a task's updates
    GetTaskPushNotificationConfigRead one registration
    ListTaskPushNotificationConfigsRead a task's registrations
    DeleteTaskPushNotificationConfigRemove a registration, idempotently
    GetExtendedAgentCardRead the card shown to authenticated callers

    Errors are the specification's own. A refused call carries one of TaskNotFound, TaskNotCancelable, PushNotificationNotSupported, UnsupportedOperation, ContentTypeNotSupported, InvalidAgentResponse, ExtendedAgentCardNotConfigured, ExtensionSupportRequired or VersionNotSupported, each with the code section 5.4 assigns it and a google.rpc.ErrorInfo detail.

    #The server's parts

    NameWhat it is
    ServerHolds the agent, the store and the card; answers bodies and streams
    ExecutorThe agent: execute emits events, cancel stops a task
    EventSinkWhere events go — a stream, a webhook, or a test's list
    TaskStoreWhere tasks live between requests; MemoryStore is the default
    NotifierWhere push notifications go; moona2a/fetch has the HTTP one
    ReplySinkWhere a client's replies go, one per event
    ServerConfigHistory, page sizes, and whether an unknown media type is refused

    A task is created when the agent first says something about it, so an agent that answers with a message leaves no empty task behind. A run that fails still leaves a task in TASK_STATE_FAILED, which is what a subscriber needs to stop waiting.

    #Streaming

    A streaming answer is a JSON-RPC reply per event, each wrapping one StreamResponse:

    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",...}}}

    The stream opens with the task, carries what changed, and closes at a terminal state. A call that is refused before it starts — an unknown task, a version the interface does not serve — is an ordinary JSON-RPC error body instead, because a stream that carries one error is worse than no stream.

    Several callers can watch the same task. Each reads the task's log from its own cursor, so every stream sees the same events in the same order, and one leaving affects no other.

    #Push notifications

    A caller leaves a URL, and the agent posts each update there: the same StreamResponse a stream would carry, over plain HTTP, with the credentials the registration names.

    let config = @moona2a.TaskPushConfig::new("cfg-1", task_id, "https://caller.example/hook")
    .authenticating(@moona2a.AuthenticationInfo::bearer("secret"))
    client.create_push_config(config) |> ignore

    #Versioning

    The interface speaks 1.0. A request that names another version is refused with VersionNotSupported, and so is one that names none: section 3.6.2 reads a missing header as 0.3, which this interface does not serve. Patch numbers do not count when versions are compared, so 1.0.3 reaches a 1.0 interface. A host that builds its own transport can leave the check to itself by passing no version at all.

    #Consistency

    The official conformance suite runs against the server in src/examples/05-conformance, at commit 70f6f73, over the JSON-RPC binding. scripts/conformance/check.sh reproduces the run.

    RunResult
    --level must85 passed, 0 failed
    all levels97 passed, 0 failed, 166 skipped

    The skips are the suite's own: the requirements it marks not-automatable (card signing, TLS deployment, client tenant propagation) and the two bindings this library does not declare — it speaks JSON-RPC, so there is no gRPC or HTTP+JSON endpoint for the suite to drive. The error codes of section 5.4, the multi-stream ordering, the resubscription cases and webhook delivery are all covered.

    The same server and client were run against the official Python SDK (1.3.0) in both directions — the MoonBit client reading a card and streaming from a Python agent, and the Python client sending, streaming and reading a task back from the MoonBit server — with scripts/interop/check.sh.

    Two behaviours are worth naming:

    • Media types are not refused by default. Section 3.1.1 asks an agent to answer ContentTypeNotSupported for a media type it cannot take, but only the agent knows what it can take; the card's list is what a caller may ask for. ServerConfig.refuse_unknown_media turns the card's list into a hard filter for agents that want that.
    • The client reads a streamed answer to its end. The HTTP client this library depends on has no incremental body, so moona2a/fetch hands the events over once the response finishes. The sink is still the seam: a host with a streaming HTTP client delivers into the same trait as frames arrive.

    #Examples

    #ExampleWhat it shows
    01cardA card as JSON: interfaces, capabilities, skills
    02clientA client over a transport that answers from a table
    03serverA server answering a SendMessage body
    04streamA streaming answer, event by event
    05conformanceThe server the conformance suite drives, over HTTP
    06interopA client calling a live agent: card, message, stream

    #Tests

    moon test --target all

    async test blocks run on the native, js and wasm backends; the wasm-gc toolchain drops them, so the synchronous suite — the wire model, the state machine, the agent card, error mapping — is what runs there.

    #Licence

    Apache 2.0.

    EventSink

    pub(open) trait EventSink {
    async fn emit(Self, StreamEvent) -> Unit
    }

    Somewhere for a running task's events to go: a streamed response, a webhook, or a test's notebook.

    Executor

    pub(open) trait Executor {
    async fn execute(Self, RequestContext, &EventSink) -> Unit
    async fn cancel(Self, String) -> Unit
    }

    The agent behind a server. An implementation works a request by emitting events — status changes, artifacts, or a message that answers without a task — and stops a task when asked.

    Notifier

    pub(open) trait Notifier {
    async fn notify(Self, TaskPushConfig, StreamEvent) -> Unit
    }

    Where a server sends push notifications.

    ReplySink

    pub(open) trait ReplySink {
    async fn reply(Self,
    Message
    ) -> Unit
    }

    Where a reply goes. A transport turns each one into an SSE frame, a body, or a test's list.

    TaskStore

    pub(open) trait TaskStore {
    fn get(Self, id : String) -> Task?
    fn put(Self, task : Task) -> Unit
    fn all(Self) -> Array[Task]
    fn remove(Self, id : String) -> Unit
    }

    The storage a server needs. A store only has to remember tasks; the server decides what goes in them. Implement this to keep tasks in memory, on disk, or in a database.

    Transport

    pub(open) trait Transport {
    async fn exchange(Self,
    Message
    ) ->
    Message

    async fn stream(Self,
    Message
    , &ReplySink) -> Unit
    async fn discover(Self) ->
    Json

    }

    How a client reaches an agent. A transport carries JSON-RPC messages and finds the card; it knows nothing about tasks.

    Refused

    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)
    }

    A refused operation, named as the specification names it. The string is the server's own note on what the caller did, empty when the name says everything.

    Refused::as_failed

    The refusal as the error a JSON-RPC handler raises, so its code and details reach the caller.

    Refused::code

    fn Refused::code(self : Refused) -> Int

    The JSON-RPC error code of the refusal, as section 5.4 of the specification maps it.

    Refused::detail

    fn Refused::detail(self : Refused) -> String?

    What the caller did that the server is complaining about, when the refusal says.

    Refused::failure

    The JSON-RPC failure that carries this refusal to the caller.

    Refused::from_failure

    Read a refusal off the wire, by the code the server gave it. A code this library does not know becomes InvalidAgentResponse: the caller cannot recover from it either way, and naming it after what it is beats inventing a case for it.

    Refused::mention

    fn Refused::mention(self : Refused) -> String

    The one-line message a client shows a human, worded as the specification's error table words it.

    Refused::reason

    fn Refused::reason(self : Refused) -> String

    The machine-readable reason, which goes into the error details.

    AgentCapabilities

    pub(all) struct AgentCapabilities {
    streaming : Bool
    push_notifications : Bool
    extended_agent_card : Bool
    extensions : Array[AgentExtension]
    } derive(Eq)

    What optional parts of the protocol an agent does (A2A AgentCapabilities).

    AgentCapabilities::equal

    AgentCapabilities::not_equal

    fn AgentCapabilities::not_equal(x : AgentCapabilities, y : AgentCapabilities) -> Bool

    AgentCapabilities::plain

    An agent that answers in a single body and sends nothing out of band.

    AgentCapabilities::streaming_and_push

    fn AgentCapabilities::streaming_and_push() -> AgentCapabilities

    An agent that streams, sends push notifications and serves no extended card.

    AgentCapabilities::to_json

    AgentCard

    pub(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)

    An agent's self-description, the document a caller reads before talking to it (A2A AgentCard).

    AgentCard::capable

    fn AgentCard::capable(self : AgentCard, capabilities : AgentCapabilities) -> AgentCard

    The same card advertising capabilities.

    AgentCard::equal

    fn AgentCard::equal(AgentCard, AgentCard) -> Bool

    AgentCard::input_modes

    fn AgentCard::input_modes(self : AgentCard) -> Array[String]

    The media types the card offers to take, from its defaults and its skills together.

    AgentCard::new

    fn AgentCard::new(name : String, description : String, version : String, url : String, skills : Array[AgentSkill]) -> AgentCard

    A card with no provider, no security and no signatures, serving text in and out.

    AgentCard::not_equal

    fn AgentCard::not_equal(x : AgentCard, y : AgentCard) -> Bool

    AgentCard::preferred

    fn AgentCard::preferred(self : AgentCard) -> AgentInterface?

    The interface a client should try first, which is the one the card lists first.

    AgentCard::to_json

    AgentExtension

    pub(all) struct AgentExtension {
    uri : String
    description : String?
    required : Bool
    params :
    Json
    ?
    } derive(Eq)

    AgentInterface

    pub(all) struct AgentInterface {
    url : String
    protocol_binding : String
    protocol_version : String
    tenant : String?
    } derive(Eq)

    One address an agent answers on (A2A AgentInterface).

    AgentInterface::equal

    AgentInterface::json_rpc

    fn AgentInterface::json_rpc(url : String, protocol_version~ : String) -> AgentInterface

    An interface speaking JSON-RPC at url.

    AgentInterface::not_equal

    fn AgentInterface::not_equal(x : AgentInterface, y : AgentInterface) -> Bool

    AgentInterface::to_json

    AgentProvider

    pub(all) struct AgentProvider {
    organization : String
    url : String
    } derive(Eq)

    AgentSkill

    pub(all) struct AgentSkill {
    id : String
    name : String
    description : String
    tags : Array[String]
    examples : Array[String]
    input_modes : Array[String]
    output_modes : Array[String]
    } derive(Eq)

    One thing an agent is good at (A2A AgentSkill).

    AgentSkill::equal

    fn AgentSkill::equal(AgentSkill, AgentSkill) -> Bool

    AgentSkill::new

    fn AgentSkill::new(id : String, name : String, description : String, tags : Array[String]) -> AgentSkill

    A skill named and described, tagged with tags, with no examples or modes of its own.

    AgentSkill::not_equal

    fn AgentSkill::not_equal(x : AgentSkill, y : AgentSkill) -> Bool

    AgentSkill::to_json

    Artifact

    pub(all) struct Artifact {
    artifact_id : String
    name : String?
    description : String?
    parts : Array[Part]
    extensions : Array[String]?
    metadata :
    Json
    ?
    } derive(Eq)

    An output a task produced (A2A Artifact).

    Artifact::equal

    fn Artifact::equal(Artifact, Artifact) -> Bool

    Artifact::named

    fn Artifact::named(self : Artifact, name : String) -> Artifact

    The same artifact under name.

    Artifact::new

    fn Artifact::new(artifact_id : String, parts : Array[Part]) -> Artifact

    An artifact carrying parts under artifact_id.

    Artifact::not_equal

    fn Artifact::not_equal(x : Artifact, y : Artifact) -> Bool

    Artifact::text

    fn Artifact::text(artifact_id : String, text : String) -> Artifact

    An artifact carrying one text part.

    Artifact::text_of

    fn Artifact::text_of(self : Artifact) -> String

    The text of every text part, in order and with nothing added: a part is content, so joining must not invent characters between them.

    Artifact::to_json

    AuthenticationInfo

    pub(all) struct AuthenticationInfo {
    scheme : String
    credentials : String?
    } derive(Eq)

    How the agent proves itself to the caller's webhook (A2A AuthenticationInfo).

    AuthenticationInfo::bearer

    fn AuthenticationInfo::bearer(token : String) -> AuthenticationInfo

    A bearer token the agent sends with each notification.

    AuthenticationInfo::equal

    AuthenticationInfo::not_equal

    AuthenticationInfo::to_json

    CallableExecutor

    pub struct CallableExecutor {
    run : async (RequestContext, &EventSink) -> Unit
    stop : async (String) -> Unit
    }

    An executor built from two functions, for the common case of one agent with no state of its own.

    CallableExecutor::cancel

    async fn CallableExecutor::cancel(self : CallableExecutor, task_id : String) -> Unit

    CallableExecutor::execute

    async fn CallableExecutor::execute(self : CallableExecutor, ctx : RequestContext, out : &EventSink) -> Unit

    CallableExecutor::new

    fn CallableExecutor::new(run~ : async (RequestContext, &EventSink) -> Unit, stop~ : async (String) -> Unit) -> CallableExecutor

    An executor that works requests with run and stops tasks with stop.

    Client

    pub struct Client {
    transport : &Transport
    next : Int64
    }

    A caller's handle on a remote agent.

    Client::cancel_task

    async fn Client::cancel_task(self : Client, id : String) -> Task

    Ask the agent to stop a task.

    Client::create_push_config

    async fn Client::create_push_config(self : Client, config : TaskPushConfig) -> TaskPushConfig

    Register a webhook for a task's updates.

    Client::delete_push_config

    async fn Client::delete_push_config(self : Client, task_id : String, id : String) -> Unit

    Remove a webhook registration. Removing one that is already gone is not an error.

    Client::discover

    async fn Client::discover(self : Client) -> AgentCard

    Read the agent's public card.

    Client::extended_card

    async fn Client::extended_card(self : Client) -> AgentCard

    Read the card the agent shows only to authenticated callers.

    Client::get_push_config

    async fn Client::get_push_config(self : Client, task_id : String, id : String) -> TaskPushConfig

    Read one of a task's webhook registrations.

    Client::get_task

    async fn Client::get_task(self : Client, id : String, history_length? : Int) -> Task

    Read a task as it stands.

    Client::list_push_configs

    async fn Client::list_push_configs(self : Client, task_id : String) -> Array[TaskPushConfig]

    Read every webhook registration a task has.

    Client::list_tasks

    async fn Client::list_tasks(self : Client, query : ListTasksQuery) -> TaskPage

    Read the tasks the caller may see.

    Client::new

    fn Client::new(transport : &Transport) -> Client

    A client talking to the agent behind transport.

    Client::send_message

    async fn Client::send_message(self : Client, message : Message, configuration? : SendMessageConfig, metadata? :
    Json
    , tenant? : String) -> StreamEvent

    Send message and return what the agent answered with: a task, or a message.

    Client::send_streaming_message

    async fn Client::send_streaming_message(self : Client, message : Message, sink : &ReplySink, configuration? : SendMessageConfig, metadata? :
    Json
    , tenant? : String) -> Unit

    Send message and hand every event to sink as it arrives.

    Client::subscribe_to_task

    async fn Client::subscribe_to_task(self : Client, id : String, sink : &ReplySink) -> Unit

    Join a task that is already running, and follow it to its end.

    Collected

    pub struct Collected {
    taken : Array[StreamEvent]
    }

    A sink that keeps every event it is handed, for a caller that waits for the whole answer.

    Collected::emit

    async fn Collected::emit(self : Collected, event : StreamEvent) -> Unit

    Collected::events

    fn Collected::events(self : Collected) -> Array[StreamEvent]

    The events the sink took, in the order they happened.

    Collected::message

    fn Collected::message(self : Collected) -> Message?

    The message the agent answered with directly, when the run produced one instead of a task.

    Collected::new

    fn Collected::new() -> Collected

    An empty collection.

    Discard

    pub struct Discard {
    }

    A sink that throws its events away, for a run nobody is watching.

    ListTasksQuery

    pub(all) struct ListTasksQuery {
    context_id : String?
    state : TaskState?
    page_size : Int?
    page_token : String?
    history_length : Int?
    status_timestamp_after : String?
    include_artifacts : Bool
    tenant : String?
    } derive(Eq)

    The filters a list request carries (A2A ListTasksRequest).

    ListTasksQuery::all

    No filter at all: every task the caller may see.

    ListTasksQuery::equal

    ListTasksQuery::in_context

    fn ListTasksQuery::in_context(self : ListTasksQuery, context_id : String) -> ListTasksQuery

    The same query, limited to one conversation.

    ListTasksQuery::not_equal

    fn ListTasksQuery::not_equal(x : ListTasksQuery, y : ListTasksQuery) -> Bool

    ListTasksQuery::to_json

    MemoryStore

    pub struct MemoryStore {
    tasks : Map[String, Task]
    order : Array[String]
    capacity : Int
    }

    A store that keeps tasks in memory, good for a single process and a bounded session.

    MemoryStore::all

    fn MemoryStore::all(self : MemoryStore) -> Array[Task]

    MemoryStore::get

    fn MemoryStore::get(self : MemoryStore, id : String) -> Task?

    MemoryStore::new

    fn MemoryStore::new(capacity? : Int) -> MemoryStore

    An empty store holding up to capacity tasks.

    MemoryStore::put

    fn MemoryStore::put(self : MemoryStore, task : Task) -> Unit

    MemoryStore::remove

    fn MemoryStore::remove(self : MemoryStore, id : String) -> Unit

    Message

    pub(all) struct Message {
    message_id : String
    role : Role
    parts : Array[Part]
    context_id : String?
    task_id : String?
    reference_task_ids : Array[String]?
    extensions : Array[String]?
    metadata :
    Json
    ?
    } derive(Eq)

    One turn of a conversation between a client and an agent (A2A Message).

    Message::agent

    fn Message::agent(message_id : String, text : String) -> Message

    A message from the agent, carrying one text part.

    Message::equal

    fn Message::equal(Message, Message) -> Bool

    Message::in_task

    fn Message::in_task(self : Message, task_id : String, context_id : String) -> Message

    The same message, continuing the task and context given.

    Message::new

    fn Message::new(message_id : String, role : Role, parts : Array[Part]) -> Message

    A message from role carrying parts.

    Message::not_equal

    fn Message::not_equal(x : Message, y : Message) -> Bool

    Message::text

    fn Message::text(self : Message) -> String

    The text of every text part, in order and with nothing added: a part is content, so joining must not invent characters between them.

    Message::to_json

    Message::user

    fn Message::user(message_id : String, text : String) -> Message

    A message from the client, carrying one text part.

    Method

    pub(all) enum Method {
    SendMessage
    SendStreamingMessage
    GetTask
    ListTasks
    CancelTask
    SubscribeToTask
    CreatePushConfig
    GetPushConfig
    ListPushConfigs
    DeletePushConfig
    GetExtendedCard
    } derive(Eq)

    Every method of the JSON-RPC binding.

    Method::equal

    fn Method::equal(Method, Method) -> Bool

    Method::from_name

    fn Method::from_name(name : String) -> Method?

    The method that goes by name, when it is one of the protocol's.

    Method::name

    fn Method::name(self : Method) -> String

    The name the method goes by on the wire: PascalCase, as section 9.1 requires.

    Method::not_equal

    fn Method::not_equal(x : Method, y : Method) -> Bool

    Method::streams

    fn Method::streams(self : Method) -> Bool

    Whether the answer to this method is a stream of events rather than a single body.

    Part

    pub(all) struct Part {
    content : PartContent
    metadata :
    Json
    ?
    } derive(Eq)

    One piece of a message or an artifact (A2A Part).

    Part::bytes

    fn Part::bytes(bytes : Bytes, filename? : String, media_type? : String) -> Part

    A part carrying bytes inline.

    Part::data

    A part carrying a structured JSON value.

    Part::equal

    fn Part::equal(Part, Part) -> Bool

    Part::media_type

    fn Part::media_type(self : Part) -> String?

    The media type this part declares, when it declares one.

    Part::not_equal

    fn Part::not_equal(x : Part, y : Part) -> Bool

    Part::text

    fn Part::text(text : String) -> Part

    A text part.

    Part::text_of

    fn Part::text_of(self : Part) -> String?

    The text this part carries, when it is a text part.

    Part::to_json

    Part::uri

    fn Part::uri(url : String, filename? : String, media_type? : String) -> Part

    A part pointing at content by URL.

    Part::with_metadata

    fn Part::with_metadata(self : Part, metadata :
    Json
    ) -> Part

    The same part with metadata attached.

    PartContent

    pub(all) enum PartContent {
    Text(String)
    Raw(bytes~ : Bytes, filename~ : String?, media_type~ : String?)
    Url(url~ : String, filename~ : String?, media_type~ : String?)
    Data(
    Json
    )
    } derive(Eq)

    What a part carries (A2A Part.content oneof).

    PartContent::equal

    fn PartContent::equal(PartContent, PartContent) -> Bool

    PartContent::not_equal

    fn PartContent::not_equal(x : PartContent, y : PartContent) -> Bool

    PushConfigPage

    pub(all) struct PushConfigPage {
    configs : Array[TaskPushConfig]
    next_page_token : String
    } derive(Eq)

    One page of a task's registrations (A2A ListTaskPushNotificationConfigsResponse).

    PushConfigPage::equal

    PushConfigPage::not_equal

    fn PushConfigPage::not_equal(x : PushConfigPage, y : PushConfigPage) -> Bool

    PushConfigPage::to_json

    RequestContext

    pub(all) struct RequestContext {
    message : Message
    task_id : String
    context_id : String
    task : Task?
    }

    What an executor is given when it is asked to work a request: the message, the ids it belongs to, and the task as it stands when the message continues one.

    RequestContext::artifact

    fn RequestContext::artifact(self : RequestContext, artifact : Artifact, append? : Bool, last_chunk? : Bool) -> StreamEvent

    The event that hands this request's caller artifact, or more of one.

    RequestContext::replying

    fn RequestContext::replying(self : RequestContext, parts : Array[Part]) -> StreamEvent

    A message this request's caller receives outside the task. An executor that says only this answers with a message instead of opening a task.

    RequestContext::status

    fn RequestContext::status(self : RequestContext, state : TaskState) -> StreamEvent

    The event that moves this request's task to state.

    Role

    pub(all) enum Role {
    Unspecified
    User
    Agent
    } derive(Eq)

    The sender of a message (A2A Role).

    Role::equal

    fn Role::equal(Role, Role) -> Bool

    Role::from_wire

    fn Role::from_wire(name : String) -> Role

    The role named by its wire form. Both the proto name and the bare word are read, because clients written against earlier drafts send the bare word.

    Role::not_equal

    fn Role::not_equal(x : Role, y : Role) -> Bool

    Role::to_json

    Role::wire

    fn Role::wire(self : Role) -> String

    The wire name of the role, as ProtoJSON writes an enum.

    SendMessageConfig

    pub(all) struct SendMessageConfig {
    return_immediately : Bool
    history_length : Int?
    accepted_output_modes : Array[String]
    push : TaskPushConfig?
    } derive(Eq)

    How a send request should be handled: whether to wait, how much history to bring back, and what the caller is willing to receive.

    SendMessageConfig::at_most

    fn SendMessageConfig::at_most(self : SendMessageConfig, history_length : Int) -> SendMessageConfig

    The same configuration, bringing back at most history_length messages.

    SendMessageConfig::equal

    SendMessageConfig::immediately

    The same configuration, returning before the task finishes.

    SendMessageConfig::not_equal

    fn SendMessageConfig::not_equal(x : SendMessageConfig, y : SendMessageConfig) -> Bool

    SendMessageConfig::pushing

    The same configuration, registering push for the task it creates.

    SendMessageConfig::to_json

    SendMessageConfig::waiting

    Wait for the task to finish, and bring back its whole history.

    SendMessageRequest

    pub(all) struct SendMessageRequest {
    message : Message
    configuration : SendMessageConfig?
    metadata :
    Json
    ?
    tenant : String?
    } derive(Eq)

    Everything a send request carries (A2A SendMessageRequest).

    SendMessageRequest::equal

    SendMessageRequest::new

    A request to send message with nothing else set.

    SendMessageRequest::not_equal

    SendMessageRequest::to_json

    Server

    pub struct Server {
    state : ServerState
    }

    An A2A endpoint: it holds the agent, the store and the card, and answers the protocol's methods.

    Server::answer

    async fn Server::answer(self : Server, body : StringView, extensions? : Array[String], version? : String) -> String?

    Answer one body of JSON-RPC, as a body. The answer is None when the body was a notification and nothing goes back.

    extensions are the extension URIs the caller declared for this request — the values of the A2A-Extensions header, in the JSON-RPC binding. An extension the card requires that is not among them is refused.

    Server::answer_stream

    async fn Server::answer_stream(self : Server, body : StringView, out : &ReplySink, extensions? : Array[String], version? : String) -> Unit

    Answer one body of JSON-RPC, handing every reply to out as it is ready. A streaming method produces one reply per event; everything else produces exactly one.

    Server::attach

    fn Server::attach(self : Server, group :
    TaskGroup
    [Unit]) -> Server

    Let the server start runs in group when a caller asks not to wait. A run started this way outlives the request that asked for it, and stops when the group does.

    Server::card

    fn Server::card(self : Server) -> AgentCard

    The card this server publishes.

    Server::card_json

    The public card as the well-known document serves it.

    Server::configured

    fn Server::configured(executor~ : &Executor, card~ : AgentCard, store~ : &TaskStore, notifier~ : &Notifier, config~ : ServerConfig) -> Server

    A server over the given store, notifier and configuration.

    Server::new

    fn Server::new(executor : &Executor, card : AgentCard) -> Server

    A server for executor and card, keeping tasks in its own memory and sending no notifications.

    Server::notifying

    fn Server::notifying(self : Server, notifier : &Notifier) -> Server

    The same server, sending its push notifications through notifier.

    Server::storing

    fn Server::storing(self : Server, store : &TaskStore) -> Server

    The same server, keeping its tasks in store and sending notifications through notifier.

    Server::under

    fn Server::under(self : Server, config : ServerConfig) -> Server

    The same server under config.

    Server::with_extended_card

    fn Server::with_extended_card(self : Server, card : AgentCard) -> Server

    The same server, handing an authenticated caller card instead of the public one.

    ServerConfig

    pub(all) struct ServerConfig {
    history_limit : Int
    page_size : Int
    max_page_size : Int
    refuse_unknown_media : Bool
    }

    The defaults a server runs with.

    ServerConfig::new

    A server that brings back the whole history, 50 tasks to a page, and no more than 100.

    ServerState

    type ServerState

    Everything a server mutates. Held behind the Server handle so a handler that captured the handle sees the same maps as the one that made it.

    StreamEvent

    pub(all) enum StreamEvent {
    TaskSnapshot(Task)
    TaskMessage(Message)
    StatusChange(TaskStatusUpdate)
    ArtifactChunk(TaskArtifactUpdate)
    } derive(Eq)

    One item of a streamed response (A2A StreamResponse.payload oneof).

    StreamEvent::equal

    fn StreamEvent::equal(StreamEvent, StreamEvent) -> Bool

    StreamEvent::is_final

    fn StreamEvent::is_final(self : StreamEvent) -> Bool

    Whether the event ends the task for good.

    StreamEvent::not_equal

    fn StreamEvent::not_equal(x : StreamEvent, y : StreamEvent) -> Bool

    StreamEvent::state

    fn StreamEvent::state(self : StreamEvent) -> TaskState?

    The state this event reports, when it reports one.

    StreamEvent::task_id

    fn StreamEvent::task_id(self : StreamEvent) -> String?

    The task this event belongs to, when it belongs to one.

    StreamEvent::to_json

    Task

    pub(all) struct Task {
    id : String
    context_id : String
    status : TaskStatus
    artifacts : Array[Artifact]
    history : Array[Message]
    metadata :
    Json
    ?
    } derive(Eq)

    A unit of work an agent runs on a caller's behalf (A2A Task).

    Task::artifact

    fn Task::artifact(self : Task, artifact_id : String) -> Artifact?

    The artifact with artifact_id, when the task has one.

    Task::encode

    fn Task::encode(self : Task, view? : View) ->
    Json

    The task as the wire wants it, cut down to view.

    Task::equal

    fn Task::equal(Task, Task) -> Bool

    Task::new

    fn Task::new(id : String, context_id : String) -> Task

    A new task in context_id, submitted and not yet worked on.

    Task::not_equal

    fn Task::not_equal(x : Task, y : Task) -> Bool

    Task::recent

    fn Task::recent(self : Task, limit : Int?) -> Array[Message]

    The most recent limit messages of the history; all of them when limit is unset.

    Task::saying

    fn Task::saying(self : Task, message : Message) -> Task

    The same task with message appended to its history.

    Task::taking

    fn Task::taking(self : Task, artifact : Artifact, append : Bool) -> Task

    The same task with artifact added, or with the parts of an existing artifact of the same id replaced or appended to.

    Task::to_json

    Task::with_state

    fn Task::with_state(self : Task, state : TaskState) -> Task

    The same task in state, with the clock stamped.

    TaskArtifactUpdate

    pub(all) struct TaskArtifactUpdate {
    task_id : String
    context_id : String
    artifact : Artifact
    append : Bool
    last_chunk : Bool
    metadata :
    Json
    ?
    } derive(Eq)

    An artifact was produced, or more of it arrived (A2A TaskArtifactUpdateEvent).

    TaskArtifactUpdate::equal

    TaskArtifactUpdate::not_equal

    TaskArtifactUpdate::of

    fn TaskArtifactUpdate::of(task_id : String, context_id : String, artifact : Artifact, append? : Bool, last_chunk? : Bool) -> TaskArtifactUpdate

    An artifact update event carrying artifact for the task with task_id.

    TaskArtifactUpdate::to_json

    TaskLog

    pub struct TaskLog {
    events : Array[StreamEvent]
    open : Bool
    }

    One task's events, kept in the order they happened, for subscribers who arrived late or twice.

    TaskLog::new

    fn TaskLog::new() -> TaskLog

    An empty log for a task that has not finished.

    TaskPage

    pub(all) struct TaskPage {
    tasks : Array[Task]
    next_page_token : String
    page_size : Int
    total_size : Int
    } derive(Eq)

    One page of tasks (A2A ListTasksResponse).

    TaskPage::equal

    fn TaskPage::equal(TaskPage, TaskPage) -> Bool

    TaskPage::not_equal

    fn TaskPage::not_equal(x : TaskPage, y : TaskPage) -> Bool

    TaskPage::to_json

    TaskPushConfig

    pub(all) struct TaskPushConfig {
    id : String
    task_id : String
    url : String
    token : String?
    authentication : AuthenticationInfo?
    tenant : String?
    } derive(Eq)

    Where the agent posts a task's updates, and how it authenticates there (A2A TaskPushNotificationConfig).

    TaskPushConfig::authenticating

    fn TaskPushConfig::authenticating(self : TaskPushConfig, authentication : AuthenticationInfo) -> TaskPushConfig

    The same registration, authenticating with authentication.

    TaskPushConfig::equal

    TaskPushConfig::new

    fn TaskPushConfig::new(id : String, task_id : String, url : String) -> TaskPushConfig

    A registration of url for task_id, under id.

    TaskPushConfig::not_equal

    fn TaskPushConfig::not_equal(x : TaskPushConfig, y : TaskPushConfig) -> Bool

    TaskPushConfig::to_json

    TaskPushConfig::with_token

    fn TaskPushConfig::with_token(self : TaskPushConfig, token : String) -> TaskPushConfig

    The same registration, carrying the caller's own token in each notification's headers.

    TaskState

    pub(all) enum TaskState {
    Unspecified
    Submitted
    Working
    Completed
    Failed
    Canceled
    InputRequired
    Rejected
    AuthRequired
    } derive(Eq)

    The lifecycle state of a task (A2A TaskState).

    TaskState::equal

    fn TaskState::equal(TaskState, TaskState) -> Bool

    TaskState::from_wire

    fn TaskState::from_wire(name : String) -> TaskState

    The state named by its wire form. The proto name, the bare word and the hyphenated draft name are all read: a client written against an earlier draft sends input-required.

    TaskState::interrupted

    fn TaskState::interrupted(self : TaskState) -> Bool

    Whether the task is waiting on the caller and processing can resume from here.

    TaskState::not_equal

    fn TaskState::not_equal(x : TaskState, y : TaskState) -> Bool

    TaskState::terminal

    fn TaskState::terminal(self : TaskState) -> Bool

    Whether the task has finished for good: no further message, cancel or transition can apply.

    TaskState::to_json

    TaskState::wire

    fn TaskState::wire(self : TaskState) -> String

    The wire name of the state, as ProtoJSON writes an enum.

    TaskStatus

    pub(all) struct TaskStatus {
    state : TaskState
    message : Message?
    timestamp : String?
    } derive(Eq)

    The state of a task, with the message and time that go with it (A2A TaskStatus).

    TaskStatus::equal

    fn TaskStatus::equal(TaskStatus, TaskStatus) -> Bool

    TaskStatus::not_equal

    fn TaskStatus::not_equal(x : TaskStatus, y : TaskStatus) -> Bool

    TaskStatus::of

    fn TaskStatus::of(state : TaskState) -> TaskStatus

    A status with no message and no timestamp.

    TaskStatus::recorded

    fn TaskStatus::recorded(state : TaskState) -> TaskStatus

    The status of state, recorded now.

    TaskStatus::to_json

    TaskStatusUpdate

    pub(all) struct TaskStatusUpdate {
    task_id : String
    context_id : String
    status : TaskStatus
    metadata :
    Json
    ?
    } derive(Eq)

    A task changed state (A2A TaskStatusUpdateEvent).

    TaskStatusUpdate::equal

    TaskStatusUpdate::not_equal

    fn TaskStatusUpdate::not_equal(x : TaskStatusUpdate, y : TaskStatusUpdate) -> Bool

    TaskStatusUpdate::of

    fn TaskStatusUpdate::of(task_id : String, context_id : String, state : TaskState) -> TaskStatusUpdate

    A status update event for the task with task_id, moving it to state.

    TaskStatusUpdate::to_json

    View

    pub(all) struct View {
    artifacts : Bool
    history : Int?
    } derive(Eq)

    How much of a task a reply carries: whether its artifacts travel with it, and how many of the most recent messages do. None history means all of them.

    View::bare

    fn View::bare() -> View

    The task alone, without what it produced.

    View::equal

    fn View::equal(View, View) -> Bool

    View::full

    fn View::full() -> View

    Everything the task has.

    View::not_equal

    fn View::not_equal(x : View, y : View) -> Bool

    card_path

    let card_path : String

    Where the card is served (section 8.2).

    default_capacity

    let default_capacity : Int

    How many tasks an in-memory store keeps before it starts forgetting the ones that finished. 1024 is enough for a session's worth of work and small enough that a forgotten task cannot grow without bound.

    follow_tick_millis

    let follow_tick_millis : Int

    How long a follower waits between looks at the log it is watching.

    The log is the ordering guarantee, not the wake-up: a follower reads forward from its own cursor, so it sees every event once, in order, whether it woke on the first tick or the fifth. 25 milliseconds is short enough that a stream feels live and long enough that an idle subscription costs nothing worth measuring.

    is_streaming_method

    fn is_streaming_method(name : String) -> Bool

    Whether name is one of the methods whose answer is a stream, for a transport that has to decide before reading the whole request.

    protocol_version

    let protocol_version : String

    The protocol version this library speaks, as a client writes it in A2A-Version.

    request_method

    fn request_method(body : StringView) -> String?

    The method this request asks for, so a transport can decide whether its answer is a stream before reading the whole body.

    timestamp_now

    fn timestamp_now() -> String

    The current instant as an ISO 8601 timestamp in UTC, e.g. 2026-11-06T02:02:55.123Z.

    Millisecond precision is what the specification asks for and all @env.now offers on every backend; a timestamp that is a few milliseconds coarse still orders correctly.