Skip to content

Plugin system

The plugin system is inseam’s extension surface. A plugin is an NPM package whose identity is its package name (@inseam/plugin-<name> first-party, inseam-<name> third-party); the loader verifies NPM provenance attestation, materializes a durable plugin_package row, and admits the plugin to an ephemeral LoadedPluginSet. Plugins contribute through typed registries on RegistrationContext and run handlers against a freshly-scoped RuntimeContext whose capabilities the host has granted.

A plugin’s packageName is captured in the closure that builds its contexts; plugins never pass their own name in, so they cannot forge another plugin’s tag or register under another plugin’s identity. The plugin_package row is durable and network-synced (rides the Events outbox); the LoadedPluginSet is process-local and regenerated on every host boot.

Capability authority is the intersection of three layers: the plugin’s manifest declares requested, the host config declares the allowed ceiling, and installation persists the granted set the loader passes in per PluginLoadRequest. Registration-time add calls and runtime-time method calls both check the granted set and throw CapabilityDeniedError synchronously when missing.

Two surfaces, split by package:

  • @inseam/plugin-contractPluginPackageName, PluginPackageRow, PluginManifest, PluginModule, RegisterFn, RegistrationContext (with its Registries bag), RuntimeContext (with source / emit / kv / http / clock / log / self), Capability, and every error class. Zero runtime deps.
  • @inseam/core (plugin-system contribution) — openPluginRegistry, PluginRegistry, AttestationVerifier (test seam — production uses the sigstore-backed default), DEFAULT_PLUGIN_SYSTEM_LIMITS, ALL_CAPABILITIES.
  • PluginModule.register(ctx) — the only thing a plugin author exports. Pure declaration: ctx.registries.*.add(...) only. No I/O, no fetch, no KV. A throw fails the load and rolls back partial contributions.
  • RegistrationContext — exposed to register. Bundles the area registries (connections, sources, locationKinds, events, operations, …). Auto-tags every contribution with the plugin’s packageName.
  • RuntimeContext — passed as the first argument to each handler the plugin registered. source.write (auto-tagged), kv (durable per-plugin scratch), http (capability-gated), clock, log, self.
  • Seven-step load sequence. Tarball verification → manifest parse → capability intersection → row materialization → context construction → register invocation → registry admission. Each step has its own error class; a failure lands in that plugin’s PluginLoadOutcome and sibling plugins in the batch continue.
  • developmentMode: true — opens the registry with attestation skipped and workspace:* package names stored verbatim, so in-tree dev data is visibly tagged. Production hosts refuse workspace:* names outright.

Exact signatures and the full load sequence: @inseam/plugin-contract API · arch/plugin-system/spec.md.

Every inseam extension — Connection kind, Location Kind, identifier kind, listener, future area — ships through this same shape. A third-party plugin depends on @inseam/plugin-contract plus whichever area contract(s) it implements against, and on nothing else from the framework.

The minimum a plugin author needs:

  1. Name the package @inseam/plugin-<name> (first-party) or inseam-<name> (third-party). The name is the durable identity recorded on every row the plugin asserts.
  2. Publish with npm publish --provenance from CI. The loader refuses tarballs without a valid attestation.
  3. Export a register(ctx) function. Pure declaration only.
  4. Run runtime work inside the handlers registered through the area registries. Write triples through ctx.source.write (auto-tagged), keep cursors / refresh tokens in ctx.kv, fetch through ctx.http, time through ctx.clock.
  5. Declare every capability the plugin will exercise in inseam.capabilities.requested. The host’s granted set is requested ∩ allowed; touching a service without its capability throws CapabilityDeniedError.

If you’re tempted to put runtime work directly in register, stop — register is declaration-only by design. Move the work into a handler registered through ctx.registries.

  • LLM summary — dense reference for agents.
  • ConnectionConnectionDefinitions are plugin contributions.
  • Source — Location Kinds are plugin contributions; uninstallPlugin triggers the envelope-keyed sweep.
  • Access — identifier kinds and envelope-party roles flow through ctx.registries.
  • Eventsplugin_package.observed / plugin_package.revoked ride the outbox.
  • arch/plugin-system/design.md — why identity is the package name and why plugin_package is a row.
  • arch/plugin-system/spec.md — types, the 7-step load sequence, errors, acceptance criteria.