Research / Benchmarks and empirical studies

P69 · empirical · draft

How Often Do Plugins Leak? A Residue Probe for Raw Cordis Plugins, and What a Compile-Time Lifecycle Gate Would Refuse

partial empirical Benchmarks and empirical studies

State of the work. The probe and an unload-surface survey are on main with fixtures and a self-check; a first measurement exists only on a self-authored 10-plugin corpus, and the population study has not been run.

This is a draft, not peer reviewed. Repository references link to revl at commit 45bec431f.

Planned to be combined with P68 (The Gate Is the Oracle: Scoring LLM Component Generation by Admission, First-Pass Rate and Tokens-to-Green, with a Refusal Column that Leads) into a single paper.

Pitch

We give a model-free probe that mounts and unmounts any Cordis plugin N times and names which of four runtime categories failed to return to baseline, and we use it to put a number on how often unconstrained plugin code leaks, which is the share a compile-time lifecycle check is designed to refuse.

Abstract

Plugin runtimes let code be loaded and unloaded while a process runs, and every plugin is trusted to undo what it acquired. Nothing in the host language checks that. We present a residue probe for Cordis, a TypeScript plugin runtime. The probe mounts and unmounts a plugin N cycles on a real Cordis instance and compares four observable categories (registry, provisions, effects, listeners) against a baseline taken before the first mount. A plugin is classified clean, leaked or error, and no model is in the loop, so any corpus can be re-scored for free. The four categories are the same ones a revl composition must prove back at baseline on teardown, so "leaked" is meant to mean "revl would have refused this at compile time or on the exit proof", a mapping we have not tested by experiment. The repository's self-check asserts that the probe separates a clean fixture from a leaky fixture with a known leak set (we read that check and did not run it), and we report a first measurement on a 10-plugin corpus that we wrote ourselves: 4 of 10 leak. We state plainly that this figure shows the probe detects the leaks we planted and is not a population rate. We also survey six agent-framework packages at pinned versions (five projects; LangChain's JavaScript and Python packages are counted separately) for a published way to retire one registered tool: none publishes one, five publish no unload path under the names and paths searched, and one publishes teardown only at toolset scope. We describe the protocol that would turn the instrument into a population study of third-party plugins and of model-written plugins.

Problem and claim

Hot-loadable plugin systems depend on a discipline: whatever a plugin binds to a long-lived context must be undone by its own teardown. In Cordis the common mistake is attaching a listener, a provision or a sub-plugin to ctx.root instead of the plugin's own fiber. That works the first time and leaks on every unload. The cost shows up as growing listener counts and stale services after hot swap, far from the cause.

The claim has two parts. First, the leak class can be measured by a small, deterministic, model-free oracle on the real runtime, with no change to the plugin under test. Second, a language that makes the same four categories a compile-time and exit-time obligation would refuse the plugins this oracle flags. That is a mapping, not yet an experiment. If it holds, the probe's count is a lower bound on the four-category leaks a gate would refuse, because the probe omits resources outside those four categories.

The paper does not claim a population leak rate today. The only measured rate is on a corpus the author wrote. The claim about frameworks is narrower and is checkable: it concerns published API surface at pinned versions, not behaviour.

Contributions

  1. A residue probe for Cordis plugins: N mount/unmount cycles, four categories, three outcomes (clean, leaked, error), exit codes 0/1/2, no model and no cost (tools/residue-probe/README.md, tools/residue-probe/probe.ts, tools/residue-probe/run.mjs).
  2. A contract equivalence between the probe and the revl exit proof: each probe category maps to the check in _Driver._teardown (src/revl/run.py:2589-2622), so a probe-flagged plugin is one revl is expected not to admit (a mapping; see Threats).
  3. A scoring path that treats raw TypeScript as a control arm scored on lifecycle correctness rather than compile rate, because raw TypeScript always compiles (bench/score_raw_ts.py:1-25, bench/prompts/raw-ts.md).
  4. A first measurement on a 10-plugin hand corpus (4 leak, 6 clean) with the ordinary mistake behind each leak, labelled as not a population number.
  5. A falsifiable survey of unload surface across six agent-framework packages (five projects), one tool host and one control, with a checker that can fail and a written account of the first version being wrong (bench/framework_unload_survey.py, bench/results/framework-bench/unload-survey.json).

Evidence on main

Tree: revl main at commit 45bec431f.

