Skip to content
DOCS — PRODUCT · PROVIDERS

Providers

A provider is a remote inference service. OpenMed supports OpenAI (via an API key or a ChatGPT OAuth sign-in), Anthropic, OpenRouter, Xiaomi MiMo token plans, Doubleword, and custom endpoints for the interactive agent. Doubleword also supports async Responses and 24-hour JSONL batches. The interactive agent uses the same medical tools, workflows, and audit trail across providers.

This page covers credentials, model selection, and the behaviour specific to each provider. For runtime settings that are not provider-related, see Configuration.

Choosing a provider

Provider Auth Use it for
OpenAI ChatGPT OAuth (openmed login) or OPENAI_API_KEY The default path. GPT-6.1 Sol and GPT-6 models.
Anthropic ANTHROPIC_API_KEY Claude models, with prompt caching enabled by default.
OpenRouter OPENROUTER_API_KEY Models OpenMed does not host itself — Gemini, Kimi, GLM, Qwen, Llama and several hundred more, on one key.
MiMo MIMO_API_KEY (starts with tp) Xiaomi MiMo token-plan subscriptions, with four native models ready to select.
Doubleword DOUBLEWORD_API_KEY Real-time TUI, async flex Responses, and 24-hour bulk inference.
Custom optional key One or more OpenAI-compatible servers you operate — vLLM, SGLang, llama.cpp, Ollama, LM Studio, or gateways.

Each provider resolves its own credentials. MiMo uses only its token-plan key and fixed endpoint; it never falls back to an OpenAI, Anthropic, or OpenRouter key.

All Doubleword modes use the same Doubleword key (from the environment or provider secrets) and the fixed https://api.doubleword.ai/v1 endpoint. See Doubleword for mode selection, interactive use, Responses jobs, and batches.

You can configure more than one at a time. Which provider handles a request is decided by the model you select — see How a provider is selected.

Where credentials live

Path Holds
~/.openmed/auth.json ChatGPT OAuth tokens, written by openmed login
~/.openmed/provider_secrets.json Provider API keys shared by interactive, async and batch requests, OpenRouter/Doubleword model lists, and custom endpoints
~/.openmed/settings.json Runtime behaviour such as permission mode — not secrets

Store keys without hand-editing JSON:

openmed config provider-set openai --api-key "sk-..."
openmed config provider-set anthropic --api-key "sk-ant-..."
openmed config provider-set openrouter --api-key "sk-or-..."
openmed config provider-set mimo --api-key "tp-..."
openmed config provider-set doubleword --api-key "sk-..."
openmed config provider-show

provider-show reports which providers are configured without printing secret values. To remove a stored key, use --unset-api-key with the same command.

Editing the file directly

~/.openmed/provider_secrets.json is written with 0600 permissions. Values may be literal strings, or {env:VAR_NAME} references that resolve at load time — useful when the file is synced between machines and you would rather not store the secret in it:

{
  "provider": {
    "openai": {
      "options": {
        "apiKey": "{env:OPENAI_API_KEY}"
      }
    },
    "anthropic": {
      "options": {
        "apiKey": "{env:ANTHROPIC_API_KEY}"
      }
    },
    "openrouter": {
      "options": {
        "apiKey": "sk-or-v1-...",
        "models": ["qwen/qwen3.8-27b", "z-ai/glm-5.2"]
      }
    },
    "mimo": {
      "options": {
        "apiKey": "{env:MIMO_API_KEY}"
      }
    },
    "doubleword": {
      "options": {
        "apiKey": "{env:DOUBLEWORD_API_KEY}"
      }
    },
    "custom": {
      "options": {
        "endpoints": [
          {"baseURL": "http://localhost:8000/v1", "model": "Qwen/Qwen3.8-27B", "apiKey": "{env:MY_SERVER_KEY}"}
        ]
      }
    }
  }
}

Only the custom section carries endpoint URLs (inside its endpoints list) — hosted provider endpoints are fixed, and a stored baseURL on them is ignored and stripped.

One JSON object, one provider map

Every provider is a key inside the single provider object above. A common mistake is to paste a second example after the first, which produces two objects and makes the whole file invalid — at which point all your keys are ignored and every provider shows as not configured. If that happens, openmed config provider-show now names the file and the line.

A flatter shape is accepted as an alternative to the block above — use one or the other, never both in the same file:

{
  "openrouter": {
    "api_key": "{env:OPENROUTER_API_KEY}",
    "models": ["qwen/qwen3.8-27b", "x-ai/grok-4.6"]
  }
}

Check the file is valid at any time:

openmed config provider-show

It prints which providers are configured, and exits non-zero with the parse error if the file is malformed.

Precedence is explicit runtime config → environment variable → this file → default. Provider config is read once per process, so restart OpenMed after editing the file by hand.

Hosted provider endpoints are fixed: OpenAI, Anthropic, OpenRouter, MiMo, and Doubleword do not take a base URL through provider-set. Use the custom provider for a server or gateway you operate.


OpenAI

The default provider. Two independent auth paths — OAuth, or an API key.

Sign in with ChatGPT (OAuth)

Uses your ChatGPT subscription rather than API billing:

openmed login             # writes ~/.openmed/auth.json
openmed oauth status
openmed oauth logout

Credentials are reused until they expire. If auth status reports the token is no longer valid, openmed login -f refreshes the single global auth file. openmed login --no-browser prints the authorization URL instead of opening a browser, and --timeout <seconds> extends the default five-minute wait.

An interactive sign-in can only be started by openmed login — never implicitly from inside the TUI. If you sign in from another terminal while the TUI is open, your next message picks up the new credentials without a restart.

API key

openmed config provider-set openai --api-key "sk-..."
# or
export OPENAI_API_KEY=sk-...

No openmed login step is required when using a key.

Models

gpt-6.1-sol (default), gpt-6-astra, gpt-6-sol, and gpt-6-luna. GPT-5.6 models and the gpt-5.6 alias are no longer supported by OpenMed; update saved model selections to one of these IDs. GPT-5.5 remains hidden from the picker; explicit gpt-5.5 selections use the OpenAI API when a key is configured. For the current picker models, OpenMed selects OAuth when both OAuth and an API key are available; use --provider openai to select API billing explicitly.

GPT-6.1 Sol uses the Responses API for agent tools, has a 1,050,000-token context window, and supports low, medium (default), high, xhigh, and max reasoning. It requires reasoning: an existing none setting is mapped to low. OpenAI and Codex auxiliary calls use the cheaper gpt-6-luna.


Anthropic

openmed config provider-set anthropic --api-key "sk-ant-..."
# or
export ANTHROPIC_API_KEY=sk-ant-...

openmed agent --provider anthropic --model claude-sonnet-4-6

