Skip to main content
Executing Code

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 VMPurpose
vm.javascriptexecute, evaluate, executeFile, spawn, spawnFile
vm.typescriptThe same, plus check and checkProject
vm.npminstall, runScript, runPackage
vm.filesystemRead and write guest files from the host
vm.networkSend requests to a server running in the VM
vm.processManage 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.

await vm.dispose();

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.