Claimpath:line or testStatusNote
Probe mounts/unmounts N cycles and reports four categoriestools/residue-probe/README.md:1-60, tools/residue-probe/probe.tsshippedREADME gives the field mapping to run.py; Node >= 23.6
Probe separates clean from leaky fixturetools/residue-probe/test.mjs:29-60, tools/residue-probe/fixtures/clean-plugin.ts, tools/residue-probe/fixtures/leaky-plugin.tsshippedLeaky fixture must leak exactly {registry, provisions, effects, listeners}; listener count must grow per cycle. Run via npm test in that directory; not run for this draft
Same contract as the revl exit proofsrc/revl/run.py:2589-2622shippedregistry, provisions, effects, listeners checks, plus extra checks (inverse residue, models) the probe does not have
Probe is blind to host resources and function correctnesstools/residue-probe/README.md:39-43, bench/results/hand-corpus/summary.md (Method note)shipped (as a stated limit)A plugin can be probe-clean and miss its brief
Raw-ts scored as clean / leaked / errorbench/score_raw_ts.py:1-25, :45shippedDEFAULT_CYCLES = 5
Raw-ts is the control arm of the same briefsbench/prompts/raw-ts.md:1-40, bench/specs.jsonshipped30 specs
Hand-corpus result: 4/10 leak, categories registry 2, provisions 3, effects 4, listeners 1bench/results/hand-corpus/summary.mdshipped6 cycles per plugin. Authored by this repository, by hand (bench/framework_bench.py:455-468)
The report itself marks that number as non-populationbench/framework_bench.py:462-467, tests/test_framework_bench.py:323-328shippedThe test requires the caveat to be rendered
Model-written raw-ts on a pinned local model: 1/6 "carry residue"bench/results/pinned-three-host/summary.mdpartialThe one cell is a 900 s runner timeout on 03-user-cache, not a leak. 0/6 leaked. Do not quote 17%
Unload survey: of 6 agent-framework packages (5 projects), 0 publish a per-registration retirement, 5 publish no unload path, 1 publishes per-toolset teardownbench/results/framework-bench/report.md ("Unload paths across the ecosystem"), bench/results/framework-bench/unload-survey.jsonshippedPinned versions listed there; tool host (MCP SDK 1.30.0) and control (cordis 4.0.0-rc.10) excluded from the denominator
Survey claims are checked against the published artifact and the checker can failbench/framework_unload_survey.py:403 (check_claim), :536 (check), tests/test_framework_unload_survey_tarslip.pyshippedThe report notes that an earlier keyword version was wrong and was deleted
MCP SDK registration handle has remove() that is name-scoped, not resource-scopedbench/hosts.json:163-174, bench/results/framework-bench/unload-survey.json (mcp.d.ts:289, a logged hit; the tarball is not in the tree, so the line was not re-fetched)shippedA presence claim: a symbol exists, not that it releases anything
MCP third host probed for residuebench/mcp_host/probe.mjs, bench/results/pinned-three-host/summary.mdpartial4/6 clean, 2/6 could not be probed (generated code did not parse)
revl is compile-gated so no leaking component reaches a corpusbench/results/framework-bench/report.md table, residue rowpartialStated by the report; no experiment yet takes the 4 leaky plugins, expresses them in revl and shows each refused

Numbers that exist

Paper outline

  1. Introduction. Hot-loading plugins and the unwritten teardown contract; the research question; headline numbers once a third-party corpus exists.
  2. Background. Cordis fibers, contexts, the four categories; why ctx.root is the common mistake.
  3. The probe. Method, baseline and warmup semantics, outcomes, limits (host resources not covered).
  4. Contract equivalence with the revl exit proof, and what "would have refused" means precisely.
  5. Study design and corpora: third-party plugins, model-written plugins, hand corpus as probe validation.
  6. Results. Rates by category, growth versus fixed offset, error class, refusal experiment.
  7. Ecosystem survey of unload surface. Method, the failed first version, results.
  8. Threats to validity.
  9. Related work and conclusion.

Threats to validity and limits

Fact-check

Claims in this draft were checked against revl main at commit 45bec431f: 31 checked, 4 corrected.

Cite this draft

@misc{revlp69,
  author       = {Thomas Moussajee},
  title        = {How Often Do Plugins Leak? A Residue Probe for Raw Cordis Plugins, and What a Compile-Time Lifecycle Gate Would Refuse},
  year         = {2026},
  howpublished = {\url{https://inso1337.github.io/revl/research/P69.html}},
  note         = {Research draft, not peer reviewed. Written against revl commit 45bec431f. Licensed CC BY 4.0.}
}

Text licensed CC BY 4.0, author Thomas Moussajee.

← All research drafts