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

# Tardigrade

> Run a Tardigrade agent's shell and file tools on an isolated boxd machine that each thread keeps across host restarts.

![Tardie, the Tardigrade mascot, inside the boxd card.](https://mintcdn.com/azin/7IWa9uzq2xw7Urj2/images/boxd-x-tardigrade.png?fit=max\&auto=format\&n=7IWa9uzq2xw7Urj2\&q=85\&s=09a110b68e6c7b98a57d8792db80f7b0)

[Tardigrade](https://tardigrade.sh) is a TypeScript framework for building durable, event-driven actors, built on Effect. Its agent template gives an agent `bash`, `read`, `write`, and `edit` tools, and a workspace decides where those tools run.

The [boxd workspace](https://github.com/clavia-labs/tardigrade/pull/848) runs them on an isolated boxd machine. The actor, its event log, and its model calls stay on your Bun host, and so does your API key. It ships in `tardie` 0.56.0 and later.

## Set up

**1. Create an agent project**

```bash theme={"theme":"github-dark"}
bunx tardie init meeseeks --template agent
cd meeseeks
bun add @boxd-sh/sdk
```

`@boxd-sh/sdk` is an optional peer dependency of `tardie`, so add it yourself. In an existing project, run `bun add tardie @boxd-sh/sdk`.

**2. Create an API key**

```bash theme={"theme":"github-dark"}
boxd auth keys create tardigrade --org <your-org>
```

Put it in the project's `.dev.vars`, which `bun run dev` loads:

```bash theme={"theme":"github-dark"}
BOXD_API_KEY=bxd_...
```

Machines are created and billed in the key's organization.

**3. Mount the boxd workspace**

In `server.ts`, replace `localWorkspace` with `boxdWorkspace`:

```ts server.ts theme={"theme":"github-dark"}
import { Boxd } from "@boxd-sh/sdk"
import { createBunHost, methodHttp } from "tardie/platform/bun"
import { boxdWorkspace, boxdWorkspaceName } from "tardie/platform/boxd"

const boxd = new Boxd()
const host = await Effect.runPromise(createBunHost({
  actor: definition,
  actorContext,
  storage: process.env.TARDIGRADE_ACTOR_DATA ?? ".tardigrade",
  services: coordinate => services(config.vars.TARDIGRADE_CONFIG.models, process.env, boxdWorkspace({
    client: boxd,
    name: boxdWorkspaceName(coordinate),
    network: { mode: "allowlist", hosts: ["registry.npmjs.org"] },
    release: "retain",
  })),
}))
```

`boxdWorkspaceName(coordinate)` names a machine after the actor, instance, and thread, so each thread gets its own machine. The name is `tardie-` followed by 16 hex digits. Start the host with `bun run dev`.

## Check the workspace without a model

This script gives the workspace a name, runs commands on the machine, and writes a file, with no model or host involved:

```ts check.ts theme={"theme":"github-dark"}
import { Boxd } from "@boxd-sh/sdk"
import { Effect } from "effect"
import { FilesystemWorkspace } from "tardie/libraries"
import { boxdWorkspace } from "tardie/platform/boxd"

const workspace = boxdWorkspace({
  client: new Boxd(),
  org: process.env.BOXD_ORG,
  name: process.argv[2] ?? "tardie-check",
  labels: { harness: "int", integration: "tardigrade" },
  network: { mode: "allowlist", hosts: ["registry.npmjs.org"] },
  release: "retain",
})

const program = Effect.gen(function* () {
  const fs = yield* FilesystemWorkspace
  console.log(yield* fs.bash("hostname && pwd && whoami"))
  console.log(yield* fs.bash("cat note.txt 2>/dev/null || echo 'no note yet'"))
  yield* fs.write("note.txt", `written at ${new Date().toISOString()}\n`)
  console.log(yield* fs.bash("curl -s -o /dev/null -w 'npm registry: %{http_code}\\n' https://registry.npmjs.org/tardie"))
  console.log(yield* fs.bash("curl -s -m 5 -o /dev/null https://example.com && echo 'example.com: reachable' || echo 'example.com: blocked'"))
})

await Effect.runPromise(program.pipe(Effect.provide(workspace)))
```

```bash theme={"theme":"github-dark"}
bun --env-file=.dev.vars check.ts t-tardigrade-a1
```

The first run creates the machine and hibernates it at the end. The second run adopts the same machine and finds the note the first one wrote, and the allowlist lets the npm registry through and blocks everything else:

```text theme={"theme":"github-dark"}
t-tardigrade-a1
/home/boxd/workspace
boxd

written at 2026-11-10T21:24:51.470Z

npm registry: 200

example.com: blocked
```

`org` is only needed when the key can reach more than one organization.

## Lifecycle

The first command creates the machine, or adopts the machine that already has the name. A restarted host adopts the same machine with its files. Without `region`, adoption looks in every region, and it fails when two regions hold the name or when a region doesn't answer. A hibernated or suspended machine wakes on the next command, and a stopped machine is started first.

`release` decides what happens to the machine when the workspace layer is released:

| `release` | The machine |
| - | - |
| `destroy` | Is deleted. This is the default. |
| `retain` | Is [hibernated](https://docs.boxd.sh/guides/suspend-resume.md), with its disk kept for the next host. |
| `unmount` | Keeps running. |

Release covers a machine from the moment it is created, so a first command interrupted during startup leaves nothing behind under `destroy`.

A command that runs past `policy.commandTimeoutMs`, 30 seconds by default, is cancelled, and its process on the machine is stopped.

## Network

`network` becomes the machine's [egress allowlist](https://docs.boxd.sh/guides/egress) when the machine is created. It defaults to `none`.

| Policy | What the machine can reach |
| - | - |
| `{ mode: "none" }` | Nothing. |
| `{ mode: "all" }` | Anything. |
| `{ mode: "allowlist", hosts: [...] }` | Only the listed hosts. |

The policy is fixed at creation. Adopting a machine whose allowlist differs from the requested policy fails, with both lists in the error.

## Options

| Option | What it does |
| - | - |
| `client` | A `Boxd` client from `@boxd-sh/sdk`. |
| `name` | The machine's name, unique within a region. Required. |
| `org` | The organization that owns the machine. |
| `region` | The region for a new machine, and the only region adoption looks in. |
| `size` | One of boxd's [machine sizes](https://docs.boxd.sh/guides/machine-specs.md), such as `2x8` or `4x16`. The organization's default applies when it is omitted. |
| `snapshot` | A [snapshot](https://docs.boxd.sh/guides/snapshots.md) that new machines start from. |
| `labels` | Labels for a new machine. |
| `directory` | The working directory for commands and relative paths. Default `/home/boxd/workspace`. |
| `network` | The egress policy above. |
| `release` | `destroy`, `retain`, or `unmount`. |
| `idempotency` | A store for the outcomes of keyed writes and commands, such as `sqlIdempotencyStore` from `tardie/platform/workspace`, so they replay after a host restart. |
| `policy` | Overrides for `startupTimeoutMs` (default 120000), `startupPollIntervalMs`, `commandTimeoutMs`, `maxOutputChars`, and `maxReadChars`. |

## What each machine gets

* **Isolation:** every machine is created isolated, and the workspace refuses to adopt one that isn't. Code the agent runs holds no boxd credentials and can't reach your other machines.
* **Commands as the `boxd` user** in `/home/boxd/workspace`, through boxd's exec and file APIs.
* **The default image:** Ubuntu 24.04. For your own tools, start from a snapshot with `snapshot`.
* **A machine per thread:** with `boxdWorkspaceName(coordinate)`, two threads never share files or processes.

Tardigrade's [agent guide](https://tardigrade.sh/docs/agent) covers services, tools, and its other workspaces.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.