Skip to content

WASM vs Native

PromptFleet agents compile to two targets from the same crate. This page explains when to pick each, what differs, and shows real code side by side.

Choose wasm32-wasip1 (WASI Preview 1) when you need:

  • Spin runtime — local runs with spin up, plus production serverless on SpinKube/Kubernetes using the same WASM; ~4.8 ms cold start, ~0.6 MB memory per agent on the serverless path.
  • Sandboxed execution — WASM’s capability model means no filesystem, no raw sockets, no ambient authority.
  • Density — run 2 000+ agents per node with fractional-CPU scheduling.
  • Production workloads where the agent serves A2A requests and doesn’t need to call other agents or stream AG-UI events.

The WASM target uses Spin SDK for HTTP handling and the Spin async executor. There is no Tokio, no threads, no background tasks.

Choose native (x86_64 / aarch64) when you need:

  • Local development and testingcargo run, attach a debugger, inspect with curl.
  • Full async runtime — Tokio provides multi-threaded scheduling, timers, channels.
  • Sub-agent delegation — call other A2A agents with streaming support.
  • AG-UI streaming — serve real-time UI event streams to frontends.
  • Redis/Valkey storage — shared task state across replicas.

The agent logic — skills, message handlers, LLM tool definitions — stays identical. Only the entry point and runtime wiring differ:

use agent_sdk::{
a2a::A2aApp,
agent::{AgentConfig, Response},
Agent,
};
use spin_sdk::http_component;
static APP: std::sync::OnceLock<A2aApp> = std::sync::OnceLock::new();
#[http_component]
async fn handle_request(
req: spin_sdk::http::Request,
) -> anyhow::Result<spin_sdk::http::Response> {
let app = APP.get_or_init(|| {
let config = AgentConfig::new("a2a-echo-wasm", "A2A v1.0 echo agent (WASM)")
.with_base_url("http://127.0.0.1:3001");
let mut agent = Agent::new_with_config(config).expect("agent init");
agent
.add_skill("echo")
.description("Echoes back user messages")
.register()
.expect("register echo skill");
agent.set_message_handler(|msg_ctx, task_ctx| async move {
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),
)
});
A2aApp::from_agent(agent).expect("app init")
});
Ok(app.serve_async(req).await?)
}

Key differences:

AspectWASMNative
Entry point#[http_component] via Spin SDK#[tokio::main]
Agent initOnceLock (Spin reuses the instance across requests)Direct in main
HTTP serverSpin runtime handles listeningA2aApp::serve("addr") starts Axum
Async executorSpin executor (single-threaded, cooperative)Tokio (multi-threaded)
StreamingRequest/response only (no SSE)Full SSE via event-stream feature

Not all SDK features work on both targets. Features that depend on Tokio, OS sockets, or background tasks are native-only.

FeatureNativeWASMNotes
agent-coreProtocol-agnostic types
a2a-serverAxum on native, Spin SDK on WASM
a2a-clientReqwest on native, Spin SDK on WASM
llm-enginellm_client via protocol_transport_core
context-windowPure logic, no I/O
mcp-clientrmcp on native, mcp_protocol on WASM
agent-observabilityFacade adapts to target
config-loaderJSON + env vars
a2a-toolsLLM tools wrapping A2A client
interactive-toolsPure agent-core types
event-streamRequires Tokio streams
agui-streamBuilt on event-stream
sub-agentsRequires event-stream + streaming A2A client
redis-storageRequires redis crate (TCP sockets)
GoalPreset / featuresTarget
Minimal A2A respondera2a-server + llm-engineWASM or native
Full interoperable agenta2a-agentNative (SSE streaming)
User-facing UI agentagui-agentNative (AG-UI streaming)
Both A2A + AG-UIdual-agentNative
Everything + MCP + obspf-agentNative