@deepseek-ai/dsh-skill
Pure agent skill provider registry.
This package owns the ctx.skills interface. It does not know whether skills come from local files, embedded plugin data, HTTP, or another backend; providers register those sources with ctx.skills.registerProvider(...). The shipped local implementation is @deepseek-ai/dsh-skill-local.
Service: SkillService (ctx key: skills)
Public API
ctx.skills.registerProvider(provider): () => Promise<void> | voidRegisters a provider by uniqueprovider.name. Duplicate provider names throw, andruntimeis reserved forctx.skills.register(...). The registry snapshots the name and callback identities at registration, so replacing those fields later cannot change lookup or HMR cleanup; callbacks remain bound to the original provider object and can still read its mutable state. The registration is effect-scoped and HMR-safe, and the exact Cordis disposer supports ordered composite teardown.ctx.skills.list({ cwd?, signal? })Returns model-invocable skill summaries for the current workspace, merged across providers and sorted by name.ctx.skills.get(name, { cwd?, signal? })Returns the full winning skill, including disabled-for-model skills.ctx.skills.register(skill): () => Promise<void> | voidRegisters a runtime embedded skill. Same-name runtime registrations are first-wins: a duplicate logs a warning and gets a no-op disposer. Successful registrations return the exact Cordis disposer for ordered composite teardown.
Config
| Field | Default | Meaning |
|---|---|---|
collectCacheMaxEntries |
128 |
Maximum completed cwd/provider catalog snapshots kept in memory. |
Provider Contract
A provider registers synchronously from its apply() and returns SkillCandidate[] from list(options) when discovery is requested. Registration copies name and binds the current list and get methods once; replacing those fields on the caller-owned object later does not rewrite the live registry entry, and disposal always removes the original name. Remote setup, authentication, and discovery belong in the awaited list() call rather than plugin registration. Providers should stop promptly when options.signal aborts; the registry also stops awaiting an uncooperative provider so agent cancellation cannot hang prefix composition. The provider later receives the winning candidate back in get(candidate, options). The candidate's locator is opaque to the registry, so a local provider can store a file path while a remote provider can store a URL, id, or version token.
The registry validates candidate names, descriptions, ranks, and provider ownership. Candidate contract violations fail fast because the provider plugin is malformed; a provider list() rejection is treated as a transient source failure, logged, skipped for that request, and not cached. Only completed catalogs are cached, and a provider/runtime revision change during discovery discards the stale result and retries. Duplicate skill names are resolved first-wins by rank, provider registration order, then the provider's own local order. The final summary list is sorted by skill name for deterministic consumers.
Runtime Skills
ctx.skills.register(...) is a convenience for embedded runtime skills. Runtime skills use rank 250: project providers can override them, while they override the shipped local provider's custom and user roots. Runtime registration is also first-wins within runtime contributions, so a duplicate contribution cannot remove the active one through its disposer.
Consumer boundary
The registry does not render model guidance or register model-facing tools. @deepseek-ai/dsh-tool-skill consumes ctx.skills to provide the session-prefix catalog and skill tool, so providers remain independent of the model surface.
Model Experience
| Context surface | What the model sees | Token effect |
|---|---|---|
| None directly | The registry renders nothing and registers no tool. dsh-tool-skill turns list() summaries into a session prefix and a selected get() body into a tool result; provider failures can remove entries from that request's catalog. |
Zero direct tokens. Catalog size, descriptions, and loaded body length affect context only through the consumer. |
Known Limitations and Deferred Work
- Completed catalogs have no TTL or watcher invalidation — a provider's underlying files or remote data can change without a registration revision, so a cached cwd stays stale until eviction or provider/runtime reload.
- Providers are queried sequentially — one slow cooperative provider delays every provider registered after it; cancellation stops the caller's wait but cannot terminate work an uncooperative provider keeps running.
- A provider-list failure removes that whole source for the request — the registry logs and skips it, with no model-visible diagnostic or partial-catalog recovery contract.
- Duplicate resolution is first-wins — later lower-priority candidates are logged and hidden; there is no API to inspect all shadowed definitions.