# Plugins

> Connected integrations that own an external surface and register their own tools — the bundled Google trio, the signed in-binary catalog, the HTTP, local MCP, and hosted MCP rails, OAuth and api_key auth, and the commands that manage them.

A plugin is a connected integration that owns an external surface — an API, a vault, a SaaS account — and registers its own tools into Fermix's [capability registry](/docs/capabilities-and-tools). Each tool a plugin contributes is named `<plugin>_<tool>` (for example `gmail_send_message` or `slack_list_channels`), so tools from different plugins cannot collide and the agent can see at a glance which surface a tool belongs to. Once a plugin is connected, the agent prefers its tools over the generic `browser`/`web_fetch`/`shell` capabilities for that surface.

## Bundled plugins

Three plugins are always present and cannot be uninstalled (you disable them instead):

- **Gmail**
- **Google Calendar**
- **Google Drive**

All three authenticate through Google OAuth. They follow a graceful-403 model: a connected plugin registers *all* of its tools regardless of which OAuth scopes you granted. If you call a tool whose scope was not granted, you get a clear "reauthorize" error telling you to reconnect with the missing scope — the tool does not silently vanish from the agent's view. Other Google 403s are classified distinctly (file-permission, organizer-only, rate-limit) so the error message says what actually failed instead of collapsing every denial into one generic auth error.

## Where additional plugins come from

Beyond the bundled trio, plugins come from a **signed catalog bundled inside the Fermix binary**. This is the only catalog source — there is no remote index and no runtime refresh. New and updated plugins reach you with the next Fermix release, so you get them by running `fermix upgrade` (see [distribution and upgrade](/docs/distribution-and-upgrade)).

