API keys and gateways
Several keys, custom gateways, and secret references.
Status. Provider profiles route for real: the endpoint, credential, headers, TLS setting, proxy, timeout and model-prefix rewrite are all applied to a live call, asserted against a recording server in
tests/test_provider_routing.py. Per-provider rate limits parse but are not yet enforced. See ../roadmap/status.md.
Today: environment variables
export ANTHROPIC_API_KEY="sk-ant-..."
export OPENAI_API_KEY="sk-..."
export GEMINI_API_KEY="..."
arena evaluate --project projects/my_project
Per model:
models:
- key: work_gpt
model: gpt-5
api_key_env: WORK_OPENAI_KEY # a different variable
A model whose key is missing is skipped, not failed — the run continues and the leaderboard reports the skip with its reason.
.env files
agent_arena/core/env.py is complete and tested. It reads
~/.config/agent-arena/.env and a project .env, with real environment
variables winning over file values.
# projects/my_project/.env
OPENAI_API_KEY=sk-...
export ANTHROPIC_API_KEY="sk-ant-..." # `export` prefix is accepted
# comments and blank lines are fine
Loaded in cli.main() before any command reads a credential. .env is already
in .gitignore.
Named provider profiles
The capability this exists for: two API keys for the same vendor in one run, which v1 could not express at all.
providers:
- id: work
kind: openai
api_key: ${env:WORK_OPENAI_KEY}
headers: {OpenAI-Organization: org-123}
rate_limit: {rpm: 500, tpm: 200000, concurrency: 4}
- id: personal
kind: openai
api_key: ${keyring:agent-arena/openai-personal}
models:
- key: gpt5_work
provider: work
model: gpt-5
- key: gpt5_personal
provider: personal
model: gpt-5
Both entries are the same model on different accounts, competing in one run.
A corporate gateway
providers:
- id: corp_gateway
kind: openai_compatible
base_url: https://gateway.corp.internal/v1
api_key: ${keyring:agent-arena/corp}
headers: {X-Portkey-Config: cfg_abc}
verify_tls: /etc/ssl/corp-ca.pem # private CA
proxy: http://squid.corp:3128
model_prefix: "openai/" # rewrite ids on the way out
timeout_s: 60
Works with LiteLLM proxy, Portkey, Cloudflare AI Gateway, a Bedrock proxy, or anything else speaking the OpenAI API.
verify_tls: false disables certificate checking entirely. Prefer the CA bundle;
see ../security/hardening.md.
Back-compatibility
provider: naming a bare vendor kind (anthropic, openai, …) keeps its v1
meaning — ProjectConfig.provider_for() returns None and the existing registry
handles it. Only an id declared in providers: changes resolution. Every config
written before this block existed behaves identically, asserted for all four
example projects.
Secret references
Never a literal key in config. The connector registry resolves the reference when it builds the connection, so no caller ever holds the raw value.
| Scheme | Resolves from |
|---|---|
${env:NAME} |
Environment |
${keyring:service/account} |
OS credential store |
${file:~/.secrets/openai} |
File contents; refuses a world-readable file |
${cmd:op read op://vault/item/field} |
Command stdout — 1Password, Vault, aws secretsmanager |
Resolved keys are wrapped in Secret, whose repr and str are ***. See
../security/secrets.md.
Managing profiles from the CLI
arena providers add work --kind openai --api-key '${env:OPENAI_API_KEY}'
arena providers add gw --kind openai_compatible \
--base-url https://gateway.corp.internal/v1 \
--api-key 'sk-...' --header 'X-Portkey-Config=cfg_abc'
arena providers list
arena providers test gw # can we reach it, and how fast
arena providers discover gw # what models does it serve
arena providers rm gw --purge-key
A literal key passed to providers add is moved into your OS keyring and
only the reference is written to settings.json. That file is mode 0600 and
never holds a credential.
arena secrets set my-account # prompts, or reads stdin
arena secrets get my-account # prints *** unless --reveal
arena secrets rm my-account
Keep literal keys out of config
A config snapshot is stored with every run, so a key pasted into config.yaml
also lands in your results database and in any export of it. Exports scrub
anything key-shaped on the way out, but the database still has it.