Models, providers and reasoning
Connect a provider and choose a model and reasoning level.
Manual for v0.9.5. Stable release: v0.9.5.
Documentation · Configuration · Media
export ANTHROPIC_API_KEY='...'
octet --safe-mode --model claude-sonnet-4-6/model [id] picks a model, and /status shows its route and capabilities.
octet uses live model discovery where the provider offers it. --offline skips
optional discovery, not inference traffic. Which models you can use depends
on your account and endpoint, and octet's tests don't cover every live provider.
New here? Start with first-run setup, or set your provider's variable from cloud setup.
First-run setup#
When an interactive launch has no available models and no explicit model selection, the setup menu offers, in order:
- Add an API key. Choose a supported built-in provider, paste into a masked input and review before saving. This is a dedicated secret input, not the conversation composer, and no environment variable is required.
- Sign in with ChatGPT / other supported OAuth subscriptions. Choose ChatGPT (OpenAI Codex) or GitHub Copilot. Codex offers browser sign-in (PKCE) or a device code, and Copilot uses device authorization. No other subscription login is implied.
- Local/self-hosted models. Choose LM Studio or an explicit OpenAI-compatible endpoint, then discover or select a model and review the custom registry change.
- Continue without a provider. Leave setup without saving provider data.
Existing available models and explicit model selections don't trigger this menu.
In the TUI, /setup opens the same wizard on demand, even with a configured
provider. A saved provider refreshes the model catalog but keeps your current
session, active model and default model, so use /model to switch. If you
replace the active route's stored key, reselect that model to rebuild its
credential. When octet has something saved, /setup first asks whether to add or remove.
Remove a provider or sign-in lists only what octet stored (ChatGPT/Codex,
GitHub Copilot and other subscription sign-ins, built-in API keys, custom
providers), asks you to confirm (Cancel is the default), deletes it through the
same private-file path it was saved with, and refreshes the model list. It
refuses to remove the provider behind your current model (switch with /model
first) and never changes an environment variable, so a provider can still work
through one afterwards. A credential environment variable still takes precedence, and the
wizard warns before saving a fallback key. Print and RPC don't open the wizard.
Subscription sign-in needs an online launch, and --offline isn't a
local-inference guarantee. After a credential is saved the catalog refreshes and
model selection uses the ordinary picker. Saving isn't a successful inference
check, and a discovery failure can leave the saved credential in place for a
retry.
Built-in API keys are saved in ~/.octet/credentials/api-keys/<provider>.json,
with owner-private directories (0700) and files (0600), atomic publication
and explicit consent before replacement. Environment credentials take precedence
over saved keys. Native provider routes stay native: saving a key doesn't turn
Anthropic, Gemini or OpenAI into a custom OpenAI-compatible endpoint.
A saved API key has to stay recoverable to authenticate provider requests, so owner-private storage is not hashing or encryption at rest. Keep the store out of repositories, support reports and shared backups. Keys aren't copied to prompts, config, model metadata or setup receipts. AWS/Bedrock, Azure, Vertex and Cloudflare need extra account, deployment, region or endpoint configuration and aren't offered as one-field API-key setup, so use their documented configuration below.
See Getting started and the CLI alternatives. This setup flow is part of the current 0.9.5 source candidate; availability in older published versions differs.
Cloud setup#
Alternatively, set the credentials for your provider, then run
octet --model ID. Don't put credentials in prompts or repository config.
| Provider | Set | Example model ID |
|---|---|---|
| Anthropic | ANTHROPIC_API_KEY |
claude-sonnet-4-6 |
| OpenAI | OPENAI_API_KEY |
gpt-5.4 or gpt-6-astra |
| OpenRouter | OPENROUTER_API_KEY |
openrouter/anthropic/claude-sonnet-4.6 |
| Mistral | MISTRAL_API_KEY (native Mistral Chat Completions request and reasoning conventions) |
mistral/mistral-small-latest |
| Cloudflare Workers AI | CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_KEY |
cloudflare-workers-ai/@cf/openai/gpt-oss-120b |
| Cloudflare AI Gateway | CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_GATEWAY_ID (not secret), CLOUDFLARE_API_KEY. Gateway paths for Claude, OpenAI and Workers AI. |
cloudflare-ai-gateway/claude-sonnet-4-5 |
| Amazon Bedrock | AWS_REGION (such as us-east-1) or OCTET_BEDROCK_REGION, plus AWS credentials (SigV4) |
bedrock/anthropic.claude-3-7-sonnet-20250219-v1:0 |
| Azure OpenAI | AZURE_OPENAI_API_KEY, AZURE_OPENAI_DEPLOYMENT, and AZURE_OPENAI_RESOURCE or AZURE_OPENAI_ENDPOINT |
azure-openai/my-gpt-deployment |
| Gemini Developer API | GEMINI_API_KEY (native Google generateContent) |
gemini/gemini-2.5-flash |
| Vertex AI | ADC, GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_LOCATION |
vertex/gemini-2.5-flash |
| Baseten | BASETEN_API_KEY (OpenAI Chat) |
baseten/<model-id> |
| Qwen Token Plan | QWEN_TOKEN_PLAN_API_KEY (OpenAI Chat) |
qwen-token-plan/<model-id> |
| Qwen Token Plan CN | QWEN_TOKEN_PLAN_CN_API_KEY (OpenAI Chat) |
qwen-token-plan-cn/<model-id> |
| Z.AI Coding CN | ZAI_CODING_CN_API_KEY (OpenAI Chat) |
zai-coding-cn/<model-id> |
The built-in OpenRouter route sends fixed app-attribution defaults on model discovery, Chat Completions and Batch API requests:
| Header | Default value |
|---|---|
HTTP-Referer |
https://octet.skaft.org |
X-OpenRouter-Title |
octet coding agent |
X-OpenRouter-Categories |
cli-agent |
These headers identify the app for OpenRouter's public rankings and app analytics. They contain no user, project or session identifiers and add no separate telemetry request. The same identity is used whether the credential comes from the environment or saved setup. Other built-in providers and custom OpenAI-compatible endpoints don't inherit these defaults. Explicit model and request header overrides keep their normal precedence. Attribution applies to new requests, not past usage.
Other built-in presets: DeepSeek, Groq, Cerebras, xAI, Together AI, Fireworks AI, NVIDIA, Hugging Face, Moonshot AI, Xiaomi, MiniMax and OpenCode Zen. A preset name doesn't promise every API of that provider. The provider declarations list what each route covers.
Bedrock takes AWS credentials in this order: an AWS_ACCESS_KEY_ID /
AWS_SECRET_ACCESS_KEY pair (with an optional session token), a web-identity
role, the selected AWS_PROFILE, then ECS or EC2 instance metadata. A Bedrock
API key in AWS_BEARER_TOKEN_BEDROCK is sent as Authorization: Bearer … and
takes precedence over SigV4, so an API-key user never needs or pays for the AWS
credential chain. Model availability depends on your account and region. Quote
IDs that contain shell characters:
octet --model 'bedrock/anthropic.claude-3-7-sonnet-20250219-v1:0'.
Bedrock web identity and instance metadata
A web-identity role uses AWS_ROLE_ARN and AWS_WEB_IDENTITY_TOKEN_FILE, with
an optional AWS_ROLE_SESSION_NAME. It does one bounded STS
AssumeRoleWithWebIdentity exchange (3 s and 64 KiB) against
sts.<region>.amazonaws.com or the AWS_ENDPOINT_URL_STS override. A
half-configured web identity (only one of the two required variables) fails
closed instead of silently resolving a different identity.
AWS instance and container metadata credentials are opt-in, because octet can't tell an EC2 instance with a role from an unrelated laptop that would only time out. Probing them on every start costs about a second when no metadata service is reachable. The metadata sources are consulted only when the local environment indicates them, and the first matching indication wins:
AWS_EC2_METADATA_DISABLED=false: the standard AWS switch, set to an explicitfalse, which allows the metadata sources.OCTET_AWS_METADATA_CREDENTIALS=1: octet's explicit opt-in, for an instance whose configuration carries no other marker. (0,false,nooroffkeep it disabled. An unrecognized value stays closed: unknown state never probes.)AWS_CONTAINER_CREDENTIALS_RELATIVE_URIorAWS_CONTAINER_CREDENTIALS_FULL_URI, set by ECS and EKS-style platforms.AWS_EC2_METADATA_SERVICE_ENDPOINTor_MODE(the standard AWS names), or octet's earlierAWS_METADATA_SERVICE_ENDPOINTor_MODEalias: the host pinned the IMDS endpoint.- The effective
AWS_PROFILEdeclarescredential_source = Ec2InstanceMetadataorEcsContainer, or pins IMDS withec2_metadata_service_endpoint. - The local DMI/SMBIOS markers
(
/sys/class/dmi/id/{sys_vendor,board_vendor,product_name,bios_vendor}) nameAmazon EC2: a bare EC2 instance with an instance profile and no other marker. This is a bounded local file read, not a metadata request, so it costs a laptop nothing (the files are absent or name another hypervisor), and it keeps genuine instance-profile users working without an opt-in.
The probe target follows the same order (AWS_EC2_METADATA_SERVICE_ENDPOINT,
then AWS_METADATA_SERVICE_ENDPOINT, then the profile's
ec2_metadata_service_endpoint), and every candidate is validated fail-closed
(plain HTTP is accepted only for loopback or link-local hosts) before a request
is made.
Anything else keeps the metadata sources disabled: a profile that merely exists,
AWS_PROFILE alone, AWS_CONFIG_FILE or AWS_SHARED_CREDENTIALS_FILE
presence, or a DMI vendor that names another hypervisor. An unrecognized
AWS_EC2_METADATA_DISABLED value stays disabled rather than probing on a typo.
Static environment keys and profile keys are unaffected: they're local reads and
are always consulted first.
The cost this rule removes is measurable. With an unreachable IMDS,
octet doctor was measured at 1,025/1,027 ms, and at 22/19 ms with
AWS_EC2_METADATA_DISABLED=true, and the difference is the bounded 1 s metadata
timeout. The rule is asserted by request counts, never millisecond
thresholds (crates/octet-coding-agent/src/providers/auth.rs).
unrelated_provider_launch_makes_zero_aws_metadata_requests and
disabled_activation_opens_no_connection_to_a_live_metadata_endpoint (a live
loopback listener that drains zero connections) pin the zero-request launch.
opt_in_activation_resolves_and_signs_with_live_metadata_credentials,
an_ec2_instance_identity_still_resolves_instance_credentials and
indicated_metadata_probe_reaches_the_ec2_source pin that an indicated EC2 or
ECS-backed Bedrock run still resolves and signs.
Azure and Vertex details
Azure. Deployments use Responses. The resource can be my-resource or its
endpoint, https://my-resource.openai.azure.com/. The deployment must name your
deployment. AZURE_OPENAI_API_VERSION is optional and defaults to the bundled
preview version.
Vertex. The optional GOOGLE_APPLICATION_CREDENTIALS must name an absolute,
owner-private ADC file. Otherwise octet checks the owner-private default ADC
file. authorized_user and PKCS#8 service_account files work. Access tokens
refresh in memory, and octet neither runs gcloud nor stores credential values.
Gemini presets support tools, structured JSON output, images and inline audio
input. See audio formats and limits.
Codex subscription login#
octet --login codex
octet --model gpt-5.6Codex offers Sign in with your browser (recommended) and Use a device code
(SSH/headless), instead of a manually managed API key. Browser sign-in opens
the OpenAI authorization page, also prints its URL, and listens only on
127.0.0.1:1455 (or the registered fallback port 1457 when 1455 is busy) for
up to five minutes. The browser shows a self-contained signed-in page once the
credential is saved. --headless, SSH, an unavailable browser opener, or both
callback ports being busy uses the hosted device-code flow, and you can choose
it explicitly too.
A successful account-scoped live inventory is authoritative. octet asks your
account which models you can use, and doesn't guess Ultra, collaboration
or model availability from a name or plan. Codex inference follows Pi's ordinary
Responses contract with parallel tool calls enabled, even when inventory prefers
Responses Lite; the inventory observation is retained without selecting that
serial wire contract. If the metadata is missing or unusable, octet falls back to
the more limited behavior. If the live inventory omits a model, no corresponding
GPT-6 Codex route is injected. When
advertised, select codex/gpt-6.1-sol, codex/gpt-6-astra, codex/gpt-6-sol
or codex/gpt-6-luna. These stay namespaced separately from direct OpenAI
presets. When discovery is unreachable, 6.1 Sol leads the fallback suggestions.
Discovery version, GPT-6 account contracts and pricing
Codex discovery sends compatibility version 0.159.2. GPT-6 Sol and Luna
need at least 0.155.0: older query versions filter them out on the server,
even when the account has access. Read-only checks on 2026-09-23 confirmed that
changing only this query version from 0.153.2/0.154.0 to 0.156.1 returned
gpt-6-sol and gpt-6-luna with the same OAuth credential. Cache
version 8 invalidates the older filtered inventories, and the next online launch
refreshes them without another login. Offline launches never perform this
refresh.
The 2026-09-23 OAuth contracts include text and image input, a 272K working
window, medium reasoning by default, and low, medium, high, xhigh and max
choices. Sol also advertises Ultra, and both advertise Responses Lite and V2
delegation. All three GPT-6 routes positively advertise
supports_reasoning_effort_updates. That independent capability is kept only
with fresh online account metadata, and offline reduction disables it, just like
Lite and V2. These are account-scoped inventory observations, not successful
inference checks or claims about other accounts. Public API none support isn't
imported into the OAuth choices. Output budgeting and separately sourced prices
are unchanged, because the inventory check didn't establish those values. New
slugs need account inventory, not a static alias.
The Codex 6.1 Sol bundled catalog advertises a 272K default and 872K maximum context, low reasoning by default with low through Ultra, Lite/V2 and priority 1. Offline fallback removes dynamic Ultra/V2. That catalog lists a 0.153.0 minimum, but the live backend withheld 6.1 Sol from the 0.156.1 query version while Codex 0.159.2 listed it for the same account. Discovery therefore sends 0.159.2, and cache schema 10 refreshes older inventories. Its subscription price stays unknown.
Ultra/V2 reasoning with subagents
For models that advertise Ultra/V2, first review and enable the subagents source, inside an OS isolation boundary you trust:
octet --extension-dir ./extensions \
--enable-extension octet-subagents --trust-extension octet-subagents \
--model gpt-5.6-sol --reasoning ultraThis needs a live, owner-bound child-session service. Interactive startup may attach subagents after the first frame; Ultra becomes selectable once that process has negotiated and bound its service. An explicit Ultra startup choice initializes the host runtime before the deferred process attaches.
To rebuild or replace an installed bundle, run
./scripts/reinstall-octet-subagents.sh. cargo run doesn't update
~/.octet/extensions. See the subagents
package and its API 0.4 exact-version
boundary. Catalog install works once the matching release is published:
Optional packages.
Subscription OAuth logins#
Beyond Codex, octet signs in to paid plans directly rather than asking for a
long-lived API key. Each provider gets its own owner-private credential file
under ~/.octet/credentials/, and its models appear in the catalog only while
that file says you are signed in.
| Command | Plan | Flow |
|---|---|---|
octet --login grok |
SuperGrok or X Premium | xAI device code |
octet --login kimi |
Kimi Code | Kimi device code |
octet --login meta |
Meta Muse | Meta device code, then an API-key mint |
octet --login openrouter |
OpenRouter account | PKCE in a browser, pasted redirect |
These are separate providers from the same vendors' API-key presets, so signing in with a plan never replaces a key you already configured:
| Plan | API key |
|---|---|
xai-subscription/<model> |
xai/<model> |
kimi-coding-subscription/<model> |
kimi-coding/<model> |
meta-subscription/<model> |
meta/<model> |
openrouter-oauth/<model> |
openrouter/<model> |
--headless prints the verification URL and code without opening a browser, so
the device flows work over SSH. --logout <provider> removes only that
provider's credential and never contacts the provider; for the plans, revoke
access from the vendor's own account page.
Meta is worth calling out because its grant is two steps. The device flow yields
an identity token that the inference surface will not accept, so octet
immediately exchanges it for a short-lived Model API key. The identity token is
what makes the exchange repeatable; when Meta rejects it, the session is over and
octet asks you to sign in again rather than retrying. OpenRouter is the opposite:
its login mints a durable API key with no refresh token, so octet never renews it
and --logout only deletes the local copy. Its authorization URL is printed even
when browser launch fails or an opener fails to settle promptly. Opener children
are bounded and reaped; waiting for a pasted redirect is cancellable without
leaving a background stdin reader behind.
Credentials are refreshed automatically. A refresh is serialized both within one octet process and across octet processes, and the credential file is re-read after the cross-process lock is taken, so two concurrent launches cannot spend the same single-use refresh token and leave you signed out. Once refresh work starts, it owns the exchange, both locks and persistence independently of the request waiting for it: cancelling that wait does not abandon an issued rotated token. This does not promise recovery from process termination, runtime shutdown or an indeterminate provider response. A provider that rotates its refresh token but does not return a new one is treated as a failure and the existing credential is left untouched, because that provider has already revoked it.
Not yet available#
- Anthropic (Claude Pro/Max). The login is a PKCE grant rather than a device
code, which octet does not yet support end to end. It also needs a request-path
change: the Anthropic Messages codec decides whether to send the
claude-code-*andoauth-*beta headers from the credential that is bound to the route, and it currently recognizes only an environment bearer token. A privately resolved OAuth credential therefore would not receive the betas a subscription token needs. Fixing that means teaching the codec about a dynamically resolved Anthropic route, which is a change tooctet-aiand is tracked separately rather than approximated here.ANTHROPIC_AUTH_TOKENandANTHROPIC_OAUTH_TOKENcontinue to work today. - Radius. Its login is defined against a Pi-hosted gateway that also serves Pi's own wire protocol. octet has no such codec, and adding one is a separate change.
GitHub Copilot#
octet --login copilot --headless
octet --logout copilotgithub-copilot is an alias. Login uses GitHub.com's device flow, and
--headless prints the verification URL and code without opening a browser.
Only GitHub OAuth state is saved, in the owner-private
~/.octet/credentials/copilot.json. Inference tokens stay in memory, and no
editor, Codex or environment credentials are imported.
Online startup registers authenticated, eligible models as github-copilot/<id>
in the ordinary picker and the shared native-host catalog. Missing credentials,
failed discovery and unsupported models contribute no Copilot entries.
--offline skips even Copilot-store access and advertises no cached inventory.
Only explicit Chat and Responses routes are supported. Reasoning-flagged models,
Anthropic-only routes, vision and structured output aren't advertised. Custom
Enterprise authorities and environment endpoint overrides aren't supported.
Later credential resolution rejects local logout or replacement and rejected
inference origins, but logout doesn't remotely revoke already-running requests.
First-run setup offers GitHub Copilot device sign-in. The TUI /login and
/logout slash commands aren't wired to Copilot yet, so use the CLI flags for
later account changes and restart existing catalog owners afterward. NDJSON
gains no login or logout commands or OAuth payload fields. Rust embedders keep
the credential-safe SDK seam. This describes
source integration, not live-provider or native-client qualification.
Local and custom endpoints#
Choose Local/self-hosted models in first-run setup for LM Studio or an OpenAI-compatible endpoint. You choose one endpoint, an optional credential source and a discovered or manual model ID, then review before saving. octet never scans localhost or the network. Compatible servers include llama.cpp, vLLM, SGLang, LM Studio and compatible gateways.
For scripts, review first, then add --yes:
# Explicit LM Studio selection permits its documented default endpoint.
octet setup --preset lm-studio --manual-model local-model
# Discover only this endpoint, then commit.
octet setup --endpoint https://models.example.test/v1/ \
--api-key-env EXAMPLE_API_KEY --yes
# Manual/offline recovery makes no discovery request.
octet setup --endpoint http://127.0.0.1:8000/v1/ \
--offline --manual-model Qwen3-Coder --yesEvery flag is in the CLI reference.
Setup writes nothing until you confirm. Even a review or cancel can probe the
selected endpoint, so use --offline --manual-model ID when you want no probe.
What setup guarantees
Saving compares against the registry snapshot taken at review, and rejects a
concurrent change instead of overwriting it. The probe (GET /models) follows
no redirects. Receipts, diagnostics, sessions and caches never contain API key
or secret header values, and setup writes no telemetry. A cancel, a review-only
run, an offline failure or a concurrent change leaves the registry unchanged.
Print and RPC modes never open the guided flow: an unresolved model reports the
octet setup --yes recovery.
Custom registry#
Keep custom endpoints together in ~/.octet/credentials/custom.json, with
chmod 600. Reference keys through environment variables, never literal values.
{
"version": 1,
"providers": {
"apple-fm": {
"label": "Apple Foundation Models",
"base_url": "http://127.0.0.1:1976/v1/",
"auth": { "kind": "none" },
"auto_discover": true,
"startup_timeout_secs": 300,
"models": [{
"api_name": "system", "context_window": 8192,
"max_output_tokens": 1024, "tools": true,
"parallel_tool_calls": false, "vision": false,
"structured_output": false, "reasoning": true,
"reasoning_configurable": false
}]
},
"home-server": {
"label": "Home Server",
"base_url": "http://192.168.1.20:8000/v1/",
"auth": { "kind": "bearer_env", "var": "HOME_SERVER_API_KEY" },
"auto_discover": true
},
"local": {
"label": "Local Qwen",
"base_url": "http://127.0.0.1:8000/v1/",
"auth": { "kind": "none" }, "auto_discover": false,
"models": [{
"api_name": "Qwen/Qwen3-Coder-Next", "display_name": "Qwen3 Coder Next",
"context_window": 131072, "max_output_tokens": 16384,
"tools": true, "parallel_tool_calls": false, "vision": false,
"structured_output": false, "reasoning": true,
"reasoning_values": ["none", "default"], "reasoning_default": "default"
}]
}
}
}Each provider is discovered on its own. IDs look like
custom/<provider-id>/<model-id>, and labels show in the picker and /status.
Set auto_discover: false with an explicit models list to pin the registry as
truth when GET /v1/models isn't useful. Old single-object files are read as
custom-openai without changing existing IDs. New files should use the
versioned registry above.
For providers with auto_discover: true, limits the endpoint asserts are
authoritative, and the registry is a seed and fallback. Every other registry
field (display name, tools and vision flags, reasoning values, pricing, presets)
keeps configured-wins behavior.
How asserted limits and the registry combine
A live max_model_len (or context_window / context_length) assertion wins
over a stale registry context_window pin. max_output_tokens is the tighter
of the endpoint and registry caps, clamped to the live window, so a vLLM profile
switch in either direction is followed without editing the registry. Only an
asserted limit is authoritative. A sparse /v1/models response that publishes
ids and nothing else asserts neither limit, so a configured context_window and
max_output_tokens survive discovery unchanged instead of being replaced by
octet's discovery fallbacks, and a model with no configured counterpart keeps
the fallback. The two limits are recorded independently, so an endpoint that
reports its served context but no output cap follows the live window while the
configured output cap survives. Cached inventories carry that provenance, so the
offline and online paths resolve to the same effective limits.
Custom models count as free for cost guardrails, so local and self-hosted models can use price-dependent features such as subagents. To track spend, give per-model rates in microdollars per million tokens. Omitted rates stay zero:
{"api_name":"metered-model","pricing":{"input":75,"output":300,"cache_read":8,"cache_write_5m":19}}Apple Foundation Models notes
Apple Foundation Models gives sparse metadata. Keep system at 8192 context
tokens, reasoning: true and reasoning_configurable: false. It thinks by
default and offers only on, with no configurable reasoning_effort. Its
separate pcc model has a 32768-token window and low, medium and high effort.
When fm serve isn't running, octet skips this optional loopback
GET /v1/models without a connection warning. The limits in the example are
model metadata, not global defaults.
Model metadata#
A pinned models.dev supplement fills in what a provider's model list leaves out: display names, pricing, input types and context and output limits. It covers only models that built-in discovery actually returns, and it's looked up per provider. It never supplies tool or structured-output flags or reasoning controls, and it never replaces a value the endpoint states.
How the supplement and its live refresh work
- Endpoint assertions stay authoritative. A live inventory that asserts any
modality field (including an explicit text-only list, or a false, null or
malformed assertion) is honored as is, and the supplement isn't consulted for
modalities at all. Only a sparse inventory that says nothing about input
modalities may inherit the snapshot's documented
imageandaudioinput. Configured and custom metadata and routes keep precedence, and Codex account inventory doesn't inherit the supplement. See the catalog source and pricing review for snapshot provenance and the difference between retained rich records and the discovery projection. - It stays current without an octet release. Builds never fetch models.dev,
but an interactive session refreshes it in the background at most every six
hours and caches the result at
~/.octet/cache/models-dev/metadata.json.--offlineskips the refresh. Each live record is checked against the built-in snapshot first (a price may not become zero or fall more than tenfold, for example), and a record that fails keeps the built-in data for that model. See Live metadata. - Why modalities and limits are included. Several providers publish a sparse
model list. Direct DeepSeek is the concrete case: its
GET /modelsreturns identifiers only, so a documented vision model such asdeepseek-flash(V4.1 Flash) used to register without image input (every attachment failed closed with "Image input is unsupported") and with the generic 128K/64K placeholder instead of its documented 1M context / 384K output, silently capping the usable window. Its snapshot record publishes no price, whichmodels-dev-source.jsonmarks as an unverified pricing provider, so DeepSeek spend stays unknown rather than estimated. The snapshot is consulted per provider and model, so an entry that declares text-only input keeps that decision (deepseek/deepseek-v4-prostays text-only), and a model absent from the snapshot gains no capability. - What
deepseek-flashgets. The supplement supplies the display name DeepSeek V4.1 Flash, its documented text+image input and its documented 1M context / 384K output. These apply only because the endpoint asserts none: a model absent from the snapshot, or an endpoint that publishes its own number, keeps the generic 128K/64K fallback or the endpoint's value. Structured-output support still isn't taken from the snapshot. Its Off/low/high/max reasoning and native DeepSeek controls and replay come from the provider-scoped source contract, not models.dev, and explicit endpoint reasoning metadata can narrow or disable that contract. The endpoint's owneffortobject (default_levelplussupported_levels) is decoded when it publishes one, so an unset preference uses the declared default level; the pinned fallback states the same default. The separatedeepseek-v4family keeps its declared 1M/384K limits and Off/high/xhigh reasoning fallback. Direct DeepSeek's current peak and off-peak tariff isn't modeled, so pricing stays unknown unless explicitly configured, and hard price-dependent ceilings fail closed.
Endpoint capability self-description#
An unchanged build can use new models on already declared Chat or Responses
routes when the selected endpoint includes an octet_capabilities v1 object
in its ordinary model inventory. Built-in OpenAI-compatible, DeepSeek and
OpenRouter discovery and custom-registry startup use the same bounded decoder.
Static-only providers, native Messages, Google, Bedrock and Conversations
routes, Codex and Copilot keep their existing contracts. This creates no new
discovery requests and doesn't bypass a declaration's model filter. Guided
octet setup doesn't consume this object yet.
An example entry in GET /models (protocol uses the canonical Rust API
spelling):
{
"id": "future-model",
"octet_capabilities": {
"version": 1,
"protocol": "open_ai_chat",
"context_window": 131072,
"max_output_tokens": 16384,
"input_modalities": ["text", "image"],
"output_modalities": ["text"],
"tools": true,
"parallel_tool_calls": true,
"structured_output": true,
"reasoning": {"values": ["none", "low", "high"], "default": "low"}
}
}The rules for this object
open_ai_responsesis the other supported protocol, and it must match the existing host-selected route. Version, protocol and positive token limits are required, and output can't exceed context. Omitted capability flags are false, omitted modalities are text-only, and omitted or null reasoning means no reasoning control. Reasoning is an exact effort list, not a guessed range, and its optional default must be in the list. The host still picks the provider-specific wire encoding.- Each object is limited to 4096 serialized bytes. Unknown keys or versions, malformed flags or options, audio or non-text output, parallel calls without tools, and unsupported protocol declarations fail closed. The schema can't enable Lite, Ultra or delegation, deferred tools, native budgets or toggles, arbitrary profiles, authentication, URLs or transport changes.
- Explicit legacy endpoint assertions (including false, null or unknown) win per field. Configured model overrides still win over discovery, except for custom-provider limits with discovery enabled, where the live endpoint window is authoritative (see the custom registry). No capability is borrowed from models.dev. The decoder's provenance names the host-selected endpoint, returned model and codec, never an authority or URL claimed by response data.
- Built-in raw caches stay URL- and account-isolated and are decoded on use without persisting synthesized fields. Custom normalized caches advance to version 10, so old sparse results can't hide self-descriptions and old configured-wins limit pins can't entomb a live window. These are deterministic source contracts, not evidence that any public provider emits the extension today.
Cold-start feedback#
Set lifecycle_feedback: true on a custom provider only if its streaming Chat
Completions endpoint implements the optional readiness extension. octet then
sends x-octet-lifecycle: 1. The endpoint may answer with that header, comments
such as : octet-lifecycle: loading; warming model, or both. Accepted states
are queued, loading and ready. Malformed values and ordinary SSE comments
stay invisible. Endpoints you haven't configured get no header, and ordinary
OpenAI clients ignore it.
What the feedback is and isn't
It's transient, redacted, bounded status. It isn't assistant content, session
history or model context. Plain and print modes write it to stderr, so print
stdout stays response-only. It adds no retries, no replay of accepted POSTs and
no special 503 handling. startup_timeout_secs still limits response headers
and feedback can't extend it. Ordinary body idle and deadline limits apply once
a stream starts. Non-streaming requests neither negotiate nor emit feedback. See
the transport notes.
Inference measurements (unreleased)#
Every supported conversation codec/transport shares attempt-scoped client
observations, independently of billing and optional native server timing.
Inference measurements defines sources, scopes,
unavailability, /status, JSONL/NDJSON/RPC events and the route coverage matrix.
Server timing is reported only when a recognized matching terminal count/duration
pair exists; client E2E throughput is never labeled server decode speed. No
live-provider timing accuracy or released availability is implied.
All live conversation routes also use the same usage-calibrated streaming decode
estimator when native timing is absent. Native reporting wins; estimates are
marked ~ and exclude E2E, prefill and completion-tail time. Short/buffered output,
unseparated reasoning and deferred retrieval may remain unavailable. See the
measurement contract for assumptions and qualification limits.
Reasoning#
octet --reasoning high
octet --reasoning budget=16000budget=N works only for compatible models. /thinking [level] offers off,
on, minimal, low, medium, high, xhigh, max or ultra, narrowed to
what the selected model supports. A model's own controls (off-only, on and off,
or custom named values) decide the picker and what's sent, not a generic effort
guess. Built-in native discovery may use a declaration-owned token-budget table
only when the whole table fits strictly below the effective output ceiling.
Otherwise the inventory model keeps its limits but advertises no reasoning
control: octet doesn't enlarge the ceiling or invent replacement budgets from
the snapshot. Compatible endpoint choices and defaults can narrow the declared
contract without changing its native codec.
ultra needs advertised Ultra/V2 metadata and the trusted, enabled, live
octet-subagents service. Otherwise it's clamped to the highest ordinary effort
that's safe. Child work uses the extension's subagent_* tools and the worker
list in /extensions. There's no parallel native root collaboration tool. See
legacy Pro configuration, context
budgeting and reasoning
display.
Defaults#
In this checkout, a new CLI session with no reasoning preference uses the
selected model's advertised default, including an advertised Off. Without a
default, a known reasoning contract uses its first supported enabled choice, and
with no usable contract octet's selection stays Off without guessing a reasoning
parameter. This fixes startup overriding model defaults with Off in octet 0.8.0.
An endpoint that publishes its own enabled levels and default (DeepSeek's
effort: {default_level, supported_levels} object) is decoded: those levels
become the exact choices, the declared level is the default, and the enabled
levels never remove a declaration-owned Off control. max stays selectable
whenever the endpoint declares it, and an explicit selection always wins over
the declared default.
The declared level is only the fallback for a user who has never chosen. The
order is: this run's --model/--reasoning flag, then the selection the user
saved, then the model's declared default. What is saved is the session's own
record — EntryValue::Config written by append_config_if_changed and restored
by persisted_session_config/launch_configuration_parts
(crates/octet-coding-agent/src/app/bootstrap.rs) — and the user-level
~/.octet/config.toml values that /model, /thinking and
/settings default model|reasoning persist through cli::persist_model /
cli::persist_reasoning. Resume prefers the session's record over that file and
an explicit --reasoning over both; the flag never rewrites the file.
cargo test --locked -p octet-coding-agent --test reasoning_persistence covers
each of those restarts.
An explicit CLI, environment or config choice still wins over the model default.
Resume keeps the saved choice (including Off) unless --reasoning overrides it,
and existing saved Off values aren't reinterpreted as unset. In the interactive
UI, submit /thinking on or another supported level to change and persist that
session's selection, then check /status.
A custom none/default contract's On leaves the reasoning control absent, so
the server uses its default, and always-on models also get no control parameter.
Unknown metadata and a lack of displayed reasoning don't establish that the
server has disabled thinking. Reasoning can raise latency and token use, so use
--reasoning off when the model supports it to opt out. See reasoning
selection and thinking.
OpenRouter reasoning contract
The OpenRouter route decodes that provider's own per-model reasoning object
(mandatory, default_enabled, supported_efforts, default_effort) rather
than inferring optionality from the mere presence of a reasoning_effort
parameter. Mandatory models offer only their advertised efforts (for GLM 5.3,
max/high/low, default max), or only on if no efforts are published. A
saved unsupported off is normalized to an advertised choice, with a
diagnostic. Local summaries use the same contract: Off only when supported,
otherwise the advertised default. A parameter list without an exact contract
offers only the endpoint default, never a guessed none/minimal/low/medium/high
range.
For the OpenRouter profile, off omits the reasoning object instead of
sending effort: "none" or enabled: false, so the endpoint may still reason
according to its default. Enabled efforts are sent verbatim, and an enabled
boolean-only contract sends enabled: true. Other providers keep their existing
explicit-disable behavior. This is a wire-compatibility policy, not a guarantee
that selecting Off disables reasoning on OpenRouter.
Protocols and transport#
| Protocol | Streaming | Tools | Reasoning | Images | Structured output |
|---|---|---|---|---|---|
| OpenAI Responses | Yes | Yes | Yes | Yes | Yes |
| OpenAI Chat Completions | Yes | Yes | Yes | Yes | Yes |
| Anthropic Messages | Yes | Yes | Yes | Yes | Yes |
| Amazon Bedrock Converse | Yes | Yes | Depends on the model (token thinking) | Yes | No |
Capabilities are model-specific and checked before sending: modalities, tools, structured output, output limits and reasoning. Google uses native generateContent, not an OpenAI translation. Knowing a protocol doesn't imply native audio support.
Codex inference uses ordinary Responses with parallel_tool_calls: true, like
Pi. Explicit Responses Lite selection by an octet-ai SDK caller still sends
parallel_tool_calls: false. Host execution is independent of the wire flag:
built-in tools says which batched calls octet runs together.
OpenAI, Codex and Responses details
- Direct OpenAI and Codex declare
WebSocketPreferredwith HTTP/SSE fallback for ordinary requests. Native steering needs its bidirectional WebSocket operation and doesn't replay accepted input through HTTP. Codex also uses endpoint-configured zstd request compression, and if compression fails, octet sends the valid uncompressed body. These come from the provider declarations, not from the Responses codec itself. - Explicit Responses Lite selection in
octet-aiapplies to ordinary and native compact requests; the coding product's Codex route does not select it. Lite sends its header, tool schemas and developer instructions as input items, reasoning context across all turns, and no unsupported image-detail hints. Exact shapes: the Lite notes and wire fixtures. - The Responses
service_tierrequest field (auto,default,flex,priority) is a declared endpoint capability, not a provider identity. The codec sends it only when the caller selects one and the route's declaredruntime.responses_profileaccepts it (Codextoday, the same gate/fastuses). Any other profile fails closed with a typed unsupported error instead of silently dropping a billing-changing control. Cost settlement and conservative request reservations use the declared Codex tier tariff with the provider's echoed tier: flex is one-half, and priority is twice the base rate (five-halves for exact API modelgpt-5.5). Unknown tiers, unresolvedautoand unsupported tariffs stay unpriced. The priority uncertainty marker stays durable, and tier settlement doesn't clear prior exposure. - The OpenAI Responses computer-use tool
(
{"type":"computer_use_preview","display_width":…,"display_height":…,"environment":…}) is likewise a declared capability. A caller must select it (ResponsesOptions::with_computer_use) and the route's declaredruntime.responses_profilemust accept it (the public Responses profile today, with any other profile failing closed with a typed unsupported error). octet carries the protocol only: it declares the tool, maps a providercomputer_callto a canonical tool call namedcomputer_use_previewwith a bounded action payload (click,double_click,drag,keypress,move,screenshot,scroll,type,wait; anything else fails closed, including a missing action), and maps the caller's result back to acomputer_call_outputitem carrying the one documentedcomputer_screenshotobject. Nothing in octet executes a computer action. There's no desktop or browser backend behind this codec, and whether any action may run is a separate host-policy decision. previous_response_idis a best-effort, in-process shortcut on a live WebSocket, used only when fixed parameters and the prior input and output prefix match. It isn't a durable cursor. On resume or a mismatch, octet replays everything locally. Native Responses uses persisted, route-affine opaque replay.
Connection loss, replay and unknown usage
The WebSocket code and the recovery notes tell a recognized pre-generation connection-lifetime rejection (retire the socket, retry safely over HTTP) from an accepted POST or a body disconnect. By default, ambiguous accepted requests aren't replayed, even before visible output. The agent has a narrow exception for host-qualified Codex Responses requests (including Lite) with local function tools: before assistant commit it may discard provisional output and replace interrupted inference from durable context. That can duplicate remote generation and incur unknown charges, and it doesn't replay committed local tool effects or prove remote cancellation. The agent keeps finite streamed-inference replacement and HTTP-admission retry budgets separate, without resetting on transport fallback. They bound attempts, not confirmed accepted generations or charges, and eligibility and hard ceilings can stop recovery earlier.
Only positively classified pre-send outages enter sustained, cancellable network waiting. Unknown failed-attempt usage blocks replacement under hard cumulative cost and token ceilings. Unknown exposure is durably recorded independently of known usage, and later success, resume or checkout doesn't restore complete totals. Displayed numeric usage and cost are then a known subtotal, and a fork starts independent accounting. See the agent recovery contract for eligibility, budgets and verification limits. Deterministic tests aren't live-provider recovery qualification.
GPT-6 contracts and execution#
Direct gpt-6-astra, gpt-6-sol, gpt-6.1-sol and gpt-6-luna are declared
on Responses with text and image input, a 1.05M-token context window and 128K
output. Astra accepts low through max (default low). Sol and Luna also
accept Off/none and default to medium. 6.1 Sol defaults to medium but accepts
only low through max (no none or minimal), and its tools need the
Responses API. Exact public pricing and its above-272K tier are recorded in the
catalog sources. New public prices aren't
borrowed for Codex Sol, Luna or 6.1 Sol, whose subscription cost stays unknown.
Model and endpoint feature declarations must both opt in:
- Async tools. Qualified host-parallel observations advertise
async: true. Complete calls are persisted before a bounded background job starts, the model can advance while those jobs run, and each result uses its original call ID. This isn't speculative execution of streamed arguments. Synchronous calls, effectful work, approvals and hard usage ceilings keep their barriers. Interrupted pending work isn't blindly rerun after a restart. - Native steering. Public GPT-6 uses
response.steeron the active socket, with durable admission before dispatch and separate accounting and persistence for every response segment. Provider acceptance isn't proof that an instruction was followed, and ambiguous disconnects aren't automatically replayed. Routes or input forms without qualification keep ordinary queued steering, and Codex doesn't gain native steering from its model name. Hard cumulative ceilings keep the ordinary queue boundary. - Thinking changes. Qualified
/thinkingchanges queue for the next response boundary without cancelling the root run. The request-level reasoning baseline stays fixed, and orderedconfiguration_updateitems carry ordinary effort changes. Off still needs an advertisednone, and Ultra or delegation-mode changes aren't ordinary wire effort updates. Durable replay keeps the baseline and the effective selection. Compaction must rebase only after a successful summary, and standalone native compact rejects update histories without separate authority.
Codex currently qualifies reasoning updates from positive account inventory, not async tools or native steering. Lite and V2 don't imply either feature. Public API support and deterministic loopback tests aren't live OAuth inference or cache-hit qualification. See reasoning controls and the protocol contract.
Third-party GPT-6 inventory doesn't inherit these capabilities from a name or
snapshot. OpenRouter must advertise its own images, tools and exact reasoning
choices, and its ordinary Chat route doesn't become a Responses control
endpoint. Provider-managed misalignment monitoring is independent of host
approval: misalignment_policy_violation stops automatic retry, and already
completed work isn't undone. octet doesn't provision project webhooks or
safety-alert subscriptions automatically.
Declarative preset metadata#
Presets are data, never provider-name branches. The typed preset surface in
octet_ai::declarations describes per-model and per-provider fields, and
validation is fail-closed. It's declared plumbing: the OpenAI-compatible codecs
and the streaming client that consume these fields are owned separately, so
preset validation alone doesn't establish codec emission or proxy behavior.
The declared fields, credential aliases and proxy resolver
The typed surface (ModelPreset, RequestOverrides,
ProviderCredentialPreset, ChatTemplateValue) covers samplingParams,
per-model headers, vllmPriority, supportsMaxOutputTokens,
thinkingTokenBudgetField, chatTemplateArgs and chatTemplateKwargs with
{ "$var": "thinking.enabled" | "thinking.effort" | "thinking.budget" }
interpolation, the string-thinking format, and credential environment aliases.
Unknown $var names, malformed headers, empty identifiers and unbounded retry
or timeout values are rejected.
Credential aliases recognize Anthropic's ANTHROPIC_AUTH_TOKEN and
ANTHROPIC_OAUTH_TOKEN (which must be sent as Authorization: Bearer) ahead of
ANTHROPIC_API_KEY (sent as x-api-key), and Vertex's GOOGLE_CLOUD_API_KEY.
Route presentation currently follows the declaration's static auth presentation,
and selecting bearer or API-key per matched variable isn't implemented.
octet_ai::declarations::proxy resolves HTTP_PROXY, HTTPS_PROXY,
ALL_PROXY and NO_PROXY for a request target with upstream root-and-subdomain
NO_PROXY semantics (exact host, .domain, *.domain, optional :port, lone
*), and rejects non-http(s) proxies. The streaming client doesn't call this
resolver.
Native Mistral Conversations (experimental codec)#
The native Conversations codec passes its deterministic request and SSE
fixtures, including rejection of credential-bearing or non-TLS destinations
before credential resolution (literal loopback HTTP is allowed for local
testing). Only native completion settles calls: a missing
conversation.response.done reports MissingFinish, never a synthesized
tool-call end or successful reply. No POST is replayed. The built-in Mistral
preset above still uses Chat. Conversations discovery, presets and broader
native capabilities are separate work, not implied by these codec repairs.
Prompt caching#
With cache retention enabled, Anthropic Messages marks eligible tools, system
and final content. Large tool batches also retain the preceding user breakpoint so it
stays reachable within the provider's 20-block lookup window (at most four
breakpoints total). Supported Claude Bedrock identities use native cachePoint
blocks on system and user boundaries, with the same large-batch bridge. Short
retention uses the native default; Long requests one hour only when the model's
cache compatibility allows it. Unsupported models and opaque Bedrock application
profiles receive no explicit points; Nova keeps automatic caching.
Responses caching follows the selected route's declared controls, not a model name alone. Flex is a service tier on that same codec; its existing endpoint gate is described above. Stable serialized prefixes do not prove that a provider's implicit breakpoint remains eligible when a user/tool-result group is extended. The local audit covers all builtin Responses models, including GPT-6 Luna, and declared Flex fixtures without claiming live hits or tier availability.
These annotations do not remove or reorder context. Compaction, rolling screenshot bounds, changed tools/instructions and context hooks can still invalidate earlier prefixes. Cache availability, minimum sizes, TTLs and routing remain provider-dependent. Cache warming is additional billable work, not a repair for unstable prefixes. The credential-free cache audit separates local estimates from live usage.
Provider request trace#
When a provider rejects a request with an opaque status (for example an HTTP 400
whose body is only "Bad Request"), set OCTET_PROVIDER_REQUEST_TRACE to a file
path (or stderr) before starting octet. Use a fresh file for each process.
Requests append JSON lines describing shape, prefix comparisons and terminal
usage; a rejection adds the status and the provider's request id. Shape includes:
- transport (
httporwebsocket), the frame type, the API model name and the body size; - the length of every top-level array, and whether
previous_response_idis set (never its value); - for Responses requests, the count of each input item type, the trailing item
type, image counts by kind (inline, URL, file id) with inline wire bytes, how
many reasoning items carry
encrypted_content(never its content), and how manycomputer_call_outputitems carry no screenshot.
cache_prefix events compare successive requests in the same client/session,
model and endpoint lane at three stages: canonical (after agent context hooks,
before model normalization), encoded (codec JSON, before payload hooks), and
wire_http or wire_websocket (after hooks or continuation rewriting). HTTP
bytes are the uncompressed JSON before compression/signing, not TLS bytes.
Opaque host transports expose only the canonical stage. Special native steering,
prewarm and standalone compact operations retain shape-only tracing where
available, not this three-stage comparison contract.
Events include client-private salted digests, tools/system/message segment
paths and lengths, exact raw JSON common-prefix offsets, a diagnostic
annotation-free prefix view, estimated invalidated suffix tokens (bytes/4),
cache-point positions and a digest of other controls. These are not provider
tokenization or cache hits. Only terminal cache_usage events carry decoded
provider usage, correlated by id. Efficiency is
cache_read / (uncached_input + cache_write + cache_read); writes are fresh,
one-hour writes are a subset, output is excluded, and zero/unreported input has
no ratio. Compatible Chat usage accepts Kimi's top-level cached_tokens alias
without adding it to other cached-token aliases.
Responses native prompt_cache_breakpoint positions are included for documented
input-text/image/file blocks in user/system/developer messages and function/custom
tool outputs. Moving only those annotations does not count as a history rewrite
in the diagnostic prefix view; raw body digests still change. Schemas, arguments,
unknown fields and opaque reasoning are not stripped. Tracing never adds markers
to outgoing requests or changes the route's cache policy.
Comparison state is shared by client clones, never across process restarts. When enabled, raw/projection bytes stay in memory only: at most 12 snapshots, 16 MiB of retained payload bytes and 2 MiB per observed body; oversized or non-JSON bodies clear the lane/stage's prior comparison. Segment output is capped at 256 entries. TTL and account affinity cannot be inferred from a byte match, and a missing comparison is not a cache miss.
The trace never writes prompts, tool arguments or output, headers, URLs,
credentials, response ids, image data or reasoning content. Digests use an
unexported client-local salt, preventing cross-run correlation of content. New
trace files are owner-only (0600) on Unix; existing permissions are unchanged.
Review metadata before attaching a trace to an issue. Leave the variable unset
to disable it. See the audit for reproducible checks and
interpretation limits.
Model-visible tool-result images#
A long computer-use run produces many screenshots. Only the newest four
tool-result images (MODEL_VISIBLE_TOOL_IMAGE_LIMIT) are sent to the model;
older ones are replaced in the request with a short text placeholder. The
session journal keeps every image, so reopening, export and audit are
unchanged. Images you attach to a prompt are not affected.