> ## Documentation Index
> Fetch the complete documentation index at: https://tbd-6fc993ce-hypeship-document-mcp-oauth-names.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Process Execution

> Run commands and manage processes alongside the browser

every KERNEL browser runs in a full linux environment. [browser control](/introduction/control) handles what happens in the page; process execution lets you run commands, scripts, and binaries alongside the browser.

use process execution when your browser automation needs software or system access outside your page-control calls. code running alongside the browser can access the same files and local browser services, so you can process downloads without transferring them to your application first, run existing command-line tools, or keep a high-frequency agent loop close to the browser.

use `process.exec` for bounded commands where you need the result before continuing. use `process.spawn` for agents, servers, watchers, interactive shells, and other long-running processes. after spawning a process, you can inspect its status, stream its output, send input, resize its pty, or terminate it.

if your task only needs to control the page itself, use one of Kernel's [browser control](/introduction/control) options instead — [playwright execution](/browsers/playwright-execution), computer use, CDP, or WebDriver BiDi. use process execution when you need an executable, operating-system tools, a long-running process, or direct filesystem access. for workloads that need their own deployment and invocation lifecycle, use [KERNEL apps](/apps).

## common production patterns

* **co-locate an agent with its browser.** browser agents often make many small tool calls. upload an agent binary with [file i/o](/browsers/file-io), run it alongside chromium, and point it at the local playwright daemon to remove a round trip through KERNEL's public api from each call. see the [fx co-located agent cookbook](https://github.com/kernel/cookbooks/tree/main/integrations/fx-colocated-agent) for an end-to-end example.
* **process downloads before retrieving them.** unpack archives, extract text from documents, resize images, or compress a directory while the files are still alongside the browser, then retrieve only the final artifacts with file i/o.
* **run existing command-line tools.** upload a pinned binary or script and call it from the same workflow instead of rewriting it as browser automation code.
* **start a session-local helper.** run a local server, callback handler, or file watcher for as long as the browser task needs it.
* **measure memory headroom.** read the session's memory allocation and per-process usage with `free` and `ps` to size a workload or catch growth before chromium runs out. see [measure memory and cpu usage](#measure-memory-and-cpu-usage).
* **inspect failed browser tasks.** run tools such as `curl`, `ps`, and `ls` to inspect network responses, running processes, logs, and downloaded files before the browser is deleted.

## Run a command synchronously

`process.exec` runs a command and blocks until it exits or times out. `stdout_b64` and `stderr_b64` are base64-encoded, and `exit_code` tells you whether it succeeded.

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  import Kernel from '@onkernel/sdk';

  const kernel = new Kernel();

  const browser = await kernel.browsers.create({});

  const result = await kernel.browsers.process.exec(browser.session_id, {
    command: 'ls',
    args: ['-la', '/tmp'],
  });

  console.log(Buffer.from(result.stdout_b64 ?? '', 'base64').toString());
  console.log('exit code', result.exit_code);
  ```

  ```python Python theme={null}
  import base64

  from kernel import Kernel

  kernel = Kernel()

  browser = kernel.browsers.create()

  result = kernel.browsers.process.exec(
      browser.session_id,
      command="ls",
      args=["-la", "/tmp"],
  )

  print(base64.b64decode(result.stdout_b64 or "").decode())
  print("exit code", result.exit_code)
  ```
</CodeGroup>

Use `cwd` to set a working directory, `env` to pass environment variables, `as_user`/`as_root` to control privileges, and `timeout_sec` to cap execution time.

## Run a command in the background

`process.spawn` starts a command without waiting for it to finish, returning a `process_id` you use to manage it afterward. This is the right call for long-running processes — a server, a watcher script, an interactive shell.

<Warning>
  If your long-running process is a server, pick a port yourself and don't assume it's free — the VM's own infrastructure (live view, CDP, the execution API) already listens on several, including `8080`, `9222`–`9225`, `8888`, `10001`, and `10002`.
</Warning>

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  const spawned = await kernel.browsers.process.spawn(browser.session_id, {
    command: 'sh',
    args: ['-c', 'while true; do echo "tick $(date +%s)"; sleep 5; done'],
  });

  console.log('process_id', spawned.process_id);
  ```

  ```python Python theme={null}
  spawned = kernel.browsers.process.spawn(
      browser.session_id,
      command="sh",
      args=["-c", 'while true; do echo "tick $(date +%s)"; sleep 5; done'],
  )

  print("process_id", spawned.process_id)
  ```
</CodeGroup>

Pass `allocate_tty: true` to attach a pseudo-terminal for interactive shells, with `cols`/`rows` to set its initial size.

## Manage a running process

### Check status

Poll for whether a spawned process is still running, and its resource usage:

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  const status = await kernel.browsers.process.status(spawned.process_id, {
    id_or_name: browser.session_id,
  });

  console.log(status.state, status.exit_code);
  ```

  ```python Python theme={null}
  status = kernel.browsers.process.status(
      spawned.process_id,
      id_or_name=browser.session_id,
  )

  print(status.state, status.exit_code)
  ```
</CodeGroup>

### Stream stdout and stderr

Read output from a spawned process as it happens over server-sent events:

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  const stream = await kernel.browsers.process.stdoutStream(spawned.process_id, {
    id_or_name: browser.session_id,
  });

  for await (const chunk of stream) {
    if (chunk.event === 'exit') {
      console.log('exited with', chunk.exit_code);
      break;
    }
    console.log(chunk.stream, Buffer.from(chunk.data_b64 ?? '', 'base64').toString());
  }
  ```

  ```python Python theme={null}
  import base64

  with kernel.browsers.process.stdout_stream(
      spawned.process_id, id_or_name=browser.session_id
  ) as stream:
      for chunk in stream:
          if chunk.event == "exit":
              print("exited with", chunk.exit_code)
              break
          print(chunk.stream, base64.b64decode(chunk.data_b64 or "").decode())
  ```
</CodeGroup>

### Write to stdin

Send base64-encoded input to a running process, e.g. to answer an interactive prompt:

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  await kernel.browsers.process.stdin(spawned.process_id, {
    id_or_name: browser.session_id,
    data_b64: Buffer.from('y\n').toString('base64'),
  });
  ```

  ```python Python theme={null}
  import base64

  kernel.browsers.process.stdin(
      spawned.process_id,
      id_or_name=browser.session_id,
      data_b64=base64.b64encode(b"y\n").decode(),
  )
  ```
</CodeGroup>

### Resize a PTY

Resizing only works on a process spawned with `allocate_tty: true` — calling it on a plain process returns a 400. Match the terminal to a live view or client window:

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  const shell = await kernel.browsers.process.spawn(browser.session_id, {
    command: 'sh',
    allocate_tty: true,
    cols: 80,
    rows: 24,
  });

  await kernel.browsers.process.resize(shell.process_id, {
    id_or_name: browser.session_id,
    cols: 120,
    rows: 40,
  });
  ```

  ```python Python theme={null}
  shell = kernel.browsers.process.spawn(
      browser.session_id,
      command="sh",
      allocate_tty=True,
      cols=80,
      rows=24,
  )

  kernel.browsers.process.resize(
      shell.process_id,
      id_or_name=browser.session_id,
      cols=120,
      rows=40,
  )
  ```
</CodeGroup>

### Kill a process

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  await kernel.browsers.process.kill(spawned.process_id, {
    id_or_name: browser.session_id,
    signal: 'TERM',
  });
  ```

  ```python Python theme={null}
  kernel.browsers.process.kill(
      spawned.process_id,
      id_or_name=browser.session_id,
      signal="TERM",
  )
  ```
</CodeGroup>

`signal` accepts `TERM`, `KILL`, `INT`, or `HUP`.

## Measure memory and CPU usage

Kernel doesn't expose a per-session memory metric in the API, CLI, or dashboard today, so read it from inside the session instead. Standard Linux tools see the browser's full memory allocation and every Chromium process.

### Read whole-session memory

`free` reports the memory allocated to your browser. A headless browser gets 1 GiB; a headful, non-GPU browser gets 8 GiB by default and 16 GiB when you set `memory` on [create](/api-reference/browsers/create-a-browser-session).

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  const usage = await kernel.browsers.process.exec(browser.session_id, {
    command: 'sh',
    args: ['-c', "free -m | awk '/^Mem:/ {print $2, $3, $7}'"],
  });

  const [total, used, available] = Buffer.from(usage.stdout_b64 ?? '', 'base64')
    .toString()
    .trim()
    .split(/\s+/)
    .map(Number);

  console.log(`${used} MiB used of ${total} MiB, ${available} MiB available`);
  ```

  ```python Python theme={null}
  import base64

  usage = kernel.browsers.process.exec(
      browser.session_id,
      command="sh",
      args=["-c", "free -m | awk '/^Mem:/ {print $2, $3, $7}'"],
  )

  total, used, available = (
      int(value)
      for value in base64.b64decode(usage.stdout_b64 or "").decode().split()
  )

  print(f"{used} MiB used of {total} MiB, {available} MiB available")
  ```
</CodeGroup>

`available` is the number to watch. `used` excludes page cache, which the kernel reclaims under pressure, so a session with almost no `free` memory can still be healthy.

### Break usage down by process

