Skip to main content
Operating System

Resource Limits

Cap per-VM resources, JavaScript CPU/wall-clock time, Python execution, and WASM runtime work so guest code can never exhaust the host.

Every agentOS VM runs with per-VM resource and runtime caps. Runaway or malicious guest code can exhaust its own VM, but it can never starve the host or any sibling VM.

  • Bounded by default: each VM ships with conservative caps. Unset fields fall back to built-in defaults that match the runtime’s historical constants.
  • Per-VM: every VM gets its own budget. Limits are not shared across VMs.
  • Enforced by the sidecar/runtime: a guest that exceeds a cap fails inside the VM (out-of-memory, EMFILE, EAGAIN, runtime timeout, etc.). The host is never affected.
  • Operator-raisable: the operator (the trusted process that creates the VM) may raise any cap for trusted workloads. Guest code can never raise its own caps.

Setting limits

Set caps on the limits object in the agentOS config. Limits are grouped by subsystem (resources, jsRuntime, python, wasm, and more). Omitted limits keep their secure default.

import pi from "@agentos-software/pi";
import { agentOS, setup } from "@rivet-dev/agentos";

const vm = agentOS({
	software: [pi],
	limits: {
		resources: {
			maxProcesses: 64, // concurrent processes
			maxOpenFds: 256, // open file descriptors
			maxSockets: 128, // open sockets
			maxFilesystemBytes: 256 * 1024 * 1024, // VFS storage budget
			maxWasmFuel: 30_000, // WASM execution budget
			maxWasmMemoryBytes: 128 * 1024 * 1024, // WASM linear memory
			maxWasmStackBytes: 4 * 1024 * 1024, // WASM call-stack ceiling
		},
		jsRuntime: {
			v8HeapLimitMb: 128, // JS isolate heap
			cpuTimeLimitMs: 30_000, // active JS CPU time
			wallClockLimitMs: 0, // 0 disables elapsed wall-clock cutoff
			importCacheMaterializeTimeoutMs: 30_000, // Node import-cache setup
			syncRpcWaitTimeoutMs: 30_000, // host sync-RPC wait
		},
		python: {
			executionTimeoutMs: 300_000, // Python wall-clock execution
			maxOldSpaceMb: 0, // 0 keeps the Pyodide runner default
		},
		wasm: {
			prewarmTimeoutMs: 30_000, // WASM compile-cache warmup
			runnerHeapLimitMb: 2048, // trusted WASI runner V8 heap
		},
	},
});

export const registry = setup({ use: { vm } });
registry.start();

Available caps

LimitControlsNotes
resources.maxProcessesConcurrent processes in the VM process tableCaps fork bombs and runaway spawning. New spawns fail with EAGAIN.
resources.maxOpenFdsOpen file descriptorsExhausting the table fails with EMFILE / ENFILE.
resources.maxSocketsOpen sockets in the socket tableBounds concurrent connections; excess connect/accept fail.
resources.maxFilesystemBytesTotal bytes stored in the virtual filesystemBounds VFS storage; writes past the budget fail with a no-space error.
resources.maxWasmFuelWASM execution budgetBounds WASM execution work; unset means no explicit fuel budget.
resources.maxWasmMemoryBytesWASM linear memory, in bytesDefault is 128 MiB.
resources.maxWasmStackBytesMaximum WASM call-stack size, in bytesDeep recursion fails with a stack overflow instead of crashing the VM.
jsRuntime.v8HeapLimitMbGuest JavaScript V8 heap, in MiBDefault is 128.
jsRuntime.cpuTimeLimitMsActive JavaScript CPU timeDefault is 30000; 0 disables the CPU watchdog.
jsRuntime.wallClockLimitMsJavaScript elapsed wall-clock backstopDefault is 0, disabled. Use this for finite commands, not long-lived adapters.
jsRuntime.importCacheMaterializeTimeoutMsNode import-cache materialization timeoutDefault is 30000.
jsRuntime.syncRpcWaitTimeoutMsJavaScript sync host-RPC waitUnset keeps the engine default, currently 30000.
python.executionTimeoutMsPython execution wall-clock timeoutDefault is 300000.
python.maxOldSpaceMbPyodide runner V8 old-space heap, in MiBDefault is 0, which keeps the engine default.
wasm.prewarmTimeoutMsWASM compile-cache warmup timeoutDefault is 30000.
wasm.runnerHeapLimitMbTrusted WASI/WASM runner V8 heap, in MiBDefault is 2048; this is not guest linear memory.

Behavior at the limit

  • WASM stack: deep recursion throws a stack-overflow error in the guest, never a host crash.
  • JavaScript CPU time: CPU-bound loops terminate with a CPU-budget error once active JS CPU exceeds jsRuntime.cpuTimeLimitMs.
  • JavaScript wall time: awaiting or blocked JS terminates only when you set jsRuntime.wallClockLimitMs; the default is disabled for long-lived adapters.
  • Filesystem bytes: writing past the VFS budget fails with a no-space error to the guest.
  • Counts (fds / processes / sockets): hitting a table cap returns the standard POSIX errno (EMFILE, EAGAIN, etc.), exactly as a real Linux kernel would under ulimit.

Warnings & observability

Limits are observable, not just enforced. Every bound — resource caps and the internal bounded queues alike — is tracked in a central limit registry that:

  • Warns before the limit is hit. As usage crosses ~80% of a cap, the runtime emits a structured warning (once per crossing, re-armed only after it recovers), so a slow consumer or a runaway guest is visible before it fails.
  • Applies backpressure instead of failing catastrophically. The internal queues between the guest, the runtime, and the host block their producer when full rather than dropping data or tearing down the session — so a transient burst degrades to “slower”, not “broken”.
  • Surfaces through logs. secure-exec logs to stderr (stdout is the wire protocol); set AGENTOS_LOG=warn (the default) to see near-limit warnings or AGENTOS_LOG=debug for live per-limit usage snapshots.

See Limits & Observability for the full architecture.