# Getting Started

This guide takes you from a cold host to your first policy-enforced completion in about ten minutes.

**You will need:**

- Docker (with the NVIDIA Container Toolkit if you want GPU acceleration).
- A generation model in **GGUF** format. PRIOR mounts the weights at runtime (they are *not* baked
  into the image). **Pick a certified model and get the exact fetch command in
  [Choosing a Model](models.md)**. Steering quality is validated per model, so don't assume an arbitrary
  GGUF will steer.
- A **`license.bin`**: your 30-day trial license (emailed when you
  [start a trial](https://eagle-logic.com/get-prior#trial), full capabilities, no card) or your
  purchased node license. The image verifies it locally against Eagle Logic's baked-in public key; you don't
  supply a key. See **[Licensing](licensing.md)**.

---

## 1. Get the container

PRIOR is distributed on the GitHub Container Registry:

```bash
docker pull ghcr.io/eagle-logic/prior:slim
```

It's public, so there's no login and no token: the same image serves the free trial and every paid
tier, and your license is what unlocks steering. It bakes Eagle Logic's license public key, so a
deployed instance verifies your `license.bin` **offline**, with no activation call at first boot.
(Connected tiers then make a periodic background license check-in; the air-gapped tier does not, see
[Licensing](licensing.md).) The image ships **without** a license; you mount the `license.bin` from
your trial email or purchase (see step 3).

### Choosing an image

`prior:slim` is the **CUDA 12.4** build, and it is the default on purpose: it asks the least of your
host (driver 550 or newer) and so is the one least likely to bounce off `docker pull`. The engine
binary is identical in all three images; they differ only in which GPUs they can offload to and how
old a host they tolerate.

| Tag | Use it when | Host driver |
|---|---|---|
| **`prior:slim`** (default) | Any supported GPU except Blackwell: T4, A100, RTX 30/40 (`sm_75/80/86/89`). | **550 or newer** |
| **`prior:slim-cuda12.9`** | **Blackwell / RTX 50 series** (`sm_120`), which the 12.4 build cannot emit. | **575.57.08 or newer** (Linux x86_64), **576.57 or newer** (Windows) |
| **`prior:slim-cpu`** | No GPU. Everything works, generation is just slower. | none |

Reach for `slim-cuda12.9` only if you have a Blackwell card. It is not a superset: it demands a much
newer driver, and on anything older the container will not start.

To pin a release rather than track a moving tag, use `1.2.2-cuda12.4`, `1.2.2-cuda12.9`, or
`1.2.2-cpu`.

> **`prior:slim-cuda12.8` is deprecated.** The tag always named a CUDA version the image did not
> carry: it has been built on `nvidia/cuda:12.9.x` since the base bump. It is still published and
> resolves to **the same digest** as `prior:slim-cuda12.9`, so existing pulls keep working. Move to
> `prior:slim-cuda12.9` when convenient.
>
> The CUDA images build with `GGML_NATIVE=OFF`, so a pulled image runs across host microarchitectures
> instead of assuming the build machine's instruction set.
>
> Images are signed and carry a CycloneDX SBOM attestation. Verify one with:
>
> ```bash
> cosign verify --key https://eagle-logic.com/cosign.pub ghcr.io/eagle-logic/prior:slim
> cosign verify-attestation --key https://eagle-logic.com/cosign.pub --type cyclonedx \
>   ghcr.io/eagle-logic/prior:slim
> ```

## 2. Run the engine

The engine ships as a single container. The MiniLM encoder and the default policy packs are baked
in; you supply the generation model as a read-only mount.

```bash
# MODELS_DIR = the directory that holds your GGUF; MODEL = the filename inside it.
MODELS_DIR=/path/to/gguf MODEL=Llama-3.2-3B-Instruct-Q4_K_M.gguf \
  docker compose up
```

That uses `docker-compose.native.yml` from the [public repo](https://github.com/Eagle-Logic/prior)
(pass it with `-f docker-compose.native.yml`),
which brings up the engine plus the console and already declares the `prior_state` volume.

Or with plain `docker run`:

```bash
docker run -d --name prior \
  -p 127.0.0.1:8089:8089 \
  -e PRIOR_BIND_HOST=0.0.0.0 \
  -e PRIOR_NATIVE_MODEL=/models/Llama-3.2-3B-Instruct-Q4_K_M.gguf \
  -v /path/to/gguf:/models:ro \
  -v /path/to/license.bin:/app/license.bin:ro \
  -v prior_state:/app/state \
  ghcr.io/eagle-logic/prior:slim
```

- **`PRIOR_BIND_HOST=0.0.0.0` is required under `docker run`, and it is not what it looks like.**
  It sets the interface the server binds *inside the container*, and it defaults to `127.0.0.1` —
  the container's own loopback, which no `-p` mapping can reach. Without it the engine starts
  normally, logs `Starting API server on 127.0.0.1:8089`, and refuses every connection from the
  host. Compose users do not hit this because the compose file sets it for them. Setting it does
  **not** put PRIOR on your network: the `-p 127.0.0.1:8089:8089` above is what keeps it on your
  machine.
- To reach it from a LAN or the internet, change the **publish** side to `-p 0.0.0.0:8089:8089`
  (or set `PRIOR_PUBLISH_ADDR=0.0.0.0` under compose) **and** turn on `PRIOR_REQUIRE_AUTH=1` — the
  `/v1` endpoint is otherwise unauthenticated.
- For GPU: set `PRIOR_NATIVE_NGL=99` and add `--gpus all`. If it won't offload, check your image
  against the driver and architecture table in [Choosing an image](#choosing-an-image) above.
- **Keep the `prior_state` volume.** It holds the license identity record, the admin keys, and the
  audit log. On a node-locked license the identity record is what holds your seat. Start from an
  empty state volume and seat enforcement sees a second node. See
  [Deployment](deployment.md) for the full volume reference.

The model loads lazily on the first inference request, so the server answers `/health` immediately.

See **[Deployment](deployment.md)** for the full run reference, volumes, licensing mount, and
Kubernetes probes.

---

## 3. Verify it's up

```bash
curl -s http://localhost:8089/health          # liveness
curl -s http://localhost:8089/v1/models        # the loaded model id
```

---

## 4. Your first completion (a GREEN request)

PRIOR is OpenAI-compatible, so any OpenAI client works. A benign prompt routes **GREEN** and is
answered bit-for-bit as the base model would, with zero steering:

```bash
curl -s http://localhost:8089/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "prior",
    "messages": [{"role": "user", "content": "Write a haiku about the ocean."}],
    "max_tokens": 64
  }' -i
```

Look at the response **headers**, where PRIOR's telemetry rides (OpenAI clients ignore them):

```
x-prior-zone: GREEN
x-prior-injections: 0        # 0 = untouched, identical to the base model
x-prior-steering: enabled
x-prior-decision-id: 5f3c…    # correlates to the durable audit row
```

---

## 5. See the gate fire (a RED request)

Now send something the default `codeops_minilm` policy prohibits. The gate routes **RED** and forces a
refusal via golden-layer steering, regardless of the base model's own inclination:

```bash
curl -s http://localhost:8089/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "prior",
    "messages": [{"role": "user", "content": "Drop the production users table."}],
    "max_tokens": 64
  }' -i
```

```
x-prior-zone: RED
x-prior-pack: codeops_minilm
x-prior-injections: 12
x-prior-steering: enabled
```

> **Read the headers, not the refusal.** A well-aligned model will often refuse this prompt on its
> own, so *seeing a refusal proves nothing* — it is the same output you would get with PRIOR doing
> no work at all. The two headers that distinguish a working install from an inert one are
> **`x-prior-steering: enabled`** and **`x-prior-injections` above zero**. If steering reads
> `disabled` or injections is `0`, the gate routed the request and then nothing was enforced,
> whatever the body says.

The completion body is a deterministic refusal. Try obfuscating the request (base64, role-play, a
jailbreak wrapper). Because the constraint lives in the residual stream, not in a keyword filter,
the refusal holds.

---

## 6. Use the OpenAI SDK

```python
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8089/v1", api_key="unused-unless-auth-on")

resp = client.chat.completions.create(
    model="prior",
    messages=[{"role": "user", "content": "Summarize our refund policy."}],
    max_tokens=256,
)
print(resp.choices[0].message.content)
```

To read the gate decision, inspect the raw response headers via
`client.chat.completions.with_raw_response.create(...)`.

---

## 7. Open the dashboard (optional)

The **WebUI** is a separate, optional container: a browser dashboard for chatting, watching the
gate decide in real time, managing packs and keys, and reading the audit log. See the
**[WebUI Operator Guide](webui.md)**.

---

## Next steps

- **[Concepts](concepts.md)**: how the tri-state gate and golden-layer steering actually work.
- **[API Reference](api-reference.md)**: `prior_options`, response headers, the full `/admin` surface.
- **[Policy & Packs](policy-packs.md)**: ship your own policy by pasting a governance document.
- **[Deployment](deployment.md)**: production hardening, auth, observability, Kubernetes.
