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

# Drivers — the substrate contract

> The five NAIVE_*_DRIVER selectors, their values and defaults, cloud↔Tower parity notes, the GovernorClient contract, and how to write your own driver. Reveal is absent on vaultd.

Every substrate a region touches — secrets, queue, inference, storage, runtime
— is behind a driver interface in `@usenaive/region-core`. Services never import
a provider SDK directly; they call a driver resolved from a `NAIVE_*_DRIVER` env
var. The interfaces are 1:1 with the cloud implementations they abstract, so the
cloud code paths port to a Tower without behavior change.

## The five selectors

| Selector                 | Values                         | Cloud default | Tower default |
| :----------------------- | :----------------------------- | :------------ | :------------ |
| `NAIVE_SECRETS_DRIVER`   | `kms` · `vaultd` · `file`      | `kms`         | `vaultd`      |
| `NAIVE_QUEUE_DRIVER`     | `sqs` · `pg`                   | `sqs`         | `pg`          |
| `NAIVE_INFERENCE_DRIVER` | `openrouter` · `vllm` · `auto` | `openrouter`  | `auto`        |
| `NAIVE_STORAGE_DRIVER`   | `s3` · `fs`                    | `s3`          | `fs`          |
| `NAIVE_RUNTIME_DRIVER`   | `ecs` · `local`                | `ecs`         | `local`       |

Defaults come from `resolveDriverSelection()`: with `NAIVE_BUILD=tower` the base
is the Tower set, otherwise the cloud set. Any individual `NAIVE_*_DRIVER` value
overrides its base. (There are also `kv` and `cron` drivers — both Postgres on a
Tower — used for the relay cursor and scheduled sweeps.)

<Note>
  The runtime pool is a **selectable driver**, not a fixed dependency. The cloud
  default is the ECS warm pool; a Tower runs `local` (docker-compose service
  instances). Neither is privileged by the code.
</Note>

## `NAIVE_SECRETS_DRIVER` — the Credential Vault

The `SecretsDriver` is envelope encryption. Regardless of driver the stored
value layout is identical — `nonce(12) || tag(16) || ciphertext`, AES-256-GCM,
with an encryption context of exactly `{ company_id, tenant_user_id }` — so
`vault_entries` rows are portable between substrates.

| Value    | Where  | Notes                                                                                  |
| :------- | :----- | :------------------------------------------------------------------------------------- |
| `kms`    | Cloud  | AWS KMS envelope driver. Implements the optional `reveal`. Needs `NAIVE_VAULT_KEK_ID`. |
| `vaultd` | Tower  | Thin UNIX-socket client to the closed TPM-rooted daemon. **No `reveal`.**              |
| `file`   | Dev/CI | HKDF tenant-KEK rooted in a local key file. No TPM. Implements `reveal`.               |

`generateDataKey` / `decryptDataKey` / `reEncryptDataKey` are the required
methods; `decryptDataKey` must **reject on a mismatched context**. `reveal` is
optional.

<Warning>
  **Reveal is absent on `vaultd` (Tower).** `reveal?` is an optional method on
  `SecretsDriver`; the vaultd driver does not implement it and vaultd compiles the
  method out entirely (`towerbuild`). The vault reveal route therefore returns
  **501** on a Tower — plaintext never leaves the box. `kms` and `file` implement
  `reveal`; `vaultd` does not. See [Security model](/docs/self-hosting/security-model)
  and [Vault encryption](/docs/architecture/vault-encryption).
</Warning>

## `NAIVE_QUEUE_DRIVER` — durable queues

`sqs` (cloud) and `pg` (Tower) implement the same `QueueDriver`: create/delete,
send (with FIFO group id + content dedup + delay), receive with a visibility
timeout, delete, purge, and attributes.

The `pg` driver implements pgmq **semantics** on plain SQL (`SKIP LOCKED`,
`read_ct`, archive) rather than requiring the pgmq extension — a documented
deviation, since FIFO group-key withholding needs custom SQL either way. It runs
on stock `postgres:16`, does FIFO group-withholding + content dedup, and moves a
message to the DLQ at `maxReceive = 5`.

