The path takes `publisher` from the manifest verbatim, so a plugin published by officerdev lands under officerdev/. `officer/core` is not a plugin at all — it is the platform's own, shipped by the setup script from the capabilities repository. Two spellings marking two different things: platform versus publisher. Arrived at by accident and kept on purpose. Recorded because the obvious tidy-up removes the distinction rather than the inconsistency. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
150 lines
8.1 KiB
Markdown
150 lines
8.1 KiB
Markdown
# Plugins
|
|
|
|
`EXTRACTING-A-PLUGIN.md` is the runbook for turning a platform feature into a plugin, and
|
|
`EXTRACT-A-PLUGIN-PROMPT.md` is the way to hand that to a fresh agent. This file is for the concepts a
|
|
plugin author needs that are not procedure.
|
|
|
|
**This directory holds documentation, not plugins.** Every plugin is its own repository; `plugins/<name>/`
|
|
appears by `git clone` when somebody installs one, and is gitignored here.
|
|
|
|
---
|
|
|
|
## Affordances
|
|
|
|
**DESIGN, NOT BUILT.** Agreed 2026-08-19. There is no registry, no API and nothing to call yet. It is
|
|
written down now because the name and the boundary are the parts that are expensive to change later, and
|
|
because a plugin author who invents a different word for this will be hard to merge with afterwards.
|
|
|
|
### What one is
|
|
|
|
> **An affordance is something the surface on the other end of the conversation offers the agent —
|
|
> declared by the code that implements it, and read by the agent at runtime.**
|
|
>
|
|
> Not what the agent can do. What the client will honour.
|
|
|
|
### Why it needs to exist
|
|
|
|
The platform hosts the agent and never introduces itself. An agent gets a shell, a filesystem and a
|
|
prompt; everything about the surface it is speaking _through_ has to be smuggled in as prose somebody
|
|
maintains by hand.
|
|
|
|
The worked example is images. Officer's chat renders markdown images — `ChatImage` is wired into
|
|
`react-markdown` — but an `<img>` carries no bearer token, so it can only load what the browser can fetch
|
|
unauthenticated. A fresh agent knows none of that. It writes ``, nothing renders,
|
|
and nothing tells it why. The knowledge currently lives in a hand-written `CLAUDE.md` on one machine,
|
|
which says "use a public URL" — a workaround, one step removed from the actual constraint, with nothing
|
|
keeping the two in sync.
|
|
|
|
Three properties make prose the wrong container for this:
|
|
|
|
- **It drifts.** The file describes a workaround; the code has a reason. Neither knows about the other.
|
|
- **It is per-machine.** It lives in one repository, copied by hand.
|
|
- **A plugin cannot contribute to it.** Install a plugin that adds a surface and no agent learns it
|
|
exists — which is the case that matters here, because a plugin is exactly a new surface arriving at
|
|
runtime.
|
|
|
|
### The boundary
|
|
|
|
Three neighbours, easily confused:
|
|
|
|
| | answers |
|
|
| -------------- | ---------------------------------------------------- |
|
|
| **tool** | what the agent can _do_ — it acts, something happens |
|
|
| **permission** | what this _account_ may reach |
|
|
| **skill** | instructions the agent _follows_ |
|
|
| **affordance** | what the _client_ will render, accept or ignore |
|
|
|
|
Two tests settle most cases:
|
|
|
|
1. **Would it change if the same agent talked to a different client?** A phone app, a terminal, a
|
|
webhook, a cron run with no human at all. If yes, it is an affordance. Rendering an image is; reading
|
|
a file is not.
|
|
2. **Does the agent have to guess it today?** If the honest answer is "it writes the markdown and hopes",
|
|
that is a missing affordance.
|
|
|
|
### The rule that makes it worth having
|
|
|
|
**An affordance is declared where it is implemented.** `ChatImage` declares that images render, because
|
|
`ChatImage` is what makes them render. The moment it becomes a list maintained somewhere else, it is the
|
|
hand-written `CLAUDE.md` problem again with a better name — and this time with a build step.
|
|
|
|
Same posture as `permissions/registry.ts`, and for the same reason: one enumeration, owned by the code it
|
|
describes, which cannot quietly disagree with reality.
|
|
|
|
### The file
|
|
|
|
Markdown with YAML frontmatter, like every other item in `capabilities/`. Frontmatter is for the
|
|
PLATFORM; the body is for the agent.
|
|
|
|
```markdown
|
|
---
|
|
name: Image rendering
|
|
description: Use when you have an image to show the user in chat.
|
|
version: 1
|
|
---
|
|
|
|
`` renders as an image. **The user's device fetches the URL**, so it must be
|
|
reachable from there — a local path, `file://`, or anything behind authentication renders nothing,
|
|
and fails silently.
|
|
```
|
|
|
|
| field | |
|
|
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| `name` | becomes the heading in the compiled block, so the agent can index it |
|
|
| `description` | when to reach for it. Costs the agent nothing — it is not sent |
|
|
| `version` | an INTEGER, not semver, matching `capabilities/agents/` (`agent-files.ts` parses it that way). A plugin-contributed affordance sits near two version numbers and they mean different things: this integer is the revision of _this text_, the plugin's semver is the plugin's. Do not reconcile them |
|
|
|
|
**Keep the body short.** Every affordance is loaded into every conversation, so it is charged to the
|
|
context budget of every turn. State the constraint, name the silent failures, say what to do instead.
|
|
|
|
There is deliberately no `surface` field yet. It was drafted and dropped: for the image affordance it
|
|
would mean _what the client renders_, and for the gitea one below _what tooling exists on the machine_ —
|
|
two different axes wearing one name. Everything is loaded unconditionally until that is worked out,
|
|
which is fine at two files and will not be at twenty. See `TODO.md`.
|
|
|
|
### Where they live
|
|
|
|
```
|
|
capabilities/affordances/
|
|
officer/core/ ← core, installed by the setup script
|
|
image-rendering.md
|
|
officer/gitea/ ← contributed by the gitea plugin on install
|
|
gitea.md
|
|
```
|
|
|
|
`<publisher>/<plugin>`, taken from the manifest verbatim — so a plugin published by `officerdev` lands
|
|
under `officerdev/`. `officer/core` is not a plugin: it is the platform's own, shipped by the setup
|
|
script from the capabilities repository. The two spellings mark two different things and should not be
|
|
normalised into one. A plugin ships an
|
|
`affordances/` directory in its repository; installing copies it in, uninstalling removes it — the same
|
|
shape as `assets/` and `publishAssets`.
|
|
|
|
### A plugin-contributed one, for reference
|
|
|
|
This is the gitea plugin's, and it shows what these are for: the agent cannot discover from the
|
|
filesystem that `tea` is authenticated, or that it should prefer `git` for the things git already does.
|
|
|
|
```markdown
|
|
---
|
|
name: Gitea
|
|
description: Use when working with git repositories, or with Gitea issues and pull requests.
|
|
version: 1
|
|
---
|
|
|
|
For anything git already does — clone, fetch, pull, push, branches, tags — use plain `git`. It talks
|
|
to the server directly.
|
|
|
|
For anything git does not — pull requests, issues, comments, releases, creating a repository — use
|
|
`tea`. It is already logged in as the current user, with that user's own permissions. Do not
|
|
configure it, and do not ask the user for a token.
|
|
```
|
|
|
|
### On the word
|
|
|
|
`capability` was taken — `CAPABILITIES` is the permission registry. `skill` was taken twice: by
|
|
`capabilities/skills/` here and by the wider AI ecosystem.
|
|
|
|
`affordance` is the exact term for what an environment offers an actor, and the precision earns the
|
|
slight awkwardness: rendering an image is not a skill the agent has, it is something the client on the
|
|
other end permits. Keeping that distinction in the name saves explaining it every time.
|