nervネルフ

Extending it

The question a module graph does not answer: how does somebody who is not the author change what this does? Three systems, three units of extension.

Three units

the unitmachineryshipped content
pia JavaScript file~160,000 lines — an embedded engine, a Node compatibility layer, a capability policy, a package manager107 embedded modules, ~78 example extensions
deepseeka Rust crate~800 lines. Its plugin port has exactly one implementor, named UnavailablePluginRuntimenone
nerva Lua declarationfour VMs, one sandbox each — a removal list, not a permission model~9,000 lines of Lua, examples in every repository

deepseek documents a plugin seam and ships the “unavailable” answer for it. That is honest, and it is the whole story. pi is thirteen times our size and is the only one of the three that has actually been extended by strangers — which is the fact worth taking seriously, and the reason for everything below.

What a third party can reach

and how
a toolmagi.tool, or casper.tool for one that runs a program
a watchermagi.watch — eight events, told after the fact, answering nothing
a wire protocolmelchior.api. The registry is readable, so a dialect that differs from a shipped one in one function borrows the other three
a providermelchior.provider — an endpoint, a credential, and either a model list or discover = true
a memory sourcebalthasar.source. No Rust file in balthasar names a harness; a new one is a file, not a release
what a model is toldbalthasar.section — weight, order, filter, and a confidence floor

Where it goes

neovim's runtimepath, unchanged, in all four. Twenty years of real plugins have been written against it and most people arriving already know it; deviating buys nothing and costs everyone the transfer.

~/.config/<program>/plugin/*.lua                        alphabetical, each on its own
~/.local/share/<program>/site/pack/*/start/*/plugin/*.lua  installed packages
~/.config/<program>/after/plugin/*.lua                  the last word

Every registrar replaces by name, so the order is the precedence. No manifest, no registration, no entry point to edit — requiring an edit to your own init.lua to enable somebody's package makes every package a merge conflict with your configuration.

What contains it

pi gives its extensions a default grant of read, write, http, events and session, and never asks. It has a capability policy because it embedded a language that can do anything; the policy exists to take that back.

nerv never handed it over. The sandbox is a removal list — os.execute, io.popen, io entirely, require, dofile, loadfile — applied to the VM before any file runs. What is left cannot spawn or touch a file. Running a command goes through the same ledger the shell tool passes; writing one goes through the same gate the write tool passes. A discovered file is held to exactly what a named one is.

The inversion that made this urgent is closed. For a while the one program loading third-party packages was the one VM still keeping os.execute. The removal list applies in all four now, and discovery arrived after it rather than before.

What a package costs to trust

pi's package manager is 8,579 lines: npm and git sources, user and project scopes, a lockfile with digests and trust states. Most of that is a package ecosystem nobody here wants to be in. The ten per cent worth having is the part about trust rather than fetching, and it is what pi conspicuously does for MCP servers and not for its own extensions.

a file you wroteruns on sight. A prompt about your own configuration is one nobody reads — it trains people to say yes.
a package you fetchedheld back until <program> acknowledge records its SHA-256, and held back again the moment it changes

Fetching is git clone. The lockfile is the idea. A manifest that will not parse reads as empty, which holds everything back rather than letting everything through.

What is promised

<program> verbs reports surface, separate from family: one versions the wire between these programs, the other versions what an extension is written against. Both move only when something already published stops working.

Five registrars were published, dead and unversioned for most of this project's life. A number an extension can read is what closes that window deliberately rather than by accident — and the examples in every repository are run by that repository's own test suite, because an example that does not load is worse than no example.

Still ahead of us. pi's ~78 example extensions are not documentation; they are how it knows the surface works, and ours has been used by nobody who did not write it. That is the measurement this section cannot yet make.