Anthropic has no OAuth path — an API key is the only option.

Models

claude-fable-5-1, claude-opus-5-5, claude-opus-5, claude-sonnet-5, claude-opus-4-8, claude-sonnet-4-6 (the Anthropic default), and claude-haiku-4-5-20251001. Opus 4.7 is no longer listed in the picker, but existing explicit model selections remain usable. Selecting any claude-* model routes to Anthropic when Anthropic credentials are available. Fable 5.1 and Opus 5.5 always use adaptive thinking; the other 5.x models also use adaptive thinking by default.

Prompt caching

Enabled by default. OpenMed marks the stable tool schema block and the system prompt as 5-minute cache breakpoints, and enables Anthropic's top-level automatic cache control for the growing conversation, so long tool runs do not resend the entire prior transcript as fresh input every turn.

Variable Purpose Default
OPENMED_ANTHROPIC_PROMPT_CACHE Enable caching for stable tool/system prefixes 1
OPENMED_ANTHROPIC_PROMPT_CACHE_TTL Cache TTL — 5m or 1h 5m
OPENMED_ANTHROPIC_AUTOMATIC_CACHE Top-level automatic caching for multi-turn conversations 1

Rate-limit guard

OpenMed also guards Claude's input-token-per-minute limits locally. Cache reads are excluded from Anthropic's rate-limit accounting, but cache writes and uncached conversation growth still count. The Anthropic SDK tracks uncached input usage, paces requests near the local budget, compacts stale history when a cache miss would make a request too large, and retries transient 429 responses using Claude's retry-after header.

Variable Purpose Default
OPENMED_ANTHROPIC_RATE_LIMIT_GUARD Pace and compact before ITPM limits are exceeded 1
OPENMED_ANTHROPIC_ITPM_LIMIT Override the input-token-per-minute limit model default
OPENMED_ANTHROPIC_ITPM_SAFETY_RATIO Fraction of the limit to use before pacing 0.85
OPENMED_ANTHROPIC_RATE_LIMIT_RETRIES Automatic retries after a 429 2

OpenRouter

OpenRouter gives you models OpenMed does not host itself — Gemini, Kimi, GLM, Qwen, Llama and several hundred others — through one API key. It is a separate provider: OpenRouter credentials are never sent to OpenAI or Anthropic, and an OpenAI key is never sent to OpenRouter.

export OPENROUTER_API_KEY="sk-or-..."
openmed agent --provider openrouter

Choosing which models appear

Configure a short list rather than all several hundred. Those models appear in their own OpenRouter group in the /model picker:

openmed config provider-set openrouter \
  --api-key "sk-or-..." \
  --models "google/gemini-3.7-flash,qwen/qwen3.8-27b,z-ai/glm-5.2"

Or in ~/.openmed/provider_secrets.json directly (as one key inside the single provider object — see Editing the file directly):

{
  "provider": {
    "openrouter": {
      "options": {
        "apiKey": "sk-or-v1-...",
        "models": [
          "google/gemini-3.7-flash",
          "qwen/qwen3.8-27b",
          "z-ai/glm-5.2"
        ]
      }
    }
  }
}

qwen/qwen3.8-27b is OpenMed's default OpenRouter model. Gemini is shown here because people ask for it, but see the warning below before using it for clinical work — Google's API terms prohibit that use.

The list is stored as models in the openrouter section of ~/.openmed/provider_secrets.json — see Editing the file directly. OPENROUTER_MODELS (comma-separated) overrides the stored list for one session, and openmed config provider-set openrouter --unset-models removes it. With nothing configured, OpenMed uses qwen/qwen3.8-27b.

Browse what OpenRouter offers:

openmed config openrouter-models --search gemini
openmed config openrouter-models --all      # include models OpenMed cannot use
openmed config openrouter-models --refresh  # bypass the 24h metadata cache

OpenRouter environment variables

Variable Purpose Default
OPENROUTER_API_KEY The OpenRouter key. Required — OpenMed never falls back to another provider's key. unset
OPENROUTER_MODELS Comma-separated model ids offered in /model; overrides the stored list stored list, else qwen/qwen3.8-27b
OPENMED_OPENROUTER_MODELS_TTL_SECONDS Lifetime of cached model metadata under ~/.openmed/cache/ 86400 (24h)

The OpenRouter base URL is fixed to https://openrouter.ai/api/v1. For a self-hosted or gateway server, use the custom provider.

These are the models OpenMed has actually run through its own agent loop with the full 93-tool schema, cross-checked against OpenRouter's τ²-Bench Airline (multi-turn tool use) and GPQA Diamond (reasoning) leaderboards.

Open-weight — self-hostable, and the starting point for most users:

openmed config provider-set openrouter --models "\
qwen/qwen3.8-27b,\
z-ai/glm-5.2,\
qwen/qwen3.5-397b-a17b,\
nvidia/nemotron-3-ultra-550b-a55b,\
xiaomi/mimo-v2.5-pro,\
qwen/qwen3.5-122b-a10b,\
minimax/minimax-m3,\
moonshotai/kimi-k3,\
qwen/qwen3.8-2.4t-a95b,\
deepseek/deepseek-v4-flash-0731,\
deepseek/deepseek-v4-pro-0813"
Model Licence Notes
qwen/qwen3.8-27b Apache-2.0 Best value: τ²-Bench #3 overall, 1M context, vision
z-ai/glm-5.2 MIT Best all-round balance; low hallucination rate
qwen/qwen3.5-397b-a17b open Strong on both tool use and reasoning
nvidia/nemotron-3-ultra-550b-a55b permissive Fastest model tested
xiaomi/mimo-v2.5-pro open Very well calibrated; 1M context
qwen/qwen3.5-122b-a10b open Outstanding cost/capability, under a cent per turn
minimax/minimax-m3 open Best-calibrated model evaluated; GPQA top-10
moonshotai/kimi-k3 custom Strongest reasoning of any open model
qwen/qwen3.8-2.4t-a95b bespoke Highest agentic score; licence is not permissive
deepseek/deepseek-v4-flash-0731 MIT Cheapest model listed ($0.0018/turn), 1M context — but see the DeepSeek warning below
deepseek/deepseek-v4-pro-0813 MIT τ²-Bench #8, GPQA #21 — verify every code it emits; see the DeepSeek warning below

Closed, hosted-only:

Model Notes
x-ai/grok-4.6 Best-placed non-OpenAI/Anthropic model on healthcare benchmarks
qwen/qwen3.8-max Top agentic tier; rarely refuses clinical content
amazon/nova-pro-v1 Fastest closed model tested
qwen/qwen3.7-max Best published clinical benchmark results of the closed set
x-ai/grok-4.5 Same posture as 4.6, roughly twice as fast

