Caveman
ReferenceAgent registry

Agent registry

The agent registry is the data behind caveman wrap. Each supported coding agent is one JSON profile: command names, wire protocol, and how the CLI points it at the gateway. Adding an agent should be a data change, not a CLI rewrite.

The registry is part of the MIT CLI surface and compiles into the CLI binary. There are 5 profiles today: aider, claude, codex, gemini, and opencode. profiles/schema.json defines the contract.

A profile is data

A profile names an agent, says which wire protocol it speaks, and says how to point it at the gateway. Here is a trimmed example — the Claude Code profile:

json
{
"schema_version": "1",
"id": "claude",
"display_name": "Claude Code",
"vendor": "Anthropic",
"homepage": "https://code.claude.com/docs",
"binary_names": ["claude"],
"install": "npm i -g @anthropic-ai/claude-code",
"wire_protocol": "anthropic-messages",
"injection": {
"method": "env",
"env": {
"ANTHROPIC_BASE_URL": "{{cave_base_url}}",
"ANTHROPIC_AUTH_TOKEN": "{{cave_api_key}}"
}
},
"attribution": { "header": "x-cave-agent" },
"fallback": "generic-env",
"maintainer": null
}

The fields:

FieldWhat it carries
idkebab-case, globally unique — the caveman wrap <id> selector
display_name, vendor, homepagehuman-facing labels
binary_names[]matched against the wrap target for auto-detect
installone-line install hint shown when the binary is missing
wire_protocolthe protocol the agent speaks to the proxy
injectionhow the CLI points the agent at the gateway
attribution.headerthe header the proxy reads to attribute telemetry to this agent
fallbackwhat to apply if detection/injection fails (generic-env is the safe default)
maintainernull for the core team, else a GitHub handle

Wire protocol

wire_protocol is one of exactly four values — only the protocols the proxy speaks natively:

  • anthropic-messages
  • openai-chat
  • openai-responses
  • gemini-generatecontent

There is no protocol translation in the wrap path. The proxy speaks these; the profile picks one.

Injection

injection.method is one of two ways to redirect the agent:

  • env — set literal environment variables. Values may use the templates {{cave_base_url}}, {{cave_api_key}}, {{cave_proxy_url}}, and {{cave_org_id}}. A variable whose rendered value is empty is omitted — never set to "" — so an empty auth token is never injected.
  • config-env-content — render a mode-selected inline JSON config (config_content.local for BYOK caveman start; config_content.managed when the gateway points off-loopback) and set it as one env var. This is how opencode is wrapped, via OPENCODE_CONFIG_CONTENT, without ever touching the user's opencode.json.

The compiler

compile.mjs is a zero-dependency Node compiler. It validates every profile against schema.json and emits two artifacts:

  • agents.json — the generated, published registry (do not hand-edit).
  • ../cli/src/agents.generated.ts — the CLI's embedded copy.

It runs in the CLI build and the CLI test, so the generated TypeScript is always in sync with the profiles.

terminal
node ../agents/compile.mjs   # validate every profile, emit the registry

The source format is JSON, not YAML — deliberately. The CLI imports the generated TypeScript and never reads a file at runtime, which keeps it zero-runtime-dependency.

Fail closed on unknown shapes

An unknown protocol or method fails the compile

The compiler never guesses. If a profile names a wire_protocol outside the four the proxy speaks, or an injection.method it does not recognize, the compile fails — it does not fall back to a plausible protocol. This mirrors the eval graders' default-to-fail. A profile that compiles is a profile the proxy can actually route; a guess that silently shipped would be a wrong route.

Your first agent

Adding an agent is the lowest-friction contribution in the stack: write profiles/<id>.json, run node ../agents/compile.mjs, commit the regenerated agents.json and agents.generated.ts. Then caveman wrap <id> can route it.

Where to go next