---
title: Vault Operator v2.11
---

# Vault Operator v2.11: Provider-only setup, advisor pattern, prompt slim-down

The v2.11 release reshapes how Vault Operator talks to language model providers. Three changes drive the line:

1. **Provider-only setup.** The settings tab "Models" is replaced by "Providers". You configure a provider once (API key or OAuth), and the plugin discovers its models and auto-classifies them into three tiers: Budget, Main, and Frontier.
2. **Advisor pattern.** The chat loop runs on the Main tier by default. When the agent hits a hard synthesis step it escalates to the Frontier tier via a new `consult_flagship` tool. Per-task budget is three calls; the answer is hard-capped at 3000 tokens.
3. **Prompt slim-down.** When you run in Auto mode without plugin-skill activity, the system prompt drops the verbose cost-heuristics section and the plugin-skills directory, shaving roughly 30 percent off the prompt. Both sections flip back to full when you trigger a plugin tool or override the model.

The chat-header model dropdown gains a search input so providers like OpenRouter (200+ models) and Bedrock (50+ models) are finally usable.

## Highlights

### Provider-only settings (FEAT-26-03)

Settings -> Providers shows one row per configured provider with a name, a sign-in-status indicator, an "Active" radio, an enable toggle, a cog icon for the detail modal, and a remove button. The legacy "Models" tab is hidden from the navigation. Each detail modal carries:

- A provider-type dropdown at the top (Anthropic, OpenAI, OpenRouter, Azure, Bedrock, Gemini, GitHub Copilot, ChatGPT-OAuth, Ollama, LM Studio, Custom, Kilo Gateway)
- Type-specific authentication fields (API key, AWS region + auth-mode, OAuth sign-in)
- An optional base URL override with the SDK default as placeholder
- A "Refresh" button that pulls the provider's model list and auto-classifies it
- Three tier-mapping slots with colored Budget / Main / Frontier badges and per-slot overrides
- A remove button in the danger zone

### Automatic tier classification (FEAT-26-02)

`ModelTierClassifier` matches discovered model IDs against family patterns (claude-opus, gpt-5, gemini-pro, deepseek-r1, ...) and assigns a tier. Unknown models go through capability-based fallback (context window, max output tokens) and, for OpenRouter, through pricing thresholds (>50 USD/M completion = Frontier, 5-50 = Main, <5 = Budget). Discovery caches results for 24 hours; the Refresh button forces a re-pull.

### Advisor pattern (FEAT-26-01)

The chat loop runs on the Main tier by default (was: whatever the user picked manually). A new `consult_flagship` built-in tool lets the agent escalate one synthesis step to the Frontier tier when the Main tier struggles. Hard limits:

- Maximum 3 calls per task. The fourth call returns `advisor budget exhausted`.
- Advisor subagent runs read-only (no write/edit/delete/MCP/spawn).
- Advisor output hard-capped at 3000 tokens.
- If your active provider has no Frontier-tier model, the tool is filtered out of the schema entirely; the agent never sees it.
- After two consecutive errors, the agent's prompt gets a one-line "consider consult_flagship" reminder (only when the tool is available).

The cost log shows `mode=auto` for normal calls, `mode=advisor` for advisor escalations, `mode=subagent` for research-profile spawns, and `mode=override` when the user picks an explicit model in the chat header.

### One-shot automatic migration (FEAT-26-04)

When you upgrade from v2.10.x, the plugin migrates your existing `activeModels[]` into the new `providerConfigs[]` shape on first load. The migration:

- Groups your models by provider type
- Picks the first enabled model's credentials as the provider's auth
- Classifies every enabled model into a tier
- Sets `activeProviderId` based on your existing `activeModelKey`
- Preserves the original list in `legacy_active_models_backup` for 30 days

A one-shot notification modal summarises what was migrated and flags anomalies (multi-auth setups, missing Frontier slot, custom endpoints that need manual tier assignment). The "Open settings" button takes you straight to the new tab.

