Sync latest SEP text in repo and add additional rationale (#124)

* docs: sync SEP copy with upstream commit b405ba5

Re-syncs docs/sep-draft-skills-extension.md from the canonical
sep/skills-extension branch (twelve commits, 2026-08-11 to 2026-08-21)
and updates the pinned commit reference in the header.

Most of the removed lines are the Rationale section, which upstream
replaced with a pointer to docs/rationale.md in this repo. That text is
not lost, it now lives in one place instead of two.

The rest: per-skill limits and a `size` field, an explicit "dynamic"
marker in place of the omit-`resources` rule, (server, URI) as a skill's
identity, on-demand retrieval with write-isolated caches, the window in
which a host is acting on a skill, and `resultType` on every sample
result.

* docs: update rationale for the SEP sync

Three passages said dynamically generated skills omit `resources`, which
is no longer valid, and the per-file-digests section offered "cache the
whole set at approval time" as the pinning primitive, which the SEP now
forbids.

Adds a section on why an enclosing skill's manifest includes its nested
skills, recording the answer given on the concerns doc: content-bound
approval binds to the `resources` set, so the set has to be complete.

* docs: re-sync SEP copy to upstream commit 753b9f2

Picks up the table reformatting from 753b9f2 and updates the pinned
commit in the file header. No specification text changed between
b405ba5 and 753b9f2.

* docs: record why hosts retrieve skill files on demand

The SEP prohibits fetching a skill's files ahead of need but explains
it in one sentence, and the reasoning for the cache-integrity rule that
accompanies it is only in the upstream commit messages.

Add a rationale section covering the server-load argument, why lazy
retrieval leaves content-bound approval intact, and what it gives up
relative to staging the whole resources set at approval time.

* docs: tighten the on-demand retrieval rationale

Merges the two paragraphs that both argued late retrieval is still safe,
and drops the restatement of the cache mechanics -- write-isolation,
re-hashing on access, digest labels not counting as verification -- which
Security Implications owns. The rationale needs the shape of the trade,
not a second copy of the rules.

Also corrects the limits sentence. 512 files and 16 MiB are host-support
floors, not a cap on what a server may serve: a skill above them is not
invalid, it just loses guaranteed portability.

Drops the threat-model link. The rotation point it supported is correct,
but the document still names `install` its recommended baseline and
argues for immutability on the grounds that there is "no second live read
to diverge" -- the model this section prohibits. Restore the link after
the threat model is revised.

* docs: trim the on-demand retrieval rationale

Server load is one reason for retrieving late, not the only one. The paragraph on what late retrieval gives up restated verification rules the spec already carries.
This commit is contained in:
Ola Hungerford 2026-08-28 09:48:39 -07:00 • committed by GitHub
commit 4870967b38
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -36,7 +36,7 @@ The baseline is therefore direct readability — a skill URI is always a valid a
Enumeration alone leaves two gaps, and both are integrity gaps rather than conveniences.
The first is the unlisted skill. Listings may be empty or partial by design, and the baseline hands hosts skill URIs that never appeared in one — from server instructions, another skill, or the user. Before `skills/get`, such a skill could be read but not verified: its digests existed nowhere, so it could not be content-bound — and a catalog too large to enumerate, or fronted by a gateway, serves static content whose digests were perfectly publishable, just unreachable. `skills/get` makes an entry reachable from a URI alone, so verification no longer depends on how the host happened to find the skill. Skills generated at read time remain unverifiable by construction, listed or not: they omit `resources` either way.
The first is the unlisted skill. Listings may be empty or partial by design, and the baseline hands hosts skill URIs that never appeared in one — from server instructions, another skill, or the user. Before `skills/get`, such a skill could be read but not verified: its digests existed nowhere, so it could not be content-bound — and a catalog too large to enumerate, or fronted by a gateway, serves static content whose digests were perfectly publishable, just unreachable. `skills/get` makes an entry reachable from a URI alone, so verification no longer depends on how the host happened to find the skill. Skills generated at read time remain unverifiable by construction, listed or not: they carry `"resources": "dynamic"` either way.
The second is the cost of picking up a change. A host does not need to poll for one: the approved `resources` set is what its reads are verified against, so a skill whose content has moved announces itself as a verification failure. What the host then needs is the skill's current entry — and before `skills/get`, the only way to get it was to re-enumerate the whole catalog, thousands of entries to learn the new digests of one skill. The same applies to a skill the user asks to update. Fetching one entry keeps the cost proportional to what changed.
@ -56,7 +56,7 @@ The virtual-filesystem model this SEP builds on had a read operation (`resources
`resources/directory/read` is the readdir analog: scoped, paginated, and composable — listings mark subdirectories with `inode/directory`, so an agent descends a skill's tree exactly as it would walk a directory locally.
Two alternatives were considered. Extending `resources/list` with a scope parameter would change a core method's semantics from inside an extension, and a URI-prefix filter misstates the model anyway — prefixes are string matching, not structure. Embedding a file manifest in `SKILL.md` or the listing was rejected as the navigation mechanism: it would freeze the listing at authoring or publication time and bloat context for skills with many files, while a method keeps listings live and on demand. The `resources` enumeration ([Resources](sep-draft-skills-extension.md#resources)) is not that manifest — it is an integrity commitment consumed by the host at verification and approval time, with no need to occupy model context, and the dynamically generated skills that need live listings most are exactly the ones that omit it.
Two alternatives were considered. Extending `resources/list` with a scope parameter would change a core method's semantics from inside an extension, and a URI-prefix filter misstates the model anyway — prefixes are string matching, not structure. Embedding a file manifest in `SKILL.md` or the listing was rejected as the navigation mechanism: it would freeze the listing at authoring or publication time and bloat context for skills with many files, while a method keeps listings live and on demand. The `resources` enumeration ([Resources](sep-draft-skills-extension.md#resources)) is not that manifest — it is an integrity commitment consumed by the host at verification and approval time, with no need to occupy model context, and the dynamically generated skills that need live listings most are exactly the ones that carry `"dynamic"` in its place.
Although introduced by this extension, the method is deliberately general — any directory resource qualifies, under any scheme — making it a candidate for promotion into the core Resources primitive if usage warrants.
@ -68,4 +68,18 @@ The listing could instead carry a curated subset of skill metadata — `name` an
An earlier revision carried one digest per entry, covering only `SKILL.md`. That left every supporting file unbound: a server could obtain approval for a benign skill, then rotate `references/GUIDE.md` — instructional content the model follows as readily as the skill body — while the approved digest stayed valid. Persisted approval covered the one file a user is least likely to be attacked through.
Enumerating every file closes rotation and addition in one move: the set is complete, so a new file is as detectable as a changed one. It also gives hosts a pinning primitive — fetch, verify, and cache the whole set at approval time — recovering the atomic-snapshot property that archive distribution ([Appendix: Deferred Features](sep-draft-skills-extension.md#appendix-deferred-features)) offered, without the unpacking surface. The cost falls on dynamically generated skills, which cannot publish stable digests; they omit `resources` and accept that hosts may decline them.
Enumerating every file covers both rotation and addition: the set is complete, so a new file is as detectable as a changed one. It also supports pinning — approval binds to the whole set, and each file is verified against it when read. That gives the consistency half of the atomic-snapshot property archive distribution ([Appendix: Deferred Features](sep-draft-skills-extension.md#appendix-deferred-features)) offered: a host can never unknowingly mix files from two versions. Unlike an archive it requires no unpacking surface, and no file is retrieved ahead of need ([Integrity and verification](sep-draft-skills-extension.md#integrity-and-verification)). The cost falls on dynamically generated skills, which cannot publish stable digests; they carry `"resources": "dynamic"` instead, and accept that hosts may decline them.
## Why Does an Enclosing Skill's Manifest Include Its Nested Skills?
An earlier revision treated a nested `SKILL.md` as a boundary: a host walking an enclosing skill stopped there rather than taking in the subtree. The shipped model treats a nested skill's files as ordinary supporting files of the enclosing skill, so they appear in both manifests ([Resources](sep-draft-skills-extension.md#resources)).
Content-bound approval binds to the `resources` set, so the set has to cover every file the skill can introduce into model context. A boundary would leave gaps in it at paths the server controls, and content in a gap could change without changing the approved set. The cost is that changing one nested skill revokes the persisted approval of every skill whose manifest encloses it.
Approval remains per skill. An enclosing skill's approval covers reading a nested `SKILL.md` as ordinary content, never activating it ([Nested skills](sep-draft-skills-extension.md#nested-skills)) — so a host does not need to detect nesting to apply the consent rule, and nothing in a listing marks the relationship.
## Why Retrieve Files Only When They Are Needed?
An earlier revision let a host fetch, verify, and cache a skill's entire `resources` set at approval time and serve every later read from that copy. That is now prohibited: no file is retrieved on connection, on listing, or at approval. `SKILL.md` is fetched when the skill is loaded and a supporting file when it is read ([Integrity and verification](sep-draft-skills-extension.md#integrity-and-verification)).
One reason for this is server load. A listing may be arbitrarily large — these are the same catalogs that make an empty or partial `skills/list` legitimate ([Why May the Listing Be Empty or Partial?](#why-may-the-listing-be-empty-or-partial)) — and a single portable skill can run to 512 files and 16 MiB ([Limits](sep-draft-skills-extension.md#limits)). A host that staged every skill it might use would put load on a server proportional to the catalog rather than to what a session reads, and every connecting host would repeat it. The same argument bounds the cost of deferring archives: a round trip per file scales with the files a session opens, not with the size of the skill.