name the thing the platform never tells an agent: affordances
DESIGN, NOT BUILT. No registry, no API, nothing to call. Written down now
because the name and the boundary are the parts that get expensive later, and
because a plugin author who invents a different word for this is hard to merge
with afterwards.
An affordance is what the surface on the other end offers the agent — declared
by the code that implements it, read at runtime. Not what the agent can do;
what the client will honour.
The worked example is images. Officer's chat DOES render markdown images, 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 says why. That knowledge
currently lives as prose in a hand-written CLAUDE.md on one machine, phrased as
a workaround ("use a public URL") rather than as the constraint — with nothing
keeping the two in sync.
Prose is the wrong container for three reasons: it drifts from the code, it is
per-machine, and a plugin cannot contribute to it. The last one is the point —
a plugin IS a new surface arriving at runtime, and no agent learns it exists.
The rule that makes it worth building: an affordance is declared where it is
implemented, like permissions/registry.ts. A list maintained elsewhere is the
CLAUDE.md problem again with a better name.
`capability` and `skill` were both already taken here, twice in skill's case.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
# 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.
|
||||
|
||||
### 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.
|
||||
Reference in New Issue
Block a user