Ship a small core, and make your own features replaceable
Its MCP support is a plugin, and a third-party plugin can replace it. That's the point.
- Problem
Core teams keep adding features to the core, and users who disagree with one have to fork.
- Idea
Build your own features on your public plugin API so users can replace them. A feature that can't be a plugin exposes a gap in the API.
Most tools grow by adding features to the core. This one grows by refusing to:
If your feature does not belong in the core, it should be an extension. PRs that bloat the core will likely be rejected.
What makes that real, and not just a slogan, is that the first-party features obey it too.
Try it: replace a built-in feature
install a third-party MCP plugin and watch the built-in step aside
MCP support, code mode and tool search ship inside the product as built-in extensions marked replaceable. An extension that registers /mcp takes over, and the built-in stops reading its config and connecting servers.
export const builtInExtensions = [
{ name: "llama.cpp", factory: llamaExtension, builtin: true },
// Replaceable: an extension that registers `codemode`, `tool_search`,
// or `/mcp` takes over instead of running alongside the built-in one.
{ name: "codemode", factory: codemodeExtension, replaceable: true, builtin: true },
{ name: "tool-search", factory: toolSearchExtension, replaceable: true, builtin: true },
{ name: "mcp", factory: mcpExtension, replaceable: true, builtin: true },
];
Why this is an architecture decision, not a feature list
- The API gets tested by its own authorsIf MCP can be built as an extension, the API already supports tools, commands, configuration, connections and renderers. The next protocol can be built the same way.
- Takeover is by registration, not by settingThe core doesn't need a "use the other MCP" flag. Whoever registers the command or tool name owns it, and removing the extension restores the built-in. Conflicts resolve the same way for every feature.
- Opinions become optionalSub-agents and plan mode aren't in the core at all. They're example extensions (
subagent,plan-mode,permission-gate,sandbox, among 79), so disagreeing with a design means swapping an extension, not forking.
Hook points cost something too
Even hook points for extensions however should be well considered and discussed to avoid adding unmaintainable bloat and complex interactions.
A small core with a hundred ad-hoc hooks isn't small: every hook is API surface that has to keep working, and every pair of hooks is an interaction someone has to reason about. In other words, a hook needs a real caller before it's worth adding.
Steal this
Build your own features on your public plugin API, and let users replace them by registering the same name. If a first-party feature can't be written as a plugin, your API is missing something, and you just found it.
Use it in your code
A prompt for your coding agent
Run it from the root of your repo. It checks whether the idea fits before it changes anything.
Check whether this project's built-in features could run on its own extension or plugin API.
The idea: keep the core small, and build first-party features on the same public extension API that third parties use, so users can replace them. If a first-party feature can't be written as a plugin, the API is missing something.
1. Find the extension, plugin or hook API. If there isn't one, tell me and say whether one is worth adding.
2. List the built-in features that sit on top of the core. For each, say whether it uses the public API or reaches into internals.
3. Pick the most self-contained feature that reaches into internals, and describe the minimal API additions it would need to work as a plugin.
4. If the gap is small, refactor that one feature onto the public API with identical behavior, and add a test showing that a replacement plugin can take its place.
Don't add speculative hooks: every new hook needs a real caller. Keep the change small.Read the source
Where this comes from
Pi (earendil-works/pi), the minimal, extensible agent harness. The files behind this lesson: