The original OpenClaw is a beautifully-designed TypeScript framework: clear primitives, sane defaults, a thoughtful plugin surface. We use it. We like it. We owe it.
So why a Rust rewrite?
The shape of the problem
Agent runtimes are unusual server workloads:
- Long-lived sessions, append-only event logs, lots of small allocations.
- High concurrency: every session has a streaming model, sometimes a tool call running in parallel, often a channel adapter pushing events from somewhere else.
- Strict security boundary requirements: untrusted tool invocations, untrusted plugin code, untrusted user input.
- A burning desire to ship as a single binary so deployment is trivial.
Node.js handles three of those four well. It loses on the fourth and on memory predictability under sustained load.
What the rewrite actually buys you
1. Sub-millisecond message routing
The hot path through openclaw-gateway is a JSON-RPC dispatch on top of axum. tokio’s task scheduler and sled’s lock-free reads keep the per-message overhead low. We’re targeting < 10 ms per message in the gateway and seeing comfortably less than that in dev.
2. Memory safety without a GC
#![forbid(unsafe_code)] is enforced across every workspace crate. No unsafe, no null pointers, no data races by construction. Sessions that hold thousands of events stay predictable under load — no GC pauses, no leaky generational nightmares.
3. Single static binary
cargo install openclaw-cli and you have:
- The HTTP/WebSocket gateway.
- The agent runtime + sandbox backends.
- The Vue 3 dashboard (embedded at build time).
- The Telegram channel adapter.
- The CLI surface (
onboard,doctor,status,config,daemon).
One binary. No Node runtime to ship. No node_modules. No Dockerfile gymnastics.
4. TypeScript still works
This was non-negotiable: openclaw-plugins lets existing TypeScript plugins keep running. Each plugin is a process, the bridge is nng (an embedded message-passing transport from the nanomsg family), the wire format is JSON-RPC, and there are eight lifecycle hooks: BeforeMessage, AfterMessage, BeforeToolCall, AfterToolCall, SessionStart, SessionEnd, AgentResponse, Error.
The cost of migration is “delete a package.json,” not “rewrite your tools.”
Concretely, what does the runtime look like?
use openclaw_agents::{AgentRuntime, SandboxConfig, SandboxLevel};
use openclaw_providers::AnthropicProvider;
use openclaw_core::secrets::ApiKey;
let provider = AnthropicProvider::new(ApiKey::new("sk-ant-...".into()));
let sandbox = SandboxConfig {
level: SandboxLevel::Strict,
allowed_paths: vec![PathBuf::from("./workspace")],
network_access: false,
..Default::default()
};
let runtime = AgentRuntime::new(provider)
.with_tools(tools)
.with_sandbox(sandbox);
let response = runtime.process_message(&mut context, "Hello!").await?;
The same runtime is reachable over JSON-RPC for clients, over IPC for plugins, and over napi-rs for Node code. One core, three surfaces.
What it costs
Honesty about trade-offs:
- The Rust compile is slower than
tsc— butcargo buildis fully incremental and most edits hit a hot crate. - Rewriting the channel adapters from scratch takes time. Telegram ships; Discord, Slack, Signal, Matrix, WhatsApp are roadmap items.
- The WASM plugin runtime is still under evaluation (wasmtime vs wasmer). If WASM is on your critical path right now, stay on the TypeScript OpenClaw plugin bridge.
Who this is for
- Teams that want a Rust core for production agent infrastructure.
- Teams that have outgrown a Node process for performance, security, or deployment reasons.
- Teams migrating from TypeScript OpenClaw who want compatibility, not a forced rewrite.
It is not for greenfield prototypers who want “the fastest path to ship.” Stay on TypeScript for that. Come back when you need the runtime properties Rust gives you.
Get it running
cargo install openclaw-cli
openclaw onboard
openclaw gateway run
Three commands. One binary. A real agent runtime.