Skip to content
GitHub Get Started
Reference

POSIX Syscalls

Not everything inside an agentOS VM is JavaScript. The shell (sh) and the coreutils behind process execution ship as WebAssembly binaries, and you can run your own WASM programs too. To make those programs behave like normal Linux tools, agentOS presents a POSIX syscall surface on top of WebAssembly.

  • WASM is a first-class guest. WASM binaries run beside JavaScript inside the same VM.
  • Same kernel, same boundary. WASM syscalls route through the same kernel that backs JS guests, so there is no extra host access.
  • POSIX shape, not host access. The extensions below add process, user, and network semantics, all virtualized.

The base standard for WASM system access is WASI (specifically wasip1). WASI is intentionally minimal:

  • It gives a guest preopened file descriptors, clocks, randomness, and basic file I/O.
  • It has no process model (no fork / exec / wait).
  • It has no users or groups (no getuid / getgid).
  • It has no general sockets (no connect / listen).

Real command-line programs expect all of those. agentOS closes the gap in two layers, and both route through the kernel rather than the host.

agentOS layers a POSIX surface over WASM. Layer 1 adds capabilities WASI does not express at all; Layer 2 adapts the standard WASI calls so a normal libc behaves correctly inside the VM. Both bottom out in the kernel.

WASM guest (sh, coreutils, your .wasm)compiled for wasm32-wasip1, linked against patched wasi-libcLayer 1: host import moduleshost_process — spawn / waithost_user — uid / gidhost_net — TCP socketshost_sleep_ms — blocking sleepLayer 2: kernel-backed WASI shimstdio through the kernel bridgemounts mirrored as preopensread-only tiers enforcedpaths confined to their mountKernel: virtual filesystem, process table, socket tablesame paths that back JavaScript guests — no host escape

Standard WASI cannot express fork / exec, getuid, or connect. agentOS declares extra WebAssembly import modules that the host runtime implements, so guest libc can call them as if they were ordinary syscalls. These bindings live in the wasi-ext crate and cover three areas:

  • host_process: process management. Spawn a child process (argv, env, inherited stdio fds, working directory), wait for a child to exit, and related file-descriptor operations. This is what gives a WASM sh real child process semantics; spawns go through the kernel process table.
  • host_user: user and group identity (uid, gid, user info). Base WASI has no concept of a user; this lets tools that call getuid / getgid see the VM’s virtualized identity.
  • host_net: TCP sockets (connect, listen, send, receive) through the kernel socket table, gated by the same network permission policy as everything else. Base WASI has no general socket API.

A small host_sleep_ms binding provides blocking sleep. Together these let a guest compiled for wasip1 behave as if it had a process model, user identity, and a network, all virtualized.

// Imported from the host runtime, declared by the wasi-ext bindings.
// Guest libc calls these as if they were ordinary syscalls.
__attribute__((import_module("host_process"), import_name("proc_spawn")))
int host_proc_spawn(const char *argv, const char *envp, int cwd_fd);
// getuid returns an errno; the uid is written through the out-pointer.
__attribute__((import_module("host_user"), import_name("getuid")))
int host_getuid(unsigned int *ret_uid);
__attribute__((import_module("host_net"), import_name("net_connect")))
int host_net_connect(int fd, const char *addr, int addr_len);

The second layer adapts the standard WASI calls themselves so that programs built against a normal libc behave correctly inside the VM. The embedded shim:

  • Routes stdio through the kernel. fd_read / fd_write on the standard descriptors go through the kernel stdio bridge rather than host file descriptors, so output stays inside the VM and honors PTYs and redirection.
  • Fills in libc expectations. For example fcntl(F_SETFL) is serviced via fd_fdstat_set_flags, so flag changes that libc performs do not fail.
  • Mirrors mounts as preopens. The preopen table reflects the VM’s guest path mappings, so mounted directories are visible to WASM path resolution exactly as they are to JS and to node:fs.
  • Enforces read-only tiers. path_open rejects create / truncate / write flags on read-only mounts while still allowing non-mutating opens (directory traversal, O_DIRECTORY), so read-only mounts stay read-only without breaking find, ls, and friends.
  • Confines paths to their mount. Targets are resolved beneath the specific preopen’s root, so .. segments cannot escape one mount into a sibling mount or a host path.
fd_read(0) -> kernel stdio bridge (not a host fd)
fcntl(fd, F_SETFL) -> fd_fdstat_set_flags (libc flag changes succeed)
path_open("/data/x") -> resolved under the /data preopen root
path_open(..O_CREAT) -> rejected on a read-only mount
path_open("../../etc")-> stays inside the mount; cannot escape