Budget tiers that also passed cleanly: upstage/solar-pro4, amazon/nova-lite-v1, google/gemma-4-26b-a4b-it, qwen/qwen3.6-35b-a3b, nvidia/nemotron-3.5-lightning.

Fast, well-calibrated additions from a volume campaign (2026-08-31). These are the base models, worth running free or paid:

Model Notes
cohere/north-mini-code Fastest clean probe of any model tested (~10s), every clean run perfect
inclusionai/ling-3.0-flash-fin Every clean run perfect, ~11s. Not ling-3.0-flash, which is excluded for hallucination — this is the -fin variant
minimax/minimax-m2.7 The reliable long-horizon pick. Slower (~21s)

Trying OpenMed for free

Several of the models above are also served on a :free tier, so you can drive the whole 93-tool agent without spending anything. The numbers below come from 5,500+ live runs of OpenMed's own agent loop against the free tiers, not from a leaderboard.

Start with one line:

openmed config provider-set openrouter --api-key "sk-or-..." \
  --models "minimax/minimax-m2.7:free,cohere/north-mini-code:free,inclusionai/ling-3.0-flash-fin:free"
openmed agent --provider openrouter --model "minimax/minimax-m2.7:free"
Free model Clean runs Perfect Honest Median Long tasks
minimax/minimax-m2.7:free 2320/2327 92% 100% 21s 281/310
minimax/minimax-m3:free 1580/1620 72% 100% 18s 154/163
cohere/north-mini-code:free 273/273 100% 100% 10s see note
inclusionai/ling-3.0-flash-fin:free 271/271 100% 100% 11s see note

minimax/minimax-m2.7:free is the one to start with. It is the only free model that reliably finishes long, multi-step clinical work — 281 of 310 sustained 10-case runs — while staying honest on every single clean run.

cohere/north-mini-code:free and inclusionai/ling-3.0-flash-fin:free are the fastest and were flawless on every clean short probe. Their long-horizon column is marked "see note" deliberately: their sustained runs did not complete in our testing, but they were cut short by the daily request cap, not by any observed failure of the model. Treat them as proven for short tasks and unproven for long ones.

Not honest, not calibrated — the same thing across every one of these: no free model fabricated a file's contents or a clinical fact in any clean run. That property is why these are listed at all; see the warning below about choosing on benchmark rank.

nvidia/nemotron-3-ultra-550b-a55b:free — use the paid tier

The free tier of this model was usable in only 23 of 386 attempts, with a median of 63s, because its free capacity routes through a single congested upstream. The paid variant of the identical model scored 80/80 perfect and completed 12/12 long runs at 20s. This is a serving problem, not a model problem — but on the free tier it will waste your daily allowance.

What the free tier actually gives you

OpenRouter allows 1,000 free-model requests per day once an account has purchased at least $10 in credits, and 50 per day before that. Free models cost nothing to run; the credit only raises the request ceiling.

The allowance is a single pool shared across every free model, not a per-model quota — measured directly: once it is spent, models the account has never called return remaining=0 with the same reset timestamp as the one that drained it. Adding more free models to your config therefore diversifies what you can run, but does not increase your daily budget. It resets at 00:00 UTC.

A long agent run can use 50-80 requests, so long-horizon work exhausts the pool far faster than short tasks.

One exception worth knowing: a few :free models are served by providers with their own free capacity and do not draw on that shared pool — minimax/minimax-m2.7:free and minimax/minimax-m3:free both keep serving after it is exhausted. If you only configure one free model, make it one of those.

Read before choosing on benchmark rank alone

Some of the highest-ranked and most-used models on OpenRouter are not suitable for clinical work, and OpenMed's own testing is why. A high leaderboard rank measures tool mechanics and reasoning, not calibration — a model can drive the whole 93-tool loop flawlessly and still state a confident, wrong clinical fact. Several providers' terms also forbid medical use outright — Google's Gemini API terms prohibit use "in clinical practice, to provide medical advice"; Z.AI's and Tencent's terms carry comparable restrictions, and Tencent's licence excludes the EU, UK and South Korea entirely. Moonshot's hosted API forbids processing HIPAA-defined PHI, so Kimi models should be self-hosted for patient data. A BAA with an upstream provider does not survive routing through OpenRouter.

DeepSeek V4: listed, but check its output

deepseek/deepseek-v4-flash-0731 and deepseek/deepseek-v4-pro-0813 are listed above for their licence (MIT), their cost and their leaderboard placings — pro-0813 is τ²-Bench #8 and GPQA #21, and flash-0731 is the cheapest entry on the list at roughly $0.0018 per turn.

They are listed despite the DeepSeek family's published AA-Omniscience hallucination rates of 91.7–94.1%, the highest of any model OpenMed recommends. Both pass our tool-discipline probe — that is the point: the probe proves a model can hold the loop and not invent a file's contents, and it does not prove calibration.

In OpenMed's own repeated testing, pro-0813 returned ICD-10 R03.0 instead of I10 on four consecutive runs for a straightforward hypertension presentation, once describing the reading as "stage 2 hypertension" while coding elevated blood pressure without a diagnosis of hypertension — a self-contradiction. flash-0731 answered correctly every time.

If you need a DeepSeek model, prefer flash-0731. With either, treat generated codes, doses and clinical claims as drafts requiring review — which is what OpenMed's reviewer-gated workflow is for.

Mistral was evaluated on request and not added: mistral-medium-3-5 scores 100/100 on our live agent probes but carries an 81.6% published hallucination rate — roughly double the range of every model on this list. A clean probe proves tool mechanics, not calibration.

The full evidence table, including per-model probe results, hallucination rates and licence verification, is in PLANS/openrouter-recommended-models.md.

Not every OpenRouter model can run the agent

OpenRouter hosts image generators, audio models, and chat models without tool calling. OpenMed's tools are the product, so a model that cannot call tools cannot drive it. OpenMed reads each model's capabilities from OpenRouter's model metadata and refuses an incompatible model at startup with the reason, rather than failing part-way through a case:

OpenRouter model 'google/lyria-3-pro-preview' cannot drive the agent:
no tool-calling support (required by the agent).

Capabilities are also enforced per request:

Capability Behavior
Tool calling Required. Models without it are refused at startup.
Text output Required. Image-only and audio-only models are refused.
Image input Scanned documents and screenshots are sent only to vision models. On a text-only model the attachment is omitted and the message says so.
Context window Taken from OpenRouter metadata, so compaction triggers at the model's real limit.
Reasoning effort Clamped to the levels the model advertises, so /model never sends an effort the model rejects.
Max output tokens Clamped to the model's provider cap.

The /model picker shows these as chips per model (vision · 1M ctx · reasoning), and marks a model incompatible before you select it.

Model quality is your call

