Phantom routes every LLM query (the main agent and every evolution judge) through the Anthropic Agent SDK by default. By setting environment variables that the bundled cli.js already honors, you can point that subprocess at any Anthropic Messages API compatible endpoint without changing a line of code.
The provider: block in phantom.yaml is a small config surface that translates into those environment variables for you. When agent_runtime: murph is selected, the same block translates into native Murph route variables instead.
agent_runtime selects the Agent SDK implementation. It is separate from provider, which selects the model endpoint.
agent_runtime: anthropic # anthropic | murphAnthropic is the default and existing deployments do not need to set this field. Murph is opt-in and loads @murph/anthropic-sdk-shim from the Phantom project. If the package is not installed or linked, startup fails with an actionable message.
Runtime selection precedence is:
phantom start --agent-sdk-module <specifier>PHANTOM_AGENT_SDK_MODULEphantom start --agent-runtime <runtime>PHANTOM_AGENT_RUNTIME- YAML
agent_runtime - Default
anthropic
Use the custom module escape hatch for a local compatibility harness:
phantom start --agent-sdk-module file:///path/to/runtime.mjsUse the named Murph runtime when @murph/anthropic-sdk-shim is installed or linked:
export PHANTOM_AGENT_RUNTIME=murph
phantom start --agent-runtime murph| Type | Base URL | API Key Env | Notes |
|---|---|---|---|
anthropic (default) |
https://api.anthropic.com |
ANTHROPIC_API_KEY |
Claude Opus, Sonnet, Haiku |
openai |
https://api.openai.com |
OPENAI_API_KEY |
Murph runtime only |
zai |
https://api.z.ai/api/anthropic |
ZAI_API_KEY |
GLM-5.1 and GLM-4.5-Air, roughly 15x cheaper than Opus |
openrouter |
https://openrouter.ai/api/v1 |
OPENROUTER_API_KEY |
100+ models through a single key |
vllm |
http://localhost:8000 |
none | Self-hosted OpenAI-compatible inference |
ollama |
http://localhost:11434 |
none | Local GGUF models, zero API cost |
litellm |
http://localhost:4000 |
LITELLM_KEY |
Local proxy bridging OpenAI, Gemini, and others |
custom |
(you set it) | (you set it) | Any Anthropic Messages API compatible endpoint |
No configuration needed. Existing deployments continue to work unchanged.
# phantom.yaml
model: claude-opus-4-7
# No provider block = defaults to anthropic# .env
ANTHROPIC_API_KEY=sk-ant-...Use this shape only when Murph is installed or linked into the Phantom project.
# phantom.yaml
agent_runtime: murph
model: gpt-5.5
provider:
type: openai# .env
OPENAI_API_KEY=sk-...Phantom passes gpt-5.5 as the concrete SDK model and sets native Murph route env such as MURPH_PROVIDER=openai, MURPH_MODEL=gpt-5.5, and MURPH_OPENAI_MODEL=gpt-5.5.
Judge calls use the same concrete model unless you add model_mappings for cheaper tier-specific routes.
# phantom.yaml
agent_runtime: murph
model: sonnet
provider:
type: zai
api_key_env: ZAI_API_KEY
model_mappings:
sonnet: glm-5.1
haiku: glm-5# .env
ZAI_API_KEY=<your-zai-key>The Murph route is native GLM, not the legacy Anthropic-compatible bridge. Phantom sets MURPH_PROVIDER=glm, MURPH_MODEL=<resolved>, and MURPH_GLM_MODEL=<resolved>.
Z.AI provides an Anthropic Messages API compatible endpoint at https://api.z.ai/api/anthropic. Phantom ships with a zai preset that points there automatically. Get a key at docs.z.ai.
# phantom.yaml
model: claude-sonnet-4-6
provider:
type: zai
api_key_env: ZAI_API_KEY
model_mappings:
opus: glm-5.1
sonnet: glm-5.1
haiku: glm-4.5-air# .env
ZAI_API_KEY=<your-zai-key>Both the main agent and every evolution judge route through Z.AI. The claude-sonnet-4-6 model name is translated to glm-5.1 on the wire by the model_mappings block.
Run any GGUF model on your own GPU. No API key needed.
# phantom.yaml
model: claude-sonnet-4-6
provider:
type: ollama
model_mappings:
opus: qwen3-coder:32b
sonnet: qwen3-coder:32b
haiku: qwen3-coder:14bOllama must be running at http://localhost:11434 (the preset default). The model must support function calling to work with Phantom's agent loop.
For organizations running their own inference clusters.
# phantom.yaml
model: claude-sonnet-4-6
provider:
type: vllm
base_url: http://your-vllm-server:8000
model_mappings:
sonnet: your-model-name
timeout_ms: 300000 # local models can be slow on first callStart vLLM with --tool-call-parser matching your model for tool use to work.
Access 100+ models through a single OpenRouter key.
# phantom.yaml
model: claude-sonnet-4-6
provider:
type: openrouter
api_key_env: OPENROUTER_API_KEY
model_mappings:
sonnet: anthropic/claude-sonnet-4.5Run a local LiteLLM proxy to bridge OpenAI, Gemini, and other formats.
# phantom.yaml
model: claude-sonnet-4-6
provider:
type: litellm
api_key_env: LITELLM_KEY
# base_url defaults to http://localhost:4000For the default Anthropic runtime, custom means any Anthropic Messages API compatible proxy. Under Murph, custom means an OpenAI-compatible custom endpoint.
# phantom.yaml
model: claude-sonnet-4-6
provider:
type: custom
base_url: https://your-proxy.internal/anthropic
api_key_env: YOUR_CUSTOM_KEY_ENV# phantom.yaml, Murph OpenAI-compatible custom endpoint
agent_runtime: murph
model: your-model
provider:
type: custom
base_url: https://your-proxy.internal/v1
api_key_env: YOUR_CUSTOM_KEY_ENV| Field | Type | Default | Purpose |
|---|---|---|---|
type |
enum | anthropic |
One of the supported provider types |
base_url |
URL | preset default | Override the endpoint URL |
api_key_env |
string | preset default | Name of the env var holding the credential |
model_mappings.opus |
string | none | Concrete model ID for the opus tier |
model_mappings.sonnet |
string | none | Concrete model ID for the sonnet tier |
model_mappings.haiku |
string | none | Concrete model ID for the haiku tier |
disable_betas |
boolean | preset default | Sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1. Defaulted true for every non-anthropic preset. |
timeout_ms |
number | none | Sets API_TIMEOUT_MS for slow local inference |
For operators who prefer env variables over YAML edits:
| Variable | Effect |
|---|---|
PHANTOM_AGENT_RUNTIME |
Override agent_runtime with anthropic or murph |
PHANTOM_AGENT_SDK_MODULE |
Override the Agent SDK boundary with a custom module specifier |
PHANTOM_PROVIDER_TYPE |
Override provider.type (validated against the supported values) |
PHANTOM_PROVIDER_BASE_URL |
Override provider.base_url (validated as a URL) |
PHANTOM_MODEL |
Override config.model |
These are applied on top of the YAML-loaded config during startup.
With the default Anthropic runtime, the Agent SDK runs as a subprocess. The SDK's bundled cli.js reads ANTHROPIC_BASE_URL and the ANTHROPIC_DEFAULT_*_MODEL aliases at call time. When ANTHROPIC_BASE_URL points at a non-Anthropic host, all Messages API requests go there instead.
With the Murph runtime, Phantom emits native Murph route variables such as MURPH_PROVIDER, MURPH_PROVIDER_CONFIG, MURPH_MODEL, and provider-specific credential keys. Ambient route keys are shadowed so stale shell credentials do not accidentally select a different provider.
The provider: block is translated by helpers in src/config/providers.ts. The resulting map is merged into main agent, chat, judge, and reflection queries, so changing providers flips every Agent SDK path in lockstep.
The bundled cli.js has hardcoded model-name arrays for capability detection (thinking tokens, effort levels, compaction, etc.). Passing a literal glm-5.1 as the model can break those checks. The recommended pattern is:
- Set
model: claude-sonnet-4-6(or Opus) inphantom.yamlsocli.jstreats the call as a known Claude model - Set
model_mappings.sonnet: glm-5.1in the provider block so the wire call goes to GLM-5.1
This is the same pattern Z.AI's own documentation recommends.
Phantom responds but the logs show Claude-shaped costs.
The bundled cli.js calculates total_cost_usd from its local Claude pricing table based on the model name string. Cost reporting is not provider-aware, so the logged cost will look like Claude pricing even when the request went to Z.AI or another provider. The actual charge on your provider's bill will differ.
Auto mode judges fall back to heuristic mode.
resolveJudgeMode in auto mode enables LLM judges when any of these are true: (a) a non-anthropic provider is configured, (b) provider.base_url is set, (c) ANTHROPIC_API_KEY is present, or (d) ~/.claude/.credentials.json exists. If none hold, judges run in heuristic mode. Set judges.enabled: always in config/evolution.yaml to force LLM judges on.
Third-party proxy rejects a beta header.
disable_betas: true is already the default for every non-anthropic preset, which sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1. If you still see beta header errors, explicitly set disable_betas: true on your provider block to make sure it overrides any custom disable_betas: false.
Tool calls fail with small local models. Phantom's tool system assumes strong function-calling capability. Models like Qwen3-Coder and GLM-5.1 handle it well; smaller models often fail on complex multi-step tool chains. Test with a strong model first, then drop down.
Subprocess fails with a missing-credential error.
Phantom does not validate credentials at load time. The subprocess only sees the provider env vars when a query runs. If api_key_env names a variable that is not set in the process environment, the subprocess will fail at call time with the provider's own error message.