VibeVM
Contents
On this page
en
Publisher
org.vibevm.core
Version
1.0.0latest
Audiences
user, author
Reading time
4 min
Rendered
Read aloud
never

Extensions and providers

01A package can plug into the build: run a step, transform a stage, or hand a task to an agent. This page explains how such plug-ins are declared, how a project turns them on, and how to see which ones ran and why.

02
vibe extensions --path hello-vibe
Extensions
  → [0] @vibe/package/skill/org.vibevm.world/wal/wal-status — point=phase:package handler=builtin provider=org.vibevm.world/wal@1.0.0 tier=preset state=effective
1 extension declaration(s), 1 effective

Points and contributions

03The lifecycle exposes named extension points, strings of the form family:name. The phase: family, the group of points that share one prefix, is the nine phases. The slot: family names places inside a phase where a stack or a discipline expects to plug something. The compile: family is the boot compiler's own pipeline, where a package may transform the text an agent will read. A contribution binds a handler to a point and is declared in a manifest as an [[extension]] table, in a package or in the project itself.

04 An extension point is a string <family>:<name>. Three families: phase: — the default-lifecycle phases and clean (project-scoped moments: phase:build, phase:create, …); compile: — the compiler's two tiers: the §7.2 staged positions (compile:source, compile:document, compile:lane, compile:emitted) and the §7.4 pass tier's single point compile:pass, whose position inside the pipeline is carried by the contribution's pass = { … } declaration; slot: — the per-package materialisation moments that already exist as PROP-020 (slot:pre-install, slot:post-install), re-stated in this vocabulary without changing their semantics, timing, or manifest spelling ([[hooks]](../modules/vibe-workspace/PROP-020-install-hooks.xml#manifest) keeps working verbatim; it is now sugar for two slot: contributions).
05 A contribution binds a handler to a point. It is declared as an [[extension]] table — in a package manifest (the package ships and offers the behaviour) or in the project manifest (the host adds its own). The shape is one grammar for every family:

06A contribution carries an id, which is also the key a project uses to disable or replace it; the same handler may be bound several times under different ids with different configuration.

07 Fields: id (execution identity — Maven's execution id; also the disable/override key), point, handler (§6), config (free TOML delivered to the handler verbatim — Babel's plugin options), auto (§3.3), and optionally when (a guard: kind = "rust-stack"-style applicability conditions, future work behind this anchor).

Five kinds of handler

08
Kind What runs
builtin a handler compiled into vibe, such as a logger
script a shell or PowerShell script shipped by the package, resolved by platform
binary a program the package delivers and vibe builds on install
native a dynamic library loaded in process through a C ABI, from an exact prebuilt for the platform
agent a prompt: work handed to the hosting agent, or to a configured model provider when a person runs vibe at a terminal
09 A contribution's handler.kind is one of five. Two wrap machinery that exists; three are new. All five are legal at every phase:/slot: point; the compile: family accepts builtin and native only (§8.5 explains why).

10Every handler receives one context envelope, a versioned JSON document that names the project, the phase, the artifacts produced so far and the configuration of the contribution, and answers with a reply envelope that may declare new artifacts. The envelope is how phases talk to each other.

11 Every handler, in every family, receives one context envelope — a versioned JSON document (the wire form for script/binary/native handlers; builtin handlers get the same data as a Rust struct; agent handlers get its prose projection). Modelled on what Maven hands a Mojo — MavenProject, MavenSession, MojoExecution, per-parameter configuration — translated to our world and extended with what the owner predicted: «в стадии будет попадать много больше, чем их просто название».

12A native handler is a dynamic library with exactly four C symbols; the request and the reply cross that boundary as versioned JSON, one family per library, and the vibe-ext crate gives an author safe macros so that no unsafe is ever written by hand. A closed-source handler ships prebuilt for each platform; the exact prebuilt wins, declared source may build instead, and absence refuses rather than skips. A compile: transform runs inside lane regeneration, so it may only be a builtin or an admitted native handler, never a script, a binary or an agent. A contribution that wants the low-level pass tier says so with compiler_internals = true, and then still needs the host's activation.

