Setup¶
This page runs the whole system from a clone — proxy, harness, dashboard, fixtures. If you only want to use Gettier on your own repository, you want the quickstart instead: it needs neither Docker nor a server.
Prerequisites¶
- Node 20+
- pnpm 10
- Docker (for the fixtures and the Postgres-backed test suite)
- At least one upstream key:
ANTHROPIC_API_KEYand/orOPENAI_API_KEY
Install and test¶
The two customer surfaces¶
Neither needs a Gettier server, and neither talks to the proxy.
pnpm --filter gettier build # Gettier for Agents (the CLI)
node packages/cli/dist/cli.js --help
pnpm --filter @gettier/node build # Gettier for Runtime (the SDK)
Full references: Gettier for Agents and Gettier for Runtime. Everything below is the rest of the system.
Run the GTR-001 fixture¶
The fixture is a real docker-compose environment (the nginx / body-parser trap), not a mock — the seeded fault is exactly what the harness is meant to expose.
Start the proxy¶
ANTHROPIC_API_KEY=sk-ant-… \
GETTIER_API_KEY=replace-with-a-long-random-value \
GETTIER_REGISTRY=fixtures/gtr-001/sensors.local.yaml \
GETTIER_SENSOR_CWD=fixtures/gtr-001 \
pnpm --filter @gettier/proxy start
The proxy listens on http://localhost:7070 and serves POST /v1/messages (Anthropic and
OpenAI wire formats). Adoption is a single base_url change in your client or coding agent.
Add the header x-gettier-consequential: 1 to force the full state machine on a turn, then
watch the response headers:
Clients authenticate with Authorization: Bearer <GETTIER_API_KEY> (OpenAI format) or
x-api-key: <GETTIER_API_KEY> (Anthropic format). The credential determines the tenant.
x-gettier-gate: held— a sensor could have settled a load-bearing belief and did not; the turn was blocked. A claim no sensor covers is a coverage gap, not a hold (v0.3).x-gettier-gate: released— a sensor verdict backed at least one load-bearing claim.x-gettier-gate: released_unverified— nothing held, and nothing measured either: the turn rests entirely on self-report. Not a clean bill of health.x-gettier-miscalibrations: N— claims tagged with evidence the model did not have.x-gettier-unverifiable: N— load-bearing claims no registered sensor covers. A sensor that covers a claim but could not run holds instead of landing here.x-gettier-unjudged: N— load-bearing claims a sensor measured and no judgment rule could read. Write a sensor forunverifiable; widen a rule for this.
Models and provider routing¶
The proxy and the harness share one routing definition, so they can never disagree about where a model id goes. The provider is inferred from the id:
claude-*→ Anthropic (Messages API).gpt-*,o1–o9,chatgpt-*→ OpenAI. Within OpenAI,gpt-5.6+andgpt-6+route to the Responses API; everything else uses Chat Completions. (Those newer families reject function tools on Chat Completions at any reasoning effort butnone— measured 2026-07-19 — and forced tool use is how the declaration is enforced, so they need the Responses surface.)- An unrecognised id throws rather than defaulting — a silent default would send the request to the wrong provider and surface as a confusing 404.
Keys and endpoints come from the environment (or a repo-local .env):
| Variable | Purpose |
|---|---|
ANTHROPIC_API_KEY / OPENAI_API_KEY |
Upstream credentials. At least one is required. |
GETTIER_API_KEY |
Client credential bound to GETTIER_TENANT. Required. |
GETTIER_API_KEYS |
Optional JSON key-to-principal map for multi-tenant service. |
ANTHROPIC_BASE_URL / OPENAI_BASE_URL |
Optional — point at a compatible gateway. |
GETTIER_PROVIDER |
Optional override for non-standard ids (e.g. a gateway serving other names). The OpenAI surface (Responses vs Chat) is still inferred from the id. |
Run the harness¶
See The measurement harness for the arms, the scoring taxonomy, and multi-run variance. The short version:
pnpm --filter @gettier/harness start -- \
--scenario fixtures/gtr-001 \
--models claude-opus-4-8,gpt-5.6-sol \
--arms raw,gated --runs 1
Reports land in runs/; per-run ledger evidence under runs/ledgers/. Put your API key in a
repo-local .env if you want it picked up automatically.
Open the dashboard¶
GETTIER_DATA_DIR=/path/to/ledger-dir GETTIER_TENANT=local \
GETTIER_DASHBOARD_TOKEN=replace-with-a-long-random-value \
pnpm --filter @gettier/web dev
Then visit http://localhost:3000.
Session signing uses GETTIER_SESSION_SECRET, falling back to GETTIER_DASHBOARD_TOKEN when
no OIDC issuer is configured — which is why the single variable above is enough for local
development. In production the secret is required and must be at least 32 characters, or
the dashboard refuses to serve rather than signing sessions with something guessable. Set
DATABASE_URL to use the hosted Postgres control plane instead of a SQLite ledger; see
Hosted control plane and Dashboard identity.
Run the public landing site¶
The public site is isolated from the authenticated dashboard in packages/landing:
It listens on http://localhost:3001 in development. Set the canonical origin before every
production build so canonical, Open Graph, robots, sitemap, and JSON-LD URLs agree.
Build this docs site¶
pip install mkdocs-material
mkdocs serve # http://localhost:8000
mkdocs build --strict # -> ./site (gitignored); --strict is what CI runs
--strict turns warnings into errors, so a broken internal link or a page missing from the
nav fails the build rather than shipping. This site deploys to docs.gettier.io on
Cloudflare Workers Static Assets from .github/workflows/deploy-docs.yml, on any push to
main that touches docs/** or mkdocs.yml.