Skip to content

Quickstart

  1. Create a new project

    Terminal window
    cargo new a2a-echo && cd a2a-echo
  2. Add dependencies

    [dependencies]
    agent_sdk = { workspace = true, features = ["a2a-server", "event-stream"] }
    tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
    anyhow = "1.0"
  3. Write the agent

    use agent_sdk::{
    a2a::A2aApp,
    agent::{AgentConfig, MessageContext, Response, TaskContext},
    Agent,
    };
    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
    let mut config = AgentConfig::new("a2a-echo-native", "A2A v1.0 echo agent (native)")
    .with_base_url("http://127.0.0.1:3000");
    config.streaming = true;
    let mut agent = Agent::new_with_config(config)?;
    agent
    .add_skill("echo")
    .description("Echoes back user messages")
    .register()?;
    agent.set_message_handler(echo_handler);
    let app = A2aApp::from_agent(agent)?;
    println!("A2A echo agent (native) listening on http://127.0.0.1:3000");
    app.serve("127.0.0.1:3000").await
    }
    async fn echo_handler(
    msg_ctx: MessageContext,
    task_ctx: Option<TaskContext>,
    ) -> agent_sdk::SdkResult<agent_sdk::agent::RuntimeResponse> {
    let input = msg_ctx.text_content.as_deref().unwrap_or("(empty)");
    Response::message_text(
    format!("Echo: {input}"),
    None,
    None,
    task_ctx.and_then(|t| t.context_id),
    )
    }
  4. Run it

    Terminal window
    cargo run

    The agent listens on http://127.0.0.1:3000.

  5. Test with curl

    Terminal window
    curl -X POST http://127.0.0.1:3000 \
    -H "Content-Type: application/json" \
    -d '{
    "jsonrpc": "2.0",
    "id": "1",
    "method": "SendMessage",
    "params": {
    "message": {
    "role": "user",
    "parts": [{"text": "Hello, agent!"}]
    }
    }
    }'

You configured an Agent with AgentConfig and Agent::new_with_config, registered a skill with add_skill, and installed a user message handler with set_message_handler. That runtime is protocol-neutral. A2aApp::from_agent wraps it as an A2A v1 HTTP app: native code calls serve on Tokio, while the Spin path uses #[http_component] and serve_async with a OnceLock<A2aApp> so the agent is built once per process.

For config-first setups (JSON or files), the SDK also offers AgentBuilder; this tutorial uses explicit AgentConfig to keep the example small.