affordances: the file format, where they live, and what is still open
Design only — nothing reads any of this yet. Markdown with YAML frontmatter, like every other item in capabilities/. Frontmatter is for the PLATFORM, the body is for the agent: `name` becomes the heading in the compiled block, `description` says when to reach for it and is not sent, `version` is an INTEGER to match capabilities/agents/ rather than the semver plugins use. A plugin-contributed affordance sits near both numbers and they mean different things — the integer is the revision of that text, the semver is the plugin's. They live at capabilities/affordances/<publisher>/<plugin>/, with `core` in the plugin slot for the platform's own. A plugin ships an affordances/ directory and install copies it in — the same shape as assets/ and publishAssets. No `surface` field. It was drafted and dropped: for image rendering it would mean what the CLIENT renders, for gitea what TOOLING exists on the machine. Two axes wearing one name. Everything loads unconditionally until that is worked out, which is fine at two files and will not be at twenty — and this codebase has already been bitten by a field that validated itself and was never consumed (unauthenticatedPaths). TODO.md carries the build list and the two deferred questions, including the honest one: "declared where implemented" is the rule, and a hand-authored file in a repository is not that. Accepted because prose reaches every harness and a registry reaches one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -298,6 +298,39 @@ pane belonging to the folder you left; and the breadcrumb opted out of that last
|
||||
restored. Deliberate as written — a dashboard can hold two browsers and one URL cannot serve both —
|
||||
but worth re-deciding rather than inheriting.
|
||||
|
||||
## Affordances (2026-08-19)
|
||||
|
||||
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).
|
||||
|
||||
To build:
|
||||
|
||||
- `capabilities/affordances/` created by the setup script, with `officer/core/` populated from an
|
||||
officer-capabilities repository. The clone is future work; the directory is not.
|
||||
- A compiler: parse the tree, take each body under its `name` as a heading, produce one small block.
|
||||
Per session, not per boot — `capabilities/` is already read fresh each agent session, and a plugin
|
||||
installed mid-day should be live on the next message.
|
||||
- Delivery. Claude sessions take `systemPrompt: { type: 'preset', preset: 'claude_code', append }`,
|
||||
which nothing passes today. **Opencode has its own driver and would need the same**, or the affordance
|
||||
is a claim that only holds on one harness — which is the drift this exists to remove. A bare `claude`
|
||||
run in a terminal on the host gets neither unless a file is also written.
|
||||
- Plugin contribution: an `affordances/` directory in the plugin repository, copied to
|
||||
`capabilities/affordances/<publisher>/<plugin>/` on install and removed on uninstall. Same shape as
|
||||
`assets/` and `publishAssets`.
|
||||
|
||||
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.
|
||||
|
||||
## Plugin ecosystem — before it opens to third parties (2026-08-18)
|
||||
|
||||
Not now. Months away. Recorded so it is not re-derived.
|
||||
|
||||
@@ -71,6 +71,71 @@ hand-written `CLAUDE.md` problem again with a better name — and this time with
|
||||
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>`, with `core` occupying the plugin slot for the platform's own. 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
|
||||
|
||||
Reference in New Issue
Block a user