Bring your own models, and let one router manage them all
Every model request from the main Session, a Subagent, a Workflow agent, and a Team member goes through MRM. See how MRM actually resolves and admits a call in MRM.
Provider: connect subscriptions, API platforms, and local models#
Provider describes a model service's protocol, endpoint, authentication method, region, capabilities, and default model. It's distinct from an account, and distinct from a specific model.
Where you manage it#
Settings' Providers area shows provider and account status. The provider registry is the authoritative source for display names, auth type, region, and default model.
Provider types#
- Subscription OAuth providers.
- API-key providers.
- Local, credential-free providers, such as Ollama.
- Custom OpenAI-compatible endpoints.
- Custom Anthropic-compatible endpoints.
The built-in provider registry#
The registry holds 44 built-in entries, listed in the same order Settings shows them. Default models come from the static model catalog — it's a seed list, not a live-availability promise; any entry that supports a /models endpoint can pull a live catalog from its account row.
Subscription OAuth#
All 11 subscription providers support in-app OAuth login, either authorization-code or device-code flow.
| Provider | key | Login flow | Region | Default model |
|---|---|---|---|---|
| Anthropic | anthropic |
Authorization code | Global | claude-sonnet-4-6 |
| OpenAI | openai |
Authorization code | Global | gpt-5.4 |
| xAI | xai |
Device code | Global | grok-build-0.1 |
| Kimi For Coding | kimi-for-coding |
Device code | Global | kimi-for-coding |
| GitHub Copilot | github-copilot |
Device code | Global | gpt-4.1 |
| Qwen Code subscription | qwen-oauth |
Device code | Global | qwen3-coder-plus |
| Google Gemini subscription | google-oauth |
Authorization code | Global | gemini-2.5-pro |
| Google Antigravity | google-antigravity |
Authorization code | Global | gemini-3-pro-preview |
| MiniMax subscription | minimax-oauth |
Device code | Global | MiniMax-M2.7 |
| MiniMax subscription (China) | minimax-cn-oauth |
Device code | China | MiniMax-M2.7 |
| AWS Kiro | kiro |
Device code (AWS SSO) | Global | claude-sonnet-4.5 |
Aggregators, gateways, and local#
| Provider | key | Auth | Region | Default model |
|---|---|---|---|---|
| OpenRouter | openrouter |
API key | Global | openai/gpt-5.4 |
| Vercel AI Gateway | vercel |
API key | Global | anthropic/claude-sonnet-4.6 |
| Hugging Face | huggingface |
API key | Global | deepseek-ai/DeepSeek-V3.2 |
| Ollama | ollama |
Local, no auth | Global | llama3.3 |
| Ollama Cloud | ollama-cloud |
API key | Global | kimi-k2.5 |
OpenRouter also supports in-app OAuth login alongside plain API keys: its authorization-code flow exchanges for a permanent API key, which is then stored as an API-key credential — it never needs a refresh.
International API-key providers#
| Provider | key | Region | Default model |
|---|---|---|---|
| DeepSeek | deepseek |
Global | deepseek-chat |
| Mistral | mistral |
Global | mistral-large-latest |
| Groq | groq |
Global | llama-3.3-70b-versatile |
| Google Gemini | google |
Global | gemini-2.5-flash |
| Together AI | together |
Global | meta-llama/Llama-3.3-70B-Instruct-Turbo |
| Fireworks | fireworks |
Global | accounts/fireworks/models/gpt-oss-120b |
| Cerebras | cerebras |
Global | gpt-oss-120b |
| SambaNova | sambanova |
Global | Meta-Llama-3.3-70B-Instruct |
| Perplexity | perplexity |
Global | sonar |
| Cohere | cohere |
Global | command-a-03-2025 |
| GitHub Models | github_models |
Global | openai/gpt-4.1-mini |
| Novita | novita |
Global | deepseek/deepseek-v3.1 |
Regional API-key providers#
| Provider | key | Region | Default model | Note |
|---|---|---|---|---|
| Kimi | kimi |
China, International | kimi-k2.5 |
|
| Zhipu GLM | zhipu |
China, International | glm-4.6 |
|
| Zhipu Coding Plan | zhipu-coding |
China, International | glm-5.2 |
Coding Plan; supports in-app OAuth |
| Qwen | qwen |
China, International | qwen3-max |
|
| Bailian Coding Plan | qwen-coding |
China, International | qwen3-coder-plus |
Coding Plan |
| MiniMax | minimax |
China, International | MiniMax-M2.5 |
|
| SiliconFlow | siliconflow |
China, International | deepseek-ai/DeepSeek-V3.2 |
|
| StepFun | stepfun |
China, International | step-3.5-flash |
|
| StepFun Step Plan | stepfun-plan |
China, International | step-3.5-flash |
Plan endpoint |
| Doubao | doubao |
Global | doubao-seed-1-6-250615 |
|
| Doubao Coding Plan | doubao-coding |
Global | ark-code-latest |
Coding Plan |
| Yi (01.AI) | yi |
Global | yi-lightning |
|
| Tencent Hunyuan | hunyuan |
Global | hunyuan-turbos-latest |
|
| Tencent Coding Plan | hunyuan-coding |
Global | tc-code-latest |
Coding Plan |
| Baidu Qianfan | qianfan |
Global | ernie-4.5-turbo-128k |
|
| Qianfan Coding Plan | qianfan-coding |
Global | qianfan-code-latest |
Coding Plan |
A Coding Plan or bundled-plan entry uses its own dedicated endpoint — a plan key and an ordinary pay-as-you-go key aren't interchangeable. Doubao Coding Plan also has its own independent model catalog; ordinary Doubao model IDs aren't available on that endpoint.
In-app OAuth login#
13 entries let you log in directly from the settings page, with no manual token pasting: the 11 subscription OAuth providers, plus OpenRouter and Zhipu Coding Plan. Every other provider uses an API key, imported CLI credentials, or runs locally without authentication.
- Authorization-code flow: Anthropic, OpenAI, Google Gemini subscription, Google Antigravity, OpenRouter, and Zhipu Coding Plan. Your browser completes authorization and a local loopback callback receives it; when the callback isn't reachable, Anthropic, OpenRouter, and Zhipu Coding Plan also support pasting the authorization code manually.
- Device-code flow: xAI, Kimi For Coding, Qwen Code subscription, GitHub Copilot, both MiniMax regions, and AWS Kiro. The UI shows a user code, and login completes automatically once you confirm it in your browser.
- Zhipu Coding Plan's login contract comes from the Z.AI ZCode desktop client: the authorization code is exchanged through a broker into a durable API key, which then stores as an API-key credential and never needs refresh. This contract carries no official third-party login guarantee, and could stop working in the future.
- GitHub Copilot exchanges its device-code OAuth token for a short-lived Copilot API JWT, which the app automatically renews.
- AWS Kiro uses the AWS SSO OIDC device flow, with client credentials dynamically issued through
registerClient. - Google Gemini subscription and Google Antigravity reuse public desktop-client credentials; Google's ToS restricts third-party reuse, so the settings page shows an account-risk warning.
Standard token-pair credentials are automatically refreshed by the app before they expire. OpenRouter and Zhipu Coding Plan settle as API-key credentials and don't participate in refresh.
Protocol differences#
Most entries use an OpenAI-compatible protocol; Anthropic and both MiniMax subscription entries use the Anthropic protocol. Three entries are special-cased by key onto a vendor's private wire protocol: Google Gemini subscription and Google Antigravity use the Gemini Code Assist protocol, and AWS Kiro uses the CodeWhisperer eventstream protocol. These entries' base URLs are only used for catalog and credential attribution — don't treat them as reusable OpenAI-compatible endpoints.
Custom providers#
Creating a custom provider needs a name, base URL, protocol, model ID, API key, and capabilities. Capabilities can include text, vision, and audio.
A custom provider's configuration and credentials are stored separately. Configuration records the endpoint and models; credentials go into the local auth store, never into the project repository.
Availability#
A provider appearing in the registry only means kxen knows how to connect to it. Actual availability still depends on account credentials, region, model access, service status, and your local network. Use the connection test in the account row to confirm current status.
Accounts: multiple credentials per provider#
An account is an independently usable authenticated identity under a provider. kxen supports multiple accounts per provider, and lets MRM rotate between them.
Ways to add one#
- Complete an in-app subscription OAuth login: the authorization-code flow shows an authorization link, and the device-code flow shows a user code — 13 entries support this, with catalog and flow differences covered under Provider.
- Import a subscription OAuth JSON.
- Import a bare OAuth access token.
- Add an official platform API key.
- Create a custom provider with credentials.
Naming an account helps you separate work, personal, or other quota pools. Account names can't contain colons or whitespace. Standard token-pair credentials are auto-refreshed by the app before they expire.
Verification#
You can run a connection test on a candidate credential before adding it. Once saved, every account row can still individually:
- Test its request and latency.
- Pull the model catalog from its endpoint.
- Switch operating region for a multi-region provider.
- See which model roles are using it.
- Delete the account or the custom provider.
"Credential present" only means local authentication data exists — it doesn't mean a request will actually succeed. Only a connection test confirms the current credential and endpoint actually work. A connection test sends a minimal, real model request to the chosen provider — it's not a local format check, so it may consume a small amount of provider quota.
Multi-account scheduling#
When a role isn't bound to a specific account, MRM can rotate across the available accounts under that provider. The provider's concurrency pool is shared across accounts; RPM is tracked per account separately.
Credential boundary#
API keys, access tokens, and refresh tokens are never written to TOML or the knowledge base. Before you delete an account currently in use by a role, the UI shows you the impact and asks for confirmation.
kxen asks for your consent before it first reads an official CLI's credential source. It never triggers a macOS Keychain ACL prompt during startup on its own — reading Claude Code-credentials only happens when you explicitly choose to re-import Claude credentials.
Keychain reads run through an independent /usr/bin/security find-generic-password subprocess, sharing a 5-second total deadline with a 1 MiB output cap. On timeout, output overrun, or a process crash, kxen terminates and reaps the subprocess, then treats the credential as unavailable — it never leaves an unbounded background task hanging.
Model routing: bind a role to a Provider, model, account, and fallback#
Model routing maps a work role to an actual inference resource. It lets your main conversation, planning, execution, and review each use a different model without you switching manually every time.
Where you configure it#
Settings' Model Routing area. Current roles: chat, thinking, planning, execution, review, research.
What each role binds#
- A provider.
- A model ID.
- An optional fixed account.
- An optional fallback role.
Without a specified account, MRM rotates across that provider's available accounts. When a provider or account has no free slot, resolution can continue down the fallback-role chain.
Live scheduling view#
The Model Routing UI shows:
- Provider concurrency slots.
- Each role's current binding.
- Trial-dispatch results.
- Recent dispatch records.
- Whether a fallback happened.
Configuration changes take effect immediately after saving. A trial dispatch actually creates a Subagent that's asked to reply PONG, and returns the real provider, model, and account it used — confirming your configuration is more than a static form value. It's not a dry run, and may consume a small amount of provider quota, so only run it when you explicitly click it.
Loop boundary#
Don't point two roles at each other as mutual fallbacks. A circular configuration never produces a new available resource — it only makes the fallback chain unable to complete a useful resolution.
Mutual pointers can't expand infinitely: resolution follows the chain at most 3 hops, truncating by deduplicating visited roles (the factory-default execution/research mutual pointer relies on exactly this truncation to guarantee termination). Truncation guarantees the chain terminates — it doesn't guarantee availability; a mutual-pointer chain still has no new resource on it.
Models: browse the live catalog and pick one per Session#
A model is an actual inference target a provider offers. Model ID, display name, context window, and capabilities all come from the app's model catalog.
The catalog#
The catalog is formed jointly from provider registration data and live endpoint responses. You can pull live models from a provider's account row — a successful pull merges into Composer's model picker.
A static seed list isn't a live-availability promise. A provider can add, rename, restrict, or remove a model at any time, and an account may not have access to a particular model even when it's listed.
Choosing per Session#
Composer's model picker sets the model for the current Session. Your choice is saved in that Session's own metadata — it never bulk-changes any other Session.
Cmd+K searches models, and applies your selection to the current Session.
Role defaults#
Without an explicit Session override, the main conversation and each agent role use the model-routing binding from the effective configuration of the Session's own Workspace. A Subagent, Workflow agent, or Team member can each pick a different model based on its role. Switching the foreground Workspace never changes a background Session's default binding.
What the picker is telling you#
The model picker shows provider, model ID, and context window. An explicit choice locks in that Session's provider and model identity — but the final request still passes through its Workspace's MRM account resolution, budget, circuit, concurrency, and RPM admission, and the actual provider dispatch verifies whether that model is actually available. Role-default routing allows fallback; an explicit choice that's unavailable simply returns an error — it never quietly switches you to a different model.
Usage: token totals, Sessions, and dispatch distribution#
The usage page aggregates the token ledger for Sessions currently loaded in this process plus system-scoped operations, recent trends, and the current process's routing resolutions — and separately reports whether the ledger is confirmed durably persisted.
Where you see it#
Settings' Usage & Stats area shows:
- Input tokens.
- Output tokens.
- Session count.
- Recent routing-resolution count.
- Recent routing resolutions broken down by model.
Every assistant message also shows TTFT, total elapsed time, and output rate, so you can watch a single request's performance.
What the numbers actually mean#
usage.json stores each Session's and each system_* global paid operation's known token lower bound or UNKNOWN count, and keeps accumulating across app restarts once it's durably persisted. usage-trend.json retains the last 90 days by provider and local date; Settings shows the most recent 14 days. Every paid call that's about to cross a provider boundary first creates an operation marker under usage-attempts/; once response usage is observed it's recorded exactly, then settled idempotently against the Session, Goal, or system scope using that same operation ID.
Provider connection tests, role trial-dispatches, and Voice uploads with no chat Session also create a system-scoped marker. They count toward total tokens or UNKNOWN completeness, but system scope never counts toward the Session count Settings displays. Provider-native search settles exactly when it returns usage, and stays UNKNOWN otherwise. Tavily, Brave, Exa, Jina, Serper, SerpAPI, Google CSE, Firecrawl, and You.com — search APIs that may bill per request — also create a marker before the network call; since they don't return token usage, an already-started request settles as UNKNOWN. Cloud audio usually bills by duration, a unit the current token ledger can't express, so any cloud transcription request that's already been sent settles as UNKNOWN.
Token totals come from what kxen itself recorded — never a provider's own bill. Provider billing rules, cache pricing, free tiers, and subscription quotas are still governed by that service. When a provider doesn't return complete usage, the UI shows a ≥ known lower bound and marks completeness UNKNOWN — it never treats unknown tokens as zero. On restart, kxen scans unfinished markers: an attempt with a recorded value settles exactly; an attempt that started but has no recoverable usage adds to the unknown count — it's never automatically resent as a paid request.
Only an explicit HTTP 401, 403, or 429 refusal that produced zero content and zero usage observation settles as known 0/0. Anything that already received content or usage, or only shows an ambiguous transport failure like a timeout or reset, is never inferred as zero usage. A 401/403 first runs one same-account self-heal: force-refresh the credential and retry once, only when this attempt produced zero output and hasn't already force-refreshed — a second failure isn't retried again, to avoid a revocation loop. A zero-output 429 runs a bounded retry: only for an explicit rate limit with no observed content or usage, up to 2 retries (3 attempts total for that request), which may rotate to a different account in the pool; ambiguous failures like 5xx, timeout, or reset are never retried. Once any usage is observed, that same request is never automatically retried again.
When storage_complete=false, Settings and the status bar show "storage UNKNOWN." At that point, the number is the current process's in-memory ledger total — it doesn't mean everything is already written to usage.json, and a restart may lose an increment that never landed. Any persistence failure keeps that state; only that same ledger successfully persisting later clears the warning. usage_complete is only true when both metering and storage are complete.
Relationship to routing#
The recent routing-resolution distribution comes from MRM's current in-process cap of 50 records. When a fallback happens, the record reflects the model that was actually resolved — not the role binding that was first requested; it clears on app restart, and it's never a substitute for real provider calls or billing records.