### Chat-header model picker with search (FEAT-26-05)

The chat-header model dropdown is replaced by a popover with a search input. The first option is always "Auto" (advisor pattern via Main tier with on-demand Frontier escalation). Below it are the discovered models of the active provider, each with a colored tier badge. Type "opus" + Enter selects the first matching Opus variant. Bedrock and OpenRouter users finally have a usable picker.

The mode switcher (Agent/Ask dropdown) is removed from the chat header. The backend mode system stays functional (`switch_mode` tool, ModeService, custom modes in settings); only the UI trigger disappears. The chat always starts in Agent mode.

### Prompt slim-down (FEAT-26-06)

Two system-prompt sections are now conditional:

- **Cost-aware heuristics**: 500-token lean variant in Auto mode (vs. 1435 tokens full). Full variant returns when the user picks an explicit Frontier model.
- **Plugin skills directory**: 30-token hint replaces the 5000-token full directory until the agent uses a plugin tool or the user `@`-mentions a plugin. After that, the full directory injects for the rest of the task.

Both conditional sections live below the cache breakpoint so cache stability is preserved. Effective prompt size for Standard Auto sessions drops by roughly 30 percent.

## Security audit (AUDIT-027)

H-1 from AUDIT-027 was resolved during the audit pass: `providerConfigs[]` and `legacy_active_models_backup` credentials are now encrypted at rest via the existing `SafeStorageService`. Pure-function walker in `src/core/security/providerCredentialCrypto.ts` plus 11 regression tests including a contract test that locks the credential-key set.

L-1 (multi-auth Provider-ID discriminator using API-key bytes) is deferred to IMP-26-04-01 as a defensive cleanup, no exploit path.

`npm audit --omit=dev` reports zero vulnerabilities across all production dependencies. Release verdict: Green.

## Migration notes

- The legacy "Models" tab is hidden but still accessible programmatically (the OAuth sign-in flow still uses it as a bridge). It re-appears for users who configure new OAuth providers until the inline OAuth flow lands in a later release.
- The migration runs automatically. Settings still showing pre-2.11 model entries will be rewritten on first plugin load.
- The mode-switcher button is gone from the chat header. If you were using "Ask" mode regularly, switch the per-mode model in Settings -> Modes; the mode is still active in the backend, you just trigger it via the `switch_mode` tool or by setting a default mode in settings.
- The `defaultMainModelTier` setting controls which tier the main loop runs on; default is `mid` (Main). Power users running on Frontier-class budgets can flip it to `flagship` as a rollback if the advisor pattern produces too many calls.

## v2.11.1 -- Review-bot pass

Maintenance patch in response to the Obsidian community plugin scanner findings on the v2.11.0 release:

- **CSS multicolumn warning cleared.** The provider-detail tier-row used `column-gap: 18px` on a grid element. The scanner's keyword heuristic flags every `column-*` property as multicolumn even when it applies to a grid layout. Renamed to the unprefixed `gap` shorthand. Identical visual result, no multicolumn keyword for the scanner to match.
- **Two unused `!important` declarations removed.** `.mcm-warn` and `.mcm-hint` carried `grid-template-columns: 1fr !important`, leftover from an earlier layout where they were rendered inside a grid row. The current modal places them as plain block children of the modal `contentEl`, so the override was a no-op. The three remaining `!important` declarations are in utility classes (`.agent-u-hidden`, `.agent-u-visibility-hidden`) where they are required by design.
- **Generated-file lint disables now carry a reason.** The bare `/* eslint-disable */` at the top of `src/_generated/*.ts` (workers, skills, templates, wasm, source-hash) now reads `/* eslint-disable -- generated build artifact, intentional unrestricted output */`, satisfying the bot's Tier-5 directive-description rule.
- **Stale eslint-disable directive removed.** `EvaluateExpressionTool` carried `// eslint-disable-next-line @typescript-eslint/no-explicit-any` on a line where `any` only appeared inside a template literal (generated sandbox source). The disable was unused and the rule it suppressed is on the bot's "do not disable" list anyway. Dropped without replacement.
- **Local-capabilities disclosure in README.** Added a new "Local capabilities" section between "Network usage" and "Directory structure" that documents the plugin's use of `fs` (KnowledgeDB, office pipeline, checkpoints, persistence), `child_process` (sandbox worker, MCP proxy, shadow git, optional LibreOffice), vault enumeration, clipboard access, and dynamic code execution in the sandbox. The scanner lists these as informational behavior warnings for the manual reviewer; the disclosure addresses them in one place.

