Files
deepseek-harness/docs/core-data-structures/skills.md
T

10 KiB

Skills

English | 中文

The skill capability family is split across three packages: the registry (dsh-skill, ctx.skills) merges provider catalogs; the local provider (dsh-skill-local) scans project/custom/user directories; the consumer (dsh-tool-skill) owns the session-prefix catalog and model-facing skill tool. Skills are optional instructions, not session events, so their vocabulary lives here rather than in core.md.

Source: packages/skill/skill/src/index.ts, packages/skill/skill-local/src/index.ts, and packages/skill/tool-skill/src/index.ts.

Provider registry

ctx.skills combines local, embedded, remote, or other providers. Registration is synchronous; remote initialization and discovery belong in awaited list(). Provider objects, options, and candidates are borrowed readonly, while semantic fields are validated.

Duplicate names resolve by rank, provider order, then local order; summaries sort by name. A rejected list() is logged and skipped without caching the degraded catalog, while malformed candidates fail fast.

/** Provider interface for one source of skills, such as local directories or a remote registry. */
interface SkillProvider {
  /** Unique provider name in the `ctx.skills` registry. */
  readonly name: string
  /**
   * List available skill candidates for the current lookup context. Provider
   * plugins register synchronously during `apply()`; remote initialization,
   * authentication, and discovery are awaited inside this method. Implementations
   * should settle promptly when `options.signal` aborts.
   * @param options - lookup options; `cwd` selects workspace-sensitive skills and `signal` cancels work.
   * @returns provider candidates with precedence ranks and opaque locators.
   */
  readonly list: (options: SkillLookupOptions) => Promise<readonly SkillCandidate[]>
  /**
   * Load a complete skill body for a previously listed candidate.
   * @param candidate - the winning candidate originally returned by this provider.
   * @param options - lookup options; `cwd` selects workspace-sensitive skills and `signal` cancels work.
   * @returns the full skill body, or `undefined` if it is no longer loadable.
   */
  readonly get: (candidate: SkillCandidate, options: SkillLookupOptions) => Promise<SkillDefinition | undefined>
}

Local discovery priority

The shipped local provider scans roots in rank order:

Rank Source Root
100 project-dsh <projectRoot>/.dsh/skills
200 project-agents <projectRoot>/.agents/skills
300 custom Config.customSkillDirs
400 user-dsh <dshHome>/skills
500 user-agents <agentsHome>/skills
600 bundled Config.bundledSkillDir when configured

The project root is the nearest ancestor containing .git; without one, the current cwd is used. When ctx.fs is available, the git-root walk probes .git through the filesystem service so remote or sandboxed workspaces do not fall back to the host filesystem boundary. The user DSH root skips its .system child. The local provider does not ship built-in system skills; deployments supply built-ins through another provider.

Skill identity

Skill names are kebab-case (^[a-z0-9]+(?:-[a-z0-9]+)*$). The local provider accepts directory bundles (<name>/SKILL.md) and flat Markdown files (<name>.md). Nested recursive **/SKILL.md discovery is intentionally outside v1.

/** Origin bucket for a skill contribution. The value is prompt-visible metadata, not precedence by itself. */
type SkillSource = 'project-dsh' | 'project-agents' | 'runtime' | 'user-dsh' | 'user-agents' | 'custom' | 'bundled' | (string & {})

Summaries, candidates, and complete definitions

SkillSummary is the registry's invocation-neutral summary shape. Consumers choose which entries and fields to render; the model session catalog uses only model-invocable name and description, never the body or absolute file path. SkillInvocationPolicy keeps the two independent invocation controls typed without turning arbitrary frontmatter into the domain model.

/** Invocation controls shared by skill discovery consumers. */
interface SkillInvocationPolicy {
  /** Whether model-facing catalogs and loaders exclude this skill. */
  readonly disableModelInvocation?: boolean
  /** Whether human-facing command catalogs and loaders include this skill. */
  readonly userInvocable?: boolean
}
/** Invocation-neutral skill metadata returned by `ctx.skills.list()`. */
interface SkillSummary {
  /** Kebab-case identifier used to address the skill. */
  readonly name: string
  /** Short routing description shown by discovery consumers. */
  readonly description: string
  /** Optional extra routing guidance. */
  readonly whenToUse?: string
  /** Optional model and user invocation controls. */
  readonly invocation?: SkillInvocationPolicy
  /** Discovery source that produced this winning skill. */
  readonly source: SkillSource
  /** Provider that owns this skill body. */
  readonly provider: string
  /** Provider-specific base for relative resources. */
  readonly resourceBase?: SkillResourceBase
}

