← Về thư mục
📄 / / profiles / r-and-d / raw_evidence / OpenAI4S / openai4s / skills_loader / README.md

Skill loading and versioning

中文说明

Skill discovery and Skill versioning live here. The loader finds recipe-centric Skills and gives the outer loop nothing but their summaries until progressive disclosure asks for the full text; the version service keeps writable Skill packages as immutable versions in the Store and swaps their on-disk views into place atomically. Optional Python sidecars are compile-checked before either path lets a kernel import one. A bundled tree that declares itself a collection collapses to a single prompt line, which is what lets hundreds of pinned third-party recipes stay individually searchable without their summaries entering every system prompt.

Where this fits

A Skill extends Code-as-Action; it is not a native JSON tool schema. A Skill directory holds SKILL.md, an optional kernel.py sidecar, and optional resources. The outer-loop prompt only ever sees the name and the one-line summary; ../tools/skills.py and the Host services pull the full recipe when a task calls for it. Agent-authored Python then imports the compile-checked sidecar inside the scientific worker.

Bundled Skills are read-only and win name collisions. Writable Skills live under the configured data and project roots, and the Store versions them. The default loader holds no repository of its own: it asks the current Store generation on every capability call, because a loader can outlive the Store that created it and would otherwise be left pointing at a closed connection.

Collections

A collection is a bundled directory that declares itself with COLLECTION.json — an id and the one prompt line it wants — and keeps its member Skills one level lower. Nothing about a particular tree is hardcoded here: the marker is the whole discovery rule, so skills/bioskills/'s 561 pinned recipes and any future collection are picked up by the same code, and a duplicate id is an error rather than a silently dropped tree. Each member keeps its own name and stays enabled, searchable and loadable; what changes is disclosure. system_context() renders one line per collection instead of one per member, with {count} substituted by the number of members the caller can see, so an allowlisted Specialist gets the collapsed line counted over its own subset. Every bucket is drained, because a second collection that reached the prompt through no branch at all would be undisclosed rather than compactly disclosed. Exact enumeration is left to the native list_skills tool, which pages one collection at a time through collection=<id> plus offset/next_offset.

Three consequences are easy to miss. search() applies the caller's allowlist after scoring and before the limit slice: with 561 recipes in the same lexical index, slicing first and filtering afterwards handed a Specialist an empty result set for the very query its allowlist existed to serve. The in-kernel bootstrap finder binds each collection root as its own import prefix, so a member resolves as <collection>.<member>, an unknown member is a ModuleNotFoundError, and the qualified <catalog>.<collection>.… form is refused outright. And the bootstrap manifest embeds only the entries that actually carry a sidecar — collection recipes carry none, so the generated snippet stopped shipping a quarter-megabyte of unusable entries into every kernel start, durable generation record and cursor checkpoint.

Files

File Responsibility
__init__.py States the Skill directory contract in its docstring and re-exports the public names: Skill, SkillLoader, SkillVersionService, and discover_skills.
bundled_network_inventory.json Loader-side explicit network mode for pinned collections. skills/bioskills/ is a byte-exact upstream tree, so collection members are tagged legacy/unknown here instead of by editing those files. The inventory never grants egress.
capabilities.py Closed-set capabilities.network schema (none / host_only / raw_required), canonical digest, frontmatter parser, and offline catalog readiness. A declaration is a requirement, never an authorization. Built-in raw_required is fail-closed. Catalog answers are local-only (checked_locally: true, probed: false) with no socket or subprocess.
frontmatter_edit.py Changes name/description/origin without touching the rest. A Web-Customize save rebuilt the frontmatter from those three fields, so license (29 of the 42 curated bundled Skills), category (25), a nested metadata block (23), requirements (14) and fold_cue (1) were deleted by anyone fixing a typo. requirements is what makes it a correctness bug rather than a metadata one: readiness is computed from it, so a skill that lost [gpu] stops reporting needs_setup and starts claiming it can run anywhere. Parsing and re-emitting does not fix it — _parse_frontmatter flattens list values and ignores nested blocks by design — so this edits raw lines, and everything it does not own survives byte for byte.
loader.py Finds the Skills and decides how much of one to reveal. It parses SKILL.md frontmatter, scans the bundled, project and user roots, resolves capability state, and scores searches by keyword overlap weighted by inverse document frequency, so a word nearly every recipe contains (the t of t-test is in 479 of 604) cannot decide the ranking; equal scores go to non-collection Skills first, then by name. The system prompt gets summaries; the full recipe, the sidecar import hint, and the in-kernel bootstrap manifest are produced on demand. A kernel.py sidecar is compile-checked before anything imports it. Two of those views take a caller's allowlist, because a delegated child is entitled to less than the corpus: system_context(only=…) renders the prompt from the permitted Skills only, and bootstrap_code(allowed=…) closes the in-kernel import gate over the complement — the gate knew disabled, the user's own switch, and had no concept of withheld, so the half that actually runs code stayed open. None keeps both unrestricted and byte-identical, because the bootstrap manifest id is a durable recovery key and must not move. Declared requirements: are finally read: readiness is ready/needs_setup/unknown, decided from local state alone — nvidia-smi is looked for on PATH and never run, since browsing a catalogue is not a request to contact anything and would otherwise cost one subprocess per Skill. unknown is a real answer, not padding: guessing ready invites a failure deep into a task, and guessing needs_setup sends a user to install what may already be there. Readiness sits beside enabled rather than inside it — a disabled Skill can be perfectly ready, and enabling one does not make its hardware appear. Collections are discovered here too: collections() reads each bundled subdirectory's COLLECTION.json, bundled_roots() reports the roots with the collection id they belong to, and bundled_directory_collision() answers the question a name-only check cannot — whether a writable slug would land on a bundled member directory.
versions.py Installs, upgrades, publishes, rolls back and deletes writable Skills. A package is validated first (bounded size, no symlinks, no paths escaping the directory) and stored as an immutable version; the personal or project directory on disk is only a materialized view, rebuilt off to the side and swapped in. Activation in the database is compare-and-swap, and if that switch fails the previous directory is restored before the error escapes. Both bundled checks — the declared name and the directory slug — run again immediately before a rollback rewrites bytes, because the public API takes a slug independently of the display name and the bundled catalog can have grown a colliding name or directory since the version was installed.

Skill authoring and safety contract