Configuration
The proxy reads one optional caveman.yaml file plus provider keys from environment variables. Secrets do not belong in YAML. The file controls mode, listen address, optimizer flags, and provider base URLs. The proxy is BSL 1.1.
A bare caveman start with no config is valid. Missing config means record mode on 127.0.0.1:8787. Add YAML only when you need a different upstream, listen address, label, or mode.
caveman.yaml
The loader (public/proxy/internal/config/config.go) reads ~/.caveman/caveman.yaml unless CAVEMAN_CONFIG points elsewhere.
# ~/.caveman/caveman.yaml
label: local # optional; tags local telemetry rows
mode: record # default; pass-through
listen: 127.0.0.1:8787 # where the proxy binds
optimizers: # provider-native optimizers, gated by id
anthropic-cache-breakpoints: true
providers: # per-provider base-URL overrides
azure_openai:
base_url: https://my-resource.openai.azure.com
openai_compatible:
base_url: http://127.0.0.1:11434/v1
compat: # named OpenAI-compatible upstreams, served simultaneously
openrouter: # → /compat/openrouter/v1/chat/completions
base_url: https://openrouter.ai/api
api_key_env: OPENROUTER_API_KEY
ollama: # → /compat/ollama/v1/chat/completions
base_url: http://localhost:11434
api_key_env: "" # empty = no auth header (needs CAVE_SSRF_ALLOWLIST=localhost)Anthropic, OpenAI, and Gemini have public defaults. azure_openai, bedrock, vertex, and openai_compatible need base_url because there is no universal upstream.
The compat: map serves several OpenAI-compatible vendors at once — each entry gets its own /compat/<name>/ route and resolves its own api_key_env. Names are validated fail-closed (stub and openai-compatible are reserved); telemetry records all of them as provider openai_compatible, with the vendor recoverable from the endpoint path. The single providers.openai_compatible entry keeps working unchanged on the bare /compat/ route. On the managed gateway the equivalent is CAVE_COMPAT_UPSTREAMS="openrouter=https://openrouter.ai/api,groq=https://api.groq.com/openai" — a malformed entry fails startup rather than being skipped.
BYOK keys from the environment
Keys are read from the environment at request time, never from YAML.
| Provider | Environment variable |
|---|---|
anthropic | ANTHROPIC_API_KEY |
openai | OPENAI_API_KEY |
gemini | GEMINI_API_KEY |
azure_openai | AZURE_OPENAI_API_KEY |
openai_compatible | OPENAI_COMPAT_API_KEY |
go build -o ./bin/caveman-proxy ./public/proxy/cmd/caveman-proxy
ANTHROPIC_API_KEY=sk-ant-… ./bin/caveman-proxy # serve on 127.0.0.1:8787The proxy checks the inbound request's own auth header first — x-api-key for Anthropic-style calls or bearer Authorization for OpenAI-style calls — and only falls back to the environment key when the request carries none. Bedrock is absent because it signs each request with AWS SigV4.
Runtime modes
mode selects how the proxy may act on a request. Accepted values are record, recommend, shadow, canary, active, compress, and pixel. Default is record, which never transforms a body.
Unknown modes fail closed to record
Any unknown mode falls back to record. A typo cannot silently enable transforms. compress and pixel are both S4 lossy modes; they only do work when the engine-backed compressor is wired with CCR recovery, and pixel additionally requires the request's model to match the CAVE_PIXEL_MODELS allowlist (default claude-fable-5,gpt-5.6). Otherwise either mode passes through unchanged. S4lossy · recoverable
The SSRF guard
standalone.StandaloneHTTPClient carries an SSRF dial guard that is always on in standalone mode. At dial time it blocks loopback, private RFC 1918 ranges, link-local addresses, and cloud metadata addresses.
Pointing at a local model server? Use the allowlist
Because the guard blocks loopback and private addresses, local model servers are blocked by default. Opt specific hosts back in with CAVE_SSRF_ALLOWLIST:
CAVE_SSRF_ALLOWLIST=127.0.0.1,localhost \
OPENAI_COMPAT_API_KEY=… ./bin/caveman-proxyAdd only hosts you run. The allowlist is the escape hatch; the guard stays on for everything else.
Driving it from the CLI
You usually don't run the binary by hand. caveman start launches caveman-proxy, and caveman wrap <agent> starts a child process with the relevant base URL environment set.