# orb.toml Reference

The `orb.toml` file tells ORB how to run your agent: what language, what to install, which LLM provider your agent calls, and what ports to expose.

## On this page

- [Full example](#full-example)
- [agent](#agent-what-to-run), entry point and language
- [agent.env](#agent-env-environment-variables), environment variables and secrets
- [source](#source-where-your-code-lives), git repo to clone
- [build](#build-how-to-install-dependencies), install steps
- [llm](#llm-where-your-agent-calls-its-llm), LLM endpoint for proxy
- [Swarm env vars](#swarm-env-vars-orb_replica_index-and-orb_replica_count), `ORB_REPLICA_INDEX` / `ORB_REPLICA_COUNT` for fleet workers
- [ports](#ports-what-to-expose), exposed ports for inbound HTTP
- [resources](#resources-ram-and-disk), RAM and disk budgets
- [lifecycle](#lifecycle-when-your-agent-sleeps), opt out of automatic sleep
- [Cron](#cron-scheduled-tasks-for-sleeping-agents), scheduled tasks
- [Common examples](#common-examples)

## Why orb.toml Matters

The orb.toml tells ORB everything it needs to run and optimize your agent. ORB auto-detects LLM providers via transparent proxy - no provider configuration needed.

## Full Example

```toml
[agent]
name = "code-reviewer"
lang = "python"
entry = "agent.py"
args = ["--mode", "review"]

[agent.env]
HOME = "/root"
GITHUB_TOKEN = "${GITHUB_TOKEN}"

[source]
git = "https://github.com/yourcompany/review-agent"
branch = "main"

[build]
steps = [
  "pip install -r requirements.txt",
]
working_dir = "/agent/code"

[llm]
base_url = "https://api.anthropic.com"

[ports]
expose = [8000]

[resources]
runtime = "2GB"
disk = "4GB"
```

## Sections

### [agent], What to run

This is the only required section.

| Field | Type | Required | Description |
|---|---|---|---|
| name | string | yes | Display name for your agent |
| lang | string | yes | `python`, `node`, `binary`, `go`, `rust` |
| entry | string | yes | Script or binary to run |
| args | [string] | no | Arguments passed to the agent |

What ORB runs when you deploy:
- `python`, `python3 {entry} {args...}`
- `node`, `node {entry} {args...}`
- `binary`, `./{entry} {args...}`
- `go`, `rust`, same as `binary`

### [agent.env], Environment variables

```toml
[agent.env]
HOME = "/root"
MY_API_KEY = "sk-literal-value"
SECRET = "${ORG_SECRET}"
```

Set environment variables for the agent process. Two modes:

| Syntax | Resolves to | Example |
|---|---|---|
| `"literal"` | Used as-is | `MY_KEY = "sk-ant-abc123"` |
| `"${VAR}"` | Looked up from `org_secrets` at deploy time | `SECRET = "${ANTHROPIC_API_KEY}"` |

**`${VAR}` resolution:** When you deploy with `"org_secrets": {"ANTHROPIC_API_KEY": "sk-ant-..."}` in the deploy request body, ORB replaces `${ANTHROPIC_API_KEY}` with the value. If no matching secret is passed, the variable resolves to an empty string.

The value must be *exactly* `"${VAR}"`, substring interpolation like `"Bearer ${VAR}"` is not supported and will be passed through literally.

**Important:** Env vars are injected into the **agent process only**. The WebSocket terminal and other one-off shell sessions run with their own environment and do not inherit agent env vars. To verify your agent has the right env vars, log them at startup or expose them via your own health endpoint.

For API keys you own and control, use literal values:

```toml
[agent.env]
OPENAI_API_KEY = "sk-proj-abc123"
```

For secrets that change between environments or that you don't want in your repo, use `${VAR}` and pass them at deploy time.

### [source], Where your code lives

```toml
[source]
git = "https://github.com/you/your-agent"
branch = "main"
```

| Field | Type | Required | Description |
|---|---|---|---|
| git | string | yes | HTTPS URL to your git repo |
| branch | string | no | Branch to clone (default: `main`) |
| token | string | no | Auth token for private repos. Supports `${VAR}` from org_secrets. |

ORB clones the repo into `/agent/code` when you trigger a build. Only HTTPS URLs.

**Private repos:** Pass a GitHub personal access token via `org_secrets` at build time. ORB auto-injects it into the clone URL, no `orb.toml` change required.

```bash
curl -X POST https://api.orbcloud.dev/v1/computers/ID/build \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"org_secrets": {"GITHUB_TOKEN": "ghp_abc123"}}'
```

Create a token at <https://github.com/settings/tokens> with `repo` scope (classic) or fine-grained access to the specific repo.

You can optionally pin the token in `orb.toml` with an explicit `token` field, equivalent behavior, only useful if you want the resolution to be visible in your config:

```toml
[source]
git = "https://github.com/yourcompany/private-agent"
token = "${GITHUB_TOKEN}"
```

**Auto-deploy on push:** see [Auto-Deploy on Git Push](guides/auto-deploy.md) for the use-and-discard PAT flow that wires `git push` → ORB build via a committed GitHub Actions workflow. No stored tokens.

### [build], How to install dependencies

```toml
[build]
steps = [
  "pip install -r requirements.txt",
  "npm install",
]
working_dir = "/agent/code"
```

| Field | Type | Required | Description |
|---|---|---|---|
| steps | [string] | yes | Shell commands to run in order. **At least one step required**, an empty `steps = []` is rejected. |
| working_dir | string | no | Directory to run in (default: `/agent/code`) |

Each step runs with full internet access. You can install any system package with `apt-get`, each computer has a writable filesystem overlay.

If your agent has no install step (e.g. pure-stdlib Python, pre-built binary), use a no-op like `steps = ["true"]`.

Available tools: `apt-get`, `pip`, `npm`, `node`, `python3`, `git`, `make`, `gcc`, `curl`, `wget`. Steps have a 10-minute timeout each.

**Browser agents example:**

```toml
[build]
steps = [
  "apt-get update",
  "apt-get install -y chromium fonts-liberation",
  "npm ci",
]
working_dir = "/agent/code"
```

Chrome, Playwright, Puppeteer, and any browser automation tool can be installed via build steps. Browser agents checkpoint and restore like any other agent, Chrome's full process tree sleeps on NVMe and wakes in ~500ms.

### LLM Provider Detection

ORB automatically detects calls to all major LLM providers via transparent proxy. No configuration needed. Your agent calls the LLM API directly - ORB intercepts and optimizes at the network level.

Supported: Anthropic, OpenAI, Google, xAI, Cohere, Mistral, AI21, OpenRouter, Groq, DeepInfra, Together AI, Fireworks, Perplexity, DeepSeek, SambaNova, Cerebras, Novita, Hyperbolic, Lepton, Voyage AI, Jina, NVIDIA NIM, Zhipu/GLM, Alibaba Qwen, Baidu ERNIE, Replicate, Hugging Face, Modal, Azure OpenAI, AWS Bedrock.

If your LLM endpoint is not in this list, traffic passes through as regular HTTPS (not optimized for sleep/wake).

### [llm], Where your agent calls its LLM

```toml
[llm]
base_url = "https://api.anthropic.com"
```

| Field | Type | Required | Description |
|---|---|---|---|
| base_url | string | yes | The LLM endpoint URL. ORB forwards all LLM calls here. |

ORB runs a plaintext HTTP proxy on port 8080 inside every computer. When your agent makes an LLM call, the proxy intercepts it, forwards to `base_url`, and buffers the full response. This enables checkpoint during LLM waits.

> **Always configure `[llm]`, even if you set [`sleep = "never"`](#lifecycle-when-your-agent-sleeps).** The two are independent. Without `[llm]`, your LLM calls bypass ORB entirely, you lose per-call metering, dashboard observability, and any future ability to enable LLM-call checkpointing without redeploying. Configuring `[llm]` costs nothing if your agent never sleeps; not configuring it costs you visibility you can't recover.

ORB automatically sets these environment variables on your agent:

| Env var | Value | Frameworks that read it |
|---|---|---|
| `ANTHROPIC_BASE_URL` | `http://127.0.0.1:8080` | Claude Code, Anthropic SDK, Mastra |
| `OPENAI_BASE_URL` | `http://127.0.0.1:8080` | OpenAI SDK, CrewAI, AutoGPT |
| `OPENAI_API_BASE` | `http://127.0.0.1:8080` | SWE-agent, older OpenAI SDK |
| `LLM_BASE_URL` | `http://127.0.0.1:8080` | OpenHands, LiteLLM-based frameworks |
| `GOOGLE_API_BASE_URL` | `http://127.0.0.1:8080` | Google AI SDK |
| `GLM_BASE_URL` | `http://127.0.0.1:8080` | Z.ai / GLM SDK (Hermes, glm-4.x clients) |
| `ORB_PROXY_URL` | `http://127.0.0.1:8080` | Custom agents (generic fallback) |

Most agent frameworks read one of these automatically. If your framework uses a config file instead of env vars, set the base URL to `http://127.0.0.1:8080` in your framework's config.

**Common base_url values:**

| Provider | base_url |
|---|---|
| Anthropic (Claude) | `https://api.anthropic.com` |
| OpenAI (GPT) | `https://api.openai.com` |
| Google (Gemini) | `https://generativelanguage.googleapis.com` |
| z.ai Coding Plan | `https://api.z.ai/api/anthropic` |
| OpenRouter | `https://openrouter.ai/api` |
| Groq | `https://api.groq.com/openai` |
| Together AI | `https://api.together.xyz` |
| DeepSeek | `https://api.deepseek.com` |
| Self-hosted | `https://your-server.com` |

### Swarm env vars, `ORB_REPLICA_INDEX` and `ORB_REPLICA_COUNT`

When you spawn a fleet of identical workers via [`POST /v1/swarms`](api-reference.md#swarms), each replica gets two env vars injected automatically:

- `ORB_REPLICA_INDEX`, `0..N-1`, this replica's slot
- `ORB_REPLICA_COUNT`, `N`, total replicas in the swarm

Use them to self-route work without coordinating between replicas, e.g. modulo partition (`item.number % $ORB_REPLICA_COUNT == $ORB_REPLICA_INDEX`), range slice, or queue-claim with the index as a worker ID.

```toml
[agent]
name = "shard"
lang = "node"
entry = "dist/clawsweeper.js"
args = ["review", "--shard-count", "50"]   # --shard-index added by your script from $ORB_REPLICA_INDEX
```

These env vars are **only set on swarm deploys**. Single-computer deploys (`POST /v1/computers/{id}/agents`) leave them undefined, so code that reads `$ORB_REPLICA_INDEX` outside a swarm gets a clear "not in a swarm" signal rather than a misleading `0`.

### [ports], What to expose

```toml
[ports]
expose = [8000]
```

If your agent runs a web server (FastAPI, Express, Flask, etc.), list the port here. Your agent is accessible at `https://{short-id}.orbcloud.dev`.

Your agent binds to the port as normal. ORB handles the routing. You don't need to configure anything else.

**Port contract.** Bind your server to `[ports].expose[0]`, the first value in the list. If your framework reads a `PORT` env var, set it yourself in `[agent.env]` to match (e.g. `PORT = "8000"`); ORB does not inject `PORT`.

**Multiple ports (e.g. HTTP + Chrome CDP):**

```toml
[ports]
expose = [8000, 9222]
```

The first port is the default, accessible at `https://{short-id}.orbcloud.dev/`. Additional ports use the `/_port/` prefix:

- `https://{id}.orbcloud.dev/` → port 8000
- `https://{id}.orbcloud.dev/_port/9222/json/version` → port 9222
- `wss://{id}.orbcloud.dev/_port/9222/devtools/browser/{guid}` → WebSocket to port 9222

WebSocket connections are fully supported through the proxy.

**Browser agents (Playwright, Puppeteer, browser-use):**

Run browser automation inside the same computer, Python or Node.js launches Chrome locally. CDP stays on localhost (no WebSocket over the internet). Expose only an HTTP API for tasks.

```toml
[agent]
name = "browser-agent"
lang = "python"
entry = "agent.py"

[build]
steps = [
    "pip install playwright browser-use",
    "playwright install chromium",
]
working_dir = "/agent/code"

[ports]
expose = [8000]
```

Your `agent.py` launches Chrome via Playwright, controls it locally, and serves results over HTTP:

```python
from playwright.sync_api import sync_playwright
from http.server import HTTPServer, BaseHTTPRequestHandler
import json

pw = sync_playwright().start()
browser = pw.chromium.launch(headless=True, args=["--no-sandbox"])

class Handler(BaseHTTPRequestHandler):
    def do_POST(self):
        body = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
        page = browser.new_page()
        page.goto(body["url"])
        title = page.title()
        screenshot = page.screenshot()
        page.close()
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(json.dumps({"title": title, "bytes": len(screenshot)}).encode())

HTTPServer(("0.0.0.0", 8000), Handler).serve_forever()
```

Send tasks via your subdomain: `POST https://{id}.orbcloud.dev/browse`. Chrome's full process tree checkpoints and restores with your agent, sleeps on NVMe, wakes in ~500ms.

**Why local CDP?** Remote CDP (exposing port 9222 externally) works for simple commands but drops under the heavy burst traffic that browser-use sends on connect (extension downloads, tab management, DOM inspection). Local CDP has zero overhead, Playwright talks to Chrome on localhost.

**Remote CDP (advanced):**

If you need external access to Chrome DevTools (debugging, remote inspection):

```toml
[ports]
expose = [8000, 9222]
```

Launch Chrome with `--remote-debugging-port=9222 --remote-debugging-address=0.0.0.0`. Access at `wss://{id}.orbcloud.dev/_port/9222/devtools/browser/{guid}`.

WebSocket connections drop when the agent checkpoints (sleeps). CDP tools reconnect automatically. Send any HTTP request to the subdomain first to wake a sleeping agent.

### [resources], RAM and disk

```toml
[resources]
runtime = "2GB"
disk = "4GB"
```

- `runtime`, RAM for your agent. If your agent's memory exceeds this, it may be put to sleep more aggressively.
- `disk`, Storage for code, dependencies, and data.

### [lifecycle], When your agent sleeps

By default, ORB automatically sleeps your agent when it's been idle for a few minutes and wakes it again on inbound HTTP. You don't pay while it's asleep. **For most agents, leave this section out.** The default is the right answer.

```toml
[lifecycle]
sleep = "auto"     # default. ORB sleeps the agent on idle and wakes it on traffic.
# sleep = "never"  # agent stays in RAM 24/7. Use only when you need it.
```

**Pick the path that matches your agent's traffic shape, in this order:**

**1. Inbound HTTP / WebSocket, the recommended shape.** If your agent receives work via webhooks, an HTTP API you expose on `[ports]`, or anything Slack-like with a public URL (Slack Events API, Discord interactions, GitHub webhooks), you don't need anything in `[lifecycle]`. ORB wakes your agent in under a second when a request arrives at `https://{computer-id}.orbcloud.dev`. WebSocket upgrades work the same way, both HTTP and WS connections trigger wake-on-request.

**2. Manual sleep / wake, explicit control.** If your agent has natural phase boundaries ("batch finished," "browser session done"), you can call the lifecycle API directly:
```
POST /v1/computers/{id}/agents/demote   # force sleep
POST /v1/computers/{id}/agents/promote  # force wake
```
You don't need `sleep = "never"` for this, `sleep = "auto"` plus manual control gives you the best of both: zero cost when you've explicitly slept the agent, automatic sleep when you forget.

**3. `sleep = "never"`, last resort.** Set this only when none of the above fits:

- Your agent holds a **persistent outbound connection** the other side will sever on idle: Slack Socket Mode, MQTT subscribers, IRC bots, long-poll clients to a third-party service.
- Your agent's process tree can't be checkpointed cleanly: browser-use, Playwright agents driving a real browser with X/GPU/DRM resources.

The trade-off: a `sleep = "never"` agent occupies RAM 24/7 and is billed accordingly. **Prefer the HTTP-shaped path whenever you can change the protocol.** For Slack specifically, Events API (HTTP) fits ORB much better than Socket Mode (WebSocket), same bot, same `xoxb` token, just a different transport.

> **Keep `[llm] base_url` configured even with `sleep = "never"`.** The two settings are independent. `sleep` controls *whether* your agent sleeps; `[llm]` controls *how* its LLM calls are observed and metered. Drop `[llm]` and you lose per-call metering, dashboard visibility, and the proxy clock that would resume idle-detection cleanly if you ever flip back to `sleep = "auto"`. Always set both.

### Cron, Scheduled tasks

Your agent (or you) can schedule commands by writing a JSON file. No SDK needed, any language can write JSON.

> **Use this, not the system cron.** Standard Linux schedulers (`cron`, `crond`, `systemd timers`) and any in-process timer (Node.js `setInterval`, Python loops, etc.) all live inside your agent's process or its sandbox, they get frozen when ORB checkpoints the agent to NVMe and miss every firing during sleep. ORB's cron runs on the host, fires at wall-clock time regardless of agent state, and executes the command inside the sandbox without waking your agent.

Write to `/agent/.orb/cron.json`:

```json
{
  "version": 1,
  "jobs": [
    {
      "name": "daily-pr-review",
      "schedule": "0 9 * * *",
      "command": "python3 review.py --since=1d"
    },
    {
      "name": "heartbeat",
      "schedule": "*/5 * * * *",
      "command": "curl -sf http://localhost:8000/health",
      "timeout_secs": 30
    }
  ]
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes | Unique per computer. Used in history and API responses. |
| `schedule` | string | yes | Cron expression. 5-field (`min hour dom mon dow`) or 6-field with seconds (`sec min hour dom mon dow`). |
| `command` | string | yes | Shell command. Runs via `sh -c` inside the computer's sandbox. |
| `env` | map[string]string | no | Extra env vars for this job (merged with the agent's env). |
| `working_dir` | string | no | Directory to run the command in. Defaults to `/agent`. |
| `timeout_secs` | int | no | Hard kill after this many seconds. Default `300`, max `3600`. |
| `enabled` | bool | no | Set to `false` to pause a job without deleting it. Default `true`. |
| `skip_if_running` | bool | no | If the previous firing is still running, skip this one. Default `true`. |

**How it works:** The runtime polls `/agent/.orb/cron.json` every 5 seconds and uploads the full job set to the cloud scheduler whenever it changes. The scheduler fires each job at its due time and executes the command inside the computer's sandbox namespaces (same isolation as your agent). Results (status, exit code, stdout/stderr, duration) land in the DB and are mirrored back to `/agent/.orb/cron-history.json`, the 20 most recent runs per computer, with 512-byte output previews.

The command runs **independently** of your agent process, it doesn't need your agent to be awake, doesn't wake your agent, and doesn't interact with the LLM proxy. Use it for maintenance tasks (cleanup, exports, health pings) and for one-shot commands that should fire on a schedule even while the agent sleeps. If you want to wake your agent on a timer, use an inbound HTTP call to your exposed port instead.

**Limits:**
- 50 jobs per computer
- 10,000 runs per computer per 24h (soft safety cap, jobs that exceed it produce `status=skipped` rows)

**Listing runs:** `GET /v1/computers/{id}/cron` for current jobs, `GET /v1/computers/{id}/cron/runs` for history. See [API Reference](api-reference.md#cron).

The `/agent/.orb/` directory is created automatically in every computer.

## Common Examples

### Python agent with Anthropic (Claude)

```toml
[agent]
name = "my-agent"
lang = "python"
entry = "agent.py"

[source]
git = "https://github.com/you/my-agent"
branch = "main"

[build]
steps = ["pip install -r requirements.txt"]
working_dir = "/agent/code"

```

### Node.js agent with OpenAI

```toml
[agent]
name = "my-agent"
lang = "node"
entry = "index.js"

[source]
git = "https://github.com/you/my-agent"
branch = "main"

[build]
steps = ["npm install"]
working_dir = "/agent/code"

```

### Python agent with Gemini via Google

```toml
[agent]
name = "reviewer"
lang = "python"
entry = "review.py"

[source]
git = "https://github.com/you/reviewer"
branch = "main"

[build]
steps = ["pip install google-generativeai"]
working_dir = "/agent/code"

```

### Agent with a web interface

```toml
[agent]
name = "dashboard-agent"
lang = "python"
entry = "server.py"

[source]
git = "https://github.com/you/dashboard-agent"
branch = "main"

[build]
steps = ["pip install fastapi uvicorn anthropic"]
working_dir = "/agent/code"

[ports]
expose = [8000]
```

Your agent runs `uvicorn` on port 8000. Accessible at `https://{short-id}.orbcloud.dev`.

### Multi-provider agent (Claude + GPT)

```toml
[agent]
name = "orchestrator"
lang = "python"
entry = "orchestrator.py"

[source]
git = "https://github.com/you/orchestrator"
branch = "main"

[build]
steps = ["pip install anthropic openai"]
working_dir = "/agent/code"


[llm]
base_url = "https://api.anthropic.com"

[ports]
expose = [8000]

[resources]
runtime = "4GB"
disk = "8GB"
```
