# Boat

Boat cloud sandboxes (full root Linux VMs) as a Bijection component: reactive state, retry-safe create, exec, files, preview URLs, coding agents.

- Package: @bijectionhq/boat
- Category: Sandboxes
- Status: Preview
- License: MIT
- Bijection source (repository access required): https://github.com/bijectionhq/boat/tree/4dd9f60f8fbc561b8ff9486bded92bd24962c640
- Upstream: https://github.com/ariana-dot-dev/boat-convex/tree/168ecde17e4d118400f5121132aaecd61e22d416
- Documentation: https://docs.bijection.com/components/using
- Directory: https://bijection.com/components/boat

## Get the source

Bijection repository access is required. Request access at https://bijection.com/contact.

```sh
git clone https://github.com/bijectionhq/boat.git
cd boat
git checkout 4dd9f60f8fbc561b8ff9486bded92bd24962c640
```

## Before using this component

Read the package README at the recorded revision for setup, dependencies, and API examples. Upstream documentation describes the original package and may differ from the Bijection adaptation. This is a source candidate for evaluation. Catalog inclusion does not establish npm availability or runtime qualification. Check compatibility with your SDK and deployment before adoption. External providers run their own compute.

## Upstream package documentation

Recorded 2026-10-23. This describes @boatdev/convex; check the Bijection adaptation separately.

Boat Convex integrates full root Ubuntu VMs into Convex applications, giving each user, job, or agent its own isolated sandbox with persistent disk state, snapshotting, and resume. Sandbox lifecycle state is stored in a Convex table and exposed reactively via queries, so UI can track provisioning through ready to archived without polling. The component wraps the Boat API server-side, keeping credentials out of client code entirely.

### Use cases

- Building AI coding agents that need a persistent workspace with real toolchains, where each user gets an isolated environment that stops when idle and resumes exactly where it left off
- Running background processes like dev servers or long builds inside a sandbox and streaming their output back through Convex queries
- Forking a configured sandbox as a template to spin up identical environments for new users without reprovisioning from scratch
- Giving end users browser-accessible preview URLs for ports running inside their sandbox, with optional token gating
- Running coding agents like Claude Code or Codex inside a sandboxed VM and following their work through the prompt and events API

### How it works

Install the package and register the component in `convex/convex.config.ts` using `app.use(boat, { env: { BOAT_API_KEY: app.env.BOAT_API_KEY } })`. The API key is bound at the component level and never surfaces to application code or the browser. Set the key with `npx convex env set BOAT_API_KEY boat_...`.

Every sandbox is addressed by an `{ ownerId, key }` pair. The `ownerId` must always be derived from `ctx.auth` inside a Convex function, not passed in from the client. The `create` and `fork` methods use Boat idempotency keys, so retries return the existing sandbox without billing a second one.

The `Boat` class exposes queries (`get`, `list`) that read cached sandbox state from a Convex table, enabling reactive UI with `useQuery`. Mutations that call the Boat API are performed through actions: `create`, `stop`, `resume`, `fork`, `destroy`, `exec`, `spawn`, `readFile`, `writeFile`, `host`, and `prompt`. Background processes are started with `spawn` and polled with `commandStatus`. Coding agent tasks are handed off with `prompt` and tracked through `events` and `promptStatus`.

For multi-tenant scenarios where users drive the sandboxes directly, pass `noEnv: true` to `create` to prevent the sandbox from inheriting account-level secrets, then supply only the specific `env` values that sandbox needs.

### Upstream README

# Boat Convex Component

