⚡ Run the demo Home Why Docs GitHub ↗

documentation

Using Firstpass

Firstpass is a drop-in, Anthropic-compatible HTTP proxy. Run it, point your agent's base_url at it, and every request is routed to the cheapest model that provably passes your gate — with a signed receipt for each decision. This page covers install, configuration, and operation. For the design rationale, read the SPEC.

Overview

Every request flows through three moves: route to the cheapest rung of a ladder, gate the real output, and escalate one rung only if the gate fails — across providers, with cross-provider failover on a 5xx. The whole decision is written to a tamper-evident, hash-chained trace.

providers Anthropic · OpenAIstore SQLite (WAL)config env + TOMLkeys BYOK, redacted

Install

MethodCommand
pip / uvxuvx firstpass · pip install firstpass
Homebrewbrew install dshakes/tap/firstpass
npmnpx @dshakesnotbot/firstpass
curl | shcurl --proto '=https' --tlsv1.2 -LsSf https://github.com/dshakes/firstpass/releases/latest/download/firstpass-proxy-installer.sh | sh
Dockerdocker run -p 8080:8080 -e FIRSTPASS_BIND=0.0.0.0:8080 ghcr.io/dshakes/firstpass:latest
Cargocargo install --git https://github.com/dshakes/firstpass firstpass-proxy
crates.iocargo install firstpass-proxy
Availability: cargo install --git and the Docker image work today. Every other channel — pip · uvx · Homebrew · npm · crates.io · curl | sh · prebuilt binaries — is wired end-to-end and lights up on the first tagged release (each registry push is gated on an operator secret). Upgrade in place with firstpass-proxy-update, or via your channel (brew upgrade, pip install -U firstpass, …).

Quickstart

See the whole loop in ~10 seconds — no API keys. The demo stands up a mock upstream and drives one real decision, escalation and all.

# clone & watch the receipt print
git clone https://github.com/dshakes/firstpass && cd firstpass
cargo run -p firstpass-proxy --example demo

Modes

Firstpass runs in one of two modes, set by FIRSTPASS_MODE:

observe (default)

Transparent pass-through. Requests are forwarded unchanged and a trace is recorded off the hot path — zero behavior change. Use this to shadow traffic and build an audit trail before enforcing anything.

enforce

Full routing: cheapest-first ladder, gate the output, escalate on failure, cross-provider failover. Requires a routing config (below).

FIRSTPASS_MODE=enforce FIRSTPASS_CONFIG=./firstpass.toml firstpass-proxy

Environment

Configuration is 12-factor. Run firstpass-proxy --help for the reference:

VariablePurposeDefault
FIRSTPASS_MODEobserve | enforceobserve
FIRSTPASS_BINDlisten address127.0.0.1:8080
FIRSTPASS_CONFIGpath to firstpass.toml
FIRSTPASS_DBtrace store pathfirstpass.db
FIRSTPASS_UPSTREAM_ANTHROPICupstream base URLapi.anthropic.com
FIRSTPASS_UPSTREAM_OPENAIupstream base URLapi.openai.com
FIRSTPASS_TENANTtenant id for the tracedefault
FIRSTPASS_PROMPT_SALTsalt for prompt hashing
RUST_LOGtracing filterinfo

Routing config

Routing is declarative TOML: ordered routes (first match wins), each binding a slice of traffic to a mode, a cheapest-first model ladder, and the gates that must pass. Start from firstpass.example.toml.

[[route]]
match  = { agent = "claude-code" }
mode   = "enforce"
ladder = ["anthropic/claude-haiku-4-5", "anthropic/claude-sonnet-5"]
gates  = ["schema", "cargo-test"]

# wildcard fallback — first match wins, top to bottom
[[route]]
match = {}
mode  = "observe"

Gates

A gate reads the candidate output and returns a verdict. Two kinds:

  • Inlinenon-empty, json-valid, and a minimal JSON-Schema gate (type / required / properties). No process spawn.
  • Subprocess plugin — your own command (tests, linter, a judge). The candidate is passed on stdin, never argv (injection-resistant); a JSON verdict comes back on stdout. Non-zero exit or timeout → abstain.
Error budgets. Each gate carries a rolling budget. A gate whose abstain rate exceeds its threshold is auto-disabled for subsequent requests (with an alarm), so one flaky gate can't take down a route.

Endpoints

EndpointPurpose
POST /v1/messagesDrop-in inference — observe or enforce per route.
POST /v1/feedbackReport a downstream outcome for a prior trace.
GET /v1/capabilitiesMachine-readable capability descriptor.
GET /healthzLiveness.

Receipts & feedback

Every decision is a hash-chained JSON trace an external auditor can re-derive — which model ran, which gates passed, what it cost, and the counterfactual saving. Downstream outcomes (did the tests pass an hour later?) flow back via POST /v1/feedback:

curl localhost:8080/v1/feedback -d '{
  "trace_id": "0192f3a1-…", "gate_id": "cargo-test",
  "verdict":  "pass", "reporter": "ci"
}'
Chain-preserving. Deferred verdicts live in a separate table keyed by trace id — they are merged on read for display but never folded into the sealed, hashed record. The chain stays re-derivable.

Offboarding

Leaving is always one environment variable — an invariant of the design:

unset ANTHROPIC_BASE_URL   # your agent talks to the provider directly again

← Back to home  ·  Read the SPEC →