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 testing — cargo 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-core✓✓Protocol-agnostic types
a2a-server✓✓Axum on native, Spin SDK on WASM
a2a-client✓✓Reqwest on native, Spin SDK on WASM
llm-engine✓✓llm_client via protocol_transport_core
context-window✓✓Pure logic, no I/O
mcp-client✓✓rmcp on native, mcp_protocol on WASM
agent-observability✓✓Facade adapts to target
config-loader✓✓JSON + env vars
a2a-tools✓✓LLM tools wrapping A2A client
interactive-tools✓✓Pure agent-core types
event-stream✓✗Requires Tokio streams
agui-stream✓✗Built on event-stream
sub-agents✓✗Requires event-stream + streaming A2A client
redis-storage✓✗Requires 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