Processes
This page is an internals deep-dive on the kernel’s process model: the data
structures and syscall paths behind every guest process. For the client-facing
API (exec, spawn, openShell, lifecycle, the process tree), see
Processes & Shell. For the surrounding component and trust
model, see Architecture.
Two invariants frame everything below:
- No real host process is ever spawned for guest work. Every guest process is an entry in a kernel-owned virtual process table, not an OS process. Guest JavaScript runs in V8 isolates; guest commands like
shand coreutils run as WebAssembly. Neither isnodeor a host binary. - Every process operation is a syscall into the kernel. Spawning, waiting, signaling, reading stdout, and resizing a PTY all cross from the untrusted executor into the sidecar-owned kernel, which services them against virtualized resources.
The virtual process table
Section titled “The virtual process table”Each VM owns one process table. It is the authority for what is “running” inside that VM; nothing in it corresponds to a host PID.
- Per-VM and isolated. Two VMs have two independent tables. A PID in one VM is meaningless in another, and processes are never visible across the VM boundary.
- Holds every guest process, not only the ones a client started explicitly. A
spawnfrom the client, a child spawned by guestnode:child_process, and the processes behind a shell pipeline are all table entries. This is why the system-wide views (allProcesses,processTree) can show more than what the client launched. - Tracks lifecycle and lineage. Each entry carries its PID, the command and arguments, parent PID (so the tree can be reconstructed), running/exited status, exit code once collected, and its attached stdio endpoints.
- Records a driver. An entry knows which execution backend services it (for example a V8 isolate versus a WASM runtime). This is the
driverfield surfaced onallProcesses. Drivers differ in how the code runs; they share the same table, the same kernel-owned stdio, and the same boundary.
How a spawn is serviced
Section titled “How a spawn is serviced”A spawn, whether it originates from a client spawn/exec call or from guest
node:child_process, follows one path through the kernel:
- The request crosses into the kernel. A client call arrives over the wire protocol; a guest call arrives as a syscall from the executor. Either way the kernel, not the caller, performs the work.
- Permission check. The kernel applies the VM’s permission policy before doing anything. Process execution is denied by default and must be granted; the policy is trusted input, the guest making the request is not.
- Resolve the program. The command is resolved against the VM’s virtual filesystem (PATH lookup over the VFS), not the host. The resolved program decides the driver: a JavaScript entrypoint runs in a V8 isolate; a
.wasmprogram (includingshand coreutils) runs on the WASM runtime. - Allocate the table entry. The kernel assigns a virtual PID, records the command, arguments, environment, working directory, and parent PID, and links stdio endpoints (see below).
- Start execution. The driver begins running the program. For a one-shot
execthe kernel additionally collects stdout, stderr, and the exit code and returns them as the call’s result; forspawnit leaves the process running and streams output via events. - Reap and record exit. When the program finishes, the kernel records the exit code on the table entry and marks it exited, which is what a
wait/waitProcessresolves against and whatprocessExitreports.
Signals (stopProcess / SIGTERM, killProcess / SIGKILL) are the same shape: a
request into the kernel, which applies it to the virtualized process rather than
to any host process.
Stdio bridging
Section titled “Stdio bridging”Standard streams are kernel-owned objects, not host file descriptors. Each process entry has stdin, stdout, and stderr endpoints that the kernel wires up when the entry is created.
- Capture vs. stream. For
exec, the kernel buffers stdout and stderr and hands them back when the process exits. Forspawn, output is delivered incrementally asprocessOutputevents tagged with the PID and the stream (stdout/stderr), andprocessExitsignals completion. - Writable stdin.
writeProcessStdinpushes bytes into the process’s stdin endpoint;closeProcessStdincloses the write side so programs that read to EOF (likecat) can finish. None of this touches a real pipe on the host. - Pipes between processes. Shell pipelines (
a | b) connect one process’s stdout endpoint to the next process’s stdin endpoint through kernel-owned pipes. The pipe is a virtual object in the kernel, so a pipeline behaves like Linux without any host IPC.
Because these endpoints are kernel objects, the same bridging works identically whether the process is a V8 isolate or a WASM program; the driver writes to and reads from kernel stdio, not from anything host-provided.
PTYs and interactive shells
Section titled “PTYs and interactive shells”An interactive shell needs a terminal, not just piped stdio: line editing, job control signals, and window size all depend on a PTY. The kernel provides virtual PTY devices for this.
- A shell is a process plus a PTY.
openShellallocates a kernel PTY and starts a shell process attached to it, returning ashellId. The PTY is a virtualized terminal device, never a host/dev/ptsentry. - Bidirectional terminal I/O.
writeShellfeeds keystrokes into the PTY master side; everything the shell and its children emit comes back asshellDataevents. This carries terminal control sequences, so full-screen TUIs behave correctly. - Resize is a terminal operation.
resizeShellupdates the PTY’s window size (columns and rows), which the kernel propagates to the foreground process the way a real terminal resize would, so programs relying onTIOCGWINSZ-style sizing redraw correctly. - Teardown.
closeShelltears down the PTY and the attached shell process. An open shell keeps the VM active, the same way an open PTY keeps a session alive on a real system.
WASM sh and coreutils on the process model
Section titled “WASM sh and coreutils on the process model”The shell and the standard commands behind process execution are not special host helpers; they are ordinary guest processes that happen to be WebAssembly. For the full WASM execution model see WASM VM; here is how it maps onto the process table specifically.
- They are normal table entries. Running
sh,ls,cat, etc. allocates virtual PIDs and table entries exactly like any other process, with the WASM driver recorded on each. A pipeline of coreutils is several entries linked by kernel pipes. - POSIX process semantics are virtualized, not borrowed from the host. Plain WASI has no process model (no
fork/exec/wait). agentOS supplies those semantics through kernel-backed host imports, so a WASM program that spawns and waits on a child drives the same kernel process table that JS guests use. A coreutil spawning a subcommand is one table entry creating another. - Same stdio, same PTY. WASM processes read and write the kernel stdio endpoints described above, and a shell built from WASM
shattaches to a kernel PTY just like any interactive shell. The driver differs; the kernel-owned plumbing does not.
This is why the process model is uniform: whether an entry is a V8 isolate or a WASM binary, it lives in the same per-VM table, goes through the same permission-checked spawn path, and uses the same kernel-owned stdio and PTYs.
See also
Section titled “See also”- Processes & Shell: the client API for running and managing processes.
- WASM VM: how WebAssembly guests get POSIX process, user, and network semantics.
- Architecture: components, the trust boundary, and the request lifecycle.
- Permissions: the policy the kernel checks on every spawn.