# API keys and agents

`o41-rrun` is built to be driven by coding agents and CI as much as by people:
every command has machine-readable output, following a job exits with the job's
result, and an organization API key can run jobs without being able to touch
clouds, storage or other keys.

## API keys

```bash
export O41_RRUN_API_KEY=$(o41-rrun api-keys create ci)   # the secret, once, alone on stdout
o41-rrun api-keys                                         # list
o41-rrun api-keys revoke <id>
```

Or **API keys → Create** in the web app. Keys start with `ak_`, belong to one
organization, and are shown once. Managing keys needs a signed-in person.

| Credential                   | Can                                                                                                                            |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `o41-rrun login`             | Everything in the organization                                                                                                 |
| API key (`O41_RRUN_API_KEY`) | Run, list, status, logs, wait and cancel jobs; see offers; list machines; turn cloud accounts and reserved machines on and off |

An API key cannot connect or remove clouds, register, remove or delete machines,
set storage, create or revoke keys, or read the audit log. It can still launch
machines through jobs, so `--max-price`, `--timeout` and `--keep` are the guard
rails to use in an agent's commands. It can turn cloud accounts and reserved
machines on and off (`o41-rrun clouds on|off`, `o41-rrun machines on|off`), so a
script can steer where work goes from outside information such as prices or
quotas.

An agent that sets up a server as a
[reserved machine](https://rrun.041.io/docs/machines-and-costs.md#reserved-machines) runs as a signed-in
person and runs the printed command over ssh:

```bash
ssh lab-1 "$(o41-rrun machines register --name lab-1 --json | jq -r .command)"
```

## Environment

| Variable           | Effect                                                                     |
| ------------------ | -------------------------------------------------------------------------- |
| `O41_RRUN_API_KEY` | Use this organization API key. It wins over a saved login.                 |
| `O41_RRUN_URL`     | The rrun server (default `https://rrun.041.io`); `--url` overrides it      |
| `O41_RRUN_TOKEN`   | A raw bearer token, with `O41_RRUN_ORG` naming its organization; for tests |
| `XDG_CONFIG_HOME`  | Where `login` saves its sign-in: `$XDG_CONFIG_HOME/o41-rrun/auth.json`     |

A machine or CI runner without a browser needs only `O41_RRUN_API_KEY` and the
binary:

```bash
curl -fsSL https://rrun.041.io/install.sh | sh
export O41_RRUN_API_KEY=ak_…
o41-rrun run --gpu L4,A6000 --spot --keep 0 -f -- python eval.py
```

## Output

- Results go to stdout, progress and hints to stderr.
- Errors go to stderr as `error: <code>: <message>`, such as
  `error: not_logged_in: …` or `error: usage: …`.
- `--json` prints one JSON document on stdout. Log streams print JSON Lines of
  `{"seq","at","stream","text","attempt","machineId"}`, one per line the command
  wrote, with `"cut":{"bytes"}` on a line cut at 1 MiB; `run -f --json` prints
  the job first, then the lines. Under `--json` stderr carries no progress or
  hints, only warnings (such as logs past retention or a cloud that did not
  answer `offers`) and errors.
- `--json` works before or after the command: `o41-rrun --json ls` and
  `o41-rrun ls --json` are the same. For `run`, put it before the command to
  run, since everything after that belongs to the command.

## Exit codes

| Code  | When                                                                     |
| ----- | ------------------------------------------------------------------------ |
| 0     | Success; when waiting, the job succeeded                                 |
| 1     | An error; when waiting, a failure other than the command's own exit code |
| 2     | A usage error: a bad flag or argument                                    |
| 1–255 | When waiting: the command's own non-zero exit code                       |
| 130   | When waiting: the job was cancelled; or Ctrl-C stopped following         |

"Waiting" is `run -f`, `run --wait`, `logs -f` and `wait`. They exit with the
job's result, so a shell `&&` or a CI step fails exactly when the job does.
Ctrl-C stops only the CLI; the job keeps running.

## Patterns

Fire and forget, then check back:

```bash
id=$(o41-rrun run --gpu RTX5090,RTX5080,A6000 --ram 64 --spot -- ./train.sh)
o41-rrun status "$id" --json | jq '{status, reason, exitCode, attempt}'
o41-rrun logs "$id" --tail 50
```

Block until done, with the job's exit code:

```bash
o41-rrun run --gpu L4 --wait --json -- pytest -x   # prints the final job as JSON
o41-rrun wait "$id"
```

A job restarts on another machine when its machine fails, so a script should
resume from a checkpoint it saved to durable storage. One that must not run
twice takes `--restarts 0`:

```bash
o41-rrun run --gpu L4 --restarts 0 --wait -- ./migrate.sh
```

Stream logs into a tool:

```bash
o41-rrun logs "$id" -f --json | jq -r 'select(.stream == "err") | .text'
```

Pick a machine by price first:

```bash
o41-rrun offers --gpu RTX5090,A6000 --ram 64 --spot --json | jq '.offers[0]'
```

## Docs for agents

Every page of these docs is markdown at the same URL plus `.md`, and any page
answers with markdown to curl or `Accept: text/markdown`. `/llms.txt` lists
every page. `o41-rrun <command> --help` prints the flags of each command.

---

rrun by 041 documentation. Every page: https://rrun.041.io/llms.txt
