VMs
Create a Secure Exec VM to keep files, installed packages, and background processes across calls.
evaluate and execute at the top level are conveniences: each call creates
a VM, runs your code, and disposes it. When something should outlast one call,
create the VM yourself.
import { createVm } from "secure-exec";
// A VM lives until you dispose it. It is an agentOS VM with Secure Exec's
// defaults, so files, installed packages, and processes persist across calls.
const vm = await createVm();
createVm takes the same VM options as a one-shot call: permissions,
limits, mounts, hostFunctions, and the rest.
What a VM gives you
| On the VM | Purpose |
|---|---|
vm.javascript | execute, evaluate, executeFile, spawn, spawnFile |
vm.typescript | The same, plus check and checkProject |
vm.npm | install, runScript, runPackage |
vm.filesystem | Read and write guest files from the host |
vm.network | Send requests to a server running in the VM |
vm.process | Manage background processes |
vm.createContext() | JavaScript state that survives between calls |
vm.dispose() | Shut the VM down |
A Secure Exec VM is an agentOS VM with Secure Exec’s defaults, so each namespace behaves exactly as the agentOS docs describe.
Files across calls
Every call on a VM gets fresh JavaScript memory and the same filesystem.
// Hand the guest a file from the host.
await vm.filesystem.writeFile(
"/workspace/orders.csv",
"item,price\ncoffee,4\nbagel,3\n",
);
// Guest code uses ordinary `node:fs`. Each call gets fresh JavaScript memory
// and the same filesystem.
await vm.javascript.execute(`
import { readFileSync, writeFileSync } from "node:fs";
const rows = readFileSync("/workspace/orders.csv", "utf8").trim().split("\\n").slice(1);
const total = rows.reduce((sum, row) => sum + Number(row.split(",")[1]), 0);
writeFileSync("/workspace/report.json", JSON.stringify({ orders: rows.length, total }));
`);
// Read what it produced back on the host.
const report = await vm.filesystem.readFile("/workspace/report.json");
console.log(new TextDecoder().decode(report)); // {"orders":2,"total":7}
Run files
// Run a file that is already in the VM, and type-check the project around it.
await vm.filesystem.writeFile(
"/workspace/main.ts",
'const total: number = 42;\nconsole.log("total", total);\n',
);
const ran = await vm.typescript.executeFile("/workspace/main.ts", {
output: { capture: "all" },
});
console.log(ran.stdout?.trim()); // total 42
A file resolves its imports from its own directory, the same as in Node.js, so a
file under /workspace finds packages installed there.
Dispose
A VM holds memory until you dispose it. Use try and finally, or
await using with TypeScript 5.2 or newer.
What you usually do not need
Most stateful work needs a VM and nothing else. Files, packages, and servers all live in the VM. Reach for a context only when JavaScript variables must survive between calls, as in a REPL or a notebook.