OpenMed evaluates the curated OpenAI and Anthropic models it ships with. It does not evaluate the OpenRouter catalog. A model that passes the compatibility check can still be a poor fit for clinical work — weaker instruction-following, worse tool discipline, or a smaller effective context than its advertised window. Treat an OpenRouter model as unvalidated until you have evaluated it on your own cases. Every PHI, de-identification, and finalization gate behaves identically regardless of provider.

Prompts you send — including any PHI in them — go to OpenRouter and on to whichever upstream provider serves that model. Check OpenRouter's privacy settings for how your data is handled and whether prompt logging is enabled on your account.


Doubleword

OpenMed Agent 0.5.0 supports three Doubleword inference modes with one key. The built-in default interactive model is DeepSeek V4.1 Flash (deepseek-ai/DeepSeek-V4.1-Flash); a configured model list uses its first entry. Choose the mode based on when you need the answer and whether requests depend on earlier results.

Mode OpenMed entry point Suitable work
Real-time openmed agent --provider doubleword (streaming TUI), or openmed responses submit request.json --service-tier priority (direct CLI) Interactive questions and work where latency matters; the TUI executes agent tools
Async Responses (flex) openmed responses submit request.json Background handoffs, document extraction, dependent review steps that tolerate a delay
24-hour Batch openmed batch submit requests.jsonl Independent overnight chart reviews, research screening, indexing and other backlogs

Doubleword describes flex as lower cost than real-time, and batch as lower cost than flex. background: true changes how you collect a response; it does not make a priority request cheaper. The 24-hour batch window is a completion window, not a required wait. Each dependent batch stage gets its own window. See the async, real-time, and batch service descriptions and current model pricing before choosing a tier.

Doubleword credentials

export DOUBLEWORD_API_KEY="sk-your-doubleword-key"
# Or, instead of exporting the variable, store a per-user secret:
openmed config provider-set doubleword --api-key "sk-your-doubleword-key"
openmed config provider-show

Alternatively, put this entry in ~/.openmed/provider_secrets.json, merging it inside the existing provider object if other providers are configured:

{
  "provider": {
    "doubleword": {
      "options": {"apiKey": "sk-your-doubleword-key"}
    }
  }
}

The top-level key is provider (singular). Replace the placeholder with your key, or use "{env:DOUBLEWORD_API_KEY}" and export that variable. Keep the file private (chmod 600 ~/.openmed/provider_secrets.json) and restart OpenMed after manual edits. provider-set applies those permissions automatically. An exported environment key takes precedence. All three modes use https://api.doubleword.ai/v1; no base URL or OpenAI key is needed. openmed login is unrelated. Export or store the key for consistent behavior. In a development checkout, the CLI also loads .env when the optional python-dotenv dependency is installed; the source demo runner loads the repository .env explicitly.

Try all three Doubleword modes

After configuring your key above, check openmed --version reports 0.5.0 or later. These examples work with the installed CLI and need no repository files or native medical services. Each submission uses credits; the examples use synthetic text and cap each output at 4,096 tokens, including reasoning.

openmed agent --provider doubleword --model deepseek-ai/DeepSeek-V4.1-Flash

Paste: “A synthetic document queue has six items: four reviewed and two awaiting review. Summarize that status in one sentence. No tools needed.” The answer streams in the TUI. /model lets you change models; select the Doubleword provider group.

Save this JSON object as request.json:

{
  "model": "deepseek-ai/DeepSeek-V4.1-Flash",
  "input": "A synthetic document queue has six items: four reviewed and two awaiting review. Summarize that status in one sentence.",
  "max_output_tokens": 4096
}
openmed responses submit request.json --service-tier flex
# Replace <response-id> with the ID printed above. You can close the terminal.
openmed responses wait <response-id> --timeout 300
openmed responses results <response-id>

Submission prints a durable ID. results prints the Responses JSON object; the answer is in its output items. If waiting times out, run wait again with the same ID. To try on-demand priority through the CLI, submit a new request with --service-tier priority; polling does not make it flex-priced.

Save these two lines as requests.jsonl (one complete JSON object per line):

{"custom_id":"queue-a","method":"POST","url":"/v1/chat/completions","body":{"model":"deepseek-ai/DeepSeek-V4.1-Flash","messages":[{"role":"user","content":"Synthetic queue A has six items, four reviewed. State how many await review."}],"max_tokens":4096}}
{"custom_id":"queue-b","method":"POST","url":"/v1/chat/completions","body":{"model":"deepseek-ai/DeepSeek-V4.1-Flash","messages":[{"role":"user","content":"Synthetic queue B has ten items, seven reviewed. State how many await review."}],"max_tokens":4096}}
openmed batch validate requests.jsonl  # offline; no credits spent
openmed batch submit requests.jsonl
# Replace <job-id> with the ID printed above; collect later.
openmed batch wait <job-id> --timeout 300
openmed batch results <job-id> --json

results prints the paths to output.jsonl and errors.jsonl. Open those files and match each row by custom_id; successful answers are under response.body.choices[].message.content. Check errors and finish_reason too. A five-minute local timeout is normal for a batch with a 24-hour window: resume wait or results using the same ID. Never resubmit to check progress.

Use openmed responses list or openmed batch list if you lose an ID. All three examples use the same DeepSeek model. Only the TUI runs the interactive agent loop; direct Responses and batch requests do not execute tools for you. The mode guide explains when to use each path, and batch inference covers overnight NLP/HCC evidence review and result handling.

Doubleword models and picker configuration

With just a key, the picker offers these generation models in this order. Use the exact API IDs, including capitalization, in configuration and request bodies. The display names are not API aliases.

Display name API model ID
DeepSeek V4.1 Flash deepseek-ai/DeepSeek-V4.1-Flash
GLM 5.3 Flash zai-org/GLM-5.3-Flash
GLM 5.3 zai-org/GLM-5.3
DeepSeek V4 Pro 0813 deepseek-ai/DeepSeek-V4-Pro-0813
DeepSeek V4 Flash 0731 deepseek-ai/DeepSeek-V4-Flash-0731
Kimi K3 moonshotai/kimi-k3
Inkling thinkingmachines/Inkling-NVFP4
GLM 5.2 zai-org/GLM-5.2-FP8
Nemotron 3 Ultra 550B A55B nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-NVFP4
Kimi K2.6 moonshotai/Kimi-K2.6
Nemotron 3 Super 120B A12B nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4

These are selectable defaults, not a quality ranking. Check Doubleword's catalog for current availability and pricing. Shared real-time access can be rate limited.