Give every user, job or agent in your Convex app its own [Boat](https://boat.dev) sandbox: a full root Ubuntu VM
(Docker, every toolchain, public IPv4) that stops to a snapshot when idle and resumes exactly where it left off.

- **Reactive.** Sandbox state lives in a Convex table. `useQuery` shows `provisioning` → `ready` → `archived` live; the
  component follows every transition itself.
- **Retry-safe.** One sandbox per `(ownerId, key)`. A retried `create` or `fork` returns the same sandbox and never bills
  a second one (Boat idempotency keys).
- **Your key stays server-side.** `BOAT_API_KEY` is a component env var; it never reaches your app code or the browser.
- **Everything a coding product needs:** exec, background processes, files, public preview URLs, stop/resume, disk-level
  fork, and coding agents (Claude Code, Codex, pi, OpenCode) running inside the sandbox.

## Install

```sh
npm install @boatdev/convex
```

```ts
// convex/convex.config.ts
import { defineApp } from "convex/server";
import { v } from "convex/values";
import boat from "@boatdev/convex/convex.config.js";

const app = defineApp({ env: { BOAT_API_KEY: v.string() } });
app.use(boat, { env: { BOAT_API_KEY: app.env.BOAT_API_KEY } });
export default app;
```

```sh
npx convex env set BOAT_API_KEY boat_...   # from the Boat dashboard, API keys tab
```

## Use

```ts
// convex/workspace.ts
import { Boat } from "@boatdev/convex";
import { components } from "./_generated/api";
import { action, query } from "./_generated/server";
import { v } from "convex/values";

const boat = new Boat(components.boat);

async function owner(ctx: { auth: { getUserIdentity(): Promise<{ subject: string } | null> } }) {
  const user = await ctx.auth.getUserIdentity();
  if (!user) throw new Error("Not signed in");
  return user.subject;
}

export const status = query({
  args: {},
  handler: async (ctx) => boat.get(ctx, { ownerId: await owner(ctx), key: "workspace" }),
});

export const start = action({
  args: {},
  handler: async (ctx) =>
    boat.create(ctx, {
      ownerId: await owner(ctx),
      key: "workspace",
      options: { type: "small", ttlSeconds: 1800, noEnv: true },
    }),
});

export const run = action({
  args: { command: v.string() },
  handler: async (ctx, { command }) =>
    boat.exec(ctx, { ownerId: await owner(ctx), key: "workspace", command }),
});
```

```tsx
const sandbox = useQuery(api.workspace.status); // live: provisioning -> ready
```

Always derive `ownerId` from `ctx.auth`, never from client arguments. The component has no auth of its own.

## API

Every call names a sandbox by `{ ownerId, key }`.

| Method | What it does |
|---|---|
| `get`, `list` | Reactive reads of the cached state (queries). |
| `create({ options })` | Create, or return the existing sandbox for this key. Options: `name`, `type` (`small`/`default`/`large`), `ttlSeconds` (auto-stop, `null` = never), `env`, `noEnv`, `environment`, `setupScript`, `from` (named snapshot). |
| `refresh` | Pull the live state from Boat, e.g. after an auto-stop. |
| `stop({ force? })` | Snapshot the disk and stop billing. |
| `resume({ options? })` | Bring it back exactly as it was, optionally on another size. |
| `fork({ newKey })` | Copy the sandbox, disk and all, into a new one. Copies the latest snapshot, which every stop takes. |
| `destroy` | Permanently delete the sandbox and its snapshots. |
| `exec({ command, cwd?, timeoutSeconds? })` | Run and wait (up to 600 s). |
| `spawn({ command })` → `commandStatus({ processId })` | Background process (dev server, long build) and its logs. |
| `readFile`, `writeFile` | `utf8` or `base64`; paths under `/home/user` or `/tmp`. |
| `host({ port, public? })` | Public HTTPS URL for a port. Token-gated unless `public: true`. |
| `prompt({ prompt, provider })` → `events`, `promptStatus` | Hand a task to a coding agent inside the sandbox and follow its work. |

## Multi-tenant safety

By default a sandbox inherits your Boat account's secrets and repos. For sandboxes your users drive, pass
`noEnv: true` and give each sandbox only what it needs through `env`.

## Develop

```sh
npm install
npm test                                # offline, Boat API mocked
CONVEX_AGENT_MODE=anonymous npx convex init
npx convex env set BOAT_API_KEY boat_...
npx convex dev                          # local backend + the example app
npm run e2e                             # real sandboxes: create, exec, files, preview, stop, fork, resume, destroy
```

## License

MIT