Dagon Inference / Use Dagon from your boat box
Use Dagon from your boat box
Published · Last updated
Store a Dagon Inference key as a secret variable in a boat environment, set DAGON_BASE_URL=https://inference.dagon.so/v1 next to it, and give the harnesses in the sandbox a custom OpenAI-compatible provider: one setup script does it for pi, Prime Agent, OpenCode, Mistral Vibe, Claude Code and Codex, and Kimi Code takes four variables. boat bills the sandbox's time; Dagon bills the tokens, from your prepaid USDC balance.
How do I create a Dagon key for a boat sandbox?
Create a key in the console or with curl -X POST https://inference.dagon.so/v1/keys (no email or password), and fund it with USDC from $5. Every model and price is on the pricing page.
Give the sandboxes a child key, not the key that holds your balance: a child has its own balance and an optional expiry, so a sandbox can spend at most what you put on it.
# On your own computer, with the root key (never put the root key in a sandbox)
curl https://inference.dagon.so/v1/keys/children \
-H "Authorization: Bearer $DAGON_ROOT_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "boat: dagon environment", "amount_usd": 5, "expires_at": "2027-01-01T00:00:00Z"}'
# The reply's "key" is shown once: put it in DAGON_KEY for the next stepHow do I store the key as a boat secret?
As an environment variable of a boat environment. boat puts the environment's variables into every shell of every new sandbox that uses it (Secrets), so the key is never in a repository, a prompt or a saved snapshot. boat's docs say not to put secrets in prompts, URLs, command-line arguments that can reach logs, Docker build args or committed files, and to keep them in an environment rather than a saved snapshot, which keeps every file on its disk (Which one to use). boat env set-var reads the value from stdin when you leave it off the command line (CLI reference).
# One environment for the sandboxes that use Dagon (or use your default one, "base") boat env new dagon # The key, read from stdin so it stays out of your shell history and process list printf %s "$DAGON_KEY" | boat env set-var dagon DAGON_API_KEY boat env set-var dagon DAGON_BASE_URL=https://inference.dagon.so/v1 # Check: Secrets on, safe for third parties off, both variables listed boat env info dagon
- An environment marked safe for third parties, a sandbox made with
--no-env, or an environment with its Secrets toggle off passes no variables at all (Access and credentials). - A running sandbox keeps the environment version it started with. After a change, run
boat env upgrade dagon(Versions and upgrades). - For one sandbox only,
boat new --env DAGON_API_KEY=…sets a variable on that sandbox, but puts the key on your command line.
What does every sandbox get?
Two variables, the same in every sandbox of the environment:
| Variable | Value |
|---|---|
DAGON_API_KEY | Your gk_ key (secret) |
DAGON_BASE_URL | https://inference.dagon.so/v1: the OpenAI-compatible base URL. Anthropic SDKs use https://inference.dagon.so, without /v1. |
boat has no file format to import an environment from: POST /environments takes only a name, and each variable is one more call (Manage environments). The commands above are the template. For the harness configs, boat runs a script you pass at create with --setup-file, once, as the sandbox's user (Setup scripts):
curl -fsSLO https://inference.dagon.so/boat/dagon-setup.sh boat new --environment dagon --setup-file dagon-setup.sh boat info <id> # wait for setupStatus: done
Which harnesses can use Dagon?
Every boat sandbox comes with seven coding-agent harnesses (Harnesses and models). We ran each one against a capture of its first request and checked that request with the gateway's own validation, on the versions named here (2026-11-11); Claude Code and Codex also completed a turn with a tool call through the gateway, with a stand-in model behind it. boat installs the latest release of each, so later versions can differ.
| Harness | Works with Dagon | How |
|---|---|---|
| pi (1.2.0) | Yes | Custom provider in ~/.pi/agent/models.json. Its Responses and Messages modes were accepted too. |
| Prime Agent (0.9.4) | Yes, on Chat Completions | Same file format in ~/.prime/agent/models.json. Its Messages mode sends anthropic-beta: interleaved-thinking-2025-05-14, which the gateway now accepts; we have not re-run that mode. |
| OpenCode (1.19.5) | Yes | Custom provider in ~/.config/opencode/opencode.json. |
| Kimi Code (2.5.3) | Yes | The KIMI_MODEL_* variables, no file. |
| Mistral Vibe (3.1.2) | Yes, on Chat Completions | Provider in ~/.vibe/config.toml. Its Anthropic style sends anthropic-beta values and was refused in our test; we have not re-run it since the gateway began accepting interleaved thinking. |
| Claude Code (2.1.310) | Yes, on Messages | ~/.claude/settings.json: the base URL, a key helper that prints $DAGON_API_KEY and the model. The gateway accepts its anthropic-beta values and context_management and forwards neither. |
| Codex (0.164.0) | Yes, on Responses | A profile, ~/.codex/dagon.config.toml, run with codex --profile dagon. Codex speaks only Responses to a custom provider; the gateway drops its client_metadata, its namespace tool groups (multi-agent and MCP tools are not offered) and a listed web_search tool. |
The sandboxes also have Hermes Agent, OpenClaw and the Cursor CLI agent (Pre-installed tools); we have not tested them. We have not verified boat prompt with a custom provider: run the harness yourself with boat ssh or boat exec, which boat documents for every built-in harness (Customize the harness).
Which Codex capabilities are unavailable?
Hosted web search and namespace tools (including multi-agent and MCP groups) are unavailable. Optional tools are omitted and reported in the x-dagon-dropped-tools response header, for example web_search,namespace:multi_agent_v1. If nothing remains, the gateway removes both the tools and an optional tool_choice. Forced choices naming omitted tools, namespace choices, allowed_tools, and required/any with no callable tools are refused before upstream work. The setup profile sets web_search = "disabled" and [features] multi_agent = false (Codex configuration reference); existing MCP configuration is untouched.
How do I set up the harnesses?
Run the setup script once per sandbox, with --setup-file as above or with bash dagon-setup.sh in a running sandbox. It reads the model list from GET /v1/models, adds a provider named dagon to the pi, Prime Agent, OpenCode and Mistral Vibe configs with every model, points Claude Code at the gateway and adds a dagon Codex profile, keeps whatever else the files hold (boat registers its own computer MCP server in some of them), and writes no key: each config names the DAGON_API_KEY variable. Running it again changes nothing. It is served at https://inference.dagon.so/boat/dagon-setup.sh:
#!/usr/bin/env bash
# Dagon Inference in a boat sandbox: adds Dagon as a custom provider to pi, Prime Agent, OpenCode, Mistral Vibe, Claude
# Code and Codex, with every model GET /v1/models lists. Kimi Code needs no file: it takes the KIMI_MODEL_* variables.
#
# boat new --setup-file dagon-setup.sh # once, at create (runs as the user "user")
# bash dagon-setup.sh # or by hand in a running sandbox; running it again changes nothing
#
# The key is never written to disk: each config names the DAGON_API_KEY variable, which the boat environment supplies.
# DAGON_BASE_URL, if set, replaces the default base URL. Existing configs are kept, and a provider named "dagon" that
# is already there is left as it is.
set -euo pipefail
python3 - <<'PY'
import json, os, urllib.request
base = os.environ.get("DAGON_BASE_URL", "https://inference.dagon.so/v1").rstrip("/")
home = os.path.expanduser("~")
with urllib.request.urlopen(f"{base}/models", timeout=30) as r:
models = [m for m in json.load(r)["data"] if not m.get("deprecated")]
if not models:
raise SystemExit(f"no models at {base}/models")
def path(*parts):
p = os.path.join(home, *parts)
os.makedirs(os.path.dirname(p), exist_ok=True)
return p
def add_json(file, key, name, value):
data = {}
if os.path.exists(file):
with open(file) as f:
data = json.load(f)
section = data.setdefault(key, {})
if name in section:
return print(f"{file}: {key}.{name} already set, left as it is")
section[name] = value
with open(file, "w") as f:
json.dump(data, f, indent=2)
f.write("\n")
print(f"{file}: added {key}.{name}")
MARK = "# Dagon Inference provider (dagon-setup.sh)"
def add_toml(file, text):
old = open(file).read() if os.path.exists(file) else ""
tables = [l for l in text.splitlines() if l.startswith("[") and not l.startswith("[[")]
if MARK in old or any(t in old.splitlines() for t in tables):
return print(f"{file}: Dagon provider already set, left as it is")
with open(file, "a") as f:
f.write(("\n" if old and not old.endswith("\n") else "") + ("\n" if old else "") + MARK + "\n" + text)
print(f"{file}: added the Dagon provider")
q = json.dumps # a JSON string is a valid TOML basic string for these ids and URLs
# pi: ~/.pi/agent/models.json, the key read from $DAGON_API_KEY ("$NAME" interpolation).
pi_models = [{"id": m["id"], "name": m.get("name", m["id"]), "contextWindow": m["context_length"]} for m in models]
add_json(path(".pi", "agent", "models.json"), "providers", "dagon",
{"baseUrl": base, "api": "openai-completions", "apiKey": "$DAGON_API_KEY", "models": pi_models})
# Prime Agent: the same file format, but apiKey names the variable without "$".
add_json(path(".prime", "agent", "models.json"), "providers", "dagon",
{"baseUrl": base, "api": "openai-completions", "apiKey": "DAGON_API_KEY", "models": pi_models})
# OpenCode 1.x: a custom provider on the OpenAI-compatible AI SDK package; {env:NAME} reads the key.
add_json(path(".config", "opencode", "opencode.json"), "provider", "dagon",
{"npm": "@ai-sdk/openai-compatible", "name": "Dagon Inference",
"options": {"baseURL": base, "apiKey": "{env:DAGON_API_KEY}"},
"models": {m["id"]: {"name": m.get("name", m["id"])} for m in models}})
# Mistral Vibe: an OpenAI-style provider and one model alias "dagon/<id>" per model. Pick one with VIBE_ACTIVE_MODEL.
add_toml(path(".vibe", "config.toml"),
f'[[providers]]\nname = "dagon"\napi_base = {q(base)}\napi_key_env_var = "DAGON_API_KEY"\napi_style = "openai"\n'
+ "".join(f'\n[[models]]\nname = {q(m["id"])}\nprovider = "dagon"\nalias = {q("dagon/" + m["id"])}\n' for m in models))
def first(endpoint):
return next((m for m in models if endpoint in m.get("endpoints", [])), models[0])
# Claude Code: ~/.claude/settings.json. apiKeyHelper prints $DAGON_API_KEY, so no key is written; the Anthropic base
# URL has no /v1. "[1m]" tells Claude Code a model's window is 1M tokens; it drops the suffix before sending.
claude = path(".claude", "settings.json")
settings = json.load(open(claude)) if os.path.exists(claude) else {}
if "apiKeyHelper" in settings or "ANTHROPIC_BASE_URL" in settings.get("env", {}):
print(f"{claude}: a key helper or base URL already set, left as it is")
else:
m = first("/v1/messages")
cc_model = m["id"] + ("[1m]" if m["context_length"] >= 1_000_000 else "")
settings["apiKeyHelper"] = 'printf %s "$DAGON_API_KEY"'
settings.setdefault("env", {}).update({"ANTHROPIC_BASE_URL": base.removesuffix("/v1"),
"ANTHROPIC_MODEL": cc_model, "ANTHROPIC_DEFAULT_HAIKU_MODEL": cc_model})
with open(claude, "w") as f:
json.dump(settings, f, indent=2)
f.write("\n")
print(f"{claude}: added the Dagon base URL, key helper and model {cc_model}")
# Codex: a profile file, ~/.codex/dagon.config.toml, layered over config.toml by codex --profile dagon. It holds a
# Responses provider (the only API Codex speaks to a custom provider); env_key names the variable. Web search is off:
# the gateway does not run it. features.multi_agent=false disables the collaboration namespace (Codex config reference).
add_toml(path(".codex", "dagon.config.toml"),
f'model_provider = "dagon"\nmodel = {q(first("/v1/responses")["id"])}\nweb_search = "disabled"\n'
f'\n[features]\nmulti_agent = false\n'
f'\n[model_providers.dagon]\nname = "Dagon Inference"\nbase_url = {q(base)}\nenv_key = "DAGON_API_KEY"\nwire_api = "responses"\n')
PY
Then, in the sandbox, with GLM-5.3 Abliterated (glm-5.3-abliterated, served by Refuseless, Experiential Labs and Silk Compute, $1.10 input, $0.165 cached input and $2.20 output per 1M tokens) as the example model:
pi -p --model dagon/glm-5.3-abliterated "Summarize this repo" prime-agent -p --model dagon/glm-5.3-abliterated "Summarize this repo" opencode run -m dagon/glm-5.3-abliterated "Summarize this repo" VIBE_ACTIVE_MODEL=dagon/glm-5.3-abliterated vibe -p "Summarize this repo" KIMI_MODEL_NAME=glm-5.3-abliterated KIMI_MODEL_PROVIDER_TYPE=openai \ KIMI_MODEL_BASE_URL="$DAGON_BASE_URL" KIMI_MODEL_API_KEY="$DAGON_API_KEY" \ KIMI_MODEL_MAX_CONTEXT_SIZE=1048576 kimi -p "Summarize this repo" claude -p "Summarize this repo" # model: ANTHROPIC_MODEL in ~/.claude/settings.json codex exec --profile dagon "Summarize this repo"
What does each harness config look like?
What the script writes, for one model, if you would rather edit the files yourself.
pi, ~/.pi/agent/models.json. "$DAGON_API_KEY" reads the variable (pi models configuration):
{
"providers": {
"dagon": {
"baseUrl": "https://inference.dagon.so/v1",
"api": "openai-completions",
"apiKey": "$DAGON_API_KEY",
"models": [{ "id": "glm-5.3-abliterated", "contextWindow": 1048576 }]
}
}
}Prime Agent, ~/.prime/agent/models.json: the same, except "apiKey": "DAGON_API_KEY", without the $. Prime Agent sent the $DAGON_API_KEY form as a literal key in our test.
OpenCode, ~/.config/opencode/opencode.json, merged into what is there. {env:NAME} reads a variable. This is the schema of OpenCode 1.x, the current release; opencode.ai's docs already describe a newer providers schema, so check opencode --version first.
{
"provider": {
"dagon": {
"npm": "@ai-sdk/openai-compatible",
"name": "Dagon Inference",
"options": { "baseURL": "https://inference.dagon.so/v1", "apiKey": "{env:DAGON_API_KEY}" },
"models": { "glm-5.3-abliterated": { "name": "GLM-5.3 Abliterated" } }
}
}
}Mistral Vibe, appended to ~/.vibe/config.toml. Pick the model with the VIBE_ACTIVE_MODEL variable (Vibe configuration): --model alone still asks for the active model's key.
[[providers]] name = "dagon" api_base = "https://inference.dagon.so/v1" api_key_env_var = "DAGON_API_KEY" api_style = "openai" [[models]] name = "glm-5.3-abliterated" provider = "dagon" alias = "dagon/glm-5.3-abliterated"
Kimi Code builds a model from KIMI_MODEL_NAME, KIMI_MODEL_PROVIDER_TYPE, KIMI_MODEL_BASE_URL and KIMI_MODEL_API_KEY, without touching its config file (Kimi Code environment variables), as in the commands above. A model alias in config.toml works only when default_model is set.
Claude Code, ~/.claude/settings.json, left alone if it already sets a key helper or base URL. The base URL has no /v1; apiKeyHelper runs through the sandbox's shell and prints the variable, so no key is written (Connect Claude Code to an LLM gateway). [1m] after a model id tells Claude Code its window is 1M tokens; it drops the suffix before sending. Structured output (output_config.format) is refused, and Claude Code retries without it.
{
"apiKeyHelper": "printf %s \"$DAGON_API_KEY\"",
"env": {
"ANTHROPIC_BASE_URL": "https://inference.dagon.so",
"ANTHROPIC_MODEL": "glm-5.3-abliterated",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "glm-5.3-abliterated"
}
}Codex, ~/.codex/dagon.config.toml, a profile layered over config.toml by --profile dagon (Codex profiles). Web search is off: the gateway does not run it.
model_provider = "dagon" model = "glm-5.3-abliterated" web_search = "disabled" [features] multi_agent = false [model_providers.dagon] name = "Dagon Inference" base_url = "https://inference.dagon.so/v1" env_key = "DAGON_API_KEY" wire_api = "responses"
How do I test it?
# In the sandbox (boat ssh <id>), or wrap each line in boat exec <id> '...'
echo ${DAGON_API_KEY:+key set} ${DAGON_BASE_URL:+base URL set}
# The key, its balance and expiry. Free: nothing is generated
curl -sS "$DAGON_BASE_URL/key" -H "Authorization: Bearer $DAGON_API_KEY"
# One short paid request
curl -sS "$DAGON_BASE_URL/chat/completions" \
-H "Authorization: Bearer $DAGON_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "glm-5.3-abliterated", "max_tokens": 50, "messages": [{"role": "user", "content": "Say hi"}]}'GET /v1/key answers 401 for a wrong, revoked or expired key, and 402 insufficient_credits on a request means the key is short of the request's maximum cost: fund it or lower max_tokens.
Who bills what?
- boat bills the sandbox: its time, per second, at the hourly rate of its size, from your plan's included time and then your boat wallet (boat pricing). Model tokens are not on the boat bill.
- Dagon Inference bills the tokens: each request is charged to the key's prepaid USDC balance at the serving provider's price plus our 10% markup, with a signed receipt. There is no subscription.
- boat's AI credentials proxy, if you turn it on, is a separate boat charge per day and per request (AI credentials). Nothing here needs it.
How do I keep the key safe?
- One child key per environment or per sandbox, with a balance and an
expires_at. A runaway agent stops at402 insufficient_credits, and a leaked key costs at most what is on it. A root holds at most 100 live children (Child keys). - The root key stays on your computer. Anyone with a shell in a sandbox can read its variables, so a teammate's or customer's sandbox should run on an environment marked safe for third parties, which gets none of your variables.
- Rotate by minting a new child, swapping the variable and deleting the old child:
# 1. Mint a new child key (above) and store it in place of the old one printf %s "$NEW_DAGON_KEY" | boat env set-var dagon DAGON_API_KEY # 2. Move running sandboxes to the new environment version boat env upgrade dagon # 3. Delete the old child; its unspent balance moves back to the root key curl -X DELETE https://inference.dagon.so/v1/keys/children/$OLD_CHILD_ID -H "Authorization: Bearer $DAGON_ROOT_KEY"
- boat can also keep the real key out of the sandbox: a custom API named
DAGON_API_KEYwith the AI credentials proxy on gives the sandbox a placeholder key and a proxy URL inDAGON_API_KEY_BASE_URL. We have not tested Dagon or the harnesses behind that proxy.
Common questions
Can I run Claude Code on Dagon Inference in a boat sandbox?
Yes, on the Anthropic Messages endpoint: the setup script writes ~/.claude/settings.json with the base URL https://inference.dagon.so, a key helper that prints $DAGON_API_KEY and a model. The gateway accepts the anthropic-beta values Claude Code sends and forwards none of them. The models are open-weight models, not Claude.
Can I use Codex with Dagon Inference?
Yes, on Responses with codex --profile dagon. The setup profile disables web search and multi-agent. Optional web-search and namespace tools are omitted and listed in x-dagon-dropped-tools; forced unavailable choices are refused. The model cannot call omitted multi-agent or MCP tools. Codex's client_metadata is never forwarded.
Does boat charge for the tokens?
No. boat bills the sandbox's time. Dagon Inference charges the tokens to the key's prepaid USDC balance. boat's optional AI credentials proxy is a separate boat charge; this setup does not use it.
Where does the key live?
In a boat environment variable, DAGON_API_KEY, which boat puts into the shells of each new sandbox of that environment. The harness configs name the variable and never hold the key.