Automatic compaction uses Doubleword's published serving windows for all 11 models: 1M for the DeepSeek, GLM, Kimi K3 and Inkling entries; 262K for Kimi K2.6 and both Nemotron entries. OpenMed uses conservative decimal values from the Models dashboard, with a default 80% compaction threshold and space reserved for output. For example, DeepSeek V4.1 Flash has a published 1M window; 28K tokens is about 3% of it. The /v1/models API currently omits context lengths, so OpenMed carries these limits locally. Dedicated aliases are not assumed to share the same window. See context and compaction for details.

Like OpenRouter, Doubleword accepts a models list under its provider options:

{
  "provider": {
    "doubleword": {
      "options": {
        "apiKey": "{env:DOUBLEWORD_API_KEY}",
        "models": [
          "deepseek-ai/DeepSeek-V4.1-Flash",
          "zai-org/GLM-5.3-Flash",
          "moonshotai/kimi-k3"
        ]
      }
    }
  }
}

Export DOUBLEWORD_API_KEY for this example, or replace its environment reference with a literal key. Merge the entry into an existing provider object instead of replacing other providers. You can also update only the list without changing the saved key:

openmed config provider-set doubleword --models "deepseek-ai/DeepSeek-V4.1-Flash,zai-org/GLM-5.3-Flash,moonshotai/kimi-k3"
openmed config provider-show
openmed --models

The list replaces the built-in picker list; its first entry is the default for openmed agent --provider doubleword. Duplicates and blank entries are removed while order and ID capitalization are preserved. Dedicated aliases are accepted. Explicit --model selections still work outside the picker list. Restart after manual edits, or reopen /model to reload provider configuration.

DOUBLEWORD_MODELS (comma-separated IDs) overrides the file list. An empty or missing effective list falls back to the 11 built-ins. To restore the defaults:

unset DOUBLEWORD_MODELS
openmed config provider-set doubleword --unset-models

The list controls interactive selection only. Responses JSON and batch JSONL still require an explicit model; their request bodies are not rewritten from the picker configuration. The models list does not select an execution tier.

Choosing the Doubleword execution mode

Async is selected per request, not in provider_secrets.json. Doubleword does not read an async, service_tier, or mode setting from that file, or from settings.json. The same stored key works for all modes:

What you set Where it goes
API key DOUBLEWORD_API_KEY or provider.doubleword.options.apiKey in provider secrets
Model, input and output-token limit The Responses JSON object or each batch JSONL row; --model for the TUI
Async flex or on-demand priority openmed responses submit ... --service-tier flex (default) or --service-tier priority
Bulk processing with a 24-hour completion window openmed batch submit requests.jsonl
Wait in this terminal --wait on submission, or responses wait / batch wait later

For example, an overnight backlog of independent, closed-encounter reviews fits a batch: gather NLP/HCC service evidence first, then ask the model to review each record. A single report or the next step of a dependent workflow can use flex when a delay is acceptable. Interactive work belongs in the real-time TUI. See when a 24-hour batch helps.

Doubleword real-time TUI

openmed agent --provider doubleword
openmed agent --provider doubleword --model deepseek-ai/DeepSeek-V4.1-Flash
# Account-specific dedicated aliases work too:
openmed agent --provider doubleword --model yourorg-zai/GLM-5.3

Open /model and select Doubleword → DeepSeek V4.1 Flash. Provider headings remain visible while searching. This is real-time streaming, including tool calls through OpenMed's existing agent loop. Helpers use Doubleword too. Selecting Doubleword in the TUI does not enable flex or batch processing. There is no async mode toggle or Responses/batch job screen in the TUI in 0.5.0; use the CLI commands below for those modes. The interactive adapter currently uses the model's default reasoning behavior; --reasoning-effort does not override it. For explicit model-supported effort settings, use reasoning.effort in a Responses request below.

Always pass --provider doubleword for an explicit model when several provider keys are configured: a namespaced model ID alone can otherwise select OpenRouter. The built-in or configured picker is a starting point; --model accepts other generation models and dedicated aliases your account can serve. Model capabilities and tier availability vary. Embedding models cannot run the interactive agent.

Doubleword's shared self-serve real-time endpoint is intended for development and testing and is rate limited. Production real-time capacity is arranged separately with Doubleword; dedicated deployments use their assigned model aliases on the same API. See dedicated real-time.

Doubleword async and on-demand CLI

Save this JSON object, not JSONL, as request.json:

{
  "model": "deepseek-ai/DeepSeek-V4.1-Flash",
  "input": "Synthetic review backlog: four records have supporting evidence and two have conflicting mappings. Write a short handoff preserving the unresolved items for human review. Do not invent diagnoses or calculate risk scores.",
  "max_output_tokens": 4096
}
# Async flex: return local and remote IDs once Doubleword accepts the request.
openmed responses submit request.json --service-tier flex

# Read status or resume from another terminal, even after restarting OpenMed.
openmed responses list
openmed responses status <response-id>
openmed responses wait <response-id> --timeout 300
openmed responses results <response-id>

# On-demand priority: same lifecycle, different price/latency tier.
openmed responses submit request.json --service-tier priority --wait --timeout 120

Replace <response-id> with the local or remote ID printed by submit or list. Omitting --service-tier selects flex.

What happens after submission:

  1. OpenMed saves the request locally and sends one POST /v1/responses to Doubleword with service_tier: "flex", background: true, and stream: false.
  2. Doubleword returns a remote response ID and processes the work on its servers. You can close the terminal after submission; no local worker needs to stay up.
  3. status, wait, and results retrieve that response with GET /v1/responses/{id}. They reuse the saved ID and do not create another paid inference request.
  4. When complete, results prints the output. It stays pending while the remote work is unfinished; failures and incomplete responses must be handled separately.

Flex is a scheduling and pricing tier. background: true lets a request return an ID while work continues. This CLI also uses background polling for priority requests, which still receive priority scheduling and pricing. Async flex uses /v1/responses; bulk batches use the separate /v1/files and /v1/batches lifecycle. Flex has no 24-hour completion window. Doubleword documents a target of starting flex work within a minute, with exceptions; this is not a completion guarantee. See Doubleword's async contract.

--wait controls local polling, not provider scheduling or price. --poll-interval defaults to five seconds and must be at least one second. Omit --timeout to keep polling; a timeout applies to the polling loop and can overrun by one HTTP request.

Use --json for machine-readable status. results always prints the complete Responses object, including output items, tool calls, usage, errors and incomplete_details. A terminal incomplete response is not a successful answer, even if it contains partial text. Exit codes are 0 for success/status, 1 for terminal failure or incomplete results, 2 for errors, 3 for a local wait timeout, and 130 for interruption.

Closing the terminal or pressing Ctrl-C does not cancel remote work. Resume the saved ID; do not submit the same request again to check progress. If a creation request loses its connection, the local state is submission_unknown (or submitting after a hard interruption). Inspect Doubleword's Responses dashboard and attach the existing remote ID with:

