---
title: "Processes & Shell"
description: "Execute commands, spawn long-running processes, and open interactive shells in agentOS VMs."
skill: true
---

- **One-shot execution** with `exec` for simple commands
- **Long-running processes** with `spawn`, stdout/stderr streaming, and stdin writing
- **Process lifecycle** management with stop, kill, wait, and inspect
- **Interactive shells** with PTY support for terminal I/O
- **Process tree** visibility across all VM runtimes

## One-shot execution

Use `exec` to run a command and wait for completion. Returns stdout, stderr, and exit code.

<CodeGroup>
```ts @nocheck client.ts
import { createClient } from "rivetkit/client";
import type { registry } from "./server";

const client = createClient<typeof registry>("http://localhost:6420");
const agent = client.vm.getOrCreate(["my-agent"]);

const result = await agent.exec("echo hello && ls /home/user");
console.log("stdout:", result.stdout);
console.log("stderr:", result.stderr);
console.log("exit code:", result.exitCode);
```

```ts @nocheck server.ts
import { agentOs } from "rivetkit/agent-os";
import { setup } from "rivetkit";
import common from "@rivet-dev/agent-os-common";
import pi from "@rivet-dev/agent-os-pi";

const vm = agentOs({
  options: { software: [common, pi] },
});

export const registry = setup({ use: { vm } });
registry.start();
```
</CodeGroup>

## Spawn a long-running process

Use `spawn` for processes that run in the background. Output is streamed via `processOutput` and `processExit` events.

<CodeGroup>
```ts @nocheck client.ts
import { createClient } from "rivetkit/client";
import type { registry } from "./server";

const client = createClient<typeof registry>("http://localhost:6420");
const agent = client.vm.getOrCreate(["my-agent"]);

// Subscribe to process output
agent.on("processOutput", (data) => {
  const text = new TextDecoder().decode(data.data);
  console.log(`[pid ${data.pid}] ${data.stream}: ${text}`);
});

agent.on("processExit", (data) => {
  console.log(`[pid ${data.pid}] exited with code ${data.exitCode}`);
});

// Spawn a dev server
const { pid } = await agent.spawn("node", ["/home/user/server.js"]);
console.log("Started process:", pid);
```

```ts @nocheck server.ts
import { agentOs } from "rivetkit/agent-os";
import { setup } from "rivetkit";
import common from "@rivet-dev/agent-os-common";
import pi from "@rivet-dev/agent-os-pi";

const vm = agentOs({
  options: { software: [common, pi] },
});

export const registry = setup({ use: { vm } });
registry.start();
```
</CodeGroup>

## Write to stdin

Send input to a running process.

<CodeGroup>
```ts @nocheck client.ts
import { createClient } from "rivetkit/client";
import type { registry } from "./server";

const client = createClient<typeof registry>("http://localhost:6420");
const agent = client.vm.getOrCreate(["my-agent"]);

const { pid } = await agent.spawn("cat", []);

// Write to stdin
await agent.writeProcessStdin(pid, "hello from stdin\n");

// Close stdin when done
await agent.closeProcessStdin(pid);

// Wait for the process to exit
const exitCode = await agent.waitProcess(pid);
console.log("exit code:", exitCode);
```

```ts @nocheck server.ts
import { agentOs } from "rivetkit/agent-os";
import { setup } from "rivetkit";
import common from "@rivet-dev/agent-os-common";
import pi from "@rivet-dev/agent-os-pi";

const vm = agentOs({
  options: { software: [common, pi] },
});

export const registry = setup({ use: { vm } });
registry.start();
```
</CodeGroup>

## Process lifecycle

<CodeGroup>
```ts @nocheck client.ts
import { createClient } from "rivetkit/client";
import type { registry } from "./server";

const client = createClient<typeof registry>("http://localhost:6420");
const agent = client.vm.getOrCreate(["my-agent"]);

const { pid } = await agent.spawn("node", ["/home/user/server.js"]);

// List all spawned processes
const processes = await agent.listProcesses();
console.log(processes);

// Get info about a specific process
const info = await agent.getProcess(pid);
console.log(info.running, info.exitCode);

// Graceful stop (SIGTERM)
await agent.stopProcess(pid);

// Force kill (SIGKILL)
await agent.killProcess(pid);
```

```ts @nocheck server.ts
import { agentOs } from "rivetkit/agent-os";
import { setup } from "rivetkit";
import common from "@rivet-dev/agent-os-common";
import pi from "@rivet-dev/agent-os-pi";

const vm = agentOs({
  options: { software: [common, pi] },
});

export const registry = setup({ use: { vm } });
registry.start();
```
</CodeGroup>

## System-wide process visibility

View all processes across all VM runtimes, not just those started via `spawn`.

<CodeGroup>
```ts @nocheck client.ts
import { createClient } from "rivetkit/client";
import type { registry } from "./server";

const client = createClient<typeof registry>("http://localhost:6420");
const agent = client.vm.getOrCreate(["my-agent"]);

// All processes
const all = await agent.allProcesses();
for (const p of all) {
  console.log(p.pid, p.driver, p.command, p.status);
}

// Process tree (parent-child hierarchy)
const tree = await agent.processTree();
for (const node of tree) {
  console.log(node.pid, node.command, "children:", node.children.length);
}
```

```ts @nocheck server.ts
import { agentOs } from "rivetkit/agent-os";
import { setup } from "rivetkit";
import common from "@rivet-dev/agent-os-common";
import pi from "@rivet-dev/agent-os-pi";

const vm = agentOs({
  options: { software: [common, pi] },
});

export const registry = setup({ use: { vm } });
registry.start();
```
</CodeGroup>

## Interactive shells

Open an interactive shell with PTY support. Shell data is streamed via `shellData` events.

<CodeGroup>
```ts @nocheck client.ts
import { createClient } from "rivetkit/client";
import type { registry } from "./server";

const client = createClient<typeof registry>("http://localhost:6420");
const agent = client.vm.getOrCreate(["my-agent"]);

// Subscribe to shell output
agent.on("shellData", (data) => {
  const text = new TextDecoder().decode(data.data);
  process.stdout.write(text);
});

// Open a shell
const { shellId } = await agent.openShell();

// Write commands to the shell
await agent.writeShell(shellId, "ls -la /home/user\n");

// Resize the terminal
await agent.resizeShell(shellId, 120, 40);

// Close the shell when done
await agent.closeShell(shellId);
```

```ts @nocheck server.ts
import { agentOs } from "rivetkit/agent-os";
import { setup } from "rivetkit";
import common from "@rivet-dev/agent-os-common";
import pi from "@rivet-dev/agent-os-pi";

const vm = agentOs({
  options: { software: [common, pi] },
});

export const registry = setup({ use: { vm } });
registry.start();
```
</CodeGroup>

## Recommendations

- Use `exec` for short commands where you need the full output. Use `spawn` for long-running processes where you want streaming output.
- Subscribe to `processOutput` and `processExit` **before** calling `spawn` to avoid missing events.
- Active processes prevent the actor from sleeping. Stop or kill them when they are no longer needed.
- Active shells also prevent sleep. Close shells when the user disconnects.
- Use `allProcesses` and `processTree` for debugging. They show everything running in the VM, including agent processes.
