Caveman
The proxyConfiguration

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.

yaml
# ~/.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.

ProviderEnvironment variable
anthropicANTHROPIC_API_KEY
openaiOPENAI_API_KEY
geminiGEMINI_API_KEY
azure_openaiAZURE_OPENAI_API_KEY
openai_compatibleOPENAI_COMPAT_API_KEY
terminal
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:8787

The 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:

terminal
CAVE_SSRF_ALLOWLIST=127.0.0.1,localhost \
OPENAI_COMPAT_API_KEY=… ./bin/caveman-proxy

Add 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.

See also