openmed responses adopt <remote-response-id>
openmed responses wait <remote-response-id>

There is no Responses cancellation command in this release. Batch cancellation remains available through openmed batch cancel. An HTTP 402/403/429 indicates a credits, access, or rate-limit issue; automatic paid retries are disabled.

Doubleword structured input and dependent steps

Responses request JSON passes through instructions, message-array input, previous_response_id, tools, tool_choice, text.format, reasoning, and other provider-supported fields. Always set an explicit max_output_tokens. The default model may consume part of that budget for reasoning. Use only the reasoning levels your model supports.

Direct Responses and Batch commands do not execute OpenMed tools. If output contains a function_call, your orchestrator must execute the tool, then submit its result in the next request. A completed inference response is not evidence that the whole workflow finished. For automatic interactive tool execution, use the TUI. For overnight NLP/HCC work, prepare independent service evidence first, then submit one batch of model reviews; see the retrospective backlog example.

Model-supported image input, structured output and tool definitions can be included in request JSON. Use an embedding model only with the embeddings API or embedding batches. For a direct on-demand embedding call using the optional openai dependency:

from openai import OpenAI
from openmed.agent.sdk.provider_secrets import get_provider_config
from openmed.agent.sdk.doubleword import resolve_doubleword_base_url

key = get_provider_config("doubleword").api_key
client = OpenAI(
    api_key=key,
    base_url=resolve_doubleword_base_url(key),
    max_retries=0,
)
client.organization = client.project = None
result = client.embeddings.create(
    model="Qwen/Qwen3-Embedding-8B",
    input=["Synthetic research abstract for indexing"],
)
print(len(result.data[0].embedding))
client.close()

The same explicit client can call responses.create with service_tier="flex" or "priority" and background=False for blocking operation. The durable CLI is preferable when the process may restart or the connection may time out.

Doubleword storage and data boundary

Individual Responses snapshots, submitted request bodies and collected output are stored with owner-only permissions under ~/.openmed/responses/. Override with OPENMED_RESPONSES_DIR or relocate the whole home with OPENMED_HOME. status --no-refresh, results --no-refresh, and list work offline. Batch files remain under ~/.openmed/batches/; TUI sessions use normal session storage. Credentials are not written into job snapshots.

These are hosted inference calls. Direct JSON/JSONL input is sent as supplied and is not transformed by the TUI's PHI setting. Review your inputs and the provider's storage policies before submitting sensitive material. Background Responses retain state remotely for retrieval; store:false is rejected by the resumable CLI. Local deletion does not delete provider-side data.

Doubleword source demos

The demo library entry PLANS/DEMOS/14-doubleword-inference.md covers streaming tool execution and restartable flex/priority requests. Run from a development checkout with agent dependencies and python-dotenv installed:

python3 scripts/demo/run_doubleword_inference_demo.py
python3 scripts/demo/run_doubleword_inference_demo.py --run --mode real-time
python3 scripts/demo/run_doubleword_inference_demo.py --run --mode flex
python3 scripts/demo/run_doubleword_inference_demo.py --run --mode priority
python3 scripts/demo/run_doubleword_inference_demo.py --resume artifacts/doubleword-inference-demo/<run>

The default invocation is offline. Each --run spends credits on synthetic inputs, with a 4,096-token output limit per model turn. Real-time allows up to three agent turns; flex/priority submits one request. Actual native NLP/HCC calls are covered by the separate overnight and stress demos.

To compare the 11 default models, use the source probe:

# Offline: inspect the task and request limits.
python3 scripts/benchmarks/doubleword_model_probe.py
# Live: one synthetic tool workflow per model.
python3 scripts/benchmarks/doubleword_model_probe.py --run

It uses the real Doubleword adapter and production tool schemas, with tool execution restricted to synthetic in-memory fixtures. It checks planning, missing-file recovery, conflicting evidence, structured output and read-back verification, and records latency and token usage under artifacts/doubleword-model-probe/. One run can reveal compatibility failures; it does not establish clinical quality or prove a model is consistently better.


Xiaomi MiMo token plan

For a Xiaomi MiMo token-plan subscription, store your key once. The commands below use a placeholder; replace it with your own tp key:

openmed config provider-set mimo --api-key "tp-..."
openmed agent --provider mimo

Or export the key for the current shell:

export MIMO_API_KEY="tp-..."
openmed agent --provider mimo

Neither method requires openmed login. An exported MIMO_API_KEY takes precedence over the stored key. If MiMo is your only configured provider, openmed also selects the default MiMo model.

Only the API key is required. All four models are preconfigured in Ctrl+M / /model:

  • mimo-v2.5-pro — default; text input.
  • mimo-v2.5 — text and image input.
  • mimo-v2.6-pro — text input.
  • mimo-v2.6-flash — text input.

Select a model at launch with:

openmed agent --provider mimo --model mimo-v2.6-pro
openmed agent --provider mimo --model mimo-v2.6-flash

Keys starting with tp use https://token-plan-ams.xiaomimimo.com/v1. This provider currently supports token-plan subscriptions only. Pay-as-you-go keys starting with sk are rejected with an explanatory message; they are never sent to the token-plan endpoint. No base URL or model list needs to be configured.

MiMo provider secrets example

For a MiMo-only setup, the complete ~/.openmed/provider_secrets.json file is:

{
  "provider": {
    "mimo": {
      "options": {
        "apiKey": "tp-..."
      }
    }
  }
}

Replace tp-... with your token-plan key. If the file already contains other providers, add only the mimo entry inside its existing provider object; keep the other entries. The combined example shows all providers together. Do not add a second top-level JSON object.

To keep the actual key in the environment, use this alternative complete file:

{
  "provider": {
    "mimo": {
      "options": {
        "apiKey": "{env:MIMO_API_KEY}"
      }
    }
  }
}

Export MIMO_API_KEY before launching OpenMed. A reference resolves only when that variable is set in the OpenMed process. When editing the file manually, keep its permissions private (chmod 600 ~/.openmed/provider_secrets.json) and restart OpenMed to reload it. provider-set applies those permissions automatically. Keys belong in this per-user file or the environment, never in project settings or a repository.

MiMo configuration reference

Setting Behavior
Provider ID mimo
Environment variable MIMO_API_KEY; overrides the stored key
Stored key provider.mimo.options.apiKey
Key type Starts with tp; sk pay-as-you-go keys are unsupported
Base URL https://token-plan-ams.xiaomimimo.com/v1; selected automatically
Default model mimo-v2.5-pro
Built-in models mimo-v2.5-pro, mimo-v2.5, mimo-v2.6-pro, mimo-v2.6-flash
Model list Built in; no --models configuration needed
Transport Streaming OpenAI-compatible Chat Completions
Inference boundary Hosted by Xiaomi; the token plan is not local or offline inference

