ext-skills/docs/rationale.md
Ola Hungerford 4870967b38
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.
2026-08-28 09:48:39 -07:00

13 KiB

Rationale

This document records the design rationale for SEP-2640: Skills Extension, extracted from the canonical SEP.

Why Resources Instead of a New Primitive?

The Working Group's decision log records this as settled. Skills are files; Resources exist to expose files. Reusing Resources inherits URI addressability, resources/read, resource subscriptions (the resourceSubscriptions filter on subscriptions/listen), and the existing client tooling for free. A new primitive would duplicate most of this and add ecosystem complexity. Using resources to describe files is also aligned with Composability over specificity and other MCP design principles.

SEP-2076 originally proposed the new-primitive alternative. That approach offers cleaner capability negotiation and dedicated list-changed notifications, but at the cost of flattening skills to name-addressed blobs — losing the directory model that the Agent Skills specification defines and that supporting files depend on.

Why skill://<path>/<file> With an Explicit SKILL.md?

Several independent implementations converged on skill:// as the scheme without coordination — a strong signal. They diverged on structure. This SEP adopts the explicit-file form because:

  • It directly mirrors the Agent Skills specification's directory model. A skill is a directory; its URI space should look like one.
  • SKILL.md being explicit means supporting files are siblings at the same level, with no special casing for "the skill URI" versus "a file in the skill."
  • Hosts implementing both filesystem and MCP skills can use one path-resolution codepath.

The cost — SKILL.md is always typed out rather than implied — is small, and where discovery is supported the response already points clients at the right URI.

Why Allow a Path Prefix But Constrain the Final Segment?

Earlier drafts required <skill-path> to be a single segment equal to the frontmatter name. That breaks down when a server needs hierarchy: an organization serving both acme/billing/refunds and acme/support/refunds cannot satisfy "single segment" without renaming one skill to dodge the collision. Allowing a prefix (acme/billing/, acme/support/) solves this — both skills can be named refunds and the prefix disambiguates.

A subsequent draft went further and fully decoupled the path from the name. That was too loose: a URI like skill://a/b/c/SKILL.md tells you nothing about what the skill is called until you fetch and parse frontmatter. Clients listing skills, hosts displaying them in a picker, and models reasoning over URIs all want the name visible without a round trip.

Constraining the final segment to match the frontmatter name gets both properties. The prefix carries the server's organizational structure; the final segment carries the skill's name; and the two together form a locator from which the name can be read directly.

Why May the Listing Be Empty or Partial?

Requiring every server to implement a complete skills/list fails for at least three server shapes: a documentation server that synthesizes a skill per API endpoint (thousands), a skill gateway fronting an external index (unbounded), and a server that generates skills dynamically at read time (unenumerable by construction). For these, the list is either too large to be useful in the model's context or does not meaningfully exist.

The baseline is therefore direct readability — a skill URI is always a valid argument to resources/read. The method is universal but exhaustiveness is not: a server that cannot enumerate returns what it can, or nothing. A host that assumes enumeration is exhaustive will miss skills on servers where it is not, hence the requirement that hosts MUST NOT treat empty enumeration as proof of absence.

Why skills/get Alongside skills/list?

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 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.

Both are single-entry reads of a shape the listing already defines, so skills/get adds a method but no new schema: the skill object is a listing entry.

Why a Method Instead of a Well-Known Index Resource?

An earlier revision served enumeration as a reserved resource at skill://index.json, read like any other resource. Replacing it with skills/list — same entry schema, different carrier — buys four things. Pagination: a resource is one monolithic document, while a method pages large catalogs with the same cursor/nextCursor contract as every base list method. Caching: list methods inherit the base protocol's caching attributes (SEP-2549) uniformly, instead of this extension inventing a parallel freshness signal for one resource. Discoverability: support is implied by the extension declaration itself, rather than probed by reading a URI and interpreting the error. And uniformity: clients reuse the machinery they already have for tools/list and resources/list, and the skill:// namespace no longer needs a reserved-URI carve-out for a document that was never a skill.

Why Delegate the Format to agentskills.io?

The Agent Skills specification already defines YAML frontmatter fields, naming rules, directory conventions, and the progressive-disclosure model. It has its own governance, contributing process, and multi-vendor participation. Redefining any of this in an MCP SEP would create a second source of truth and a drift risk. This SEP is a transport binding; the payload format is someone else's concern.

Why a Directory Read Method?

The virtual-filesystem model this SEP builds on had a read operation (resources/read) but no readdir. That gap is harmless while skill instructions name files explicitly, but skills routinely defer the choice to the agent — "use the template in templates/ that matches the document type." On a filesystem the agent lists the directory; over MCP the only enumeration was resources/list, which is global: it returns the server's entire resource space, cannot be scoped to a subtree, and is precisely what large or generative servers — the ones whose partial listings this SEP accommodates — decline to implement. A server that cannot enumerate its catalog can still trivially enumerate one directory it is already serving.

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) 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.

Why Verbatim Frontmatter in the Listing?

The listing could instead carry a curated subset of skill metadata — name and description as dedicated top-level fields. That forces a choice every time the Agent Skills frontmatter grows a field: amend this SEP to mirror it, or leave listing consumers to fetch and parse every SKILL.md for it. Copying the frontmatter verbatim removes the choice. The listing carries exactly what the skill author wrote; the fields hosts need for a registry (name, description) are guaranteed present because the Agent Skills specification requires them; and new frontmatter fields flow through with no change to this extension. A host builds its complete skill registry from the listing alone.

Why Per-File Digests Instead of a Single Skill Digest?

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 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) 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). 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).

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) — 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).

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?) — and a single portable skill can run to 512 files and 16 MiB (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.