Which plugins exist, and at which versions, is a moving target that this page deliberately does not duplicate. Run `fermix plugins catalog` to see what the binary you are running actually offers, and browse [`tezra-io/fermix-plugins`](https://github.com/tezra-io/fermix-plugins) for the source each one is built from.

Installing a plugin streams its tarball, verifies the artifact against the catalog's recorded **sha256** hash and a **cosign** signature, and re-validates the decoded manifest before activating it into a versioned store under `$FERMIX_HOME/plugins/`. The guarantee is simple: a plugin that installs will load. (Cosign verification needs the `cosign` binary on the daemon's `PATH`; a genuinely missing one is refused with an install hint, while one that exists but is not on the supervised daemon's `PATH` can surface as a misleading signature error — see [distribution and upgrade](/docs/distribution-and-upgrade).)

The catalog currently ships:

| Plugin | What it does | Rail | Auth |
|--------|--------------|------|------|
| **GitHub** | Work with repositories, issues, and pull requests. | HTTP | OAuth |
| **Notion** | Search, read, create, and update pages and data sources. | HTTP | OAuth |
| **X** | Post, reply, search, like, and read on X (Twitter). | HTTP | OAuth |
| **Slack** | Read channels, messages, threads, and members. | HTTP | api_key |
| **Discord** | Read servers, channels, messages, and members. | HTTP | api_key |
| **AgentMail** | Create agent-owned email inboxes; send, read, and reply to mail. | HTTP | api_key |
| **Obsidian** | Search, read, create, and append to notes in your local Obsidian vault. | Local MCP | none |
| **Eden** | Search and read your Eden second brain; capture into it if you opt in. | Hosted MCP | api_key |
| **Computer Use** (`computer_use_sidecar`) | The native helper binary behind the computer-use capability — a setup card, not a tool set, and not managed by `fermix plugins` (see below). | — | none |

One catalog entry is special. **Computer Use** (`computer_use_sidecar`) is not a tool-registering plugin: it is the native helper binary behind the **experimental, off-by-default** computer-use capability (see [capabilities and tools](/docs/capabilities-and-tools)). It runs on Apple Silicon (M-series) macOS and Linux x86_64 only — on an Intel Mac the download fails with a clear "no build for this machine" error.

It is also the one entry that does not go through the signed-tarball pipeline above, and `fermix plugins` does not manage it. The helper is fetched from the [compux](https://github.com/tezra-io/compux) project's own signed release and verified against a SHA-256 checksum baked into your Fermix build, then cached under `~/.fermix/plugins/compux/`. Enabling its card on the setup **Plugins** page does both halves — downloads the helper and flips `[fermix_core.computer_use] enabled` — and both are needed, with the change applying on the next daemon restart.

You only do that once. After that, a daemon that has computer use enabled but is missing the helper matching its build — the state an upgrade lands in when a new release pins a newer helper — downloads it itself on its next start. That happens on daemon boot only, is bounded to about thirty seconds, and fails soft: if the download does not land, computer use simply stays off until the next restart or until you use the setup card again.

## The three rails

Every tool-registering catalog plugin runs on one of three execution rails (the Computer Use helper, which registers no tools, is the one entry with none). The rail says where a tool's work happens: inside Fermix itself, in a process on your machine, or on the vendor's own servers.

### HTTP rail

An **http** plugin is a set of declarative request templates that run **in-VM** — Fermix makes the HTTP calls itself, no external process. The rail is double-guarded:

- **Install time:** the templates are validated, and every endpoint must be `https://` with no placeholder or userinfo (`user:pass@`) host.
- **Runtime:** an SSRF floor blocks requests to loopback, private, and cloud-metadata addresses, so a template can never be steered at an internal host.

Responses are guarded too: only JSON is accepted, and bodies are capped at 5 MiB — enforced while the response streams in, so the transfer halts before the chunk that would cross the ceiling and a compressed payload cannot inflate past it after the fact. Compressed responses are refused outright with a named error (plugin endpoints must serve identity encoding), and multi-page listings are fetched through bounded pagination — large downloads belong on the local MCP rail.

### Local MCP rail

A **local MCP** plugin is a **supervised local process** the daemon launches and monitors. On connect Fermix discovers the process's tools and registers them as `<plugin>_<tool>`, exactly like the HTTP rail. Installing a local MCP plugin first probes the host runtime its manifest declares (a Node or Python interpreter, or a vendored standalone binary); a missing or too-old runtime is refused with a `missing_host_runtime` status rather than a broken install.

Supervision is bounded, not infinite. A server that fails tool discovery is retried with exponential backoff — up to five attempts in total; if it never answers, it gives up terminally and is quarantined: the process tree is torn down and its tools stay unregistered instead of respawning forever. A genuine transport blip still reconnects on its own. Enabling or disabling a plugin asks the running daemon to re-apply its configuration, which starts a quarantined server fresh — as does restarting the daemon with `fermix restart`.

This rail is plugin-managed and distinct from the manually-configured `[mcp.servers]` blocks documented in [inbound and outbound MCP](/docs/mcp) — those are MCP servers you wire up by hand in `config.toml`; plugin mcp processes are installed and supervised through the plugin system.

### Hosted MCP rail

A **hosted MCP** plugin sends real content off your machine. Its tools run on the vendor's own servers over HTTPS, so the queries Fermix composes and the content it selects for them are transmitted to that vendor; the results then enter your configured model provider, this machine's conversation history, and, when content capture is on, local trace files and the configured exporter (see [traces and telemetry](/docs/traces-and-telemetry)). Connect one only if sending that account's content to the vendor is acceptable to you.

The plugin ships no code. Its artifact is a signed manifest plus a skill: there is no process to launch and nothing that executes locally. What the manifest signs is the enforcement boundary — the endpoint, the exact set of agent-visible tool names, and every tool's input schema, output schema, and annotations. On connect, Fermix asks the server for its tool list and checks it against that signature.

The check is all-or-none. If the vendor changes its tool set upstream — a named tool disappears, or any schema or annotation changes — **none** of that plugin's tools register, the status becomes `upstream_contract_mismatch`, and the tools disappear from the agent. There is no retry: the fix is a re-signed plugin version, which reaches you with a Fermix upgrade. Extra tools the vendor adds that the manifest does not name are ignored by name before their schema is parsed, which is intended rather than a failure.

Provenance is re-checked at load time, on every read of the plugin registry, not only at install: a hosted manifest loads only from a catalog-installed artifact whose publisher signature, published checksum, and file-tree digest all verify again. Editing an installed manifest cannot point the credential at a different endpoint — it makes the plugin refuse to load.

The runtime is bounded in three ways:

- **Startup retry covers unreachability only** — three attempts inside a 60-second budget. A rejected token, an endpoint-policy refusal, a protocol mismatch, or a contract mismatch stops immediately and stays visible, because retrying a rejected token cannot succeed.
- **Calls are paced** at one per 500 ms behind a 16-deep queue — a burst overflows with `remote_busy` instead of queueing indefinitely.
- **Tool results are text only** — inline images, audio, base64 media, and embedded resources are refused.

Live state (`connecting`, `ready`, `reauthorization_required`, `upstream_contract_mismatch`) exists only in the running daemon's memory and is never persisted. `fermix doctor`'s plugins row annotates a hosted entry with "(remote; …)" and reports that live state; with no daemon running it says the runtime status is unavailable rather than guessing. `fermix capabilities` and `fermix plugins list` do not distinguish hosted from local — a hosted plugin's tools appear as ordinary `mcp` capabilities. The other place the distinction is visible is the setup **Plugins** page: a catalog card's pre-install consent line is derived from the plugin's own manifest, so a hosted plugin's card discloses hosted execution instead of the local-process wording.

## Eden

Eden is a hosted second brain and the catalog's hosted MCP plugin. You connect a personal access token, pin exactly one workspace, and get read-only retrieval tools. How it is set up is how every hosted plugin is set up. Run `fermix capabilities` to see the tools it registers.

Read-only is the default and it is enforced, not advised: the default access profile registers **no** write tools, so a request to save something cannot be fulfilled rather than merely being discouraged. Switching to the write profile is an explicit choice on the web setup **Plugins** page and also needs a read/write token — picking the profile does not widen a read-only one.

Exactly one workspace is pinned. The agent never sees the workspace-discovery tool — that call happens once, during setup — and a model-supplied workspace argument is refused before any network request. Until you choose a workspace the plugin registers zero tools and reports `needs_workspace`.

Workspace and access-profile selection exist only on the web setup **Plugins** page. There is no CLI verb for either, and `fermix plugins config set` cannot reach those keys.

Eden is a paid service. Three of its tools spend credits — semantic search, board reads, and highlight search — and refuse with `out_of_credits` once the balance is gone, while literal search, listing, and note reads keep working. Fermix signs no delete tool for Eden, so anything written there has to be removed by hand in Eden.

A personal access token is the only way to connect. Disabling the plugin, forgetting the local credential, and revoking the token with Eden are three different actions: `fermix plugins auth clear eden` deletes the keychain item and drops the config reference, and revokes nothing upstream — only Eden can invalidate the token itself.

## Authentication

A plugin that needs a credential authenticates one of two ways — the same two for an HTTP-rail plugin and a hosted MCP one. Either way, the credential is injected using the scheme the plugin's manifest declares for the `Authorization` header — `Bearer` by default, or another scheme such as `Bot` for a provider like Discord. (A plugin that needs no credential at all — like Obsidian, whose process reads a local vault — declares auth `none` and is ready as soon as it is enabled.)

| Auth mode | How you connect | Not-ready status |
|-----------|-----------------|------------------|
| **OAuth** | An OAuth provider client configured under `[fermix_core.oauth.<provider>]` (client id + secret), then `fermix plugins auth login NAME` — logging in also enables the plugin. | `needs_client_config` until the client is configured, then `needs_auth` until connected. |
| **api_key** | A static secret the operator sets with `fermix plugins auth set NAME`, which asks for the value on the terminal without showing what you type — stored in the OS keychain, never plaintext config. | `needs_secret` until set. |

In today's catalog, **GitHub**, **Notion**, and **X** connect over OAuth; **Slack**, **Discord**, **AgentMail**, and **Eden** use an api_key (a bot token, API key, or personal access token you create with that service). For more on how OAuth clients and keychained secrets are stored, see [auth and secrets](/docs/auth-and-secrets).

An api_key is never taken as a command argument: an argument is visible in `ps` (the process list any account on the machine can read) and lands in your shell history, so `fermix plugins auth set NAME VALUE` is refused with a usage error rather than stored. The prompt needs a terminal — for a scripted install, pipe the value in instead:

```bash
printf '%s' "$SLACK_BOT_TOKEN" | fermix plugins auth set slack --stdin
```

### When a credential stops working

A failing credential reaches the agent as a named, actionable tool error — never a bare status code — so it can tell you the exact fix (or, for an operator, run it):

- **OAuth token rejected** (HTTP 401): *"`<plugin>` needs reconnection. Run `fermix plugins auth reauthorize <plugin>`."*
- **API key rejected**: *"API key rejected — update it with `fermix plugins auth set <plugin>`"* (a missing key points at the same command).
- **Enabled but never connected**: *"`<plugin>` is enabled but not connected. Run `fermix plugins auth login <plugin>`."*

On an OAuth 401 Fermix first attempts one silent token refresh and retry — but only for tools the manifest marks read-only. A write that comes back 401 is never blindly retried; it surfaces the reauthorize error immediately.

Tokens can also go stale from disuse, without any tool call failing: a token well past its expiry is flagged before next use — as a **Reconnect needed** badge on the setup page and in `fermix doctor`'s `auth tokens` check (see [auth and secrets](/docs/auth-and-secrets)).

## Readiness states

When a plugin is enabled but not yet ready to use, the agent sees a `status=` marker in its prompt instead of the tool simply disappearing — so it can tell you what to fix rather than failing blindly:

| Status | Meaning | Fix |
|--------|---------|-----|
| `needs_client_config` | An OAuth plugin whose provider client (client id + secret under `[fermix_core.oauth.<provider>]`) is not set up. | Configure the OAuth client on the setup page. |
| `needs_auth` | An OAuth plugin that has not been connected. | `fermix plugins auth login NAME` (or connect it on the setup page). |
| `reauthorization_required` | An OAuth plugin whose stored token was invalidated by the provider. | `fermix plugins auth reauthorize NAME`. |
| `needs_secret` | An api_key plugin with no secret set. | `fermix plugins auth set NAME` (it asks for the value), or `--stdin` to pipe it in. |
| `needs_config` | A manifest-declared config value is missing. | `fermix plugins config set NAME KEY VALUE`. |
| `needs_workspace` | A hosted MCP plugin whose credential is stored but whose workspace is not chosen. It registers zero tools until it is. | Choose a workspace on the setup **Plugins** page. |
| `missing_host_runtime` | A local MCP plugin whose host runtime is missing or too old. | Install or upgrade the runtime, then `fermix plugins doctor NAME`. |
| `not_installed` | A catalog plugin that is enabled but not installed locally. | `fermix plugins install NAME` (or `enable` auto-installs it). |
| `incompatible` | An installed version outside the running Fermix's compatibility window (typically after `fermix upgrade`). | `fermix plugins upgrade NAME`, or upgrade Fermix. |

## How the agent discovers plugins

The agent learns what is live from the `## Plugins` section of its runtime prompt. **There is no agent tool that lists plugins** — a ready plugin's tool names are listed in that prompt section, and the agent calls them directly (with schemas resolved on demand through tool-schema deferral; see [capabilities and tools](/docs/capabilities-and-tools)). An enabled-but-not-ready plugin shows its `status=` there instead, which signals "not connected" — so the agent offers to connect it on the setup page rather than scraping the surface another way.

To inspect things yourself: `fermix plugins list` shows each plugin's status and connected account, and `fermix plugins installed` shows the local store. Note that `fermix plugins catalog` lists the plugins the registry already knows about (bundled, installed, and dev-local) — the full catalog, including plugins you have not installed yet, is shown on the web setup **Plugins** page. Enable, disable, and logout take effect on the agent's next turn (the reload drops cached runtime context — no daemon restart needed), and calling a disabled plugin's tool refuses with an enable hint rather than a generic auth error.

## Operator commands

The `fermix plugins` command group manages the whole lifecycle. The full signatures live in the [CLI reference](/docs/cli-reference); the key verbs are:

- **Inspect:** `list`, `catalog`, `installed`, `doctor`, `auth status [NAME]`
- **Toggle:** `enable`, `disable`, `reload`
- **Manage the local store:** `install`, `upgrade`, `pin`, `uninstall`, `gc`
- **OAuth auth:** `auth login NAME`, `auth reauthorize NAME`, `auth refresh NAME`, `auth logout NAME`
- **api_key auth:** `auth set NAME [--stdin]`, `auth clear NAME`
- **Per-plugin config:** `config NAME` (shows declared keys and values), `config set NAME KEY VALUE` (sets manifest-declared `[fermix_core.plugins.<name>]` values)

The web setup **Plugins** page renders the full catalog: install-on-connect cards that verify sha256 + cosign, with OAuth client id/secret entered through a per-provider modal. (The one exception is the **Computer Use** card, which installs the native helper via the sha256-verified download described above and shows readiness from the computer-use feature itself — "Ready" or "Needs setup".)

## For plugin authors

To develop a plugin without an install or tarball step, point Fermix at a local directory:

```toml
[fermix_core.plugins]
dev_local = "/path/to/fermix-plugins/plugins"
```

The path's immediate subdirectories are treated as plugin directories (for a `fermix-plugins` checkout, that is its `plugins/` directory). They load into the registry like installed plugins — full manifest validation, skills loaded in place — with no signing or packaging. A missing or unreadable path, a bad manifest, or a name that collides with a bundled or installed plugin fails plugin loading loudly rather than being skipped.

One case is quieter, and deliberately so: a hosted MCP plugin cannot be developed this way at all. Its manifest has no publisher signature for a plain directory to verify, so it is refused with `unverified_remote_runtime` — and that refusal excludes only that one plugin, logging a warning while the rest of the directory still loads. The symptom is a plugin that is simply absent, so read the log when one does not appear. The author loop for a hosted plugin is release-then-install, not edit-in-place.
