You decide your coding agent should talk to one vendor only: for cost, for a compliance rule, or because the code must stay on your laptop. You open ~/.omp/agent/config.yml, point default, plan, slow, and smol at that vendor, and call it done.
Setting those roles is not enough. In omp (Oh My Pi), a role says which model you prefer for a kind of work, not which providers may see your code. Roles you did not set, a project file in the repo you opened, an API key exported in your shell, and the advisor role can all route a session somewhere else.
This article covers what the docs say, what I tested on November 3, 2026 (omp 18.13.3; as of that date the latest GitHub release was v18.16.0, published November 2, 2026), and role maps for seven common setups. For how roles, hashline edits, and subagents work, see the omp tour. This piece is about where the boundary actually is.
TL;DR
modelRolesis a preference, not an allowlist. Per the omp roles docs, a role you leave unassigned resolves automatically from any available, authenticated model. An unassignedadvisor“seeks a separate strong reasoning model rather than inheritingdefault.”disabledProvidersis the boundary. In a scratch project it cutomp modelsfrom 6 providers to 1. With the mergeddefaultrole still naming a disabled provider, the main response reported the remaining one.- Layers merge, they do not reset. Mappings deep-merge and arrays replace, so your global roles survive under a project file that sets only one. A
--profileresets settings but not shell environment variables. - The advisor is a data-egress decision. It sends prompts, agent output, and tool results to its own provider. In a one-provider or offline setup, pin it inside the boundary and check for roster files that name another model.
- Check every catalog kind. Plain
omp modelslists chat models only.omp models --kind allalso showed 22websearch runners and 14localrunners here; both can be disabled. - Verify with three checks, shown below, instead of trusting the YAML. This is a model-routing boundary, not a network firewall.
The assumption that fails
The community examples I reviewed emphasize role maps and fallback chains rather than provider exclusion. DeepakNess’s setup (updated August 12, 2026) maps default, plan, vision, and tiny. The developersdigest overview (November 2, 2026) suggests starting with two roles. Both are fine for the goal they state: a cheap driver with a stronger planner and a fallback chain.
Neither is trying to restrict providers, and the docs say why that would not work. Here is the relevant text from the roles page:
With an empty
modelRolesmap, omp resolves roles from the models that are available and authenticated.
Setting four roles leaves the rest of the role list (commit, vision, tiny, designer, task, advisor, plus any custom role from an extension or agent) on automatic. Automatic selection draws on whatever is available: logged-in cloud providers, keyless local engines, and credentials from your shell. Some roles first reuse default. Pinning every role makes routing predictable. Excluding a provider is a separate job, and disabledProviders does it.
What I tested
I used a scratch directory with a project-level .omp/config.yml that sets one role and disables five providers. My global config (not the repo’s) names github-copilot/* for most roles and google-antigravity/* for a role called web.
# /tmp/omp-probe/.omp/config.yml
modelRoles:
plan: anthropic/claude-opus-5-5:high
disabledProviders:
- github-copilot
- cursor
- opencode-zen
- opencode-go
- google-antigravity
| # | Command | Result on omp 18.13.3 |
|---|---|---|
| 1 | omp models without the project file | 6 providers: anthropic (19 models), cursor (81), github-copilot (88), google-antigravity (11), opencode-go (53), opencode-zen (122) |
| 2 | omp models with the project file | 1 provider: anthropic (19) |
| 3 | omp config get modelRoles with the project file | plan is the project’s Anthropic value; default, slow, smol, advisor, code-review, pr-fixes, fast still name github-copilot/*, and web still names google-antigravity/* |
| 4 | omp -p --mode json "reply with the single word ok", then the same prompt in text mode | The JSON run used provider: anthropic, model: claude-opus-5-5. The text-mode run printed prewalk disabled — provider "github-copilot" is disabled and answered ok |
| 5 | omp --profile probe config get modelRoles | {} |
| 6 | omp --profile probe models, with OPENCODE_API_KEY set in my shell | opencode-go (53 models) listed anyway |
| 7 | the same, run as env -u OPENCODE_API_KEY omp --profile probe models | No models available. Set API keys in environment variables. |
| 8 | omp models --kind all from the repo directory (no disabledProviders set) | 8 groups: anthropic (19), cursor (81), github-copilot (88), google-antigravity (13), local (14), opencode-go (53), opencode-zen (124), web (22) |
| 9 | omp models --kind all in a second scratch project whose only setting is disabledProviders: [web, local] | web and local no longer listed; the other six groups unchanged |
What this shows, with its limits:
disabledProvidersremoves providers from selection (rows 1 and 2), matching the providers doc: “disabledProvidersis checked before credentials.”- Roles you do not touch keep their old values (row 3). The settings docs say mappings deep-merge while scalars and arrays replace. Disabling
github-copilotdid not rewrite the roles that name it. - With
defaultstill naming disabled Copilot, the main response reportedanthropic/claude-opus-5-5(row 4). This single observation neither identifies the resolution path nor audits auxiliary requests. The docs say omp “skips that route or shows an actionable model/authentication error” when no usable model satisfies a role. I did not read the resolution code, so I cannot give you the rule for every role. - A profile resets settings, not the process environment (rows 5 to 7).
--profilegives “isolated auth, sessions, settings, and caches” per the CLI reference, but an exported key still makes its provider available. - Chat models are not the whole catalog (rows 8 and 9). Search (
web), on-device (local), image, and judge runners sit under other kinds, anddisabledProvidersremoves them too.
The four layers
| Layer | Setting | What it controls | Is it a boundary? |
|---|---|---|---|
| Preference | modelRoles | Which model a role picks first | No. Unset roles auto-resolve. |
| Boundary | disabledProviders | Which providers can be selected at all | Yes, but it is a denylist you maintain. |
| State | --profile, project .omp/config.yml | Which settings, auth, and sessions are loaded | Only for what they hold. Env vars still apply. |
| Recovery | retry.fallbackChains | Where a failing role moves next | No. Candidates still pass through disabledProviders. Remove out-of-policy entries so recovery works as you expect. |
Two details on the last row. A default chain is “also used for configured roles that do not have their own chain.” And an explicit [] disables fallback for that role. Model-kind roles such as web, image, speech, dictation, and judge resolve their own named chains and never use default. tiny is an ordinary chat-style role, so set tiny: [] when you want no fallback for it.
There is no provider allowlist. enabledProviders controls foreign discovery sources (rules and skills read from other agents), not model providers, so disabledProviders plus a check that the catalog lists only what you expect is the closest thing you have. A new provider that appears later, because you export a new key, is allowed until you list it.
Arrays replace across layers, which makes one hazard easy to miss. If the global file says disabledProviders: [openai, github-copilot] and a project file says disabledProviders: [google], the effective list inside that project is [google]. The two globally excluded providers are available again.
Role maps for seven setups
Selectors marked “seen” appeared in omp models on November 3, 2026. Everything else needs omp models find <name> on your machine before you rely on it. Since v18.14.0 a near-miss provider-qualified selector fails instead of picking a sibling model, per the omp tour.
Single-provider setups
| Setup | Provider set | default | plan / slow | smol | advisor | Status |
|---|---|---|---|---|---|---|
| Anthropic only | one | anthropic/claude-sonnet-5-5:high | anthropic/claude-opus-5-5:high | anthropic/claude-haiku-5-5:medium | anthropic/claude-opus-5-5:medium | Selectors seen |
| OpenAI only | one | openai/gpt-6.1-sol:medium | same model at :high / :xhigh | openai/gpt-6-luna:low | openai/gpt-6.1-sol:high | Not verified: no OpenAI login here. A third-party gateway lists openai/gpt-6.1-sol. |
| Ollama local | ollama plus local | ollama/qwen3.8:27b | same | same | ollama/qwen3-coder:30b | Not run: no Ollama on this machine |
Pros
- Smallest set to audit. One provider ID (or
ollamapluslocal) is easy to compare againstomp models --kind all. - The advisor stays inside the boundary. Every map pins
advisorto the same provider, so the second reader does not add a vendor. - Ollama keeps requests on a machine you control, provided
OLLAMA_BASE_URLandOLLAMA_HOSTare unset or point at loopback. - Anthropic only is the one I could observe. Its selectors appeared in
omp models, and it was the provider that remained in the denylist probe.
Cons
- No cross-vendor recovery. A fallback chain entry that names another vendor is out of policy here. Keep chains inside the provider or set
[]. - An allowed provider ID does not name the server.
models.ymlendpoint overrides can send it elsewhere. - OpenAI and Ollama are drafts. I had no OpenAI login and no Ollama install, so neither map was run.
- Ollama needs extra checks. Context length has to be set and confirmed with
ollama ps, and the two models are 18 GB and 19 GB downloads. Those are download sizes, not memory needs.
Gateway setup: Chinese models through one key
| Setup | Provider set | default | plan / slow | smol | advisor | Status |
|---|---|---|---|---|---|---|
| Chinese models | one gateway | opencode-go/deepseek-v4.1-flash:high | opencode-go/glm-5.3:high / opencode-go/kimi-k3.1-pro:high | opencode-go/minimax-m3.1-flash:low | opencode-go/qwen3.8-max:high | Selectors seen, all via the opencode-go gateway |
Pros
- One provider ID, several model labs.
opencode-golisted 53 models here, and every selector in this map was seen. - One provider to allow. Roles, advisor, and fallback can all stay inside
opencode-go, so the denylist check is the same as for a single-provider setup.
Cons
- “Chinese models only” through
opencode-gostill sends code to the gateway. The model lab and the vendor in the path are different parties. Direct providers (deepseek,moonshot,zai,minimax, and others in the provider table) need their own keys, and DeepSeek’s integration guide, written for omp v14.5, says a custommodels.ymlcompat block was needed then. I did not check whether 18.x still needs it. - Thinking levels differ per model. In my
opencode-gocatalog on November 3, 2026,kimi-k3.1-pro,deepseek-v4.1-flash, andglm-5.3listlow, high, maxwith nomedium. - Vision needs a different model.
glm-5.3anddeepseek-v4-prolist no image input;kimi-k3lists images.
Mixed-provider setups: Fast, Exploration, Balanced
| Setup | Provider set | default | plan / slow | smol | advisor | Status |
|---|---|---|---|---|---|---|
| Fast (candidate) | mixed | opencode-go/deepseek-v4.1-flash:low | anthropic/claude-sonnet-5-5:medium | opencode-go/minimax-m3.1-flash:minimal | anthropic/claude-haiku-5-5:medium, with advisor.syncBacklog: "off" | Selectors seen; unbenchmarked |
| Exploration (candidate) | mixed | anthropic/claude-sonnet-5-5:high | same model | opencode-go/qwen3.8-flash:low (1M context) | github-copilot/gpt-6.1-sol:medium | Selectors seen; scouts via task.agentModelOverrides.scout: "@smol" |
| Balanced (candidate) | mixed | opencode-go/deepseek-v4.1-flash:high | anthropic/claude-opus-5-5:high | anthropic/claude-haiku-5-5:medium | openai/gpt-6.1-sol:medium | Selectors seen, except openai/*; unbenchmarked |
Pros
- Each role gets its own model. A cheap or large-context driver can sit next to a stronger planner and advisor, which is the goal the community setups state.
- Selectors were seen for Fast and Exploration, and for Balanced except the
openai/*advisor.
Cons
- Not a boundary setup. Fast mixes an aggregator and Anthropic; Exploration adds
github-copilot; Balanced adds OpenAI. Every provider named here has to stay out ofdisabledProviders, and each one sees what its role sends. - Speed is unmeasured here. The only public number I found is a personal 10-task eval (three runs per configuration, private tasks) reported by haimaker on October 14, 2026, run on Pi 0.85.1 (not omp); see the original leaderboard and its methodology. It listed DeepSeek V4.1 Flash at
maxeffort at 93.3% task success and 4 minutes 30 seconds per task, against Sonnet 5.5 athigheffort at 100% and 46 seconds. Cheap per token did not mean quick per finished task there. Measure on your own repo before trusting “fast.” - Balanced depends on an unverified selector.
openai/gpt-6.1-solwas not checked against a real OpenAI login.
One caveat for every map
Ignore YAML that uses api_base or a top-level roles:. One otherwise useful cost write-up (September 22, 2026) does. The documented keys are baseUrl in models.yml and modelRoles in config.yml.
Anthropic only: the boundary version
Putting a role map in config.yml is the easy half. The boundary half is the denylist and the check. The denylist below is illustrative, not exhaustive: add every non-target provider that omp models --kind all shows in your environment, including custom providers, and check the effective list from the repo you will work in. A project-level disabledProviders array replaces the global one. Also inspect models.yml for endpoint overrides, because an allowed provider ID does not by itself say which server receives the requests.
# ~/.omp/agent/config.yml
modelRoles:
default: anthropic/claude-sonnet-5-5:high
plan: anthropic/claude-opus-5-5:high
slow: anthropic/claude-opus-5-5:high
smol: anthropic/claude-haiku-5-5:medium
commit: anthropic/claude-haiku-5-5:low
vision: anthropic/claude-sonnet-5-5:medium
advisor: anthropic/claude-opus-5-5:medium
# Optional: pin every other role in `omp config get modelRoles`
# (task, tiny, code-review, ...) for predictable routing. The denylist enforces the exclusion.
retry:
fallbackChains:
default:
- anthropic/claude-sonnet-5:high
disabledProviders:
- github-copilot
- cursor
- opencode-zen
- opencode-go
- google
- google-antigravity
- web # also removes the 22 search runners; drop this line if you want omp web search
- openai
- openai-codex
Ollama local: use a profile and say what you did not test
For an offline or private setup, start from nothing: a separate profile, no project file, and the cloud providers disabled. The intended provider set here is ollama plus local (on-device tiny models), not a single provider ID.
# Prepare while online: pull both Ollama models and the on-device tiny model.
ollama pull qwen3.8:27b
ollama pull qwen3-coder:30b
omp --profile local tiny-models download lfm2.5-230m
# Optional shortcut. This appends a shell function to your shell startup file.
omp --profile local --alias omp-local
# The env -u form affects only this one invocation. Exported keys and .env files still apply.
env -u ANTHROPIC_API_KEY -u OPENAI_API_KEY -u OPENCODE_API_KEY omp --profile local models --kind all
# ~/.omp/profiles/local/agent/config.yml
modelRoles:
default: ollama/qwen3.8:27b
plan: ollama/qwen3.8:27b
slow: ollama/qwen3.8:27b
smol: ollama/qwen3.8:27b
commit: ollama/qwen3.8:27b
advisor: ollama/qwen3-coder:30b
tiny: local/lfm2.5-230m
retry:
fallbackChains:
default: []
tiny: []
disabledProviders:
- anthropic
- openai
- openai-codex
- google
- google-antigravity
- github-copilot
- cursor
- opencode-zen
- opencode-go
- web
What I confirmed: Ollama’s library lists qwen3.8:27b (18 GB download) and qwen3-coder:30b (19 GB) in listings checked November 3, 2026 (the Qwen3.8 page shows an October 30, 2026 update), and omp auto-discovers a keyless Ollama at http://127.0.0.1:11434 per the providers doc. Those sizes are download sizes, not memory needs. Check that OLLAMA_BASE_URL and OLLAMA_HOST are unset or point at loopback; otherwise “local” may be a remote server. I have not run this profile. The colon in qwen3.8:27b sits next to omp’s :thinking suffix, so run omp --profile local models ollama first and copy the selector it prints.
I left vision unset because I have not confirmed a local model with image input; the roles docs say an unusable role is skipped or reports an error.
Ollama’s context setting needs an explicit check. Its own pages disagree: the FAQ says “By default, Ollama uses a context window size of 4096 tokens.” The context-length guide sets 4k, 32k, or 256k by VRAM tier (under 24 GiB, 24 to 48 GiB, 48 GiB and up). The Modelfile page labels num_ctx “(Default: 2048)”. Do not guess. Start the server with OLLAMA_CONTEXT_LENGTH=32768 ollama serve and confirm with ollama ps.
The advisor is a data-egress decision
The advisor is a second model that reviews each turn. The advisor docs are direct about what it receives: “your prompts, the main agent’s output and reasoning, and relevant tool results; investigative tools can expose any files they read.” Session-managed secrets are obfuscated first, but the docs say that is not a substitute for choosing a provider allowed to see the repository.
On my install omp config list shows advisor.enabled = true, advisor.syncBacklog = 3, and advisor.immuneTurns = 3. The docs describe syncBacklog as defaulting to off, so trust your own omp config list. A value of 3 can pause the main agent for up to 30 seconds when review falls three turns behind, which is the wrong default for the fast setup.
Three consequences for single-provider setups:
- Set
modelRoles.advisorinside the boundary, as every map above does. - If you do not want a second reader, set
advisor.enabled: false. - A
WATCHDOG.ymlorWATCHDOG.yamlroster can give each advisor its ownmodel, so one entry can quietly name another vendor. The docs say omp reads these from the active agent directory, the current directory,.omp/, and each parent up to the Git root. After a restart,/advisor statusshows the models actually in use.
Verify the boundary in three checks
Run every command from the repo you will work in, with the same profile you intend to use. For the Ollama example, replace each omp with omp --profile local. Project files merge on top of global ones, so a check from another directory proves little.
# 1. Which providers can this session select, across every catalog kind?
omp models --kind all
# 2. What does the merged config say, including roles you never set?
omp config get modelRoles
omp config get disabledProviders
omp config get advisor.enabled
# 3. Smoke test: which provider and model answered the main request?
smoke_file="$(mktemp)"
omp -p --no-session --mode json "reply with the single word ok" > "$smoke_file" \
&& grep -o -E '"(provider|model)":"[^"]+"' "$smoke_file" | sort | uniq -c
Pass criteria: check 1 lists only your intended providers, check 2 shows a disabledProviders list that matches your policy, and check 3 exits with status 0, answers ok, and names the expected provider and model. Provider and model fields alone do not prove the request succeeded, so read the transcript in $smoke_file if anything looks off, then delete it if it holds sensitive output. Inspect advisors separately with /advisor status. Use these checks to see which providers are selectable and to observe one execution path, not to prove network isolation. Repeat them after changing credentials, project settings, endpoint overrides, or omp versions.
Common mistakes
- Setting roles and stopping. Run
omp models --kind all. If it lists providers you did not choose, you have a preference, not a boundary. - Trusting a profile to isolate credentials. It isolates stored auth, not exported variables.
- Fallback chains to another vendor. The denylist still applies, but a stale entry means recovery fails when you need it. Keep every entry inside the boundary, or use
[]. - Confusing stale role values with a failed boundary. Clean up inherited roles for predictable routing, and use the effective denylist and catalog listing to check exclusion.
Related Reading
- Oh My Pi (omp.sh): A Quick Look at the Hardened Coding Agent CLI: roles, hashline edits, and subagents.
- Low Effort on the Current Claude Line: pinning
slow,default,smol, and the advisor within one model family. - Keep Secrets Out of the Agent: Brokering, Rotation, and Infisical: why exported credentials follow the process into every agent session.
Sources
- omp: Model roles (undated, opened November 3, 2026): automatic role resolution, role syntax, fallback chains.
- omp: Advisor models (undated): what the advisor receives,
syncBacklog,WATCHDOG.yml. - omp: Settings, omp: Environment variables, and omp: CLI reference (undated): merge rules, credential order, profile isolation.
- omp: Providers and omp: Models (undated,
mainbranch):disabledProviders, local engine discovery, provider IDs, themodels.ymlschema. - oh-my-pi releases: v18.16.0 released November 2, 2026; v18.14.0 on October 29, 2026 made provider-qualified selectors exact.
- DeepSeek: Using DeepSeek with Oh My Pi (undated; written for omp v14.5): custom
models.ymlcompat for DeepSeek V4. - DeepakNess: Setting Up and Testing OMP (August 2, 2026, updated August 12, 2026): a hands-on role map and fallback chain.
- developersdigest: Oh My Pi roles, advisor and fallbacks (November 2, 2026): overview, secondary.
- Adrian Wyssmann: Optimizing AI Agent Costs (September 22, 2026): cost routing; its YAML does not match the documented schema.
- haimaker: Best models for Pi coding agent (October 14, 2026): third-party eval reported on Pi 0.85.1; a gateway vendor’s page. Original: Ibragim’s leaderboard and methodology.
- Ollama: Context length, FAQ, and Modelfile (undated); qwen3.8 (updated October 30, 2026) and qwen3-coder library pages.
- Local runs: omp 18.13.3 on macOS arm64, November 3, 2026 (the table above). Web pages were opened on the same date.