Insurgently

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.

  1. Callers

    Library Hub, Topic Map, your own plugin, the Note chat, and over MCP: Claude Code, Claude Desktop, Codex

  2. API

    The Hub API

    complete stream embed decide batch agent · registerRoles registerTools registerSkills

  3. route

    Roles → tiers

    A role (books.summarize) names a tier. A tier is your ordered list of models across providers.

  4. check

    The gate

    Consent per provider, the per-call limit, the monthly budget, and for tools, your run / ask / off setting.

  5. 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 cost
  • tool-calls.jsonl: every tool call, arguments hashed
  • snapshots.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.

  1. Register. At load, a plugin registers its roles. A role is namespace.name, and the first plugin to use a namespace owns it.
  2. 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.
  3. 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.
  4. Send. Provider adapters are built on the Vercel AI SDK (ai 7). 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.
  5. 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.servedModel

Refusals 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-call instead of running.
Budget
budgetUsd per run, on top of the tier's own limits. When it runs out, the run ends with budget.
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.search runs on.
Library releases ↓

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.

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.2anchorsIn: 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.

Get involved, for the whole family →

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