`ps` gives you resident set size per process, in KiB, highest first:

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  const processes = await kernel.browsers.process.exec(browser.session_id, {
    command: 'sh',
    args: ['-c', 'ps -eo rss= -o comm= --sort=-rss | head -10'],
  });

  console.log(Buffer.from(processes.stdout_b64 ?? '', 'base64').toString());
  ```

  ```python Python theme={null}
  processes = kernel.browsers.process.exec(
      browser.session_id,
      command="sh",
      args=["-c", "ps -eo rss= -o comm= --sort=-rss | head -10"],
  )

  print(base64.b64decode(processes.stdout_b64 or "").decode())
  ```
</CodeGroup>

```
287044 chromium
189836 chromium
163964 chromium
140968 mutter
131616 Xorg
129084 chromium
112336 chromium
103600 chromium
96816 chromium
78604 chromium
```

Chromium splits its work across processes — a browser process, one renderer per tab group, a GPU process, and network and storage utilities — so a tab-heavy workload shows up as many mid-sized `chromium` rows rather than one large one. Sum them for the browser's real footprint:

```bash theme={null}
ps -C chromium -o rss= | awk '{s+=$1} END {print s}'
```

Renderer processes aren't labeled with the tab they serve, so use this to size a workload and spot growth, not to attribute memory to a specific page.

### Sample over time

A single reading tells you little about a run that fails after twenty minutes. Spawn a sampler and stream it to compute percentiles or alert on a threshold:

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  const sampler = await kernel.browsers.process.spawn(browser.session_id, {
    command: 'sh',
    args: [
      '-c',
      "while true; do ps -C chromium -o rss= | awk -v t=$(date +%s) '{s+=$1} END {print t, s}'; sleep 10; done",
    ],
  });

  const stream = await kernel.browsers.process.stdoutStream(sampler.process_id, {
    id_or_name: browser.session_id,
  });

  for await (const chunk of stream) {
    if (chunk.event === 'exit') break;
    const [timestamp, rssKib] = Buffer.from(chunk.data_b64 ?? '', 'base64')
      .toString()
      .trim()
      .split(/\s+/);
    console.log(new Date(Number(timestamp) * 1000), Number(rssKib) / 1024, 'MiB');
  }
  ```

  ```python Python theme={null}
  import base64
  import datetime

  sampler = kernel.browsers.process.spawn(
      browser.session_id,
      command="sh",
      args=[
          "-c",
          "while true; do ps -C chromium -o rss= | awk -v t=$(date +%s) '{s+=$1} END {print t, s}'; sleep 10; done",
      ],
  )

  with kernel.browsers.process.stdout_stream(
      sampler.process_id, id_or_name=browser.session_id
  ) as stream:
      for chunk in stream:
          if chunk.event == "exit":
              break
          timestamp, rss_kib = base64.b64decode(chunk.data_b64 or "").decode().split()
          print(datetime.datetime.fromtimestamp(int(timestamp)), int(rss_kib) / 1024, "MiB")
  ```
</CodeGroup>

Redirect the loop to a file instead if you'd rather collect samples without holding a stream open, then pull the file down with [file i/o](/browsers/file-io) before you delete the browser. Either way, persist the samples on your side — nothing inside the VM survives deletion.

<Note>
  `process.status` reports `mem_bytes` only for processes you started through `process.spawn`, so it won't tell you anything about Chromium, and it doesn't populate CPU usage. Use `ps` or `top -b -n1` for both.
</Note>

For memory at the moment of a failure, enable the `system` [telemetry category](/browsers/telemetry/categories). Its `system_oom_kill` event carries the killed process's RSS along with total and free memory, which tells you what the session looked like when it ran out — but only after the fact, so pair it with sampling if you need to catch pressure before a crash.

## Root and per-user execution

Pass `as_root: true` or `as_user: "<name>"` on `exec` or `spawn` to control which user the command runs as. This is safe because a Kernel browser is a [unikernel VM](/security#2-4-security-features) with no shared host kernel — root inside your session has no path to other customers or platform infrastructure.

## Via CLI

The [CLI](/reference/cli/browsers#process-control) exposes the same operations:

```bash theme={null}
# Synchronous
kernel browsers process exec <session-id> --command ls --args -la

# Background, then inspect
kernel browsers process spawn <session-id> --command python3 --args -m --args http.server
kernel browsers process status <session-id> <process-id>
kernel browsers process kill <session-id> <process-id>

# Memory
kernel browsers process exec <session-id> --command free --args -m
kernel browsers process exec <session-id> --command top --args -b --args -n1
```

<Warning>
  The CLI splits every `--args` value on commas, including inside a quoted `sh -c` pipeline, so `ps -eo rss,comm` arrives as two arguments and runs as `ps -eo rss`. Use comma-free equivalents such as `ps -eo rss= -o comm=`, or call the SDKs, which pass arguments through unchanged.
</Warning>

## Related

<CardGroup cols={3}>
  <Card title="File I/O" icon="folder" href="/browsers/file-io">
    Upload and download files from a browser VM
  </Card>

  <Card title="SSH Access" icon="terminal" href="/browsers/ssh">
    Open an interactive SSH session for debugging
  </Card>

  <Card title="CLI reference" icon="square-terminal" href="/reference/cli/browsers#process-control">
    All `kernel browsers process` subcommands and flags
  </Card>
</CardGroup>