No runtime code changes. No new features, no settings changes, no migrations. Build output: `main.js` 4.3 MB, under the 5 MB Obsidian Sync ceiling.

## v2.11.2 -- Checkpoint hang on iCloud-backed vaults (FIX-28-00-02)

Critical fix for users with checkpoints enabled. The v2.11.1 release introduced a `safeFs` filesystem wrapper that centralises every `fs` call behind a path allowlist (security hardening for the Obsidian community-store reviewer angle). One of the call-site migrations routed `isomorphic-git`'s `fs` plugin through the wrapper. On iCloud-Drive-backed vaults this combination caused `git.resolveRef` to return a Promise that never resolved or rejected, blocking the plugin's `onload` indefinitely. Symptom: the DevTools console showed `Loading Vault Operator plugin` and went silent, the sidebar never appeared.

The fix narrows the wrapper migration: every other call-site keeps going through `safeFs`, only `GitCheckpointService.getFs()` reverts to the raw `fs` module. The shadow git repo is fully contained under `<vault>/.obsidian/plugins/vault-operator/checkpoints/` -- isomorphic-git has no mechanism to escape that directory even with the raw fs handle, so the security boundary is preserved by scope rather than by allowlist gate. The change is documented in `scripts/check-safe-fs-imports.sh` as the fifth allowed exception alongside the wrapper file itself, the spawn-allowlist file, the MCP-server worker, and the vitest setup stub.

Users hit by the hang: pull the update, reload Obsidian, and the plugin completes its startup as normal. No data migration, no setting changes.

## v2.11.3 -- Provider polish + ChatGPT-OAuth GPT-5 reasoning fix + security tightening

Bundled fix and hardening release. Four threads land together:

### ChatGPT-OAuth Test Connection 400 fix

GPT-5 family models routed through the chatgpt.com Codex backend (gpt-5, gpt-5.1, gpt-5.2, gpt-5-codex, gpt-5-codex-mini, gpt-5.1-codex variants, gpt-5.2-codex, gpt-5.3-codex) require a `reasoning` block in the request body. Without it the Codex endpoint rejects the call with `HTTP 400`. The plugin previously omitted the field, so the Test Connection button in `Settings -> Provider -> ChatGPT (OAuth) -> Edit Model` returned "Request rejected 400" even when OAuth itself was fully signed in. Same failure mode on every actual chat turn.

The fix adds `reasoning: { effort: 'low', summary: 'auto' }` to every outgoing request body for matching models. `low` is the narrowest effort level accepted by both `gpt-5` (which also accepts `minimal`) and the stricter codex variants (which start at `low`), so it minimises cost while staying compatible across the family.

The hardcoded model lineup was also expanded to include the 2026 Codex releases (gpt-5.1-codex-max, gpt-5.2-codex, gpt-5.3-codex), and the `Refresh` button in the provider modal now returns the full Codex lineup instead of hitting a non-existent `/v1/models` endpoint.

### Provider settings polish

When you add a new local provider (Ollama, LM Studio), the Base URL field now pre-fills with the well-known default (`http://localhost:11434` for Ollama, `http://localhost:1234` for LM Studio). The previous placeholder-only behaviour left the form blank and Discovery fell back to the wrong port. Cloud providers stay placeholder-driven.