13 A native handler is a cdylib exposing exactly four ABI-1 C symbols: vibe_ext_abi, vibe_ext_manifest, vibe_ext_invoke, and vibe_ext_free. The Rust ABI is never the wire; versioned JSON request/reply roots carry lifecycle, compiler or mechanism-family values, one family per image. ABI/manifest admission precedes invoke, replies are bounded, plugin panic is contained, and the host frees owned reply memory exactly once. Evidence: fd81a003, bfaea140, 9f7b8854, ed6e7c2a, 9465291e, 24b1fe4a.
14 vibe-ext publishes safe lifecycle, compiler and mechanism-family author macros over the same four ABI-1 symbols; one cdylib belongs to one family. Authors never touch unsafe. Evidence begins at bfaea140 and extends through 24b1fe4a.
15 Closed-source plugins ship prebuilt. The exact current-platform prebuilt wins; otherwise declared source may build under the provider root, and absence refuses rather than skipping. Prebuilt bytes are ordinary package content and both paths enter the same record, immutable-image, ABI/schema and loader admission laws. Platform projection occurs before build/load. Evidence: 1baac652, 269bec0d, bb50aeab.
16 A compile: transform runs inside lane regeneration, potentially per document. Script/binary spawn and agent nondeterminism are therefore forbidden: compiler contributions execute only as builtin or admitted native handlers. R5/R6 retain this closed production dispatch.
17 A contribution enters the pass tier by declaring compiler_internals = true on its [[extension]] table — the owner-mandated flag, a single conspicuous boolean whose presence means "this plugin asked for the low-level API". Without it, pass = … is a validation error. With it: host activation is always required; the activated pass list and positions bind the generated artifact header and freshness; and narration names each internals plugin. Grammar and refusal landed in b2a6efb5; retained owner-plan carriage landed through cf51582a.

Switching contributions on

18Installing a package is the consent to run its contributions: a dependency's phase and slot contributions are active as soon as the package is installed. The consuming manifest can activate a contribution that is not automatic, disable one by its id, or override its configuration. There are no consent dialogs at run time; there is total transparency instead.

19 Host activation and override. The consuming manifest activates a dependency's non-auto contribution, or re-configures an auto one, by reference: [[extensions.use]] ref = "org.vibevm.x/y#xml-squeeze" with optional config override; [extensions] disable = ["org.vibevm.x/y#announce"] turns any contribution off. The plural extensions namespace is the host-control surface; singular [[extension]] remains only the declaration table nested in the manifest that provides the contribution. The reference key is <group>/<name>#<id> — stable, printable, greppable.
20 The owner's trade (2026-08-25): no consent dialogs, total transparency instead — debuggers, scanners, and internals specialists must be able to see instantly what the extension machine is doing. Observability is therefore a REQUIREMENT of the machine, specified with it, not a later courtesy:

21Within one point, contributions run in a fixed order: the built-in bindings of the effective preset first, then the packages' contributions in dependency order, then the project's own; the order is derived, never negotiated.

