> ## Documentation Index
> Fetch the complete documentation index at: https://iii.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Developing Sandbox Workers

> Develop and test workers locally in an isolated microVM using iii worker add.

## Goal

Use `iii worker add ./path` to register a local worker project as a managed sandbox worker connected to the engine — no containers or Dockerfiles needed.

## Context

Local-path workers are first-class managed workers with the same lifecycle as container workers (`add`, `start`, `stop`, `remove`, `list`). Each worker runs inside a lightweight microVM with its own filesystem, network, and dependencies. The CLI detects local paths automatically — any argument starting with `.`, `/`, or `~` is treated as a local directory.

Requires **macOS Apple Silicon** or **Linux with KVM**.

### What is a sandbox?

A sandbox is a hardware-isolated microVM powered by [libkrun](https://github.com/containers/libkrun). Each sandbox has its own root filesystem, network stack, and process tree — completely separate from the host and from other workers. The engine manages the VM lifecycle, so you interact with sandboxed workers through the same `iii worker` commands you use for container workers.

## Steps

### 1. Create a Worker Project

The CLI auto-detects your project type from well-known files and infers setup, install, and start commands.

| Project type         | Detected via                           | Default package manager           |
| -------------------- | -------------------------------------- | --------------------------------- |
| Node.js / TypeScript | `package.json`                         | npm (or bun if `bun.lock` exists) |
| Python               | `pyproject.toml` or `requirements.txt` | pip                               |
| Rust                 | `Cargo.toml`                           | cargo                             |

With auto-detection, you can add a worker immediately:

```bash theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
iii worker add ./my-project
```

### 2. Add a Manifest

Create an `iii.worker.yaml` at the root of your worker folder to configure the sandbox:

<Tabs>
  <Tab title="Node.js / TypeScript">
    ```yaml title="iii.worker.yaml" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    name: my-worker
    runtime:
      kind: typescript
      package_manager: npm
      entry: src/index.ts
    resources:
      memory: 2048
      cpus: 2
    scripts:
      setup: "apt-get update && apt-get install -y build-essential"
      install: "npm install"
      start: "npx tsx src/index.ts"
    env:
      MY_API_KEY: sk-abc123
      LOG_LEVEL: debug
    ```
  </Tab>

  <Tab title="Bun">
    ```yaml title="iii.worker.yaml" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    name: my-worker
    runtime:
      kind: bun
      entry: src/index.ts
    resources:
      memory: 2048
      cpus: 2
    scripts:
      install: "bun install"
      start: "bun run src/index.ts"
    env:
      MY_API_KEY: sk-abc123
      LOG_LEVEL: debug
    ```
  </Tab>

  <Tab title="Python">
    ```yaml title="iii.worker.yaml" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    name: my-worker
    runtime:
      kind: python
      entry: worker.py
    resources:
      memory: 2048
      cpus: 2
    scripts:
      setup: "apt-get update && apt-get install -y build-essential"
      install: "pip install -r requirements.txt"
      start: "python worker.py"
    env:
      MY_API_KEY: sk-abc123
      LOG_LEVEL: debug
    ```
  </Tab>

  <Tab title="Rust">
    ```yaml title="iii.worker.yaml" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    name: my-worker
    runtime:
      kind: rust
    resources:
      memory: 2048
      cpus: 2
    scripts:
      setup: "apt-get update && apt-get install -y build-essential"
      install: "cargo build --release"
      start: "./target/release/my-worker"
    env:
      MY_API_KEY: sk-abc123
      LOG_LEVEL: debug
    ```
  </Tab>
</Tabs>

#### Manifest Fields

| Field                     | Required | Default      | Description                                                                                                                                                                                                                                                                                                       |
| ------------------------- | -------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                    | Yes      | —            | Worker name (used as the identifier in `config.yaml`)                                                                                                                                                                                                                                                             |
| `runtime.kind`            | No       | `typescript` | `typescript`, `javascript`, `bun`, `python`, or `rust`. Older manifests with `runtime.language` still parse (with a deprecation warning) — rename to `kind`.                                                                                                                                                      |
| `runtime.package_manager` | No       | —            | `npm`, `yarn`, `pnpm`, or `bun` (only for `kind: typescript`/`javascript`; `kind: bun` doesn't need this)                                                                                                                                                                                                         |
| `runtime.entry`           | No       | —            | Entrypoint file                                                                                                                                                                                                                                                                                                   |
| `runtime.base_image`      | No       | kind default | OCI image used as the sandbox rootfs. Overrides the per-kind default. Defaults: `kind: typescript/javascript` → `iiidev/node:latest`; `kind: bun` → `oven/bun:latest`; `kind: python` → `iiidev/python:latest`; `kind: rust` → `library/rust:slim-bookworm`. See [Custom base images](#custom-base-images) below. |
| `resources.memory`        | No       | `2048`       | Memory in MiB allocated to the sandbox VM                                                                                                                                                                                                                                                                         |
| `resources.cpus`          | No       | `2`          | Number of vCPUs allocated to the sandbox VM                                                                                                                                                                                                                                                                       |
| `scripts.setup`           | No       | inferred     | One-time system setup (runs only on first start)                                                                                                                                                                                                                                                                  |
| `scripts.install`         | No       | inferred     | Dependency installation command                                                                                                                                                                                                                                                                                   |
| `scripts.start`           | No       | inferred     | Worker start command                                                                                                                                                                                                                                                                                              |
| `env.*`                   | No       | —            | Custom environment variables injected into the sandbox                                                                                                                                                                                                                                                            |

#### Custom base images

By default iii picks a rootfs image per `runtime.kind`: `iiidev/node:latest` for `typescript`/`javascript`, `oven/bun:latest` for `bun`, `iiidev/python:latest` for `python`, `library/rust:slim-bookworm` for `rust`. Most of the time you won't need `base_image` — just pick the right `kind`. Use `base_image` when you need a specific version (e.g. pin `oven/bun:1.3.12`) or a custom image with your team's deps pre-baked:

```yaml title="iii.worker.yaml" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
name: my-worker
runtime:
  kind: bun
  base_image: oven/bun:1    # pin a specific bun version
  entry: src/index.ts
scripts:
  install: "bun install"
  start: "bun run src/index.ts"
```

Each distinct `base_image` gets its own cache directory under `~/.iii/rootfs/<slug>/`, so pinning a specific bun version won't clobber the default `oven/bun:latest` rootfs used by other `kind: bun` workers. The image is pulled on first start and reused for every subsequent start that names the same image.

<Info title="Reserved environment variables">
  `III_URL` and `III_ENGINE_URL` are set automatically and cannot be overridden through the manifest.
</Info>

If the `scripts` block is omitted entirely, commands are inferred from `runtime.kind`, `runtime.package_manager`, and `runtime.entry`. For example, `kind: typescript` + `package_manager: npm` + `entry: src/index.ts` infers `npm install` for install and `npx tsx src/index.ts` for start. `kind: bun` + `entry: src/index.ts` infers `bun install` + `bun run src/index.ts` (no `package_manager` needed).

### 3. Add the Worker

```bash theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
iii worker add ./my-project
```

The CLI validates the directory, loads the manifest (or auto-detects the project type), prepares an isolated rootfs, copies your project files into the sandbox, runs setup and install scripts, and registers the worker in `config.yaml` with a `worker_path:` field:

```yaml title="config.yaml" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
workers:
  - name: my-worker
    worker_path: /absolute/path/to/my-project
```

If the engine is already running, the worker auto-starts immediately after being added.

#### CLI Flags

| Flag             | Description                                                                        |
| ---------------- | ---------------------------------------------------------------------------------- |
| `--force`, `-f`  | Delete existing artifacts and re-add the worker from scratch                       |
| `--reset-config` | Also remove the existing `config.yaml` entry before re-adding (requires `--force`) |

Examples:

```bash theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
# Force a clean re-add (re-runs setup and install)
iii worker add ./my-project --force

# Re-add and also reset the config.yaml entry
iii worker add ./my-project --force --reset-config
```

### 4. Manage the Worker

Local-path workers support the same lifecycle commands as container workers:

```bash theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
# List all workers (includes a TYPE column: Local, OCI, Binary, Config)
iii worker list

# Stop a running worker
iii worker stop my-worker

# Start a stopped worker
iii worker start my-worker

# Remove a worker (deletes config.yaml entry and cached artifacts)
iii worker remove my-worker
```

### 5. Understand Dependency Caching

Each sandbox is cached at `~/.iii/managed/<worker-name>/`. The caching lifecycle works as follows:

**First start** — the CLI clones a base rootfs, copies your project into the sandbox, runs the `setup` and `install` scripts, then starts the worker. A marker file is written to signal that dependencies are installed.

**Subsequent starts** — setup and install are skipped entirely. Only the project files are re-copied and the `start` script runs, making restarts fast.

**`--force` re-add** — running `iii worker add ./my-project --force` deletes the cached sandbox at `~/.iii/managed/<worker-name>/` and re-runs the full setup from scratch. Use this when you change dependencies, update the base image, or modify install scripts.

<Info title="Skipped directories">
  When copying your project into the sandbox, the following directories are excluded automatically: `node_modules`, `.git`, `target`, `__pycache__`, `.venv`, `dist`.
</Info>

### 6. Check Platform Requirements

<Warning title="Platform support">
  Intel Macs are **not supported**. The microVM requires hardware virtualization only available on Apple Silicon or Linux with KVM.
</Warning>

**macOS (Apple Silicon)** — uses Hypervisor.framework. On first run, the CLI automatically codesigns itself with Hypervisor entitlements. This may prompt for confirmation.

**Linux** — requires `/dev/kvm` to be accessible with read and write permissions. Verify with:

```bash theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
ls -l /dev/kvm
```

## Result

Your worker runs inside an isolated microVM connected to the engine. The sandbox has its own filesystem, network stack, and process tree. The worker is registered in `config.yaml` and managed through the standard `iii worker` lifecycle commands — `start`, `stop`, `list`, and `remove`. To rebuild the sandbox after changing dependencies, re-add the worker with `iii worker add ./my-project --force`.
