> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://rivet.chrisboulton.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://rivet.chrisboulton.com/_mcp/server.

# Agent runs

> Configure rivet run to pull from a source release or the compile service, and run offline.

`rivet run` is the agent. Each run gets a catalog, from a [source release](/operations/distribution) or the [compile service](/operations/compile-service), verifies every asset, and then applies it with the normal local runner. It runs once and exits; [schedule it](/operations/scheduling) with a systemd timer. Real runs require root on Linux.

```sh
sudo rivet run --noop    # predict changes
sudo rivet run           # apply
```

The agent reads `/etc/rivet/agent.hcl`. This file uses a small configuration format of its own, separate from your managed configuration: it accepts only literal values, no expressions. The examples below are shown as plain text for that reason.

## Source mode

The host reads the environment's release from storage and compiles it locally:

```text
environment = "production"

source {
  uri = "gs://example-rivet-config/site"
}
```

The host's storage identity needs read access to the release and any cloud assets. It can read everything in the store, so HCL conditions don't hide one host's secrets from another. Use the compile service if that matters. S3 stores use `s3://bucket/prefix`, and the same [credential chains](/language/assets#credentials) apply.

You can also skip the config file and pass the store directly:

```sh
sudo rivet run --source gs://example-rivet-config/site --noop
sudo rivet run --environment staging --noop --format json
```

## Server mode

The host authenticates to the compile service, which compiles and returns its catalog. On a GCP VM:

```text
server {
  url = "https://rivet.example.com"
  auth {
    type = "gcp"
  }
}
```

The agent requests an instance identity token from the GCP metadata server, with the server URL as the audience. Set `server.auth.audience` if a load balancer expects a different one. The equivalent command line is `sudo rivet run --server https://rivet.example.com --auth gcp`.

With mTLS, the host presents a client certificate:

```text
server {
  url = "https://rivet.example.com"
  auth {
    type      = "mtls"
    ca_file   = "/etc/rivet/pki/server-ca.pem"
    cert_file = "/etc/rivet/pki/node.pem"
    key_file  = "/etc/rivet/pki/node-key.pem"
  }
}
```

Here `ca_file` is the CA that signed the server's certificate. Rivet doesn't issue or renew client certificates; your deployment must.

With OIDC, the host presents a workload identity token from a file:

```text
server {
  url = "https://rivet.example.com"
  auth {
    type       = "oidc"
    token_file = "/run/rivet-identity/token"
  }
}
```

The file is read on every request, and something else must keep it fresh. OIDC and GCP also accept `ca_file` for a private server CA.

In every mode, the server's HTTPS certificate is always verified and redirects are refused. Tokens and keys are read from files, never from command-line arguments. Authentication to the service is separate from storage access: hosts still need storage credentials to download assets, unless the service uses [signed asset delivery](/operations/compile-service#signed-asset-delivery).

## Settings

| Setting                                         | Default                                        | Command-line override  |
| ----------------------------------------------- | ---------------------------------------------- | ---------------------- |
| `environment`                                   | `production`                                   | `--environment`        |
| `source.uri` or `server.url` with `server.auth` | Exactly one is required                        | `--source`, `--server` |
| `cache_dir`                                     | `/var/cache/rivet`                             | `--cache-dir`          |
| `facts_dir`                                     | `/etc/rivet/facts.d`                           | `--facts-dir`          |
| `offline_max_age_secs`                          | `86400` (one day); at most 604800 (seven days) | None                   |

`--config PATH` reads a different file. Without `--source` or `--server`, the config file is required. A config file that exists but is unreadable or invalid is an error, never ignored.

`--source` replaces the configured mode with source mode. `--server` replaces it with server mode, keeping the configured `server.auth` unless you also pass `--auth gcp`. mTLS and OIDC settings can only come from the file. See the [CLI reference](/reference/cli#run).

The config file can be at most 64 KiB, and unknown or duplicate settings are errors. Rivet refuses to read config and PEM files that are symlinks, or that are writable by anyone other than root or the caller. Private keys and token files must also be unreadable by group and others.

## What each run does

1. Collects fresh facts, including [custom facts](/language/facts#custom-json-facts). Executable custom facts run as the caller, even with `--noop`.
2. Reads the environment pointer once, then compiles locally or requests a catalog from the service.
3. Downloads and verifies every asset into the cache. A missing or corrupt asset stops the run.
4. Runs the local runner, which takes the host lock and recovers any interrupted work first.

`--noop` does steps 1 to 3 normally, including authenticating and filling the cache, then predicts changes without making any. Add `--format json` for a report that includes the environment, release, promotion generation, and cache statistics.

The cache must be an absolute directory owned by the caller and private to it. Managed files and home directories can't be inside it. Runner state stays in `/var/lib/rivet` (or `--state-dir`), and the host lock is always `/run/rivet/apply.lock`.

## Prepare a bundle without applying

```sh
rivet run --source gs://example-rivet-config/site \
  --cache-dir /tmp/rivet-controller-cache \
  --prepare-only --facts facts.json --out node.bundle --format json
rivet graph node.bundle
```

`--prepare-only` does the acquisition steps and writes a verified bundle instead of applying. It works on macOS too, and is useful for inspecting what a host would receive. Pass the host's real facts with `--facts`; without them, Rivet uses the built-in Ubuntu baseline, which the compile service rejects because it has no host identity.

## Offline runs

```sh
sudo rivet run --offline --noop
sudo rivet run --offline
```

`--offline` applies the last catalog that a previous online run fully prepared, without contacting storage or the service. That catalog must have been prepared for the same mode, source or server, environment, and host, and be younger than `offline_max_age_secs`. Every cached asset is verified again before use.

An offline run applies what was compiled last time. It doesn't see new promotions, new facts, or revoked credentials. An online run that fails never falls back to offline on its own; you have to ask for it.