Appearance
API Stability & Deprecation Policy
This page is the authoritative reference for what an extension author may rely on, how the extension API is versioned, and how breaking changes are communicated. It governs the public @hapi/protocol/ext surface — the manifest schema, the three host interfaces, and the contribution-point types documented in Authoring Extensions.
v0.1 is pre-stable
The extension API is at version 0.1.0 (PLUGIN_API_VERSION). Under SemVer §4, a 0.y.z API is pre-stable and anything MAY change. This policy publishes an intent classification on top of that: surfaces marked stable carry a good-faith commitment to the deprecation process below; surfaces marked experimental may change — even break — in a minor release with only a changelog note. The full deprecation guarantee activates at 1.0.
Who this is for
At the current build-time-composition stage, first-party extensions are compiled into the single binary and the maintainer controls both sides — so this policy is not yet externally binding. It becomes load-bearing once the runtime filesystem loader ships and externally-authored extensions exist. It is published now so that loader has a contract to enforce and so the manifest's apiVersion field means something concrete. See Scope & third-party authorship.
Public API surface classification
Every export of @hapi/protocol/ext is classified below. Stable = the shape is settled and (where applicable) proven by one or more shipped extension migrations; changes follow the deprecation policy. Experimental = net-new, not yet exercised end-to-end, or deliberately loosely typed; may change in a minor release with only a changelog note.
Version primitives — stable
| Surface | Kind | Classification | Notes |
|---|---|---|---|
PLUGIN_API_VERSION | const | Stable | shared/src/ext/version.ts — the canonical version constant |
PluginApiVersion | type | Stable | Literal type of the constant |
Manifest contract — mostly stable
| Field | Classification | Notes |
|---|---|---|
id, version, apiVersion, displayName, description, defaultEnabled, entryPoints | Stable | The core third-party author contract. See the manifest schema. |
configSchema | Stable | Zod schema for the extension's own config; validated by host.config.get() |
requiredAtBuild | Experimental | v0.1-specific disable semantics (bypasses the enable check); behavior may change as the preferences UI lands |
dependsOn, conflictsWith | Experimental | Declared but not enforced in v0.1 — reserved for a future resolver |
Register signatures & config helpers — stable
| Surface | Classification | Notes |
|---|---|---|
ServerRegister, WebRegister, CliRegister | Stable | register(host) => Promise<void> | void |
validateExtensionConfig, safeValidateExtensionConfig, InferExtensionConfig | Stable | Zod config-validation helpers |
ServerExtensionHost
| Surface | Classification | Notes |
|---|---|---|
notifications.addChannel | Stable | Proven by notifications-push / notifications-telegram |
agents.register | Stable | Wraps the shared AgentRegistry seam. The shipped agent backends (agent-codex / agent-gemini) exercise the CLI host's agents.register, not this one; no server-side agent extension ships yet — so this row is stable by virtue of the shared seam, not a server-side exerciser. |
store.addTable, store.addMigration | Stable | Proven by scheduler |
config.get<T>() | Stable | Reads the extension's own extension_prefs row |
events.subscribe, events.emit | Stable | The extension event bus (ExtensionEvent) |
lifecycle.onStartup, lifecycle.onShutdown | Stable | Long-running services start here, never in register() |
log | Stable | Structural ExtensionLogger |
routes.add | Stable API, experimental typing | Proven by scheduler / watchdog / git-rpc / heartbeat / github-issues. The method is load-bearing; its HonoRouteFactory<TDeps = unknown> argument is deliberately opaque — narrow TDeps at the call site. The typing may tighten without an API bump. |
capabilities.add | Experimental | Contribute a capability-catalog entry (#3752), gated on catalog.contribute. Server host only — the CLI/web hosts are stateless/singleton with no live-catalog assembler. The CatalogEntry shape is pre-stable (@hapi/protocol capabilityCatalog, #3741) and may tighten. |
services.register, services.get | Stable API, experimental typing | Cross-extension handle registry (scheduler publishes, heartbeat reads); handles are unknown — narrow at the call site |
late-bound accessors — getSyncEngine, getPushService, getNotificationHub, getSystemEventQueue, getRpcRegistry, getNodeRegistry, getFederationApiToken, getJobRegistry, getJobNodeId, getRawDb, getCoreConfig, getStore, getErrorLogger | Stable API, experimental typing | Return unknown and may be null before boot completes — narrow at the call site and null-check. The set of accessors and their return types will tighten as core surfaces stabilize. |
WebExtensionHost
| Surface | Classification | Notes |
|---|---|---|
toolViews.addCompact, addFullInput, addResult | Stable | Proven by tool-views; the ToolViewLike component arg is narrowed to ComponentType<ToolViewProps> at the web layer |
sessionDetail.addTab, addInfoField, addAction | Experimental | Net-new slot infrastructure; the InfoField/Action slots ship renderable-but-unmounted (mounts land with future extensions) |
preferences.addSection | Experimental | Consumption site is owned by the (unlanded) admin-drawer epic; do not rely on it yet |
navigation.addRoute | Experimental | NavigationRouteSpec still settling; wiring is consumed at router module-eval |
CliExtensionHost
| Surface | Classification | Notes |
|---|---|---|
commands.add | Stable | Proven by agent-codex / agent-gemini |
agents.register | Stable | Proven by the CLI agent backends |
lifecycle.onSessionStart, onSessionEnd | Stable | Handler receives the shared Session |
Contribution-point shapes
The argument/return shapes in shared/src/ext/contributions.ts inherit the classification of the host method that consumes them. Shapes for stable methods (NotificationChannel, CommandDefinition, CommandContext, AgentBackend, AgentBackendFactory, ExtensionTableSpec, ExtensionMigration, ExtensionEvent, ExtensionEventListener, ExtensionLogger) are stable. Shapes for experimental methods (SessionDetailTabSpec, SessionDetailInfoFieldSpec, SessionDetailActionSpec, PreferenceSectionSpec, NavigationRouteSpec) are experimental. ToolViewLike is an opaque unknown placeholder narrowed per-runtime.
Versioning semantics
The Extension API version is PLUGIN_API_VERSION in shared/src/ext/version.ts. It is distinct from a manifest's own version (the extension's release) and its apiVersion (the API version it targets); see the manifest schema.
The pre-1.0 rule (current — 0.y.z)
While the API is 0.y.z:
- Additive changes do not bump the version at all. New contribution points and new optional manifest fields are added in place — this is the explicit behavior encoded in
version.ts("host interfaces may gain contribution points additively without an API-version bump"). - Breaking changes to a stable surface bump the minor (
0.1.0→0.2.0) and follow the deprecation policy. - Experimental surfaces may change or break in any release with only a changelog note.
The post-1.0 rule (activates at 1.0.0)
Once the API reaches 1.0.0, standard SemVer 2.0.0 applies to the stable surface:
| Bump | Meaning |
|---|---|
major (1.0.0 → 2.0.0) | A breaking change to a stable surface item (removal, rename, signature change) |
minor (1.0.0 → 1.1.0) | An additive change (new contribution point, new optional field), OR the removal/break of an experimental item |
patch (1.0.0 → 1.0.1) | A bug fix, documentation change, or type narrowing with no signature change |
Deprecation policy
Modeled on the Node.js deprecation policy: a deprecation moves through three stages before removal.
- Documentation-only — the surface is marked
@deprecatedin its JSDoc, listed in the Active deprecations table with a stableHAPI-EXT-DEPxxxxidentifier, and noted in the version history. No runtime change. - Runtime warning — using the surface logs a once-per-process warning naming the deprecation ID and the migration path. The surface still works.
- Removal — the surface is deleted.
Cadence & notice period
| Surface class | Requirement before removal |
|---|---|
| Stable | At least one released version at stage 1 (documentation-only) and at least one released version at stage 2 (runtime warning), with a minimum of ~4–6 weeks at documentation-only. Removed only at a minor bump pre-1.0, or a major bump post-1.0. |
| Experimental | May be removed in any release with a changelog note. No staged requirement. |
Migration-guide requirement
Every stable-surface deprecation MUST ship a migration entry — before/after code — in Migrating to Extensions (or a dedicated deprecations section), referenced by its HAPI-EXT-DEPxxxx ID. Experimental-surface changes are noted in the version history but do not require a full migration guide.
Active deprecations
None. No @hapi/protocol/ext surface is deprecated as of 0.1.0.
| Deprecation ID | Surface | Stage | Since | Removal target | Migration |
|---|---|---|---|---|---|
| — | — | — | — | — | — |
Compatibility matrix
The matrix records which Extension API versions are compatible with which Hapimatic releases. An extension declares the API version it targets via manifest.apiVersion; the host publishes PLUGIN_API_VERSION.
Extension API apiVersion | Compatible Hapimatic releases | Status |
|---|---|---|
0.1.x | Every release shipping PLUGIN_API_VERSION 0.1.0 (current) | ✅ Supported |
Compatibility rule
An extension declaring apiVersion X.Y.Z is compatible with a host at PLUGIN_API_VERSION A.B.C when:
- Pre-1.0 —
X.Y == A.Bexactly. Any0.yAPI is compatible only with a host on the same0.y(because pre-1.0 minor bumps may be breaking). - Post-1.0 —
X == A(same major) and hostB ≥extensionY(the host is at least as new as the extension, within the same major).
Enforcement is not yet live
This rule is documentation-only today. Runtime enforcement — rejecting an incompatible manifest.json at load time — is delivered by the runtime filesystem loader (epic #1264 Phase 5). At the current build-time-composition stage every extension compiles against the same PLUGIN_API_VERSION, so a mismatch cannot occur.
Update process
The matrix is a release-time artifact. Whenever PLUGIN_API_VERSION changes, add a row here (and mark superseded rows) as part of the same change that edits shared/src/ext/version.ts. The matrix grows meaningfully once multiple API versions and externally-authored extensions coexist.
Version history
A changelog of PLUGIN_API_VERSION, newest first. Records the semantics above as applied to each release.
| API version | Date | Change |
|---|---|---|
0.1.0 | 2026-07 | Initial pre-stable release. Manifest schema, three host interfaces, contribution-point types, register signatures, config validators. Classification and this policy established. |
Scope and third-party authorship
Hapimatic's extension system ships today as Pattern 1 (build-time composition): extensions are TypeScript modules compiled into the single binary, and there are no externally-authored extensions yet. This policy is authored now, ahead of that audience, on purpose:
- The subject of the policy — the
@hapi/protocol/extsurface and themanifest.apiVersionfield — exists today and is exactly what the future runtime loader will validate against. Publishing the version/compatibility rules now gives that loader a spec to build to. - The classification and compatibility matrix are the inputs a future extension marketplace / discovery surface renders (API maturity, version compatibility, deprecation notices).
Until the runtime loader ships, treat the stable/experimental classification as a maturity signal and a commitment of intent, not a runtime-enforced guarantee. See the Extension System Architecture epic for the Pattern 1 → Pattern 2 roadmap.