The Refresh button for OpenAI providers now drops every model ID that is not callable via `/v1/chat/completions`. The previous filter caught only legacy variants (`-32k`, `-0314`) and let Realtime / Audio / TTS / Transcribe / Whisper / Image / Search / Deep-Research / `*-pro` / `*-codex` identifiers through. The tier classifier then mapped flagship to e.g. `gpt-5.5-pro` (Responses-API only) and Test Connection returned a confusing 404 / 400. The tightened filter is also applied at boot to scrub any polluted entries that earlier refreshes left in `providerConfigs[].discoveredModels`.

### Security tightening (FIX-28-00-02 fix-loop closure)

Two defense-in-depth gaps that AUDIT-028 flagged as Low followups are closed:

- **GitCheckpointService snapshot path traversal**: the FIX-28-00-02 revert put the shadow-repo write back on raw `fs`, so the safeFs allowlist no longer fires as a backstop. The snapshot function now rejects paths containing `..` segments, absolute roots, or NUL bytes before they reach the raw-fs writeFile. Upstream `write_file` tool validators already reject traversal, but the guard restores defense-in-depth at the only call site that uses raw fs.
- **safeFs.promises.symlink removed**: the wrapper resolves paths lexically, not via `realpath`, so a caller could have created a trapdoor symlink inside an allowed root that pointed outside it. Subsequent reads would have passed the lexical allowlist check while resolving through the underlying Node fs to the off-allowlist target. Zero callers existed; `promises.symlink` and `promises.readlink` are removed rather than guarded.

### Other changes

- **`doLoad` rejection now surfaces**: the plugin's onload runs as a fire-and-forget promise. Previously any thrown error inside it was silently swallowed by `void this.doLoad().finally(...)`, which is how the FIX-28-00-02 hang stayed invisible during diagnosis until breadcrumb logs pinpointed it. The plugin now logs to `console.error('[Boot] doLoad threw before completion:', err)` so the next reload shows the failure in DevTools instead of hanging silently.
- **CSS duplicate selectors merged**: stylelint flagged five bare class selectors (`.approve-btn`, `.deny-btn`, `.context-segment-current/.reserved/.available`) that appeared in both a comma-list and a standalone block. Each is now a single block; visual result identical, scanner score improved.

No data migration, no setting changes.

## v2.11.7 -- Plugin caches moved out of vault (iCloud Sync fix)

The shadow git repo used by `GitCheckpointService` and the esbuild WASM cache used by `EsbuildWasmManager` used to live inside the vault at `.obsidian/plugins/<id>/checkpoints/` and `.../dev-env/`. iCloud and Obsidian Sync treat anything under the vault as user content and replicate it to every device. The checkpoints folder routinely grows past 100 MB across thousands of tiny git-object files, which stalls Obsidian Mobile's startup until iCloud has indexed every file. Affected users saw the "restart in safe mode?" dialog on every vault open.

Both caches now live next to the cross-vault shared root at `{vault-parent}/obsilo-shared/checkpoints/` and `.../dev-env/`. Outside the vault means outside the sync stream.

A one-shot migration on plugin onload moves existing data via `fs.rename` (atomic, single op) with a recursive copy-and-delete fallback for cross-device mounts. Idempotent via the `_pluginDataDirsMigrated` settings flag. On first start after the upgrade, users see a Notice confirming the move.

### What changes for the user

- Existing checkpoints and esbuild caches are preserved, just at a new path.
- iCloud / Obsidian Sync syncs the deletion of the in-vault folders to all devices.
- Once the deletion has propagated, Obsidian Mobile opens vaults containing the plugin without the safe-mode timeout.
- No settings changes, no data loss, no manual cleanup required.

The plugin manifest stays `isDesktopOnly: true` for now. Mobile plugin loading itself is a separate initiative (BA-23). This release only removes the cross-device sync stall that affected mobile vault access.

## v2.11.6 -- AUDIT-030 fix-loop + provider modal polish