## `NAIVE_INFERENCE_DRIVER` — chat completions

An OpenAI-compatible `InferenceDriver` (non-streaming, streaming, and
`listModels`). Both the `llm` primitive and the agent proxy (`/v1/proxy/*`) go
through the **same** router (`packages/api/src/services/llm.ts` → `drivers.ts`),
so a box has one inference source, not two.

| Value        | Behavior                                                                                                        |
| :----------- | :-------------------------------------------------------------------------------------------------------------- |
| `openrouter` | Forward to OpenRouter. Needs `OPENROUTER_API_KEY`.                                                              |
| `vllm`       | Forward to a local vLLM (`NAIVE_VLLM_URL`, default `http://vllm:8000/v1`).                                      |
| `auto`       | Route a model to vLLM **when the box serves it**, else fall through to OpenRouter (via the gateway on a Tower). |

### The three box inference modes

On a Tower the *mode* (persisted per box in `boxes.inference_mode`, chosen in the
dashboard **Settings → Inference** or the onboarding wizard) decides where model
calls actually go. The effective mode resolves as **env override
(`NAIVE_INFERENCE_MODE` / `NAIVE_LLM_FORWARD` / `NAIVE_INFERENCE_DRIVER`) → the
persisted choice → `cloud`**.

| Mode                    | Provider key on the box? | Billing                   | How it routes                                                                                                                                                                                                                                                                                                                                                        |
| :---------------------- | :----------------------- | :------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`cloud`** *(default)* | **No**                   | Owner's **cloud credits** | `/v1/llm/*` + `/v1/proxy/*` are forwarded through the egress-gateway to Naïve Cloud, which injects the owner's **sealed** cloud key (vaultd — never on the box) and meters. Requires the box **claimed** + a gateway forward named by `NAIVE_LLM_FORWARD` (e.g. a `cloud` forward with `vaultKey: cloud_api_key`). The box does **zero** local billing on this path. |
| **`local`**             | No                       | None (on-box)             | The `vllm` driver serves the model on the box; Hermes points at it via `HERMES_LOCAL_MODEL`. Fully offline.                                                                                                                                                                                                                                                          |
| **`byo`**               | Yes (your key)           | Your provider account     | The box calls the provider directly with `OPENROUTER_API_KEY` / `ANTHROPIC_API_KEY` (today's simplest path).                                                                                                                                                                                                                                                         |

If a box is set to `cloud` but no forward is wired, it **degrades to the local
provider driver** (BYO key) rather than failing — so `cloud` is safe as a default.

<Warning>
  **Honest hardware feasibility for local models.** Running a model on the box
  needs real VRAM. A **4 GB Jetson Nano cannot** serve a useful LLM — use `cloud`
  (credits) or `byo`. A **Jetson Orin** (16–64 GB unified memory) can serve a
  **small quantized** model (≈3–8B, AWQ/GGUF) at modest speed (expect
  seconds-per-response, not instant) — fine for light tasks, not for large-context
  or high-throughput work. Bigger models want a discrete NVIDIA GPU on an x86 box.
  Enable local inference with the opt-in `vllm` compose profile
  (`docker compose … --profile vllm up -d`) after setting `NAIVE_INFERENCE_DRIVER=vllm`

  * `VLLM_MODEL` (and a JetPack/ARM64 `VLLM_IMAGE` on Jetson).
</Warning>

## `NAIVE_STORAGE_DRIVER` — object storage

`StorageDriver` = put / get / presigned URL / delete.

* `s3` (cloud) — AWS S3, real presigned URLs.
* `fs` (Tower) — files under `NAIVE_STORAGE_FS_ROOT` (e.g. `/data/storage`).
  Fetch URLs are **HMAC-signed** and served by `/storage/local/*key`; it guards
  against path traversal.

## `NAIVE_RUNTIME_DRIVER` — the runtime pool

`RuntimePoolDriver` is the spec's "supervisor": claim/release/get/list of runtime
instances for tenants.

* `ecs` (cloud) — the ECS warm pool (hot-claim an idle task).
* `local` (Tower) — the box's compose `agent-runtime` service (the real Hermes
  agent-container). It boots in `POOL_MODE=idle`; a claim HOT-CLAIMS it in place
  by POSTing `/provision` (identical contract to the ECS path — no restart),
  then records the binding so `sidecarFetch`/`getContainerForSubject` resolve it.
  Point the driver at it with `NAIVE_LOCAL_RUNTIME_URL` (default
  `http://agent-runtime:3100`) + `CONTAINER_AUTH_TOKEN`.