Thinking and attachments

Thinking is enabled by default. Choose Thinking off in the reasoning picker, or pass --reasoning-effort none, to disable it. Other effort values enable thinking without selecting a strength. OpenMed preserves the provider's returned reasoning_content across tool calls and follow-up turns as required by MiMo's conversation protocol. Workflow helper calls use the same token-plan key and selected model, with thinking disabled for their short outputs. OpenMed currently forwards images only to mimo-v2.5, whose image support is established. Images attached with any other native MiMo model are omitted with a notice.

openmed agent --provider mimo --reasoning-effort none  # thinking off
openmed agent --provider mimo --reasoning-effort medium  # thinking on

Startup latency and TUI status

The direct token-plan route and OpenRouter's namespaced MiMo route are separate services. They use different credentials, endpoints, and serving infrastructure, so their time to first output can differ substantially even for the same model family. Fast generation after output begins does not imply a fast first token.

The TUI distinguishes the stages:

Status Meaning
Preparing request… OpenMed is applying request/privacy routing and may run a short same-provider workflow-intent helper call. The primary model stream has not started yet.
Waiting for model response… The primary request has been dispatched, but OpenMed has not received a non-empty reasoning or answer delta.
Model is reasoning… The provider has emitted reasoning_content; OpenMed is rendering it live.

Choosing --reasoning-effort none disables MiMo's generated thinking, but it does not control provider queueing, connection setup, or time to first output. If Preparing request… is slow, the delay is before the primary stream. If Waiting for model response… is slow, the request has reached the provider path and OpenMed is waiting for model output. OpenMed forwards the first non-empty reasoning_content or answer delta to the TUI as soon as it arrives.

Verify, replace, or remove the key

openmed config provider-show
openmed --models
openmed config provider-set mimo --api-key "tp-..."  # replace the stored key
openmed config provider-set mimo --unset-api-key    # remove the stored key

provider-show reports set without printing the key. After removing a stored key, an exported MIMO_API_KEY still takes precedence; use unset MIMO_API_KEY to remove it from the current shell too.

Symptom What to check
MiMo asks for a key Set MIMO_API_KEY or store provider.mimo.options.apiKey; check provider-show for malformed JSON or an unresolved environment reference.
Pay-as-you-go key rejected Use the tp key from your token-plan subscription. sk keys are not supported by this provider yet.
Unsupported model Use mimo-v2.5-pro, mimo-v2.5, mimo-v2.6-pro, or mimo-v2.6-flash. Namespaced xiaomi/... IDs belong to the separate OpenRouter route.
Slow before reasoning appears Check the current TUI status. Preparing request… is pre-stream work; Waiting for model response… means the primary request was dispatched but the provider has not emitted model output. Compare the direct token-plan route with OpenRouter only as separate serving paths.
--base-url or --models rejected MiMo's endpoint and model list are built in. Configure only the key, then select a model with --model at launch.
Replacing the stored key has no effect Check whether MIMO_API_KEY is still exported, then restart OpenMed.

Custom OpenAI-compatible endpoints

The custom provider points OpenMed at servers you operate — vLLM, SGLang, llama.cpp, Ollama, LM Studio, or a company gateway. Serving engines launch one model per endpoint, so the configuration is a list of endpoints, each with its own base URL, optional credential, and the one model it serves. Selecting a model routes the session to the endpoint that declares it. The hosted providers (OpenAI, Anthropic, OpenRouter, MiMo) deliberately have no base-URL configuration — a provider means one thing, and any stored base URL for them is ignored and stripped.

Add one endpoint per model:

openmed config provider-set custom --base-url http://host1:8000/v1 --model "nvidia/Nemotron-3-Super-120B"
openmed config provider-set custom --base-url http://host2:8000/v1 --model "Qwen/Qwen3-235B-A22B" --api-key sk-host2
openmed config provider-set custom --base-url http://host4:8000/v1 --model "poolside/Laguna"

openmed config endpoint-doctor http://host1:8000/v1   # validate each FIRST
openmed agent --provider custom --model "poolside/Laguna"

Re-running with the same --model updates that endpoint (a stored key is kept unless you pass a new one); --remove-model <id> deletes it.

Or in ~/.openmed/provider_secrets.json directly — an endpoints list under the custom section:

{
  "provider": {
    "custom": {
      "options": {
        "endpoints": [
          {"baseURL": "http://host1:8000/v1", "model": "nvidia/Nemotron-3-Super-120B"},
          {"baseURL": "http://host2:8000/v1", "model": "Qwen/Qwen3-235B-A22B", "apiKey": "{env:HOST2_KEY}"},
          {"baseURL": "http://host4:8000/v1", "model": "poolside/Laguna"}
        ]
      }
    }
  }
}

apiKey is optional per endpoint (local servers are commonly keyless) and supports literal or {env:VAR} forms. The same one-object-one-provider-map rule from Editing the file directly applies.

For a quick test without touching the file, environment variables declare one additional endpoint: OPENMED_CUSTOM_BASE_URL, OPENMED_CUSTOM_MODEL, and optionally OPENMED_CUSTOM_API_KEY. They win over a stored endpoint that declares the same model.

What the provider does for you at startup:

  • Routes by model. Each session talks only to the endpoint declaring its model, with that endpoint's own credential — keys never cross hosts, and your OPENAI_API_KEY can never silently travel to your own server.
  • Consults the endpoint's /models listing. A model the server does not actually serve fails immediately, naming what it does serve.
  • Reads the served context window (vLLM/SGLang max_model_len) for automatic compaction. A server that reports none pins the window to unknown — automatic compaction is off for that session (see Context And Compaction) and a hosted catalog namesake can never supply a fabricated number.
  • Sends the safe request shape. Chat Completions only, temperature omitted, streamed usage requested, and the runtime guard refuses a turn whose "text" is really an unparsed tool call.
  • Refuses cleartext keys to remote hosts — plain http:// with a stored key is allowed only for localhost and private networks, enforced both in the CLI and again at resolution so a hand-edited file cannot bypass it.

Configured models appear as a Custom endpoint group in the /model picker, each row naming the host that serves it.

What custom models are not

OpenMed cannot vouch for a model it has never evaluated. Tool-calling discipline, clinical quality and safety behaviour are yours to validate — endpoint-doctor proves the plumbing, not the medicine. Per-model launch flags for the common engines, including the exact --tool-call-parser value (which fails silently when wrong), are in PLANS/model-serving-matrix.md.


Checking an endpoint before you trust it

openmed config endpoint-doctor tells you whether an OpenAI-compatible server — your own vLLM, SGLang, llama.cpp or Ollama, or a gateway — can actually drive the agent. It makes no changes and is safe to run against anything.

