Skip to content

A2A Serving

The A2A serving layer wraps a configured Agent as an HTTP application that speaks the A2A v1.0 protocol — JSON-RPC dispatch, /.well-known/agent-card.json, /health, and optional SSE streaming. Two entry points are provided: A2aApp for A2A-only serving, and AgentHostBuilder when you need merged routes (A2A + AG-UI on a single port).

[dependencies]
agent_sdk = { package = "pf_agent_sdk", path = "../../crates/agent_sdk", features = ["a2a-server", "event-stream"] }
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
  1. Build an Agent with skills and/or an LLM runtime

    let mut agent = Agent::new_with_config(
    AgentConfig::new("my-agent", "My A2A agent")
    .with_base_url("http://127.0.0.1:3000"),
    )?;
    agent
    .add_skill("greet")
    .description("Greet the user")
    .register()?;
    agent.set_message_handler(my_handler);
  2. Wrap in an A2A application and serve

    use agent_sdk::a2a::A2aApp;
    let app = A2aApp::from_agent(agent)?;
    app.serve("127.0.0.1:3000").await?;

    Or extract the Axum router for custom middleware:

    let router = A2aApp::from_agent(agent)?.router();
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await?;
    axum::serve(listener, router).await?;
  3. Test the agent

    Terminal window
    # Health check
    curl http://127.0.0.1:3000/health
    # Agent card
    curl http://127.0.0.1:3000/.well-known/agent-card.json
    # Send a message (JSON-RPC)
    curl -X POST http://127.0.0.1:3000/jsonrpc \
    -H "Content-Type: application/json" \
    -d '{
    "jsonrpc": "2.0",
    "id": "1",
    "method": "SendMessage",
    "params": {
    "message": {
    "role": "user",
    "parts": [{"text": "Hello!"}]
    }
    }
    }'

Dual-protocol hosting with AgentHostBuilder

Section titled “Dual-protocol hosting with AgentHostBuilder”

Use AgentHostBuilder when you need A2A and AG-UI on one router (native only):

use agent_sdk::{Agent, AgentHostBuilder};
use agent_sdk::agent::AgentConfig;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let config = AgentConfig::new("dual-agent", "Agent with A2A + AG-UI")
.with_base_url("http://127.0.0.1:3000");
let mut agent = Agent::new_with_config(config)?;
agent.add_skill("chat")
.description("General conversation")
.register()?;
let router = AgentHostBuilder::new(agent)
.with_a2a()
// .with_agui(agui_config) // add AG-UI if needed
.build_router()?;
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await?;
axum::serve(listener, router).await?;
Ok(())
}
MethodDescription
A2aApp::from_agent(agent)Wrap an owned Agent (boxed as Arc internally)
A2aApp::from_shared_agent(arc)Wrap an already-shared Arc<Agent>
.router() → axum::RouterExtract the Axum router (JSON-RPC, agent card, health)
.serve(addr).awaitBind and listen until shutdown
MethodDescriptionRequires
AgentHostBuilder::new(agent)Start from a built Agent
.with_a2a()Mount A2A HTTP surfacea2a-server
.with_agui(config)Mount AG-UI streaming routesevent-stream, native only
.build()Produce an AgentHostAt least one adapter enabled
.build_router()Shorthand: build then extract router

a2a_serve! is a convenience for the common WASM pattern: OnceLock<A2aApp> + #[http_component]. It expands to:

static APP: std::sync::OnceLock<agent_sdk::a2a::A2aApp> = std::sync::OnceLock::new();
#[spin_sdk::http_component]
fn handle_request(req: spin_sdk::http::Request) -> anyhow::Result<spin_sdk::http::Response> {
let app = APP.get_or_init(|| {
let agent = (/* your expression */).expect("Failed to initialize agent");
agent_sdk::a2a::app(agent).expect("Failed to initialize A2A app")
});
Ok(app.serve(req)?)
}

Pass any expression that returns Result<Agent, _>:

agent_sdk::a2a_serve!({
let config = AgentConfig::new("my-wasm-agent", "WASM agent");
Agent::new_with_config(config)
});
TypeModulePurpose
A2aAppagent_sdk::a2a_appA2A application facade: owns server wiring around a shared Agent
AgentHostBuilderagent_sdk::hostBuilder for merged multi-protocol hosting
AgentHostagent_sdk::hostRuntime host combining agent with enabled protocol adapters
A2aServeragent_sdk::serverLow-level A2A JSON-RPC server (used internally by A2aApp)
AgentCarda2a_protocol_core::agentDiscovery card returned at /.well-known/agent-card.json
a2a_serve!agent_sdkWASM convenience macro for OnceLock<A2aApp> + Spin handler