Everything is a plugin
Honest count of desktop apps with real plugin systems: it is a short list. Slack, Discord, Postman, Bruno, the average SaaS Electron wrapper are great at their jobs, but they are fixed UIs with maybe a webhook integration story, and that is it. The deep exceptions live in two pockets: creative tools (Adobe’s suite with UXP and CEP, DaVinci Resolve with OFX, Blender with its Python add-ons, the DAWs with VST and AU) and code editors (Eclipse, JetBrains, VS Code, Zed, Sublime). In API tooling specifically, the deepest extension story is Insomnia’s modest npm-based plugin API for templating and request hooks. Useful, and a long way from “compose the application from pieces.” So when an API platform says “everything is a plugin,” it is worth pinning down what everything covers.
We built ours the other way around.
In Spirefy, the application shell, the menu bar, the status bar, and the main layout, lives in the desktop framework itself. Everything that runs inside that shell is a plugin. The page you are looking at (workflows editor, API workbench, the import and export consoles) is a plugin. The fifteen importers that translate OpenAPI, Postman, Insomnia, Bruno, Arazzo, AsyncAPI and the rest into our unified model are each their own plugin. The local LLM chat panel is a plugin.
And we went further than VS Code or Adobe’s suite in two specific ways. Polyglot: plugins are WebAssembly modules. Zig and Rust are first-class today, chosen because they compile to small, fast WASM with no heavy runtime to carry along, and more WASM-target languages, Go among them, are planned. VS Code locks you into JavaScript and TypeScript. Adobe UXP into HTML and JS. VSTs into native C++ with the ABI baggage that comes with it. We do not. Type-safe and auto-wired: cross-plugin contracts are declared in WIT, the WebAssembly Component Model’s interface language. The Zora engine scans plugins at load, knows what each implements and consumes, and wires up the cross-plugin subscriptions for you. No manifest-versus-runtime drift, no string-keyed RPC fragility. The engine knows the types and connects them.
Spirefy is the platform. Zora is the plugin engine underneath, and we will likely rename it once the moment is right. One bet sits behind both names: the right shape for an API and workflow platform is a small hard kernel that knows almost nothing, wrapped around a swappable cloud of WASM plugins that know everything.
This post is the tour of why we are betting that, what “everything” actually covers, and what it makes possible that we could not have built any other way.
Why make everything a plugin
The current generation of API platforms is mostly closed. Postman has no real plugin system. Bruno, none. Insomnia is the deepest case and it is a modest npm-based hook API for templating and request handlers, useful but a long way from composing the application from parts. So if you want a new kind of view, a new protocol, or your team’s quirky internal spec rendered as a first-class object, you do not get it.
The lineage we drew from is older and more radical. Eclipse, in the early 2000s, was an IDE assembled from plugins: workbench, editors, views, perspectives, all OSGi bundles composed at runtime. You did not extend Eclipse, you composed it. The base platform was eighty-something plugins, and the IDE you ran was your particular composition over them. Eclipse RCP later turned that substrate into something you could build any desktop app on, not just an IDE.
I built a JVM-based plugin engine in that lineage years ago. That work is the soil this whole project grew out of. There is a shape of architecture you only see clearly after you have built one, and Spirefy is that shape, transplanted from the JVM to a Zig host and WebAssembly substrate, with a unified mutation protocol where Eclipse had a workbench and an event bus. VS Code, more recently, revived a more accessible version of the pattern. The lesson read the same across all of them: the platform you can swap pieces out of is the platform that survives the next five years of churn.
So we made every piece swappable. The framework gives you the window and the chrome; outside that thin shell, there are no privileged builtins. A page, an importer, an exporter, a tool: each ships as a plugin package, same format, same signing, same loader, as any community plugin you would write tomorrow. We load them all from a directory at startup. The kernel that holds the rest together has roughly one job: route command batches to a unified model.
That is the whole substrate. Everything you would recognize as an API platform feature is built on top of it.
What “everything” actually means
Current inventory of what is a plugin in the system today:
- The application shell is the one part that is not a plugin. The window, the menu bar, the status bar, the main layout, and the React runtime that draws them live in the desktop framework. It is the thin base everything else mounts into, and moving it into the framework means any app built on the framework gets the same shell.
- Pages: the workflows editor, API workbench, API integrations, the import and export consoles, and a few more. Each is a separate plugin. The desktop mounts them all at once and uses opacity to swap which one is visible, so page switching is a zero-cost DOM operation with no remount.
- Importers: fifteen. OpenAPI 3, Swagger (OpenAPI 2), Postman v2.1, Insomnia v4, Bruno, HAR, Arazzo, AsyncAPI, RAML, API Blueprint, Hoppscotch, Thunder Client, raw HTTP files, curl, and OpenAPI Overlay. Each is a plugin that translates foreign data into the unified model through the command-batch protocol.
- Exporters: Arazzo, documentation, and reference servers today, with more on the way.
- Tools:
bridge-bench(engine performance testing),llmchat(local LLM chat via llama.cpp, with CPU, Vulkan, or Metal auto-detected and a switchable OpenAI-compatible remote for OpenAI, Ollama, LM Studio, and vLLM), andrecorder-pro(request recording).
Themes, by the way, are not plugins. They are JSON config bundles describing CSS custom properties. The framework loads and applies them; there is no WASM in the loop. Settings is similar: a panel in the framework, not a plugin in its own right.
The missing piece is the marketplace, the public index where the community can publish their own. The Phase 1 infrastructure is built (Go, Chi, Postgres, Stripe Connect, and ML-DSA-65 plugin signing). The public API spec is up next.
The two-minute architecture map
Skip this section if you do not want the substrate view. But it is the part that makes everything else possible.
Spirefy desktop [native host process]
│
├── Frameless WebView
│ │
│ └── bootstrap injected before first paint (no flash)
│
│ └── application shell (in the desktop framework)
│ ├── chrome: menu bar, status bar, layout
│ └── slots that host page and panel plugins
│
└── Zora plugin engine [WASM on Wasmtime, JIT + disk cache]
├── instance pools (handle WASM to host to WASM reentrancy)
├── host functions grouped by concern (model, HTTP, FS, profile, view)
└── plugins, all swappable:
├── pages, 15 importers, exporters, tools
├── read and subscribe to the unified model
└── mutate the model via command batches
The unified model lives in spirefy-core, years of design on a domain shape that can represent OpenAPI specs, Postman collections, Insomnia workspaces, AsyncAPI definitions, Arazzo workflows, and our own native types without compromising the edges. Expressions get pre-compiled at ingest rather than interpreted at every step; the internal throughput numbers are something we will prove out in a later post with a real methodology section, not a handwave.
Plugins never reach into the model directly. They emit a JSON command batch, atomic, with intra-batch references via @cid syntax so operation 2 can refer to the id operation 1 is about to create, and the kernel routes it to the executor. Every mutation leaves an ordered history every plugin can subscribe to. And every participant speaks the same wire format: human gestures from the UI, macro recordings, importers dumping a 10,000-op OpenAPI spec, and the AI module emitting its next action.
That last sentence is doing a lot of work. It is why the next post on this blog is about an AI that speaks the protocol natively.
What this buys us
Four things, roughly in the order you will notice them.
A real plugin ecosystem. Most platforms say “we have plugins” and mean “you can write a sidebar.” We mean: build a new page, a new protocol importer, a new exporter, a new AI provider, or a new tool. Whatever the system does inside the shell, the substrate does not push back on replacing it. The marketplace is how you will find the community’s work; the signed plugin package is how you will trust it.
Profile-based runtime customization. When everything is a plugin, the Plugin Manager can do something fixed-shell apps structurally cannot: save which plugins are loaded as a named profile, then swap profiles at runtime. Only doing API work today? Use a profile that loads the API workbench and the few importers you actually need; the workflows page, the exporters, and the LLM chat panel all unload, taking their menu items and panels with them. Less memory, faster startup. Tomorrow’s task needs the workflow editor and a couple more importers? Switch to a profile that includes them, or install something new from the marketplace and add it to the profile. VS Code added profiles in 2023, but switching there requires a window reload. Ours hot-swaps in seconds, and the plugins that are not touched in a switch keep their state, so what was open stays open.
Local-first AI as a first-class participant. Because every meaningful change in the system is already a spirefy.cmd.v1 command batch, an AI that emits those batches is a user of the system, no different architecturally than a human clicking. No AI sidebar bolted on the side. We are going further with this, and that is the subject of post two: fine-tuning a local GGUF model to emit our command batches directly, via grammar-constrained decoding, with an OpenAI-compatible remote fallback for cold starts or larger contexts. That is on the roadmap, not shipped, but the substrate it sits on already works.
No privileged path for plugins. Every plugin loads exactly the same way: same package format, same signature verification, same SHA-256 hash log, same world-writable refusal (fail-closed on POSIX: we do not fix bad permissions, we refuse to load). The framework provides the shell and the engine; nothing that runs as a plugin gets a back door the others do not have.
What is coming on this blog
We are starting this blog because the interesting bits of the stack are spread across six or seven repos nobody outside the org has any reason to read together. Over the next few weeks, expect:
- An LLM that speaks our protocol. Fine-tuning a local GGUF model to emit
spirefy.cmd.v1batches via grammar-constrained decoding. Training-data shape, grammar derivation, the inference path. Status: roadmap. Why it works on paper: the next post. - Six laws for plugin event routing. The discipline that keeps a multi-plugin desktop app from collapsing into spaghetti. Enforced by ESLint and CI grep, not a style guide you can ignore.
- Arazzo from the source. I co-authored the Arazzo specification. Visual workflow tools have been getting parts of this story wrong; here is where they went sideways.
- Zero-FOUC desktop on Zig and WebView. How DOCUMENT_START bootstrap injection lets a heavy React tree paint with the right theme on the first frame. Trade-offs of frameless on Linux, macOS, and Windows.
- Local-first AI in Spirefy. llama.cpp with backend auto-detection, a GGUF slot registry, switchable cloud fallback, and the agent-loop sketch.
If any of this is interesting, stick around. We will be back soon.
Kevin Duffey, founder, Spirefy