# Plugins

> A plugin is an attachable behavior that runs at the engine's gateway, the chokepoint every call passes through. It can gate, transform, observe or enrich any call boundary (models, cells, connectors, MCP servers, workers) with a built-in module or your own https endpoint, always on top of the platform floor. Manage them on Govern → Plugins. As of September 25, 2026, OpenEng AI isn't live yet.

A **plugin** is an attachable behavior that runs at the engine's gateway, the
chokepoint every call passes through. It can **gate** (block) a call,
**transform** it, **observe** it, or **enrich** it with headers or
credentials, on any call boundary — models, cells, connectors, MCP servers,
workers and more — without changing the component it guards. A plugin runs
either a **built-in module** or **your own https endpoint** (an external
policy service, an audit sink, a queue). Plugins always sit on top of the
platform [floor](govern.md#gateway-guardrails), which is always on and can't
be shadowed. You manage them on **Govern → Plugins**.

> [!WARNING] Status on September 25, 2026: OpenEng AI isn't live yet
> The Console and Web Console interfaces load, but the OpenEng AI API routes and the Broker aren't
> deployed. Nothing can be saved or published, and no AI engine can register or connect. Pro,
> Enterprise and pay-as-you-go launch in Q4 2026 and can't be purchased yet. This page describes
> OpenEng AI as it works once those services are live ([status](ai.md)).

*Figure: Govern → Plugins — the locked secret-egress floor first, then your plugins grouped by the gateway they guard (Model, Cell, Connector); the editor opens in a slide-over.*

## What a plugin does

Every plugin has one or more **capabilities**:

| Capability | What it can do |
|---|---|
| **gate** | Let the call proceed or reject it |
| **transform** | Change the call or its result |
| **observe** | See the call (telemetry, audit), changing nothing |
| **enrich** | Add or remove headers, including credentials from `${ENV}` references |

It runs at an **attach point**:

- **entry** — before the call runs;
- **exit** — after the call, before its result returns;
- **during** — alongside the call, never blocking it.

And in a **mode**: **sync** (the call waits for the plugin) or **async** (it
doesn't). Three rules keep this coherent, and the editor applies them live
exactly as the engine does:

- gate and transform require **sync**: you can't block or change a call you
  didn't wait for;
- a **during** plugin is observe-only;
- an **async** plugin can't gate or transform.

## The catalog

The shipped plugins are published at
**[plugins.openeng.app](https://plugins.openeng.app)** (ids `plg_def_…`). As of
2026-09-25 there are 14: two built-in plugins and twelve external ones.

| Plugin | Attach · mode | Capabilities | Kind |
|---|---|---|---|
| Telemetry | during · async | observe | built-in (free) |
| Trace propagation | entry · sync | enrich | built-in (free) |
| Approval gate | entry · sync | gate | external (Pro) |
| Audit sink | during · async | observe | external (Pro) |
| Database query | entry · sync | gate, enrich | external (Pro) |
| Decision endpoint | entry · sync | gate | external (Pro) |
| Enrich context | entry · sync | enrich | external (Pro) |
| HTTP call | entry · sync | gate, enrich | external (Pro) |
| Key-value lookup | entry · sync | gate, enrich | external (Pro) |
| Notify | during · async | observe | external (Pro) |
| Queue publish | during · async | observe | external (Pro) |
| Rate-limit check | entry · sync | gate | external (Pro) |
| Transform service | exit · sync | transform | external (Pro) |
| Webhook emit | during · async | observe | external (Pro) |

An external catalog plugin is a pattern: the Decision endpoint, for example,
sends the call to a policy service such as OPA or your own approver and
proceeds or rejects on its answer. You supply the endpoint. The catalog's
`catalog.json` contract is described on [Catalogs](catalogs.md#catalog-json).

## The Plugins page

The page has two tabs, **Mine** and **Catalog**.

On **Catalog**, **Use** adopts a built-in plugin into your collection in one
click; for an external plugin it opens the editor instead, because it needs
your own https endpoint before it can be saved. **Customize** opens any entry
in the editor as a copy under a fresh id.

**Mine** starts with the **secret-egress floor** as a locked note: it is
always on, runs above every plugin, and can't be attached or shadowed. Below
it, your plugins are grouped by the gateway they guard:

- **Model gateway** — every model call;
- **Cell gateway** — every cell read and write;
- **Connector gateway** — every [integration](integrations.md) call.

A plugin whose resource classes are left open (`*`) appears under all three.
Each row shows its attach point and mode, capabilities, whether it sees read
operations, write operations or both, and whether it runs a built-in module or
an external endpoint, with **Versions**, **Edit** and **Delete** actions.

## The editor, field by field

**New** (or ⌘K → *New plugin*) opens the editor in a slide-over.

| Field | Values |
|---|---|
| **Name**, **Enabled** | Name is required; a disabled plugin never runs |
| **Attach point** · **Mode** · **Capabilities** | As above; the notes under the field say when a choice was adjusted (for example *gate/transform forces sync mode*) |
| **Matcher — caller types** | Who is calling: `user`, `root`, `delegator`, `sub_delegator`, `sub_agent`, `specialist`, `system`, or `*` (the default) |
| **Matcher — gateway class / resource classes** | What is called: `model`, `cell`, `connector` (the three gateways), or other classes such as `worker`, `mcp`, `search`, `research`, `sandbox`, `host`, `agent`, `planner`; `*` (the default) is every boundary |
| **Matcher — operations** | `read`, `write`, or neither (every operation). A write-back or AML guard gates writes only |
| **Matcher — target globs** | e.g. `file.edit.*`, `mcp:*`, `role:planner` |
| **Matcher — provenance** | Optional, e.g. `mcp:*`, `cell:*` |
| **Matcher — run modes** | `interactive`, `headless`, `sandbox`, or `*` (the default) |
| **Target** | A **built-in module** (below) or an **external endpoint** (Pro/Enterprise) |
| **Header ops** | Enrich only: set or remove a header, with a value or an `${ENV}` reference |
| **Timeout (ms)** | 800 by default |
| **On timeout** | `fail-open` (the default) or `fail-closed` |
| **Async consequence** | For async plugins: `journal` (the default), `flag` or `halt` |
| **Order** | Position in the ordered table (10 by default); lower runs first |

Under the matcher, a line estimates which representative boundary types the
plugin would match. It's a static estimate, not a live query.

An **external endpoint** takes:

- **Endpoint** — an `https://` URL. The Console refuses anything else; the API and the engine
  don't check the scheme again.
- **Credential env** — a `${ENV_VAR}` placeholder for the endpoint's
  credential.
- **Signing secret env** — a `${ENV_VAR}` placeholder for the HMAC key the
  engine signs each request with; your endpoint verifies the signature with the
  same value.
- **Mutual TLS** — present the machine's client certificate when connecting.
  The certificate is provisioned on the machine separately.
- **Payload policy** — how much of the call the endpoint receives: `full`,
  `digest` (the default) or `metadata-only`.

The **built-in modules** are a closed list, so a plugin can never name a module
that doesn't exist:

| Module | Capability | What it does |
|---|---|---|
| `builtin:telemetry-v1` | observe | Fire-and-forget telemetry |
| `builtin:trace-propagate-v1` | enrich | Propagates trace/span headers downstream |
| `builtin:schema-validate-v1` | gate | Structured-output conformance gate |
| `builtin:pii-redact-v1` | transform | Redacts secret and PII spans |
| `builtin:injection-screen-v1` | gate | Heuristic prompt-injection screen |
| `builtin:injection-model-v1` | gate | Model-backed injection screen (tier 2, caution-only) |
| `builtin:groundedness-v1` | gate | Model-backed groundedness check (tier 2, caution-only) |

A raw secret anywhere in a plugin is refused: every credential is a
`${ENV_VAR}` placeholder resolved on the machine.

## Use plugins in a config

A config uses the plugins you pick in the **Plugins** section of the config
builder's **Settings** step ([Configs](configs.md#the-settings-panel)). The
picker lists your own plugins only (adopt catalog entries on this page first),
grouped by attach point; a Pro plugin can't be picked on the Free plan.

When the engine serves the config, it compiles the config's plugins into its
gateway above the floor, so they run on every matching call. On each machine
the engine resolves a plugin's `${ENV}` placeholders from its environment or
from the value stored for the config on the Web Console's **Placeholder
values** screen; the environment wins. A plugin whose variable is unset is
disabled on that machine rather than called without its credential.

[Human-in-the-Loop](human-in-the-loop.md) rules compile into the same ordered
table as gate plugins, so an approval pause and a plugin's rejection on the
same boundary resolve in `order`.

## Plan & tier notes

Built-in plugins are free on **every plan**. External endpoints are a **Pro
and Enterprise** capability: publishing a config with an enabled external
plugin on Free is refused. Pro and Enterprise launch in Q4 2026 and can't be
purchased yet ([Plans & billing](account.md#ai-pricing)).

## Troubleshooting

**"floor:* modules are reserved for the non-removable platform floor."** A
plugin can add behavior above the floor, never replace it. Pick a built-in
module or an external endpoint.

**"The matcher needs at least a caller type and a resource class."** Use `*`
for any.

**"An external endpoint must be an https:// URL."** Plain http isn't allowed
for external plugins.

**`Field "…" looks like a raw secret`.** Put the value in an environment
variable on the machine and enter `${THAT_VARIABLE}` instead.

**Publishing is refused with a plan limit.** The config has an enabled
external plugin and the account is on Free. Disable it or pick a built-in
plugin.

**A plugin never fires.** Check that it's enabled and picked in the config,
that its matcher (caller types, resource classes, operations, run modes)
covers the call, that its `${ENV}` variables are set on the machine, and that
the session runs a config version published after your change.
