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

# Launch Your Own Agent

> Launch a GitHub repository, a manifest, or a container image in a BoxRouter sandbox, from the CLI or the launch API.

A launch prepares a fresh sandbox from a repository, an image, or a manifest. An interactive CLI launch
opens a terminal after preparation; an unattended launch starts its command without one. Start it with
`boxrouter launch` or `POST /launches`.

**Before You Begin:** An [SDK](https://boxrouter.ai/docs/sdk.md) or the [CLI](https://boxrouter.ai/docs/cli.md#install), and an API key that can create sandboxes. A repository must be public or [linked to your workspace](https://boxrouter.ai/docs/launch/private-repositories.md).

## Choose a Source

| You Have | Run | What BoxRouter Does |
| - | - | - |
| A repository with `boxrouter.yaml` | `boxrouter launch --repo OWNER/REPO[@REF]` | Reads the manifest at a pinned commit and checks out that commit |
| A repository without one | the same command | Detects setup and a command at the pinned commit. Your flags override what it finds |
| A repository to inspect only | add `--checkout` | Clones it and skips manifest loading and detection. `@REF` defaults to `HEAD` |
| A container image | `boxrouter launch --image IMAGE` | Starts a shell in the image |
| A manifest file | `boxrouter launch --manifest PATH` | Uses it as written. `-` reads stdin |
| A catalog agent on your code | `boxrouter launch AGENT --repo OWNER/REPO@REF` | Keeps the [catalog](https://boxrouter.ai/docs/launch/agents.md) setup and changes only the checkout. `@REF` is required |

BoxRouter resolves a repository ref to a commit before checkout and records the pin. A new launch can
resolve a moving branch to a different commit, so specify a commit SHA when launches must use the same
code. Retrying the same API request with the same idempotency key returns the original operation.

Repeatable `--secret NAME`, `--lifetime SECONDS` and `--provider` apply to every form. `--name` and `--command` apply to a repository, image or manifest, not to a catalog `AGENT`, which keeps its catalog name and command.

If `--provider` returns `400 unrecognised_word`, the name is not a provider ID and nothing started;
`details.known` lists the valid names. Omit the flag to let BoxRouter select a route if another eligible
route meets your needs.

An `AGENT` cannot be combined with `--image`, `--manifest`, `--checkout`, `--name` or `--command`.

Add `--rm` to request sandbox deletion when the terminal session ends, including when you detach with
`Ctrl-]`. Without `--rm`, the sandbox can keep running and billing past the end of its lifetime.
[Check its state and destroy it](https://boxrouter.ai/docs/sandboxes.md#destroy-it) when you no longer need it.

## Launch a Repository

The terminal and sandbox IDs in the output stand in for your IDs. This command and the image example
below close their terminals with the command's exit status. The TypeScript launch has no
`checkout` option, so its example passes the same plain checkout as a manifest with `code`.

```python Python theme={"theme":"css-variables"}
import sys

from boxrouter import BoxRouter

with BoxRouter() as client:
    with client.sandboxes.launch(
        repo="octocat/Hello-World@master",
        checkout=True,
        command='ls "$HOME/Hello-World"; exit',
    ) as box:
        terminal = box.open_terminal()
        for chunk in terminal.output():
            sys.stdout.buffer.write(chunk)
        sys.stdout.buffer.flush()
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

await using client = new BoxRouter();
const code = { repo: 'octocat/Hello-World', ref: 'master' };
const command = 'ls "$HOME/Hello-World"; exit';
await using box = await client.sandboxes.launch({ manifest: { name: 'hello-world', code, command } });
const terminal = await box.openTerminal();
for await (const chunk of terminal.output()) process.stdout.write(chunk);
```

```bash CLI theme={"theme":"css-variables"}
boxrouter launch --repo octocat/Hello-World@master --checkout --command 'ls "$HOME/Hello-World"; exit' --rm
```

Output

```text theme={"theme":"css-variables"}
Launch succeeded: create completed, code completed
Connected to terminal <TERMINAL_ID>. Sandbox <SANDBOX_ID>. Ctrl-] detaches; --rm requests sandbox deletion.
README
logout
```

The output above is the CLI's. The SDK returns the sandbox, and `box.open_terminal()` (`box.openTerminal()` in
TypeScript) starts the launch's command in a terminal your keyboard is not attached to, so the SDK examples
print its raw bytes. Leaving the sandbox's block requests deletion, as `--rm` does. That output can include the shell prompt and the echoed command.

With no `directory` set in the manifest, the checkout lands in `$HOME/<repository-name>`, and the command starts there.
Without `--command`, a plain checkout opens a shell.

To ship the setup and command with the code, commit a `boxrouter.yaml` with the two required fields.
`OWNER/REPO@REF:path` selects a different file.

```yaml title="boxrouter.yaml" theme={"theme":"css-variables"}
name: repository-agent
command: python -m agent
```

Declare secret names in `secrets`; a value in the manifest would be committed with your code.
[Secrets](https://boxrouter.ai/docs/launch/secrets.md) covers how a value reaches the sandbox; the
[manifest reference](https://boxrouter.ai/docs/launch/manifest.md) lists the other fields.

## Launch an Image

```python Python theme={"theme":"css-variables"}
import sys

from boxrouter import BoxRouter

with BoxRouter() as client:
    with client.sandboxes.launch(image="python:3.12-slim", command="python3 --version; exit") as box:
        terminal = box.open_terminal()
        for chunk in terminal.output():
            sys.stdout.buffer.write(chunk)
        sys.stdout.buffer.flush()
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

await using client = new BoxRouter();
await using box = await client.sandboxes.launch({ image: 'python:3.12-slim', command: 'python3 --version; exit' });
const terminal = await box.openTerminal();
for await (const chunk of terminal.output()) process.stdout.write(chunk);
```

```bash CLI theme={"theme":"css-variables"}
boxrouter launch --image python:3.12-slim --command 'python3 --version; exit' --rm
```

Output

```text theme={"theme":"css-variables"}
Launch succeeded: create completed, preparing_image completed
Connected to terminal <TERMINAL_ID>. Sandbox <SANDBOX_ID>. Ctrl-] detaches; --rm requests sandbox deletion.
Python 3.12.16
logout
```

The image must contain the tools in [What the image must contain](https://boxrouter.ai/docs/images.md#what-the-image-must-contain).
[Images](https://boxrouter.ai/docs/images.md#bring-your-own-image) covers Dockerfile builds, private registries, and slow first starts.

## Call the Launch API

`POST /launches` is the endpoint the CLI and SDKs call, and [Create a launch](https://boxrouter.ai/docs/reference/launch-create.md)
lists every field. Before you run this example, install the [Python SDK](https://boxrouter.ai/docs/python.md#install), the
[TypeScript SDK](https://boxrouter.ai/docs/typescript.md#install), or the [CLI](https://boxrouter.ai/docs/cli.md#install), and set an API key through
[set up your account](https://boxrouter.ai/docs/setup.md).

| Part | Rule |
| - | - |
| Body | Exactly one of `agent` or `manifest`. |
| `Idempotency-Key` header | Required. Use a new key for each launch; reuse a key to retry, and the retry returns the first operation. |
| Reply | `202 Accepted`, with the operation in `Location`. Read it at `GET /launches/{id}` until `status` is `succeeded` or `failed`. |

The SDKs and the CLI send the key, wait for the operation to settle, and return the sandbox, the same handle
`create` returns.

```python Python theme={"theme":"css-variables"}
from boxrouter import BoxRouter

with BoxRouter() as client:
    manifest = {"name": "image-agent", "image": "python:3.13", "command": "bash", "lifetime_seconds": 300}
    with client.sandboxes.launch(manifest=manifest) as box:
        print(box.id, box.lifetime.seconds)
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

await using client = new BoxRouter();
const manifest = { name: 'image-agent', image: 'python:3.13', command: 'bash', lifetime_seconds: 300 };
await using box = await client.sandboxes.launch({ manifest });
console.log(box.id, box.lifetime.seconds);
```

```bash CLI theme={"theme":"css-variables"}
boxrouter launch --image python:3.13 --command 'echo ready; exit' --lifetime 300 --rm
```

Output

```text theme={"theme":"css-variables"}
<SANDBOX_ID> 300
```

The CLI attaches the launch's terminal and prints the sandbox ID in its connection message.
If the wait outlasts its bound, the SDK raises a timeout error that names the launch operation. Check it with
`boxrouter api launches get-operation OPERATION_ID` before you launch again; the launch may still create a sandbox.

This example prepares an interactive sandbox without attaching a terminal; run commands with
[Commands](https://boxrouter.ai/docs/commands.md) using `box.id`. With `interactive: false`, launch starts the manifest's command instead.
`box.lifetime.seconds` is the lifetime the sandbox got. A lifetime you choose is never lowered; see
[launch limits](https://boxrouter.ai/docs/launch/manifest.md#understand-launch-limits).

The sandbox can run past its lifetime unless you destroy it. Leaving the `with` block, or calling `box.destroy()`,
requests deletion; [check its state](https://boxrouter.ai/docs/sandboxes.md#destroy-it) to confirm.

If preparation fails, launch requests deletion. Check cleanup and any external effects before retrying;
see [preparation failure recovery](https://boxrouter.ai/docs/launch/manifest.md#recover-a-preparation-failure).

To give one launch a secret value, add a top-level `secret_values` object beside the manifest.
The value overrides a saved secret with the same name, applies to that launch only, and BoxRouter never stores
or returns it.

## Repository Errors

Each error carries a code, a message and a `fix` that says what to change; the CLI prints all three.

| Status | Code | Meaning |
| - | - | - |
| 400 | `source_file_not_found` | `boxrouter.yaml` is not at that commit. Add it or pass `:path` |
| 404 | `source_repository_not_accessible` | A manifest source is missing, or private and not linked to this workspace |
| 404 | `code_repository_not_found` | Same cause for a plain checkout |
| 404 | `code_ref_not_found` | The branch, tag or SHA does not exist |
| 403 | `code_repository_not_authorized` | The GitHub App lost access to a linked repository. Grant it on GitHub and retry |

A repository links to one workspace at a time, so a launch from any other workspace fails with
`source_repository_not_accessible` or `code_repository_not_found`.
[Private repositories](https://boxrouter.ai/docs/launch/private-repositories.md) covers linking.

## Next Steps

> [card] [Configure a Launch Manifest](https://boxrouter.ai/docs/launch/manifest.md)
>
> Fields, preparation order, and launch limits.

> [card] [Run Copies of an Agent](https://boxrouter.ai/docs/launch/copies.md)
>
> Start N unattended copies and read what each wrote.

> [card] [Supply an Agent's Secrets](https://boxrouter.ai/docs/launch/secrets.md)
>
> Save the environment values once.


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