ctx.skills.list() preserves all four policy combinations. isModelInvocable(skill) excludes only disableModelInvocation: true, while isUserInvocable(skill) excludes only userInvocable: false; missing fields permit both surfaces. A model-only skill sets userInvocable: false, a user-only skill sets disableModelInvocation: true, and setting both restrictive values keeps the skill available only through trusted ctx.skills.get() callers. The local provider reads these values from the exact kebab-case frontmatter keys disable-model-invocation and user-invocable.

SkillCandidate is the provider-to-registry shape. locator is opaque provider state; the registry only stores it and gives it back to the winning provider's get().

/** Provider catalog entry used by the registry to merge and later load skills. */
interface SkillCandidate extends SkillSummary {
  /** Lower ranks win duplicate skill names before provider registration order is considered. */
  readonly rank: number
  /** Opaque provider-owned handle passed back to `provider.get()`. */
  readonly locator: unknown
  /** Absolute file path when the provider has one. */
  readonly path?: string
  /** Parsed optional metadata object from provider-specific skill frontmatter. */
  readonly metadata?: Readonly<Record<string, unknown>>
}

SkillDefinition is the complete parsed result returned by ctx.skills.get() and used by the skill tool. resourceBase tells the tool how to render relative-resource guidance for local, URL, or provider-managed skills.

/** Optional provider-specific base used by loaded skill bodies to resolve relative resources. */
type SkillResourceBase =
  | { readonly kind: 'directory'; readonly path: string }
  | { readonly kind: 'url'; readonly url: string }
  | { readonly kind: 'opaque'; readonly description: string }
/** Complete parsed skill definition, including the body loaded by `ctx.skills.get()`. */
interface SkillDefinition extends SkillSummary {
  /** Markdown instruction body after any provider-specific metadata removal. */
  readonly content: string
  /** Absolute file path when the skill came from disk. */
  readonly path?: string
  /** Parsed optional metadata object from frontmatter. */
  readonly metadata?: Readonly<Record<string, unknown>>
}

Runtime skills use the same complete shape and participate in the same first-wins collection order. The returned disposer removes the contribution and invalidates discovery caches.

/** Runtime skill contribution accepted by `ctx.skills.register()`. */
type SkillRegistration = Omit<SkillDefinition, 'provider'> & { readonly provider?: string }

Lookup and configuration

Skill lookup is cwd-sensitive because providers may expose workspace-local skills, and its optional signal cancels provider work for the caller. Providers receive the same readonly options object used for cache identity and loading. Cancellation is checked before and after catalog selection, including cache hits, and races both discovery and full-definition loading. If no git root is found, the local provider treats the supplied cwd itself as the project root.

/** Caller context used for cwd-sensitive and abortable provider work. */
interface SkillLookupOptions {
  /** Workspace selector for the current lookup. */
  readonly cwd?: string | undefined
  /** Abort discovery or loading work for the current caller. */
  readonly signal?: AbortSignal | undefined
}

The registry owns only its discovery-cache bound. The local provider owns filesystem roots (dshHome, agentsHome, customSkillDirs, and optional bundledSkillDir/DSH_BUNDLED_SKILL_DIR). The consumer owns its catalog description bound.

/** Skill registry configuration. */
interface Config {
  /** Maximum number of completed cwd/provider catalogs kept in memory. */
  readonly collectCacheMaxEntries?: number
}

Session catalog and tool contract

dsh-tool-skill injects a durable user-role <system-reminder> at the first agent/step of a live session. The catalog contains sorted skill name and normalized, XML-escaped description only; it omits bodies, paths, sources, providers, and routing hints. Discovery forwards the step's abort signal through SkillLookupOptions. catalogDescriptionMaxLength is the consumer config for the description bound, with default 500 and integer minimum 3.

The model-facing skill({ name }) tool validates the kebab-case name, filters its catalog with isModelInvocable, loads the complete definition for the calling agent cwd, reports an unresolved skill as unknown or no longer available, rechecks model invocation policy before returning content, and returns a tool result containing <skill_content name="...">, <skill_resources>, and <skill_instructions>. resourceBase resolves explicitly referenced scripts, references, and assets only as needed; the loaded result does not enumerate a skill directory. The tool result is the model-visible path for complete instructions.