From 7a9e576ee65ef200f29ef792ecbd3fe14fd7a51d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Wed, 19 Aug 2026 06:52:19 +0000 Subject: [PATCH] name the thing the platform never tells an agent: affordances MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 carries no bearer token, so it can only load what the browser can fetch unauthenticated. A fresh agent knows none of that: it writes ![](/home/me/chart.png), 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 --- plugins/CLAUDE.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 plugins/CLAUDE.md diff --git a/plugins/CLAUDE.md b/plugins/CLAUDE.md new file mode 100644 index 00000000..f031ba9f --- /dev/null +++ b/plugins/CLAUDE.md @@ -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//` +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 `` carries no bearer token, so it can only load what the browser can fetch +unauthenticated. A fresh agent knows none of that. It writes `![](/home/me/chart.png)`, 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.