Installing & Operating Plugins

The operator lifecycle: install, enable, grant, configure, monitor, revoke, and remove.

This is the operator's side of the platform. Plugin administration requires the organization:manage capability — workspace owners and admins only.

Install a bundled plugin (Tier 1)

Bundled plugins are composed at build time, so installation is a code change, not a dashboard action.

Install the package

Add it as a direct root dependency (dependencies or optionalDependencies) from the npm registry. Codegen verifies the installed package identity, the bun.lock integrity entry, and realpath containment. Aliases, git/URL/file/workspace sources, and external symlinks fail closed.

Register it

bun packages/plugin-cli/src/index.ts add @vendor/owlat-plugin-example

This edits the checked-in plugins.config.ts and prints the capability diff the change introduces. Run it with --dry-run first to review the diff and the proposed file without writing.

Regenerate the composition

bun run plugins:codegen

Commit plugins.config.ts and the generated composition files in the same change. CI runs bun run plugins:check, which fails when generated output is stale or invalid.

Rebuild and deploy

The plugin now exists in the deployment. Its feature flag sits at the manifest's declared default — normally false — and no capability is granted, so nothing runs yet.

Review the capability diff before you deploy

The diff is the security review surface for a bundled plugin: it is the complete list of host operations the package can ever request. Bundled code runs with the same trust as your own deployed code — read the diff, not just the README.

Enable and grant

Settings → Plugins lists every registered plugin with its state, capabilities, and required environment variables.

Enabling a bundled plugin is an explicit consent action:

  • The deployment must have every flag.requiredEnvVars value present. Enabling fails with the list of missing variables otherwise.
  • The enable request must approve every capability the manifest declares — no more, no fewer, no duplicates. A partial or unknown approval is rejected.
  • Disabling the plugin deletes its grant record entirely.

That is how "grants can only restrict the manifest" appears in practice for Tier 1: the operator's choice is to consent to the full declared set or to leave the plugin off. Connected apps (Tier 2) are the tier where a subset of the manifest is granted per app.

Both the flag state and the grant record are read fresh inside each caller's transaction, so disabling a plugin or revoking a grant takes effect on the very next operation — no redeploy and no cache to wait out.

Configure

If the plugin declares a settingsSchema, its detail page renders a generic form: string, number, boolean, and select fields with the declared labels, descriptions, defaults, and bounds. The host validates input server-side against the schema.

A secret field is not part of the form. Owlat stores no plugin credentials: the field names a PLUGIN_-prefixed environment variable you set on the deployment, and the page shows only that variable's name and whether it is present. A save that carries a value for a secret key is rejected. "Reset to defaults" clears stored values back to the schema defaults.

If a plugin is removed from the build while settings rows remain, the index page offers "Clear plugin settings" for that orphaned id.

Operate the tiers

Tier 2 — connected apps

Registered from the dashboard; no rebuild. Each app is bound to a plugin id, requests a subset of that plugin's declared capabilities, and holds an encrypted shared secret used to sign hooks. Statuses are enabled, disabled, and revoked (terminal). See Connected Apps.

Tier 3 — sandboxed jobs

The code-worker retains the dormant plugin-job polling protocol, but no host enqueue adapter or operator job UI is shipped. The plugin-tasks profile is therefore not a usable plugin surface today:

COMPOSE_PROFILES=plugin-tasks docker compose up -d code-worker

The same container actively serves the coding-agent queue under the inbox-codetasks profile. See Sandboxed Jobs for the reserved Tier-3 contract.

Monitor

SurfaceWhat it shows
Audit logEvery hosted plugin operation, attributed with organizationId and pluginId, recording fixed operation and outcome codes only
Hook delivery logsPer-app, per-kind signed-hook resolutions: attempted or not, which side won, the fixed fallback reason, and network duration. Retained for 30 days
Webhook delivery logsOutbound webhook deliveries, including plugin-published events
Plugin task listRecent Tier-3 jobs with status, attempts, and timing

None of these surfaces contain payloads, prompts, generated text, mail content, secrets, or signatures — the redaction is structural, so no read path can leak them.

Revoke and remove

GoalActionTakes effect
Stop a bundled plugin and withdraw its authorityDisable its feature flag — this also deletes its grant recordNext operation
Stop a connected app nowDisable itNext hook call or API request
Permanently kill a connected appRevoke it (terminal)Next hook call or API request
Revoke a plugin-bound API keyRevoke the key, or disable the plugin it is bound toNext request — the effective scope set is re-derived per request
Remove a bundled pluginowlat-plugins remove <package>, regenerate, rebuild, deployAfter redeploy

Removing the package from plugins.config.ts also removes its Convex component registration. Plugin storage rows and settings survive the removal; use "Clear plugin settings" on the plugins index to purge the orphaned settings.

Incident response order

If a plugin is misbehaving, disable the flag first — it is instant and needs no deploy. Only then investigate, and remove the package afterwards. Do not start with a rebuild: it is the slowest lever you have.

Zero-plugin deployments

The zero-plugin composition is a valid no-op deployment. plugins.config.ts with an empty bundledPluginPackages array generates a composition with no plugins, and every registry falls back to its built-in members with their original order, defaults, and error semantics.