--- theme: affordances-runtime heading: "Affordances (2026-08-19)" kind: theme --- Agreed and written down in `plugins/CLAUDE.md`; **nothing is built**. An affordance is what the surface on the other end offers the agent — declared by the code that implements it, read at runtime. The first two are image rendering (core) and gitea (`git` for git, `tea` for everything else). Built 2026-08-19: `servers/affordances/load.ts` reads the tree and compiles one block, and `claude-manager.ts` appends it to every Claude session via `systemPrompt: { type: 'preset', preset: 'claude_code', append }`. Read per session, so a plugin installed an hour ago is live on the next message. Still to build: - `capabilities/affordances/` created by the setup script, with `officer/core/` populated from an officer-capabilities repository. The directory exists on this machine by hand; a fresh install gets nothing, so the first affordance is currently a local artefact rather than a shipped one. - **Opencode delivery.** It has its own driver and gets nothing, so image rendering is currently a claim that holds on one harness — exactly the drift affordances exist to remove. Deliberate: opencode work is paused pending its v2 migration and this would be thrown away. Revisit when v2 lands. - A bare `claude` run in a terminal on the host gets nothing either, since it never passes through `claude-manager`. Would need a generated file, which is the thing `user-instance.ts` refuses to write for the owner. - ~~Plugin contribution~~ — built 2026-08-19 (`affordances/publish.ts`). An `affordances/` directory in the plugin repository is copied to `capabilities/affordances///` on install and removed on uninstall. Gitea is the first to ship one. - **The other categories.** A plugin can contribute affordances and nothing else. Skills, tools, tasks and processes are the obvious next ones and were deliberately left out — one mechanism, proven, before five. When they land, `publishAffordances` generalises rather than gaining siblings. - **`officer/core` and `officerdev/` are both correct — do not normalise them.** The path uses `publisher` 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, arrived at by accident and kept on purpose. Anyone tidying this into one name is removing the distinction, not the inconsistency. Open, and deliberately deferred: - **Scoping.** Everything is loaded into every conversation. Fine at two files, not at twenty. A `surface` field was drafted and dropped because it was doing two jobs at once: for image rendering it means _what the client renders_ (a phone, a web bubble, a cron run with nobody watching), for gitea _what tooling exists on this machine_. Those are different axes and one field cannot be both. Decide the axis before adding the field — and note this codebase has already been bitten by a field that validated itself and was never consumed (`unauthenticatedPaths`, see `plugins/public-paths.ts`). - **Drift.** The rule is "declared where implemented", but a hand-authored markdown file in a repository is not that — `ChatImage` could stop rendering images and no file would notice. Accepted for now because prose that reaches every harness beats a registry that reaches one; revisit if it bites.