Security audit AUDIT-030 closure: snapshot and restore boundaries hardened against path-traversal in commit messages (`isVaultRelative` re-check at every entry point), bounded JSON parse for `NewFiles` and `FilesJson` payloads. Provider modal layout polish (info-icon alignment, label spacing).

## v2.11.5 -- Borderless toolbar + asset release order

Header toolbar buttons rendered without borders for visual consistency with the rest of the sidebar. Release pipeline fix: `${version}-assets` is now published before `${version}` so the Obsidian crawler can match the manifest in the latest tag.

## v2.11.4 -- Reviewer-ready docs + CSS lint cleanup

Documentation and lint polish ahead of the next community-store reviewer pass. No runtime behaviour changes; user-facing functionality is unchanged.

### REVIEWER_NOTES.md split out from SECURITY.md

`SECURITY.md` previously carried the full threat-model and reviewer briefing for the Obsidian community plugin scanner. That made the document the de-facto reviewer-orientation file, even though the GitHub convention reserves `SECURITY.md` for vulnerability-reporting policy. The file is now split:

- A slim **`SECURITY.md`** (20 lines) keeps the GitHub-convention vulnerability-reporting contact and SLA.
- The full reviewer briefing moves to **`REVIEWER_NOTES.md`**, with a compliance-mapping table that links each scanner finding (filesystem, shell, vault enumeration, clipboard, dynamic code) to its mitigation section.

Every claim in `REVIEWER_NOTES.md` was re-verified against the current implementation and tightened where the previous text was vague, optimistic, or stale. Specific corrections:

- **Input validation** is now described precisely as required/type/enum only via a lightweight in-tree validator (`inputSchemaValidator.ts`, no `ajv`/`zod`, not a full JSON Schema validator) -- the previous wording suggested complete schema enforcement.
- **`safeFs` allowlist** lists the actual six root categories the code uses (vault, plugin data, agent config `.obsilo-vault`, system temp, desktop config dirs, optional cross-vault `obsilo-shared`) instead of the outdated "five root directories".
- **Spawn allowlist** corrected to the six logical binaries (added missing `pandoc`) with the full shell-metacharacter regex (`[;&|`$<>(){}\\\n\r]`); previous list under-counted the rejected characters.
- **Custom recipes** are described as user-configurable via `Settings > Advanced > Shell` (they are; the old doc claimed they were not) -- bounded by the spawn allowlist plus per-parameter schema validation, so a custom recipe with a non-allowlisted binary cannot spawn.
- **MCP client** correctly described as Streamable-HTTP / SSE only; the obsolete "stdio MCP" caveat (and the equally obsolete "disable stdio MCP in settings" guidance) is removed -- there is no stdio-spawn path in the plugin today.
- **Clipboard** described as the actual image-paste flow rather than the previously-claimed "@-mention attachment feature"; explicit note that `navigator.clipboard.readText()` is never called.
- **Sandbox pipeline** spells out all 18 `AstValidator` patterns and the validator's self-characterisation as a supplementary regex filter (the real boundary is the sandbox). Replaces "frozen globals" with the accurate `vm.createContext + vm.runInNewContext` realm description (and explains why the realm globals are not blanket-frozen). Adds concrete numbers for the previously-vague "output size limit, write rate limit, HTTP rate limit": 10 MB per write, 10 writes/minute, 5 outbound HTTP requests/minute, plus a 20-error / 30-second circuit breaker.
- **npm package loading** lists all four documented CDN hosts (`esm.sh`, `cdn.jsdelivr.net`, `unpkg.com`, `registry.npmjs.org`) and describes the SHA-256 TOFU manifest plus the build-time pinned esbuild hashes. The old text named only `esm.sh`.
- **`new Function`** corrected from "twice" to four well-scoped uses, each with a line link and the trust argument (sandboxed runtime vs. SHA-256-verified bundle).
- **Audit history** brought up to date with AUDIT-028 (v2.11.2 delta) and AUDIT-029 (v2.11.3 delta).
- Unverifiable claim about "a CI grep step against new `import 'fs'`" removed (there is none; the rule is enforced by code-review discipline and a file-header comment).

Code-comment references in `src/main.ts`, `src/core/security/safeFs.ts`, and `src/core/security/spawnAllowlist.ts` now point to `REVIEWER_NOTES.md`.

### CSS `!important` removed from utility classes

The community-bot CSS lint flagged three `!important` declarations in the `.agent-u-hidden` / `.agent-u-visibility-hidden` utility classes. Both classes are used in 40+ call sites across the sidebar UI to toggle element visibility; the `!important` had been added to keep theme defaults from overriding them.

The release replaces `!important` with the class-repetition specificity pattern that the project already uses in `docs/.vitepress/theme/custom.css`:

```css
.agent-u-hidden.agent-u-hidden { display: none; }
.agent-u-visibility-hidden.agent-u-visibility-hidden {
    opacity: 0;
    pointer-events: none;
}
```

The doubled class selector raises specificity from (0,1,0) to (0,2,0), still beats single-class theme defaults, and removes the lint warning without touching any call site. No visual or behavioural change.

No data migration, no setting changes.

## Version index

| Version | What |
|---------|------|
| v2.11.7 | Plugin caches moved out of the vault. Shadow git repo (`checkpoints/`) and esbuild WASM cache (`dev-env/`) now live at `{vault-parent}/obsilo-shared/` so iCloud / Obsidian Sync no longer replicates 100+ MB of plugin internals to every device. Fixes the safe-mode timeout on Obsidian Mobile when opening a vault that has the plugin installed. One-shot migration on plugin onload, idempotent, no manual steps required. |
| v2.11.6 | AUDIT-030 fix-loop closure (path-traversal hardening at every snapshot / restore boundary, bounded JSON parse for `FilesJson` and `NewFiles`); provider-modal layout polish (info-icon alignment, label spacing) |
| v2.11.5 | Borderless toolbar buttons in the sidebar header; release pipeline ordering fix (`${version}-assets` published before `${version}` so the Obsidian crawler matches the manifest in the latest tag) |
| v2.11.4 | Reviewer-ready docs: split `SECURITY.md` into a slim vulnerability-policy file and a new `REVIEWER_NOTES.md` with every claim re-verified against the code (input validator scope, allowlist counts, recipe configurability, MCP transports, sandbox limits, CDN list, `new Function` sites, audit history); CSS lint cleanup -- `!important` removed from `.agent-u-hidden` / `.agent-u-visibility-hidden` utilities via the class-repetition specificity pattern |
| v2.11.3 | ChatGPT-OAuth GPT-5 reasoning block (fixes Test Connection 400); Ollama / LM Studio base URL pre-fill; OpenAI model list filter tightened against Realtime / Audio / Image / `*-pro` / `*-codex` pollution; FIX-28-00-02 fix-loop closure (snapshot path-traversal guard, `safeFs.promises.symlink` removed); boot error surfacing; CSS duplicate selectors merged |
| v2.11.2 | FIX-28-00-02: revert `safeFs` migration in `GitCheckpointService` -- isomorphic-git hangs indefinitely when fed the wrapper on iCloud-backed vaults; raw fs handle restored for this one call-site, all other EPIC-28 migrations stay in place |
| v2.11.1 | Review-bot pass: CSS multicolumn false-positive renamed to `gap`, two unused `!important` removed, generated-file eslint-disable directives now have `-- reason`, stale `no-explicit-any` disable in EvaluateExpressionTool dropped, README "Local capabilities" section disclosing fs/child_process/sandbox usage |
| v2.11.0 | EPIC-26: provider-only settings, advisor pattern with consult_flagship, three-tier classification (Budget/Main/Frontier), one-shot migration with notification modal, chat-header model picker with search, mode-switcher UI removed, lean cost-heuristics + plugin-skills sections; AUDIT-027 H-1 fix (per-provider credential encryption with regression test) |
