Technical details
Model Hub is a full agentic harness for Obsidian plugins. This is how it's built.
It's the base under Library Hub for your books and Topic Map for the topics across your notes. They hold no keys and name no models: they ask the Hub. Everything below is in the code, and anything still to come is marked Soon or Later.
The shape of it
Plugins never talk to a provider. They ask the Hub for a role, and the Hub picks the model that serves it, checks your limits, sends the call and writes it down. The Note chat is built the same way, on the public API only, so anything it does, another plugin can do too.
Callers
Library Hub, Topic Map, your own plugin, the Note chat, and over MCP: Claude Code, Claude Desktop, Codex
API
The Hub API
completestreamembeddecidebatchagent·registerRolesregisterToolsregisterSkillsroute
Roles → tiers
A role (
books.summarize) names a tier. A tier is your ordered list of models across providers.check
The gate
Consent per provider, the per-call limit, the monthly budget, and for tools, your run / ask / off setting.
send
Providers
OpenRouter, Anthropic, OpenAI, Google Gemini, Azure OpenAI, xAI, Mistral, DeepSeek, Groq, Cerebras; Ollama, LM Studio and llama.cpp locally; any OpenAI-compatible endpoint
Written down, in your vault
ledger.jsonl: every model call and its costtool-calls.jsonl: every tool call, arguments hashedsnapshots.jsonl: each note as it was before an edit, for Undo
All three sit in the plugin's folder in your vault's config folder. No prompts or replies are kept.
A call, step by step
What happens between a plugin asking for a summary and the summary coming back.
- Register. At load, a plugin registers its roles. A role is
namespace.name, and the first plugin to use a namespace owns it. - Route. The role names a tier, and you can move it to another. There are seven built-in tiers (quick, balanced, careful, frontier, research, embedding, decision) plus any you make. Each has a limit per call and a budget per month, starting at 1¢ a call and $5 a month for quick, up to $2 and $20 for frontier.
- Check. Before anything is sent, the most the call could cost is checked against the tier's per-call limit, what's left of its month and the monthly cap across every plugin on the Hub (
spendCap()). Note text goes to a cloud provider only once you've said yes to that provider. A call that would go over is refused with a code and a plain message. - Send. Provider adapters are built on the Vercel AI SDK (
ai7). If a model fails, the next model on the tier's list is tried, never one you didn't list, and the result says it came from a fallback. - Record. Each attempt gets a ledger row: plugin, role, model, tokens, listed cost, billed cost and the note it was about. A call billed well above its listed price is flagged. Prices come from models.dev about once a week; OpenRouter reports the exact billed cost.
// wanted: the Hub API version your plugin was built for
const found = getHub(this.app, wanted);
if (!found.hub) return new Notice(found.message);
found.hub.registerRoles(this.manifest.id, [
{ name: 'mine.summarize', description: 'Summaries of a note', tier: 'quick' },
]);
const r = await found.hub.complete('mine.summarize', messages, { for: file.path });
// r.text, r.usage, r.listedUsd, r.billedUsd, r.servedModelRefusals are typed. Every refusal is an error with a code (over-ceiling, cap-reached, capability-missing, provider-unavailable, …). A model that can't do what's asked (tools, structured output, images, web search) refuses with a reason instead of being swapped silently.
The agent loop
On the Project page I call Model Hub a full agentic harness. In code, that's hub.agent(role, opts): it streams a step, runs the tool calls the model asks for, hands back the results, and goes again. A run ends when the model answers, a finish tool succeeds, the step cap or the budget is reached, or you stop it.
- Step cap
- 8 steps unless the caller sets
maxSteps. On the last step the model is asked to answer, or offered only the finish tools. - Repeat guard
- A tool called with the same input more than twice in one run gets
repeated-callinstead of running. - Budget
budgetUsdper run, on top of the tier's own limits. When it runs out, the run ends withbudget.- Compaction
- Once the input passes a set share of the context window, old tool results over 4,096 characters are compacted.
- Conversations
- Saved as notes, in one format other plugins can read (
parseTranscript,formatTranscript).
Later
- Steering a run while it works, and resuming one after Obsidian reloads.
- Handing reasoning from one provider to another mid-run.
- Tools from other MCP servers (an MCP client).
- Workflows as notes: a note that names its tiers, tools, folders and budget.
Tools, skills and MCP
Tools are how a model does more than answer. Plugins hand the Hub theirs, and the Hub decides, by your settings, whether each one runs.
Tools
A plugin calls registerTools(pluginId, tools, { undo }). You set each tool, or a whole plugin, to run, ask or off. Reads run and writes ask by default; a write that can be undone shows an Undo button. Every call goes in tool-calls.jsonl with its arguments only as a hash, and a result over 100,000 characters is cut. Every tool declares what it does to your vault, and the Hub warns when one doesn't.
The Note chat adds the Hub's own vault tools: vault.read, vault.outline, vault.meta, vault.search (BM25), vault.list, vault.query and vault.base. Its edits go through a shared edit engine, and each one keeps a snapshot of the note so one Undo takes it back. Snapshots are kept 30 days, up to 20 MB.
Skills
A skill is a note that tells the model how you work. Markdown, Bases and Canvas skills are built in, on kepano/obsidian-skills. Your own come from a vault folder, and plugins add theirs with registerSkills.
The MCP server
MCP is the standard way AI apps share tools. Switch it on and Claude Code, Claude Desktop or Codex can use your plugins' tools, and the skills you mark arrive as prompts. It's the one server for the family; no plugin runs its own.
- Protocol
- MCP over Streamable HTTP, following the MCP spec of 2025-11-25, through the official TypeScript SDK (1.32.1).
- Where
127.0.0.1, port 27125 unless you change it. Off by default; it makes no outgoing connections.- Who gets in
- A request needs the right Host, no Origin header and the bearer token from Obsidian's secret storage before its body is read. Bodies over 1 MB are refused; a session ends after 30 idle minutes.
- What it costs
- Each session is one run with a $0 model budget, and every tool call goes through the same gate, approvals and log as the Note chat.
- Annotations
- Worked out from what each tool declares it does (
effect,replay,undo), never written by hand. - Results
- Capped at 50,000 characters and never cut mid-JSON: whole items, plus how to ask for less.
- Errors
- A code, a message and the next step, so an agent can act on them.
- Instructions
- Generated from the plugins you have installed.
- What it leaves out
- Vault tools. Your coding agent already reads the vault, and near-identical tools make a model choose between them.
Soon
- The move to the MCP spec of 2026‑07‑28, so a run outlives the session that started it.
Shared libraries
Code that more than one plugin needs lives in one place, published under MIT, so the family behaves the same everywhere.
obsidian-ui- The family design system: tokens mapped to Obsidian's own variables, layout classes, and about 40 named controls with a gallery and a doc each.
text- The edit engine: apply an edit, preview it, undo it, and find the place in a note it belongs, even after the note has changed.
retrieval- BM25 search, with no runtime dependencies. It's what
vault.searchruns on.
How it's tested
Three suites, and a set of cards that say what a person is trying to do, each one linked to the test that proves it.
- Unit
- About 1,100 Vitest tests across 92 files, run in CI.
- End to end
- 17 WebdriverIO specs against a real, sandboxed Obsidian. They run in Docker under Xvfb, one run at a time, so no window opens on anyone's screen. The same runs take the guide's screenshots, in light and dark.
- Live
- Real calls to real providers, run by hand with a spend limit. Scheduled live runs in CI are on the roadmap.
- Journeys
- 7 jobs, 16 journeys and 132 scenario cards. 129 scenarios link to a test, and a check fails the build on a gap or a broken link.
- API
- The public API's docs are generated, and CI fails when they're out of date, so every API change shows in review.
The pages
Each part above has its own section with the detail. Developer docs are for people building on the Hub; Agent docs are the same knowledge for coding agents.
How it's built
- How it worksThe whole path of a call, from a plugin's role to the ledger row./technical#shape
- The agent harnessThe tool loop: steps, finish tools, the repeat guard, budgets and compaction./technical#loop
- Tools and MCPRegistering tools, approvals and Undo, skills, and the local MCP server./technical#tools
- Models and costsProviders, tiers, suggested models, prices, limits and the ledger./technical#call
- TestingUnit, Docker end to end, live runs, and the journey cards./technical#tests
Technical roadmap
Model Hub came out with the other two plugins, since the three call each other. Soon is the next release; Later is after it. Nothing has a date.
Soon
- The MCP spec of 2026‑07‑28. The local MCP server moves to the new spec, so a run outlives the session that started it.
- Plans you approve. A long job lists what it will change and what can be undone, and the confirm names that plan.
- Skills from the family. Library Hub and Topic Map register skills through
registerSkills, so the chat can load them. - A compare call. One request across two tiers or models, with usage, cost and time for each answer.
- Note Chat edits on the edit engine. Apply at a position, act on a selection, and show a diff before every write.
- A second decide() provider. OpenRouter's Decisions API next to TypeSafe, once it leaves alpha.
Later
- The rest of the harness. Steering and follow-up during a run, resume after a reload, and sessions saved as notes.
- Workflows as notes. A note names its tiers, tools, folders and budget, and the panel runs and steers it.
- Coding agents over ACP. Drive Claude Code, Gemini CLI and others through the Agent Client Protocol, under the Hub's gate and ledger.
- A memory interface. Pluggable backends, plain notes by default.
- Search tools for local models. Exa, Tavily, Brave or SearXNG as Hub tools.
- Structured output. Replies against a schema you choose, and a note's properties filled from them.
Library releases
The shared packages the Hub is built on, newest first. Each release has its own page, and the same notes go on GitHub.
- @insurgently/obsidian-ui 0.4.0The design system: tokens, layout classes and the named controls.
- @insurgently/text 0.6.2
anchorsIn: find many spans of one text in a single pass. - @insurgently/retrieval 0.1.0The first version: tokenizing and BM25 search.
Getting involved
The source is public, under MIT. The roadmap marks the issues that are good places to start, and CONTRIBUTING.md lists the checks a pull request passes: lint, typecheck, unit tests, the API docs check, and end to end for anything that touches Obsidian.
It stands on other people's open-source work: the Vercel AI SDK, the MCP TypeScript SDK, kepano's Obsidian skills, models.dev's price list and wdio-obsidian-service. What I've built for the family goes back out as the shared libraries above.
Join
One list for everything here: an email when a plugin ships, plus the posts I write about these projects on brianspringer.com.
Want to help build this, or join the co‑op? Email me about joining →
Insurgent Methods is where we build at a different scale, to take on the biggest problems.
- API package
@insurgently/model-hub-api, on npm- Needs
- Obsidian 1.13.1, desktop
- License
- MIT