openmed config endpoint-doctor http://localhost:8000/v1

A healthy endpoint:

Endpoint: http://localhost:8000/v1
Model:    openmed-local

  [PASS] reachable: Connected to http://localhost:8000/v1.
  [PASS] auth: Credentials accepted.
  [PASS] models: 1 model(s) served: openmed-local
  [PASS] context: Context window 262,144 tokens.
  [PASS] streaming: Server streamed a response (SSE).
  [PASS] tools: Model emitted a streamed tool call.
  [PASS] tool_call_index: Tool-call deltas carry `index`.
  [PASS] temperature: Model accepts `temperature`.

Result: usable by OpenMed (2.4s)

Exit codes are 0 usable, 1 not usable, 2 bad input — so it works in CI. --json emits the same report as JSON. --model picks a specific model, and --quick sends one tool instead of OpenMed's full 93-tool schema (faster, but it will not surface large-tool-array failures).

What it checks, and why

Check Catches
reachable Wrong port, wrong scheme, DNS typo, TLS mismatch — each reported distinctly
auth Missing or wrong API key (self-hosted servers 401; public catalogs do not)
models Server up but serving nothing, or a listing that is not OpenAI-shaped
context A window too small for OpenMed's ~25,300-token per-turn overhead
streaming Gateways that buffer and break SSE — OpenMed only streams
tools Tool calling disabled, unsupported, or rejected at 93 tools
leaked_tool_call The safety check — a wrong tool-call parser returning tool syntax as prose
tool_call_index Streamed deltas missing index, which breaks parallel calls
finish_reason Shapes the runtime rejects, which would crash on the first tool turn
temperature Reasoning models that reject the parameter

The failure that matters most

If the server's tool-call parser is missing or wrong, it returns the tool call as plain text with tool_calls empty and finish_reason: "stop". There is no error, and in a clinical agent that text is indistinguishable from real content. The doctor fails the endpoint and names the fix:

  [FAIL] leaked_tool_call: The model returned tool syntax as PLAIN TEXT
         (XML <tool_call> (Qwen 3.5+, GLM, MiniMax)) with no tool_calls
         (finish_reason='stop'). The server is not parsing tool calls.
          -> The tool-call parser is missing or wrong. vLLM needs BOTH
          -> --enable-auto-tool-choice AND --tool-call-parser <name> (there is no
          -> 'auto'); SGLang needs --tool-call-parser. Use the value for this model
          -> family — e.g. Qwen 3.5+ is `qwen3_coder`, NOT `hermes`.

It also catches the harder version of this, where a parser matches the first tool call and leaks the rest as text — the case that otherwise looks healthy.

Context: the quiet killer

OpenMed sends ~93 tool schemas (~21,000 tokens) plus a ~4,500-token system prompt on every turn. A window below that truncates every request:

  [FAIL] context: Context window is 16,384 tokens; OpenMed's tool schema alone
         is 20,791. Every request would be truncated.
          -> Raise the served context to at least 65,536: vLLM --max-model-len 65536,
          -> SGLang --context-length 65536, llama.cpp --ctx-size 65536 (its default is
          -> 4096), Ollama OLLAMA_CONTEXT_LENGTH=65536 before `ollama serve`.

llama.cpp defaults to 4096, Ollama auto-scales and can land at 4k, and Google's own vLLM recipe for Gemma 4 ships 16384 — all below the floor.

Serving your own model

Per-model launch flags, including the exact --tool-call-parser value for each family, are in PLANS/model-serving-matrix.md. The short version for the recommended single-GPU model:

vllm serve Qwen/Qwen3.8-27B \
  --served-model-name openmed-local \
  --enable-auto-tool-choice --tool-call-parser qwen3_coder \
  --reasoning-parser qwen3 \
  --max-model-len 65536 \
  --host 0.0.0.0 --port 8000 --api-key sk-openmed-CHANGEME

openmed config endpoint-doctor http://localhost:8000/v1

Credentials

Prefer OPENMED_ENDPOINT_API_KEY over --api-key: arguments are visible in ps and shell history. The doctor refuses to send a key over cleartext http:// to a non-local host, strips any user:pass@ from the URL before printing anything, and never echoes the key into its output.


Web search is off by default on every provider, including OpenAI and ChatGPT OAuth, and is not a provider capability — it runs client-side so it behaves identically on Anthropic, OpenRouter, MiMo, Doubleword, or a local model. Enabling it sends search queries to a third-party vendor, so it is an explicit operator decision. See Web Search.

How a provider is selected

The TUI defaults to gpt-6.1-sol with medium reasoning. When multiple providers are configured, the model you select determines the provider:

  • claude-* models use Anthropic when Anthropic credentials are available.
  • GPT-6.1 Sol and GPT-6 models use ChatGPT OAuth first when ~/.openmed/auth.json exists, then fall back to the OpenAI key. Explicit GPT-5.5 selections prefer an API key when available.
  • Bare mimo-v2.5-pro, mimo-v2.5, mimo-v2.6-pro, and mimo-v2.6-flash IDs use MiMo token-plan credentials. If MiMo is the only configured provider, an ordinary launch selects mimo-v2.5-pro.
  • Namespaced model ids (vendor/model, such as google/gemini-3.7-flash) use OpenRouter when its key is configured. Without an OpenRouter key, a configured Doubleword key can select Doubleword for these IDs. Use --provider doubleword explicitly to select Doubleword when both keys are present.
  • Selecting the Doubleword group in /model, or passing --provider doubleword, selects its real-time adapter. Its default model is deepseek-ai/DeepSeek-V4.1-Flash; async and batch jobs use their own CLI commands.
  • With no credentials at all, the native fallback remains available.

Choose the model at launch, or at runtime with Ctrl+M or /model:

openmed agent --model gpt-6.1-sol --reasoning-effort medium
openmed agent --provider openrouter --model qwen/qwen3.8-27b
openmed agent --provider doubleword --model deepseek-ai/DeepSeek-V4.1-Flash

--provider forces a specific provider; omit it to let the model decide.

Reasoning effort

The picker exposes model-specific values. GPT-6.1 Sol and GPT-6 Astra offer low, medium, high, xhigh, and max; GPT-6 Sol and Luna also offer none. GPT-5.5 stops at xhigh. Supported Anthropic models have their own effort levels. extra_high remains accepted as a compatibility alias and resolves to xhigh on OpenAI models.

For OpenRouter models the effort is clamped to the levels that model actually advertises, so a selection is never rejected by the provider.

Provider keys vs native medical services

OpenAI, Anthropic, OpenRouter, MiMo, and Doubleword keys are model-provider credentials. OpenMed's native medical services — extraction, PII, de-identification, terminology, coding — are a separate service plane with their own configuration. See Native Medical Services.