Procházet zdrojové kódy

docs: document skill system design

Add the implemented skill-system RFC, a core data-structures page, and JSDoc for the skill public vocabulary so the generated catalogs and review-facing docs describe the new service/tool contract.
Yichen Jiang před 2 měsíci
rodič
revize
f626e569a4

+ 1 - 1
docs/cordis-catalog/services.md

@@ -176,7 +176,7 @@ async get(name: string, options: SkillLookupOptions = {}): Promise<SkillDefiniti
 async renderModelListing(options: SkillLookupOptions = {}): Promise<string>
 ```
 
-Source: [`packages/core/skill/src/index.ts:113`](../../packages/core/skill/src/index.ts)
+Source: [`packages/core/skill/src/index.ts:128`](../../packages/core/skill/src/index.ts)
 
 ## `ctx.subagents` — `SubagentService`
 

+ 1 - 0
docs/core-data-structures/core.md

@@ -21,6 +21,7 @@ Everything else is documented on a **sub-page**, not here. The rule that draws t
 | [tools.md](tools.md) | `ToolDefinition` full fields, the schema DSL, `ToolExecution`/`ToolResult`, tool-presentation UI types, the `tools/pre-execute`/`tools/post-execute` pipeline |
 | [bash.md](bash.md) | the bash executor seam: `BashExecRequest`/`Spec`, `BashRunResult`, background `BashTask`s |
 | [filesystem.md](filesystem.md) | the filesystem seam: `FsTarget`, read/write/edit outcomes, observed-file state, `FsErrorCode` |
+| [skills.md](skills.md) | the skill service: discovery priority, `SkillSummary`/`SkillDefinition`, prompt listing, model-facing `skill` loading |
 | [compaction.md](compaction.md) | the compaction seam: the `compact/*` session events, `CompactionResult`, the `CompactService` interface |
 | [subagent.md](subagent.md) | the subagent seam: the named-provider registry, `SubagentStartRequest`/`Result`/`Run`, the start-time-vs-runtime capability split |
 | [web.md](web.md) | the web access seam: `WebSearchRequest`/`Result`, `WebFetchRequest`/`Result`, `WebFetchBody`, provider/capability status, `WebError` |

+ 89 - 0
docs/core-data-structures/skills.md

@@ -0,0 +1,89 @@
+# Skills
+
+The skill stack is split across two core packages: the service ([dsh-skill](../../packages/core/skill), `ctx.skills`) discovers and parses local `SKILL.md` instructions, injects a stable request-time listing, and exposes full skill bodies on demand; the consumer ([dsh-tool-skill](../../packages/core/tool-skill), model-facing `skill`) loads one complete body for progressive disclosure. Skills are optional instructions, not session events, so their vocabulary lives here rather than in [core.md](core.md).
+
+Source: [`packages/core/skill/src/index.ts`](../../packages/core/skill/src/index.ts) and [`packages/core/tool-skill/src/index.ts`](../../packages/core/tool-skill/src/index.ts).
+
+## Discovery priority
+
+For a request with a cwd, `ctx.skills` finds the nearest git root and scans roots in first-wins order:
+
+| Priority | Source | Root |
+|---|---|---|
+| 1 | `project-dsh` | `<projectRoot>/.dsh/skills` |
+| 2 | `project-agents` | `<projectRoot>/.agents/skills` |
+| 3 | `runtime` | `ctx.skills.register(...)` |
+| 4 | `user-dsh` | `~/.dsh/skills` |
+| 5 | `user-agents` | `~/.agents/skills` |
+| 6 | `extra` | `Config.extraRoots` |
+| 7 | `system` | `~/.dsh/skills/.system` |
+
+The user DSH root skips its `.system` child during normal scanning so built-in skills are discovered exactly once. Same-name skills keep the highest-priority copy and log a warning for later duplicates. After this priority pass, model-visible summaries are sorted by `name` before prompt rendering so the `## Skills` fragment is deterministic and friendly to provider prefix caches.
+
+## Skill identity
+
+Skill names are kebab-case (`^[a-z0-9]+(?:-[a-z0-9]+)*$`). A skill can be a directory bundle (`<name>/SKILL.md`) or a flat Markdown file (`<name>.md`). Nested recursive `**/SKILL.md` discovery is intentionally outside v1.
+
+```ts type-equiv
+type SkillSource = 'project-dsh' | 'project-agents' | 'runtime' | 'user-dsh' | 'user-agents' | 'extra' | 'system'
+```
+
+## Summaries and complete definitions
+
+`SkillSummary` is the model-visible shape: the request prompt gets the name, source, description, and optional routing hint, but never the body or absolute file path. `disableModelInvocation` hides a skill from listings while allowing trusted code to load it by name.
+
+```ts type-equiv
+interface SkillSummary {
+  name: string
+  description: string
+  whenToUse?: string
+  disableModelInvocation?: boolean
+  directory: string
+  source: SkillSource
+}
+```
+
+`SkillDefinition` is the complete parsed result returned by `ctx.skills.get()` and used by the `skill` tool. `directory` is the base directory for resolving relative references in the skill body; `path` is present for disk skills; `metadata` preserves optional frontmatter for future consumers without changing v1 routing behavior.
+
+```ts type-equiv
+interface SkillDefinition extends SkillSummary {
+  content: string
+  path?: string
+  metadata?: 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.
+
+```ts type-equiv
+type SkillRegistration = Omit<SkillDefinition, 'disableModelInvocation'> & {
+  disableModelInvocation?: boolean
+}
+```
+
+## Lookup and configuration
+
+Skill lookup is cwd-sensitive because project skill roots are relative to the current workspace. If no git root is found, the supplied cwd itself is the project root.
+
+```ts type-equiv
+interface SkillLookupOptions {
+  cwd?: string | undefined
+}
+```
+
+The service can be pointed at alternate user roots in tests or deployments. `installSystemSkills` controls whether bundled system skills are materialized under `<dshHome>/skills/.system` on startup.
+
+```ts type-equiv
+interface Config {
+  dshHome?: string
+  agentsHome?: string
+  extraRoots?: string[]
+  installSystemSkills?: boolean
+}
+```
+
+## Prompt and tool contract
+
+`ctx.skills.renderModelListing()` returns a `## Skills` fragment wrapped in `<available_skills>`. Descriptions and `whenToUse` are whitespace-normalized, length-capped, and XML-escaped before rendering. The listing is appended to the same `GenerateOptions.system` string by the `agent/request` waterfall, after the base system prompt is assembled.
+
+The model-facing `skill({ name })` tool validates the kebab-case name, loads the complete definition for the calling agent cwd, rejects unknown or `disableModelInvocation` skills, and returns a `<skill_content name="...">` block with the body plus base-directory and relative-path guidance. The tool result is the only v1 path that exposes full skill instructions to the model.

+ 1 - 0
docs/rfc/README.md

@@ -95,6 +95,7 @@ Do NOT write one for a mechanical or local choice (a variable name, a one-file r
 | [dsh-hook-protocol — the shared Claude Code / Codex hook wire-protocol core](implemented/feature/2026-06-30-hook-protocol-lib.md) | 2026-06-30 |
 | [Interception seams — the typed-Decision surface a hook programs against](implemented/feature/2026-06-30-interception-seams.md) | 2026-06-30 |
 | [Subagent lifecycle enrichment — lastAssistantMessage (observe-only)](implemented/feature/2026-06-30-subagent-observe-enrich.md) | 2026-06-30 |
+| [Skill system — progressive disclosure instructions for agents](implemented/feature/2026-07-05-skill-system.md) | 2026-07-05 |
 
 ### Simplification
 

+ 45 - 0
docs/rfc/implemented/feature/2026-07-05-skill-system.md

@@ -0,0 +1,45 @@
+# Skill system — progressive disclosure instructions for agents
+
+## Status
+
+Implemented.
+
+## Context
+
+Agent products have converged on a skill pattern: keep the request prompt small by listing only available instruction bundles, then load the full body when the model decides a task matches. Codex, Claude Code, OpenCode, and Kimi Code differ in details, but all separate discovery metadata from complete instructions so a workspace can carry reusable behavior without paying the full prompt cost on every turn.
+
+DeepSeek Harness needs the same primitive because project-specific review, plugin-authoring, and tool-usage guidance should live next to the workspace or the user's agent configuration instead of being hard-coded into the loop. The repo is still unreleased, so this change establishes the foundation directly as first-class packages rather than a compatibility layer around an older format.
+
+## Decision
+
+Add `@deepseek-ai/dsh-skill` as the discovery service (`ctx.skills`) and `@deepseek-ai/dsh-tool-skill` as the model-facing loader tool. `dsh-agent-core` loads both by default so stdio and ACP apps get the same behavior.
+
+Discovery scans cwd-sensitive project roots, runtime registrations, user roots, extra roots, and system roots in first-wins priority order: project `.dsh`, project `.agents`, runtime, user `.dsh`, user `.agents`, extra roots, then `~/.dsh/skills/.system`. The user `.dsh/skills` scan skips `.system` so built-ins are not discovered twice. Same-name lower-priority skills are ignored with a warning, which lets project and user skills override built-ins deliberately.
+
+Each skill is either `<name>/SKILL.md` or `<name>.md` with YAML frontmatter. `name` and `description` are required; `whenToUse`, `disableModelInvocation`, and `metadata` are optional. Names are kebab-case. YAML frontmatter is parsed with the `yaml` package instead of a hand-written parser because the format already exposes an open `metadata` object and should behave like ordinary skill files rather than a bespoke key/value subset.
+
+The service injects a request-time `## Skills` fragment through the existing `agent/request` waterfall. It appends to `GenerateOptions.system` instead of changing `systemPrompt.assemble()`, because the available project skills depend on the calling agent's cwd. The fragment contains only stable routing metadata and is sorted by skill name after first-wins collection, so equivalent workspaces produce deterministic prompt text and better prefix-cache reuse. Full skill bodies are never included in the listing.
+
+The `skill({ name })` tool loads one full skill for the current agent cwd and returns a `<skill_content name="...">` block with the body plus base-directory guidance. Invalid names, unknown skills, and skills marked `disableModelInvocation` return tool errors. v1 does not additionally inject the loaded body into session context; the tool result is the model-visible disclosure path.
+
+System skills are ordinary skill files materialized under `~/.dsh/skills/.system` on startup. v1 ships `dsh-plugin-creator` and `dsh-skill-creator` there so the agent can help author DeepSeek Harness plugins and future skills using the same mechanism users can override.
+
+The data structures and prompt/tool contract are documented in [skills.md](../../../core-data-structures/skills.md), with service signatures in the generated [services catalog](../../../cordis-catalog/services.md).
+
+## Rejected alternatives
+
+**Inject full skill bodies into every system prompt.** Rejected because it destroys progressive disclosure and makes every request pay for instructions that may not apply.
+
+**Expose skills only as slash commands.** Rejected for v1 because model-initiated loading is the core capability; slash/ACP command advertisement can layer on later without changing discovery.
+
+**Use a separate system-reminder message.** Rejected for the current loop because `agent/request` already owns the last mutation point before the adapter call and `GenerateOptions.system` is the provider-neutral system prompt surface. A later provider-specific surface can still split this fragment if needed.
+
+**Recursively discover nested `**/SKILL.md`.** Rejected for v1. Flat files and one-level directory bundles cover the configured roots while keeping duplicate handling and prompt order easy to reason about.
+
+**Hand-parse frontmatter.** Rejected because the accepted schema includes an open `metadata` object. A narrow parser would either reject valid YAML users expect to work or grow into an unreviewed YAML subset.
+
+## Consequences
+
+The agent-core spine now includes one more request-time contributor and one more model-facing tool. Skill discovery is cwd-sensitive, so tests and callers that create agents with different session cwd values can observe different project skill overrides by design.
+
+The prompt fragment is deterministic for a fixed root set and runtime registration revision, but disk changes are not watched; discovery is memoized until runtime registration invalidates the cache or the process restarts. That keeps v1 simple and avoids adding file watching policy before there is a concrete user flow for hot-reloading skills.

+ 15 - 0
packages/core/skill/src/index.ts

@@ -25,31 +25,46 @@ export function isSkillName(name: string): boolean {
   return SKILL_NAME.test(name)
 }
 
+/** Origin bucket for a discovered skill. The value is prompt-visible metadata, not part of precedence by itself. */
 export type SkillSource = 'project-dsh' | 'project-agents' | 'runtime' | 'user-dsh' | 'user-agents' | 'extra' | 'system'
 
+/** Model-visible skill metadata returned by `ctx.skills.list()` and rendered into the request prompt. */
 export interface SkillSummary {
+  /** Kebab-case identifier used with the `skill` tool. */
   name: string
+  /** Short routing description shown to the model. */
   description: string
+  /** Optional extra routing guidance shown to the model. */
   whenToUse?: string
+  /** Whether the skill is hidden from model listings while remaining loadable by trusted callers. */
   disableModelInvocation?: boolean
+  /** Base directory for resolving skill-relative references. */
   directory: string
+  /** Discovery source that produced this winning skill. */
   source: SkillSource
 }
 
+/** Complete parsed skill definition, including the body loaded by `ctx.skills.get()`. */
 export interface SkillDefinition extends SkillSummary {
+  /** Markdown instruction body after frontmatter removal. */
   content: string
+  /** Absolute file path when the skill came from disk; runtime skills may omit it. */
   path?: string
+  /** Parsed optional metadata object from frontmatter. */
   metadata?: Record<string, unknown>
 }
 
+/** Runtime skill contribution accepted by `ctx.skills.register()`. */
 export type SkillRegistration = Omit<SkillDefinition, 'disableModelInvocation'> & {
   disableModelInvocation?: boolean
 }
 
+/** Workspace selector used for cwd-sensitive project-root discovery. */
 export interface SkillLookupOptions {
   cwd?: string | undefined
 }
 
+/** Skill plugin configuration. */
 export interface Config {
   /** DeepSeek Harness config root. Defaults to `$DSH_HOME` or `~/.dsh`. */
   dshHome?: string

+ 7 - 0
scripts/type-equiv.manifest.json

@@ -62,6 +62,13 @@
     { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsPolicyExec", "source": "packages/fs/fs-policy/src/types.ts" },
     { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FileReadOutcome", "source": "packages/fs/tool-fs/src/read-render.ts" },
 
+    { "doc": "docs/core-data-structures/skills.md", "symbol": "SkillSource", "source": "packages/core/skill/src/index.ts" },
+    { "doc": "docs/core-data-structures/skills.md", "symbol": "SkillSummary", "source": "packages/core/skill/src/index.ts" },
+    { "doc": "docs/core-data-structures/skills.md", "symbol": "SkillDefinition", "source": "packages/core/skill/src/index.ts" },
+    { "doc": "docs/core-data-structures/skills.md", "symbol": "SkillRegistration", "source": "packages/core/skill/src/index.ts" },
+    { "doc": "docs/core-data-structures/skills.md", "symbol": "SkillLookupOptions", "source": "packages/core/skill/src/index.ts" },
+    { "doc": "docs/core-data-structures/skills.md", "symbol": "Config", "source": "packages/core/skill/src/index.ts" },
+
     { "doc": "docs/core-data-structures/compaction.md", "symbol": "CompactionResult", "source": "packages/compact/compact/src/types.ts" },
 
     { "doc": "docs/core-data-structures/subagent.md", "symbol": "SubagentCapabilities", "source": "packages/subagent/subagent/src/types.ts" },