Configure model connections and profiles
Configure the connections and profiles sessions will use
What you will do
Configure workspace model connections and profiles, then verify a real model call with one session turn. Model credentials are separate from user API keys.
Before you begin
You need an owner login, a model key or private model endpoint, and a repository sessions will access. Check cost limits and the configuration contract.
Steps
1. Console connections and profiles
A session needs a model key and a profile using that key. Create both in the web console, signed in as owner:
- Open Settings > Agent connections > Model connections, then Add connection. Choose kind
anthropic, keep the default endpoint (https://api.anthropic.com), paste the Anthropic key, and save. The key is sealed in the database; only its last four characters appear on screen. - Open Profiles > Create profile, choose the connection just created, set the model, tools, and execution method, then save. Select this profile when creating an agent.
Choose repositories in the agent's GitHub tab, within the workspace's allowed scope, rather than during profile creation.
Starting a session with this profile before providing a key produces an error indicating no key is available for the model connection. The error points to the screen in step 1. Use Replace value on that same screen to rotate the key.
2. Environment-variable alternative
To keep the key in the server env file instead of the database, fill ANTHROPIC_API_KEY in /etc/werft/test-ops.env and reference the environment-variable name in the connection and profile in the single-server step 2 settings document. The document contains names rather than values, so it has no secrets. Fill connections and profiles as below, add "profiles": ["claude-coding"] to the repository entry, then regenerate the settings digest and release input in single-server step 3. For an existing installation, apply through Upgrade Compose.
{
"connections": {
"anthropic": {
"kind": "anthropic",
"endpoint": "https://api.anthropic.com",
"auth": { "kind": "api_key", "value_env": "ANTHROPIC_API_KEY" }
}
},
"profiles": {
"claude-coding": {
"runtime_kind": "claude_agent_sdk",
"runtime_version": "0.3.319",
"model": "claude-sonnet-5",
"tools": ["Read", "Edit", "Write", "Glob", "Grep", "Bash"],
"permission_mode": "default",
"provider": {
"kind": "anthropic",
"endpoint": "https://api.anthropic.com",
"auth": { "kind": "api_key", "value_env": "ANTHROPIC_API_KEY" }
},
"project_settings": { "claude_md": true }
}
}
}You can later move this connection to console-managed credentials. On its Model connections row, choose Enter key on screen and paste the key again. The same connection and its profiles then use the stored key. The server does not read and copy the env value; a person must reenter it. After conversion, importing a settings document that still includes the same connection preserves the database row.
On Kubernetes, if you do not use the model key in the Secret, remove its name from secrets.catalogEnv. Retain required names such as the fixed bootstrap token. The list replaces the entire value and must match Secret keys.
3. Import and export settings
API_CONTAINER='<api-container-id-from-this-installation>'
docker exec "$API_CONTAINER" bun run apps/control-host/src/api/settings.ts export > settings.json
docker exec -i "$API_CONTAINER" bun run apps/control-host/src/api/settings.ts import --document - --dry-run < settings.jsonIdentify API_CONTAINER from this installation's status and project label. On Kubernetes, the package scripts below require a checkout matching the installed version and host Bun/kubectl. Substitute bun run settings:k8s export and bun run settings:k8s import --document - --dry-run. Review additions and errors from dry-run, then remove --dry-run from the same command to import. This tool does not unconditionally overwrite existing database rows with file contents. Reference credential environment-variable names instead of values in the document.
4. Private models without per-token billing
Unmetered profiles (billing: none)
Self-hosted models (Ollama, private-network LiteLLM, etc.) have no per-token charge. If absent from the price table and left unchanged, they use the highest fallback rates and can hit SESSION_COST_LIMIT_USD or SDK maxBudgetUsd, stopping turns. Declare these profiles with provider.billing: none.
provider:
kind: litellm
endpoint: http://litellm:4000
billing: none
auth:
kind: bearer
value_env: LITELLM_MASTER_KEY- Omitting billing means
per_token. Omitted billing and explicitper_tokenproduce the same fingerprint and both use table/fallback prices. - Provider calls from a
noneprofile session record cost 0 and ledgerpriced_by=unmetered, regardless of the model name in the response. Tokens are still recorded. Session cost does not increase, so none of the four cost-limit checks (egress authorization,nextInput, message acceptance, engine) stop it. - Claim
remaining_budget_usdisnull, and workers do not passmaxBudgetUsdto the SDK. The SDK otherwise estimates prices for unknown models and can stop turns on that budget. With no budget to enforce, workers do not drain when SDK cost accounting restarts through/clearor similar actions. GET /v1/sessions/{id}/usagereturnscost.kind: "unmetered",amount_usdof"0.000000", andcost_limit_usdofnullbecause no cost limit applies. Dollar limits also disappear from limit attention (attention.reason).budget_exceededbecomestrueonly at the token limit.- Billing mode is fixed at session creation in
sessions.unmetered. Pricing, claim budgets, and usage displays read that value rather than the reporting API's catalog. During rolling updates, a replica unaware of the profile therefore does not add cost. Changingbillingchanges the fingerprint: add a new id. Editing the same id causesCATALOG_MISMATCHfor existing sessions. - Create
nonesessions only after updating every API replica and every scheduler'sWORKER_IMAGEto this version. Old APIs price even unmetered reports using the price table; old workers rejectremaining_budget_usd: nullas a contract violation and cannot start the engine. For the same reason, do not roll API/workers back to older versions while unmetered sessions remain. - Token limits (
SESSION_TOKEN_LIMIT) and time/concurrency limits (MAX_TURN_SECONDS,EXECUTION_SLOT_LIMIT,QUEUED_INPUT_LIMIT_PER_SESSION) still apply. Tokens from unmetered rows still contribute tosessions.provider_tokens. - The API accepts
noneonly for a private endpoint host (10/8, 172.16/12, 192.168/16, 100.64/10, 127/8, 169.254/16,::1, fc00::/7, fe80::/10) or a name absent from public DNS (dotless Compose service names,localhost,.internal,.local,.localhost,.lan,.home.arpa). Otherwise startup fails. Add the host to the egress proxy'sEGRESS_CREDENTIAL_PRIVATE_ALLOWLIST. - A private address is only a prerequisite. Private-network LiteLLM can proxy a paid upstream. Operators are responsible for declaring
nonecorrectly: an incorrect declaration permits paid calls without cost limits. SettingSESSION_TOKEN_LIMITstill bounds per-session tokens. Compare Datadogwerft.provider.unmetered_tokens_1h(unmetered row tokens over the last 1 hour) with provider-console usage.
Satisfy the upgrade gates before enabling this.
Verify
Create a session on an agent using the profile and confirm a turn completes. ready 200 does not check model keys or successful calls. After moving an environment connection to console management, verify new calls and key rotation.
Troubleshooting
For missing model keys, follow the connection screen path. A private endpoint alone does not make billing:none safe. Declaring a model proxying paid upstreams as none permits calls without cost limits.
Roll back
Do not change a live profile id in a way that changes its fingerprint. Create a new connection/profile version and move to new sessions. When discarding a console-stored key, also revoke its provider-side permissions.