On a Tower each runtime is started with a per-operator `NAIVE_RUNTIME_TOKEN` and
addresses the [egress gateway](/docs/self-hosting/security-model) as its only route
off-box. The agent-container image is built for **arm64 and amd64**
(`scripts/build-agent-container.sh`).

<Note>
  **Local inference.** Set `HERMES_LOCAL_MODEL` (+ `NAIVE_VLLM_URL`) to point the
  box's Hermes runtime at a local vLLM OpenAI-compatible endpoint — the CEO's own
  reasoning then stays on-box, complementing the `auto` inference driver that
  keeps the `llm` primitive local too.
</Note>

## The GovernorClient contract

Drivers move *bytes*; they never make the trust decision. That decision is a
wire call to the closed **governor**, and the only way to make it is
`GovernorClient` / the `governorCall()` wrapper in `@usenaive/region-core`
(pinned in [`PROTOCOLS.md`](/docs/architecture/governance-gateway) §1).

```ts theme={"theme":"css-variables"}
interface GovernorClient {
  evaluate(req: EvaluateRequest): Promise<Verdict>;         // allow | deny | freeze
  replayAuthorize(req: ReplayAuthorizeRequest): Promise<ReplayAuthorization>;
  budgetReserve(req: BudgetReserveRequest): Promise<BudgetReservation>;
  budgetRelease(referenceId: string): Promise<void>;
}
```

* **Transport** — localhost HTTP/1.1 + JSON to `GOVERNOR_URL` (default
  `http://governor:8700`), authenticated with a shared `GOVERNOR_TOKEN` bearer,
  ajv-validated on both ends.
* **Fail-closed is baked in.** `governorCall()` bounds every call (500 ms
  connect / 2 s total). Governor unreachable **or** timeout throws
  `GovernorUnavailableError`; there is no verdict caching and no call site can
  default to library behavior. The API maps it to `403 enforcement_unavailable`;
  the gateway maps it to deny-all.
* A `Verdict` carries `decision`, a `target` of `live | sandbox` (whether an
  allowed action routes to real rails or the sandbox adapter), and — on
  `freeze` — an `approval_id` for a persisted pending row.

## Writing a driver

1. **Implement the interface** from `@usenaive/region-core` (e.g.
   `SecretsDriver`, `QueueDriver`). Match the exact method shapes; for secrets,
   preserve the `nonce(12) || tag(16) || ciphertext` layout and reject on
   context mismatch so `vault_entries` rows stay portable.
2. **Register it** behind the selector. The api resolves drivers in
   `packages/api/src/drivers.ts` via lazy getters keyed on
   `resolveDriverSelection(process.env)`; add your branch there (and export a
   `create…Driver(opts)` factory from your package, as the bundled drivers do).
3. **Never talk to the governor yourself** except through `GovernorClient` —
   enforcement stays in the closed engine, and the fail-closed guarantee depends
   on the single wrapper.
4. **Add a conformance capability.** Tag any leg that needs a real third-party
   key with `hasCapability()` so a missing key is reported `BLOCKED`, never
   mocked (see [Conformance](/docs/self-hosting/conformance)).
5. **Ship a README** with the five-section template (Purpose / Consumers /
   Configuration / Run standalone / Place in the topology) — the docs gate
   checks it.

<Note>
  The set of selectors is the source of truth in `packages/core/src/drivers.ts`
  (`DRIVER_ENV_VARS`). A CI gate greps this page for every one:
  `NAIVE_SECRETS_DRIVER`, `NAIVE_QUEUE_DRIVER`, `NAIVE_INFERENCE_DRIVER`,
  `NAIVE_STORAGE_DRIVER`, `NAIVE_RUNTIME_DRIVER`.
</Note>
