> See the full index of documentation pages at https://docs.zettabyte.com/llms.txt.

# Scripting and agents

> The machine contract, and how to hand the whole CLI to an AI agent.

Source: https://docs.zettabyte.com/docs/cli/automation

Last updated: 2026-10-02

## Let an AI drive it

Autonomous agents can drive `zb` end to end: launch, wait, collect, iterate. Every command is non-interactive by default. Machine output is stable. Errors state the fix in plain English. Blocking primitives replace polling loops.

Give the agent the docs first. The complete CLI documentation ships as one plain-text file. Fetch `https://docs.zettabyte.com/docs/cli/llms-full.txt` and paste it into context (`zb docs -o json` returns the URLs). Then let the agent run `zb` directly. Set `ZB_API_KEY` in its environment and it never needs an interactive login.

```bash
# the loop an agent runs: every step blocks or returns stable JSON
zb launch train.yaml --name exp-1 -w -o json   # blocks until RUNNING (straight through QUEUED)
zb wait exp-1 --for SUCCEEDED --timeout 4h     # exit 1 on FAILED or timeout
zb pull exp-1 ./results                        # collect outputs
zb status -o json                              # read the world, decide the next launch
```

## Non-interactive by design

- Confirmation prompts appear only on destructive commands, and only when stdin is a TTY. `-y` skips them. The interactive commands (`zb init`, `zb auth login`, `zb chat`, `zb shell`) prompt only on a TTY. Without one, nothing ever waits for input.
- Spinners, progress bars, status glyphs, and color render only when the stream is a terminal. Piped or redirected streams and `-o json` get plain, line-oriented output. Transient transfer progress degrades to occasional milestone lines on stderr. stdout stays the script-facing record.
- `NO_COLOR` disables color. `TERM=dumb` disables animation too.

## Exit codes

| Code | Meaning |
| --- | --- |
| 0 | success |
| 1 | error |
| 2 | usage error |
| 3 | not found |
| 4 | ambiguous name |
| 130 | Ctrl-C at an interactive prompt (TTY only; scripts and agents never see it) |

`zb exec` is the exception: it returns the remote command's exit code.

## JSON output

`-o json` prints stable machine output on stdout. On failure, a structured error also lands on stderr:

```json
{ "error": { "type": "error", "message": "no workload named \"train-1\" — run `zb status` to see what's live" }, "exit_code": 3 }
```

`type` carries the server's error type when one exists, `error` otherwise. List rows carry `status` (normalized), `status_raw` (the server's own value), and `raw` (the full server row). The detail view's `-o json` carries `detail` plus full `spec`, `runtime`, `diagnosis` (with raw event reasons), and `metrics` sections. Secret values never render. In table mode, `zb status NAME` shows the identity block plus the next commands that make sense. It adds a plain-language Health line when something is wrong ("image can't be pulled", "container is crash-looping"). `--wide` adds the creation spec and live runtime even when healthy.

## Workload statuses

The `status` field and table column use one normalized enum across kinds. Unrecognized server states pass through uppercased rather than being hidden.

| Status | Meaning |
| --- | --- |
| `QUEUED` | the launch is parked server-side waiting for GPU quota; the workload has not been created yet |
| `CREATING` | accepted; creation still in progress |
| `PENDING` | accepted, waiting to schedule |
| `STARTING` | scheduled, containers coming up |
| `RUNNING` | up |
| `STOPPED` | not running: a serve scaled to zero (dev and box can report it too) |
| `SUCCEEDED` | ran to completion |
| `FAILED` | ended in error |
| `TERMINATING` | being deleted |
| `MISSING` | the record exists but nothing is running behind it |
| `UNKNOWN` | the platform could not verify the workload on this request; the workload itself is most likely fine |
| `DELETED` | gone, but the name still resolves so `zb logs` and `zb pull` can reach outputs |

Queued rows carry `queue_position` (1-based, ordered by priority then submit time) and `queue_expires_at` in the row's `raw` JSON. A queued launch answers HTTP 202 with `queued: true`. `UNKNOWN` is deliberately not `FAILED`. Rows carry `status_unverified: true` so scripts can tell "unobservable" from "broken". Serve rows carry a `ready_status` tri-state for the same reason: `READY`, `LOADING`, or `UNVERIFIABLE`. `UNVERIFIABLE` means the readiness check could not run, which is not the same as still loading.

## Blocking primitives and sweeps

- `zb launch -w` blocks until running. `zb wait NAME --for STATUS --timeout DURATION` exits 1 on `FAILED` or timeout. `zb logs -f` ends itself at a terminal state.
- `QUEUED` is non-terminal: `zb wait` (and `launch -w`) poll through a queued launch into `PENDING` and `RUNNING` with no extra handling.
- Sweeps: template one spec with `--set` plus `--name exp-N`. A name collision is a clean 4xx failure, never a silent duplicate. See Iterate and sweep on the Launch and specs page.

## The HTTP API underneath

The HTTP API itself is first-class. Everything zb does is `/api/public/v1/*` on `https://api.zettabytecloud.com` with an `X-API-Key` header. Agents and scripts can skip the CLI entirely.
