Plugin Platform

How Owlat is extended: one validated manifest, a capability model, and three execution tiers.

Owlat's variation points — send transports, agent steps, draft strategies, autonomy gates, automations, webhook events, import providers, crons, navigation, settings — are all reachable from plugins. A plugin is an npm package that exports one validated manifest and, optionally, static modules behind the contracts in @owlat/plugin-kit.

The platform has three deliberate properties:

  1. One manifest. Everything the host needs to compose, permission, flag, budget, and render a plugin is data in definePlugin(...). Validation never invokes plugin code.
  2. Declaration is not authority. A manifest requests capabilities. An operator grants them. Both are rechecked at the moment of every hosted operation.
  3. No arbitrary runtime code. Nothing loads JavaScript into Convex or the browser at runtime. Bundled code is composed at build time; untrusted code runs out of process (a connected app) or behind an OS boundary (a sandboxed worker job).
Where the contracts live

@owlat/plugin-kit is the public compatibility boundary and the only package a plugin author imports. Its major version is the compatibility line — individual contribution interfaces do not carry their own versions. Pre-1.0 it tracks the Owlat repository version and contracts may break with the application.

The three tiers

TierWhat it isInstallationTrust
1 — Bundled pluginAn npm package listed in plugins.config.ts, composed into the Convex and Nuxt buildsEdit config → codegen → rebuild → deploySame trust as the operator's own deployed code. Capabilities and flags reduce authority; they are not a sandbox
2 — Connected appAn external HTTPS service with plugin-bound API keys and webhooks, plus a specified signed synchronous hook protocol whose runtime adapter is not shipped — see Connected AppsRegister in the dashboard; instant, no rebuildUntrusted network peer. Every response is strictly validated, scrubbed, clamped, and bounded
3 — Sandboxed worker jobA specified worker protocol behind the code-worker OS boundary; no host enqueue adapter is shippedReservedUntrusted process. No ambient credentials, hard CPU/memory/time ceilings, host-controlled command

One maintained reference plugin covers each tier:

TierReferenceWhat it contributes
1examples/plugins/escalation-guardAn agent step, a draft strategy, all three automation registries, a webhook event, and nav/settings entries — all in-process
2examples/plugins/slack-approvalsA restrict-only hold gate served out of process over the signed hook protocol; no in-process contributions at all
3examples/plugins/deliverability-labA seed-list job on the sandboxed queue, plus a Tier-1 send gate, cron and UI entries and llm:invoke for host-mediated generation

One plugin may use several tiers at once, as the Deliverability Lab does. examples/conformance drives all three through the real host, codegen and CLI on every test run.

Anatomy of a plugin

import { definePlugin } from '@owlat/plugin-kit';

export const helloPlugin = definePlugin({
    id: 'hello-owlat',
    version: '0.1.0',
    capabilities: ['plugin-storage:read', 'plugin-storage:write'],
    // Storage capabilities REQUIRE a flag: access must be revocable at runtime.
    flag: { default: false },
});
FieldMeaning
idLowercase kebab-case, ≤ 64 characters. Namespaces flags, storage, spend, audit attribution, and every contributed kind
versionSemantic version
capabilitiesRequested host operations, domain:action, exact match, at most 64
contributesData-only descriptors per contribution bucket, at most 256 per bucket
flag{ default, requiredEnvVars? }. Required whenever the plugin contributes anything, requests plugin storage, or requests llm:invoke
llmBudget{ dailyUsd }. Required with llm:invoke; a hard per-UTC-day ceiling
componentOne condition-independent Convex component export, installed under an injective plugin_<id> namespace
settingsSchemaDeclarative operator form; the host renders, validates, and redacts it

How a bundled plugin reaches production

  1. The package name is added to the checked-in root plugins.config.ts.
  2. bun run plugins:codegen (or owlat-plugins codegen) verifies the installed package's identity, lockfile integrity and realpath containment, imports only its manifest entry, validates one immutable snapshot, and writes the checked-in Convex and Nuxt composition files ordered by manifest id.
  3. Builds and CI run bun run plugins:check, so stale or invalid composition fails the build closed.
  4. The deployment rebuilds. The plugin's feature flag starts at its declared default — normally false — so nothing runs until an operator enables it and grants capabilities.
A manifest is not a sandbox

Bundled plugin code runs in Owlat's own processes. Capability declarations, grants, flags and host-mediated services reduce what a bundled plugin can reach and make its actions attributable — they do not contain hostile code. Anything you do not fully trust belongs in a connected app (Tier 2) or a sandboxed job (Tier 3).

Package map

PackageRole
packages/plugin-kitPublic contracts: definePlugin, capability constants, every contribution type. The only package a plugin imports
packages/plugin-hostPrivate, runtime-neutral enforcement kernel shared by Convex and Nuxt: capability checks, flags, ordering, gate results, untrusted-text policy
packages/plugin-codegenBuild tool that owns the single composition point and the generated catalogs/registries
packages/plugin-cliowlat-plugins — create, add, remove, codegen, dev
examples/plugins/*Maintained reference plugins exercising the real contribution points
examples/conformanceThe conformance gallery: lifecycle (install/add/remove/disable/upgrade) and full-pipeline replay suites over those references, plus the plugin-provider parity proof — a fixture ESP bundle driven through the shipped routing, dispatch, measurement, return-path and credential-form modules

Architecture decisions: ADR-0049 (platform contract), ADR-0050 (draft strategies), ADR-0051 (ordered autonomy gates), ADR-0052 (connected apps and signed hooks), ADR-0053 (sandboxed plugin jobs).