View on npm npm install @amlalabs/kalahari

Local agent microVM sandboxes.

Free and open source. KVM on Linux, Hypervisor.framework on macOS.

Quickstart

Create a sandbox in eight lines

sandbox.ts
import { KalahariClient } from "@amlalabs/kalahari";

const client = new KalahariClient({ image: "python:3.12-alpine" });
const sandbox = await client.createSandbox();

await sandbox.writeFile("/main.py", "print('hello')");
const result = await sandbox.run("python3", { args: ["/main.py"] });

console.log(result.stdout);
await sandbox.destroy();

For one-off commands: await runCommand({ image, command, args }).

Zygotes

Boot once. Spawn many.

Configure a sandbox the way you want it (install dependencies, write files, warm a runtime), then freeze it into a zygote. Each spawn() hands back a fresh child in milliseconds. No re-installs, no re-warmup.

zygote.ts
import { KalahariClient } from "@amlalabs/kalahari";

const client = new KalahariClient({ image: "node:22-alpine" });
const sandbox = await client.createSandbox();

await sandbox.mkdir("/workspace");
await sandbox.writeFile("/workspace/state.txt", "base\n");

const zygote = await sandbox.zygote();

const a = await zygote.spawn();
const b = await zygote.spawn();

await a.writeFile("/workspace/state.txt", "child a\n");

console.log(await a.readFile("/workspace/state.txt"));
console.log(await b.readFile("/workspace/state.txt"));

await Promise.all([a.destroy(), b.destroy()]);
await zygote.destroy();

Children are isolated. Writes from one spawn() never leak to siblings.

A child can itself become a zygote, so you can branch and re-branch.

Frequently Asked Questions

How is Kalahari different from E2B or Daytona?
E2B and Daytona are managed services: every command crosses the network, sandboxes carry per-second billing, and your code runs on their infrastructure. Kalahari runs sandboxes on the local machine via KVM (Linux) or Hypervisor.framework (macOS). Calls are in-process, there is no metered runtime, and data never leaves your network. The shim modules expose the same method names, so existing code switches with a one-line import change.
What runs under the hood?
A from-scratch Rust VMM. Not another Firecracker wrapper. Linux and macOS are both first-class.

Drop-in for your existing SDK

Same shape as the upstream package. Only the import path changes.

  • E2B

    import { Sandbox } from "@amlalabs/kalahari/e2b";
    
    const sandbox = await Sandbox.create({ image: "node:22-alpine" });
    const result = await sandbox.commands.run("ls /");
    console.log(result.stdout);
    await sandbox.kill();
  • Daytona

    import { Daytona } from "@amlalabs/kalahari/daytona";
    
    const daytona = new Daytona();
    const sandbox = await daytona.create({ language: "python" });
    const result = await sandbox.process.codeRun("print('hello')");
    await sandbox.delete();
  • ComputeSDK

    import { compute } from "computesdk";
    import { kalahari } from "@amlalabs/kalahari/computesdk";
    
    const sdk = compute({
      provider: kalahari({ image: "node:22-alpine" }),
    });
    const sandbox = await sdk.sandbox.create();
    
    const result = await sandbox.runCommand("node --version");
    console.log(result.stdout);
    await sandbox.destroy();
  • Harbor

    import { harbor } from "@amlalabs/kalahari/harbor";
    
    const env = harbor({ image: "node:22-alpine" });
    const result = await env.run({ command: "node --version" });
    console.log(result.stdout);