22 Within one extension point, contributions run in this order: (1) built-in bindings of the effective kind-preset (§4.5) — Maven's «packaging first»; (2) dependency-declared contributions, in lockfile package order (deterministic, no filesystem enumeration), then declaration order within a package; (3) host-manifest contributions and [[extensions.use]] activations, in declaration order. First-to-last, no reversals anywhere — one rule a user can hold in their head, against Babel's plugins-vs-presets special case (##NOT-FROM-BABEL).

Seeing what ran

23vibe extensions lists every declared contribution in the installed world with its point, handler and origin; --json gives the same to a scanner. Everything a contribution can do is declared, so a scanner needs no execution to audit a project. Every run prints the contributions it will execute before it starts, and a run can be traced pass by pass with --trace-compile into .vibe/trace/.

24 The extension registry is a query surface: vibe extensions (with --json) lists every declared contribution in the installed world — id, point, handler kind, providing package and version, config, compiler_internals flag, auto/host-activated state, disabled state, and for natives the artifact path + build state + content hash. One command answers "what runs on my project and who brought it". vibe list/show mark internals-bearing packages, as §7.4.3 already requires.
25 Lifecycle-owned compilation is traceable, LLVM-style: the landed R3.4 surface is direct install, every default phase/chain, update and reinstall; compile sites owned by init, publish staging and uninstall remain explicitly outside this epoch until they gain their own command-owned JTD report boundary. On the landed surface, --trace-compile (or selected-manifest [compile] trace = true) attempts to certify the compiler's generated JTD IR after every successful pass while the run budget and writer permit, the -print-after-all genre, and aggregates every pass outcome/timing, the -time-passes genre. The index records why a snapshot is absent (snapshot-skipped-budget, snapshot-failed, or pass/verifier failure), so observer refusal never becomes compiler failure. One project run lives under .vibe/trace/<run>/: index.json is the generated authority, while certified snapshots use a reversible Windows-safe name carrying the global sequence, encoded pass, scope kind, encoded scope label, encoded artifact id and occurrence ordinal; exact scope/attempt identity remains in the event→scope relation in the index. A name beyond the physical cap uses a digest suffix whose full identity remains in the index. Thus two parse documents, package units and node artifacts cannot collide, and adjacent certified snapshots answer “what did THIS pass change?” without a trace-only IR dialect.

26vibe tools lists the binaries and servers the installed packages brought, which is the registry of what a contribution of kind binary may name.

27
vibe tools
vibe tools: nothing installed declares a binary or an MCP server

Providers

28A contribution of kind agent needs someone to run its prompt. Under an agent host, vibe parks the task for that agent and resumes when the declared outputs exist. At a terminal, vibe can call a configured model provider, the first one being any OpenAI-compatible endpoint, and it reads the provider's credentials only when a non-fresh agent step actually reaches the point of calling it. Every model-enhanced feature declares whether it is off, assisting or required, and off is the default.

29 The first provider id is exactly openai-compatible: synchronous object-safe LLMProvider::chat, generated epoch-1 Chat request/response, blocking bounded transport and provider-independent usage. User config owns provider/model/endpoint/token-file; selected project [llm] owns default provider/model and an optional credential env source. Project provider/model win independently; endpoint remains operator-owned. A nonempty project env source wins over a token file and fails honestly when absent. Keyed traffic requires HTTPS; keyless HTTP is literal loopback only; redirects are disabled, loopback bypasses ambient proxies, response bodies/timeouts are bounded, and keys/query/body/raw provider responses never enter diagnostics.
30 Each genuinely LLM-enhanceable feature declares off | assist | required; an undeclared enhancement mode is off. off runs only the algorithmic implementation. assist may call the configured provider and falls back to the algorithmic result with a visible degradation record on unavailable/failure. required is an explicit operator choice and fails with remediation when the paid enhancement cannot run. The existence of required never permits removal of the subsystem's algorithmic mode. A pure handler = { kind = "agent" } contribution is a separately declared agent workload, not an enhancement mode: once activated, its absence would be a silent skip and ##AGENT-CLI/##AGENT-HANDSHAKE govern it.

Edge cases and rules

31A contribution may carry a selector that limits it to matching files or targets, so a formatter binds to one language's sources and not to the rest of the tree.

32 Selectors (the Webpack loader test-rule genre, 2026-08-25 audit): a contribution may carry applies_to = { packages = ["org.x/*"], paths = ["vibevm/vibespecs/prompts/**"] } — declarative include/exclude scoping evaluated by the ENGINE, so a per-document transform (tier-1 compile:source/document) or a per-slot moment runs only where declared, visible to §3.5's registry, instead of every plugin re-implementing filtering privately in config. Absent selector = applies everywhere its point fires, today's behaviour.

33A native handler crosses a C-and-JSON boundary, never the Rust ABI, so a library built by another toolchain version still loads.

34 8.1 The ABI is C + JSON, never the Rust ABI

35Enabled model work carries per-run ceilings on calls and tokens; a step that would exceed them stops rather than spends.

36 Each enabled enhancement or explicit agent workload carries per-run call, input-token and output-token ceilings. Narration names feature/contribution, mode, provider/model, reason, cache posture and ceilings before spend without exposing secrets. Completed state records provider-reported usage. Exceeding a ceiling follows the selected contract: assist falls back; required or an explicit agent workload fails. Exact default ceilings and manifest grammar land with the first enhancement that consumes them; no global create budget exists.

For an agent

This page has a machine mirror. The citation carries the version rather than latest, so what an agent quotes does not move under it.

spec://org.vibevm.core/vibevm-docs@1.0.0/lifecycle/extensions-and-providers

.md.xmlllms.txt