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

# Process Execution

> Run commands and manage processes inside a browser VM

Every Kernel browser runs inside its own VM with a full Linux environment alongside Chromium. Process execution lets you run arbitrary commands in that VM — install a tool, inspect files, run a script, or drive a co-located agent — without leaving the session.

## 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 playwright daemon) 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`.

## 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>
```

## Example: co-locating an agent with its browser

Driving a browser from outside the VM means every tool call is a round trip through Kernel's API. Process execution lets you flip that around: upload an agent binary into the VM with [File I/O](/browsers/file-io), then run it alongside Chromium so it talks to the VM's local Playwright endpoint over loopback instead. See the [fx co-located agent cookbook](https://github.com/kernel/cookbooks/tree/main/integrations/fx-colocated-agent) for a full working example.

## 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>
