Skill Runtime 架构调研报告

September 4, 2026 · View on GitHub

只读调研。所有结论以当前生产代码真实行为为准,与文档冲突处已标注文档漂移。 生成日期:基于 src/agents/presets/analysis/skill-curation/docs/deepseek-harness/ 的真实代码与运行验证。


1. Executive Summary

当前 dsh-pentester 有一套自研的、基于文件系统快照的 Skill 机制,与 DSH 原生 Skill Runtime(@deepseek-ai/dsh-skill*完全解耦

  • 发现/解析skill-loader.ts 递归扫描 *.skill/ bundle,*.skills/ 仅作 UI 分组。
  • 注册AgentLibrary 内存 Map(builtin + custom 全局 namespace,custom 不得覆盖 builtin)。
  • 持久化:Skill 无独立 store;Worker 创建时把 bundle 原样复制到 delegation 目录 input/skills/<name>.skill/
  • 挂载:Worker persona 只注入 Skill Catalog(路径 + description 列表),Skill 全文不进入 system prompt;Worker 经 pentester_container_exec 按需 cat SKILL.md

目标架构的三大缺口(详见 Gap Analysis):

  1. Bundle Assignment 不存在 —— Agent Profile 的 skills 全是 Leaf ID,没有 .skills Collection 的递归展开语义。
  2. Runtime Skill search 不存在 —— 没有 pentester_skill_search,没有把 curation 数据接入 runtime。
  3. DSH 原生 Skill Runtime 未复用 —— preset 未 mount tool-skill,pentester 自实现了 skill loader + snapshot,与 DSH 的 registry/tool 无任何连接。

好消息:DSH 原生 Skill Runtime 完整且可复用(ctx.skills registry + skill({name}) tool + progressive disclosure),pentester 的 961 skill 中有 959 个 name 符合 DSH kebab-case 契约,仅 2 个(security_reporterhello_js_reverse_skill)含下划线需处理。


2. Current Skill Filesystem Model

根目录解析(src/agent-library/paths.ts

函数路径
Builtin skillsbuiltinSkillsRoot()npm 包根 skills/(探测链:<root>/skillslib/../skills
Custom skillscustomSkillsRoot()$DSH_HOME/dsh-pentester/skills(DSH_HOME 缺省 ~/.dsh
Builtin agentsbuiltinAgentsRoot()npm 包根 agents/
Custom agentscustomAgentsRoot()$DSH_HOME/dsh-pentester/agents

实际 skills/ 顶层(13 项):

6 个 *.skills 集合:anthropic-cybersecurity, ctf-skills, hack-skills,
                    pentest-skills, reverse-skills, strix
5 个 standalone *.skill:hello_js_reverse, hxbai-knowledge,
                         secknowledge-skill, security-reporter, src-hunter
2 个普通 md:README.md, PTES-skills.md

扫描契约(src/agent-library/skill-loader.tsscanSkillRoot()

逐条回答 A 部分:

#问题结论证据
1skills/ 根在哪解析builtinSkillsRoot() / customSkillsRoot()paths.ts
2Builtin 根npm 包 skills/paths.ts
3Custom 根$DSH_HOME/dsh-pentester/skillspaths.ts
4*.skills/ collection 语义PARTIAL — 有 collection 概念,但只是 SkillDefinition.collection 数组(UI 分组),不是可分配的 Bundle/Registry 实体skill-loader.ts walk()
5递归扫描 .skills/YESwalk() 对任意深度递归,collection 从外到内累积skill-loader.ts:101-133
6*.skill/ 停止递归YESloadBundle() 后 STOP,不深入 references/scriptsskill-loader.ts:109-121
7*.skill/ 必须含 SKILL.mdYES — 缺 → skill_manifest_missingskill-loader.ts loadBundle()
8根 .skill 与 .skills 内 .skill 等价YES — identity 来自 frontmatter.name,不依赖位置loadBundle
9README/普通 .md 忽略YES — 只认 .skill 目录,其他目录继续递归但普通文件忽略walk()
10canonical ID 来源SKILL.md frontmatter.name(id === name)loadBundle
11用 name frontmatterYESparseSkillFrontmatter() 解析 name/descriptionskill-loader.ts
12folder name ≠ nameload + warningskill_bundle_name_mismatch),不阻断loadBundle
13duplicate canonical namereject — 冲突项全部移除 + skill_duplicate_name 诊断registerSkill
14builtin/custom 重名custom 拒绝 → skill_name_reserved_by_builtin(全局 namespace 不允许覆盖)library.ts rescanCustom
15size limits / diagnostics / skipYES — bundle 上限 builtin 32MiB / custom 10MiB;SKILL.md 256KiB;symlink 拒绝;invalid 只产生 diagnostic 不阻断启动validateBundle / types.ts
16扫描 references/scripts/assets扫描但不解析 — validateBundle 统计 size,copySkillBundleTo 保留内容;不 parsevalidateBundle / copySkillBundleTo
17Bundle/Collection RegistryNO — 没有 .skills 集合的 registry 实体无对应类型
18是否丢失 .skills 父级结构NOcollection: string[] 保留(从外到内,如 ['hack-skills']SkillDefinition

3. Current Skill Registry

数据模型(src/agent-library/types.ts

interface SkillDefinition {
  id: string          // === name(canonical)
  name: string        // SKILL.md frontmatter.name
  description: string // SKILL.md frontmatter.description
  source: 'builtin' | 'custom'
  bundlePath: string  // bundle 目录绝对路径(含 *.skill)
  collection: string[] // 祖先 *.skills 目录名(去后缀,从外到内)
  revision: string    // SKILL.md 内容 sha256
}

逐条回答 B 部分:

#问题结论
1SkillDefinition 字段id, name, description, source, bundlePath, collection[], revision(7 字段
2id 与 name 关系id === name(canonical identity = frontmatter.name;id: 字段仅 legacy 兼容)
3source builtin/customYES
4保存 pathYESbundlePath(绝对路径,含 .skill
5保存 bundle/parent collectionYEScollection: string[]
6保存 descriptionYES
7保存 SKILL.md bodyNO — 只存 revision(sha256),body 不驻留内存
8lazy loadNO — scan 时读 SKILL.md 但只提取 frontmatter,body 丢弃
9启动一次性读完整 bodyNO — 只读 frontmatter(name/description),body 不读入
10registry cacheYESAgentLibrary.builtinSkills/customSkills(内存 Map)
11snapshotPARTIALLibrarySnapshot(RPC 视图),非 immutable runtime snapshot
12immutable skill snapshotPARTIAL — 只有 delegation 时 copySkillBundleTo 的磁盘副本(input/skills/)
13content 进入模型时间点Worker 运行中,经 pentester_container_exec 按需 cat SKILL.md(spawn 时只注入路径+description)

完整调用链(filesystem → model)

filesystem (skills/**/*.skill/SKILL.md)
  → scanSkillRoot()                     [skill-loader.ts] 递归扫描 + frontmatter 解析
  → AgentLibrary.builtinSkills/customSkills  [library.ts]  内存 Map(id===name)
  → AgentLibrary.listAgents()/resolveAgent()  [library.ts]  查询
  → profile-resolver.resolveAgentProfile()  [profile-resolver.ts]  继承解析 → resolved.skills
  → DelegationService.dispatch()        [delegations.ts] snapshotAssignedSkills()
       → copySkillBundleTo()            [library.ts] 复制 bundle 到 input/skills/
       → buildSkillCatalog()            [delegations.ts] 生成路径列表
  → buildWorkerPersona()                [delegations.ts] 注入 catalog(路径+description)
  → DshClient.startContinuableWorker()  [dsh.ts] ctx.subagents.startContinuable(spec)
  → Worker 运行中 cat SKILL.md          [pentester_container_exec]
  → model context(按需,非 spawn 全文)

4. Current Agent Profile Model

Schema(src/agent-library/types.tsAgentProfileDefinition

interface AgentProfileDefinition {
  id: string
  name: string
  description: string
  extends?: string             // 单继承
  systemPrompt?: string        // 完整替换父 prompt
  systemPromptAppend?: string  // 追加到父 prompt
  skills?: string[]            // 完整替换 inherited
  skillsAdd?: string[]         // 追加
  skillsRemove?: string[]      // 移除
  model?: string
}

逐条回答 C 部分:

#问题结论
1Builtin 从哪加载npm 包 agents/<id>/profile.yml(scanBuiltinProfiles)
2Custom 从哪加载$DSH_HOME/dsh-pentester/agents/<id>/profile.yml(scanCustomProfiles)
3schema 字段见上(9 字段)
4extends/skills/skillsAdd/skillsRemove/model全有prompt (叫 systemPrompt);mcp
5单/多继承单继承extends 单个 id)
6继承解析顺序inheritanceChain() 从根到子 unshift;resolveAgentProfile() 顺序遍历
7skills 语义完整替换 inherited
8skillsAdd 语义skills 不存在时 = inherited - skillsRemove + skillsAdd
9skillsRemove 语义skills 不存在时从 inherited 过滤
10remove 在 assignment 层还是 leaf 层leaf ID 层(skillsRemove 里是 leaf id,无 bundle 概念)
11builtin 用户修改是 Overlay是 Overlay 模式(builtin 只读,用户 extends 新建 custom),不是原地修改
12用户实际会改 builtin 文件吗UI 禁止(builtin 锁定);技术上古可改但 npm 包内文件会随升级覆盖
13custom 继承 builtinextends: web(test 已验证:extendsChain ['web','custom-web']
14产生 Resolved profileYESResolvedAgentProfile
15Resolved 有 skillIdsYESresolved.skills: readonly string[]

继承解析核心(profile-resolver.ts resolveAgentProfile()

for (const entry of chain) {
  if (definition.systemPrompt !== undefined) systemPrompt = definition.systemPrompt
  else if (systemPromptAppend) systemPrompt += systemPromptAppend
  if (definition.skills !== undefined) skills = definition.skills       // 完整替换
  else {
    skills = skills.filter(s => !remove.has(s))                          // - skillsRemove
    skills = [...skills, ...(definition.skillsAdd ?? [])]                // + skillsAdd
  }
  if (definition.model !== undefined) model = definition.model
}

注意(文档漂移点)skills + skillsAdd 同时存在时,skills完整替换skillsAdd 被忽略(if skills !== undefined 分支不进入 else)。目标架构 §3 描述的 "Builtin 默认 + skillsAdd" 语义在当前实现中仅当 child 不写 skills 字段时成立。这是实现细节,需在实现 Bundle Resolver 时明确。


5. Builtin Agent Skill Assignment Inventory

实际运行 AgentLibrary.create() + listAgents() 统计(非手写):

Agent IDRoleParentskillsskillsAddskillsRemoveResolved Skill CountModelStage bindings
webWeb & API Agent(root)48 leaf ids48intelligence-gathering, vulnerability-analysis
impactPost Exploitation Agent(root)16 leaf ids16post-exploitation
reconRecon Agent(root)11 leaf ids11intelligence-gathering
validationExploitation Validation Agent(root)9 leaf ids9exploitation
vulnerabilityVulnerability Analysis Agent(root)8 leaf ids8vulnerability-analysis
reportingReporting Agent(root)4 leaf ids4reporting
threat-modelThreat Modeling Agent(root)2 leaf ids2threat-model

回答 D 部分:

  1. Builtin Agent 总数:7
  2. 无 Skill 的 Agent:0(全部有 skill)。
  3. Skill 很多的 Agent:web=48(最多)。
  4. Assignment 类型:全部 Leaf IDs(运行验证:0 个 .skills 结尾的 bundle 引用)。
  5. Bundle Assignment:不存在(无任何 agent 用 .skills 集合 ID)。
  6. .skills 作为 Agent Skill ID 使用:没有
  7. 若给 skills 写 Collection ID,runtime 会发生:resolveValidated()profile_skill_missing: profile=X skill=<collection-id>(因为 registry 里没有该 id 的 skill)→ 该 profile unavailable + diagnostic(builtin 不启动失败,custom last-known-good)。

6. Stage → Agent Relationship

  • 7 个 PTES Stage 绑定:src/stages.ts DEFAULT_STAGE_AGENTS(默认值),实际存于 settings.json V2 stageAgents
  • 语义:Agent allowlist(Root 只能 delegate 当前 stage allowlist 内的 agent;delegate()allowed.has(profile.id) 校验)。
  • Root 选 Agent:pentester_delegateassignments[].agent 必须是 allowlist 内 id;Root 通过注入的 run-state 看到 "Available AgentProfiles"。
  • Skill 与 Stage 不耦合(skill 是 agent 属性,stage 只限制可用 agent)。
  • Skill 不受 Stage 过滤(stage 过滤的是 agent,不是 skill)。
  • stageAgents schema:Record<StageId, string[]>(settings.json V2)。
  • Settings 修改实时生效:PentesterSettingsStore.save() 立即更新内存 + notify;Root 下一轮 prompt 注入经 listAgentsForStage() 同步读。已运行 Worker 不受影响(skill 在 dispatch 时 snapshot)。
PTES Stage (run.currentStage)
  → settings.stageAgents[stage]        [settings-store.ts] allowlist
  → manager.listAgentsForStage(stage)  [manager.ts] 过滤 dangling
  → delegate() 校验 agent ∈ allowlist  [delegations.ts]
  → resolveAgent(agent)                [library.ts] ResolvedAgentProfile
  → snapshotAssignedSkills(skills)     [delegations.ts] 复制 bundle
  → startContinuableWorker             [dsh.ts] Worker spawn

7. Worker Spawn Skill Flow

完整链路(pentester_delegatectx.subagents.startContinuable):

Root agent
  → pentester_delegate tool             [tools.ts delegate()]
  → requireRootCaller(exec)             [dsh.ts trustedCallerFromExec]
  → requireTargetRun(caller)            [tools.ts → store/targets]
  → DelegationService.delegate()        [delegations.ts]
       → 校验 agent ∈ stage allowlist
       → scaffoldDelegationDir()        创建 input/work/artifacts/evidence + task_prompt.md
       → saveRun()                      持久化 Delegation(agentId, taskPrompt, objective)
       → docker.ensureImage/ensureRunContainer
       → dispatch()                     [delegations.ts]
            → snapshotAssignedSkills()  复制每个 skill bundle → input/skills/<name>.skill/
            → buildSkillCatalog()       生成 "## Assigned Skills" 路径列表
            → writeAgentProfileSnapshot() 写 input/agent-profile.json(skills 的 id+revision)
            → buildWorkerPersona()      WORKER_BASELINE + profile.systemPrompt + skillCatalog
            → buildWorkerTaskPrompt()   standalone task context
            → dsh.startContinuableWorker(input)  [dsh.ts]
                 → ctx.subagents.startContinuable(spec)  官方 provider "spawn"

回答 F 部分:

#问题结论
1Task 创建时如何 resolve agentdelegate()manager.resolveAgent(assignment.agent)
2Skill 在哪个函数 resolvedispatch()snapshotAssignedSkills()(读 profile.skills,逐个 getSkill()
3skillIds 出现在哪个对象ResolvedAgentProfile.skillsagent-profile.json snapshot 的 skills[]Delegation.agentId(无独立 skillIds 字段)
4WorkerLaunchSpec / AgentRun / session binding / snapshot无 WorkerLaunchSpec / AgentRun 类型;有 agent-profile.json snapshot + Delegation.sessionId binding
5skillIds 持久化PARTIALagent-profile.json 有 skills 的 id+revision;run.json 的 Delegation 只存 agentId(不存 skill 列表)
6session registry 存 skillIdsNO — session 只绑定 sessionId ↔ delegationId(经 run.delegations)
7continue 时校验 skill configNO — continue 走 DSH send_message,不重走 dispatch,不重校验
8spawn 后改 skill:运行中 worker 变吗 / continue 变吗均不变 — bundle 已复制到 input/skills/(磁盘快照),persona 已固定
9spawn 时加载 skill bodyNO — 只复制 bundle + 注入路径列表
10system prompt 列出所有 skillPARTIAL — 只列 name+description+路径(Skill Catalog),非全文
11Worker 看到 descriptionYES — Skill Catalog 含 description
12Worker 拥有 DSH 原生 skill toolNO — preset 未 mount tool-skill
13Skill 怎么被模型"调用"经 shell cat — Worker 用 pentester_container_exec 读 /workspace/.../input/skills/<name>.skill/SKILL.md

8. DSH Native Skill Runtime

存在性(CONFIRMED FROM API)

DSH 官方仓库有完整 skill 家族(docs/deepseek-harness/packages/skill/):

角色
@deepseek-ai/dsh-skill纯 provider registry(ctx.skills
@deepseek-ai/dsh-skill-filesystem本地 filesystem provider
@deepseek-ai/dsh-tool-skilldurable session catalog + model-facing skill tool
@deepseek-ai/dsh-skill-badgepackaged badge provider

权威契约:docs/deepseek-harness/docs/subsystems/skills.md

ctx.skills registry API(CONFIRMED FROM API)

registerProvider(create: (control) => SkillProvider): () => void
register(skill: SkillRegistration): () => void      // runtime in-process skill
list(options?): Promise<SkillSummary[]>             // 全量 summaries(name+description+whenToUse+invocation+source+provider+resourceBase)
snapshot(options?): Promise<SkillCatalogSnapshot>   // list + complete 状态
get(name, options?): Promise<SkillDefinition | undefined>  // 加载完整 body(content)

skill 工具(CONFIRMED FROM API,tool-skill/src/index.ts)

name: 'skill'
parameters: { name: { type: 'string', required: true } }
output: { name, provider, resourceBase?, content }  // content = 完整 SKILL.md body
execute: ctx.skills.list({cwd, scope: agent}).find(name) → ctx.skills.get(name)

逐条回答 G 部分

#问题结论
1pentester preset 启用这些组件NO — agent.cordis.yml 只有 persona / tool-ask-user / tool-subagent-control / run-state
2Worker child 继承它们NO — preset 无 skill 行,Worker 继承 preset scope 也无 skill
3DSH Skill Registry API见上(registerProvider/register/list/snapshot/get)
4动态 register/list/search/load/invokeregister ✓ / list ✓ / load(get) ✓ / invoke(经 tool) ✓ / search ✗
5model-facing tool schemaskill({name: string}){name, provider, resourceBase?, content}
6tool 返回什么返回 SKILL.md content(<skill_content> + <skill_resources> + <skill_instructions>),非注入 prompt,非临时 context —— 是 tool result
7一次多个 skillNO — 一次一个 name
8同一 Worker 后续追加 skillYES(catalog 每 pre-step 重新 snapshot,registry 变化会追加 replacement catalog)
9已加载 skill 状态记录YES — tool result 进入 session history(durable)
10skill load eventNO — 只有 skills/change(registry invalidation,无 diff),无 "skill loaded" event
11skill unloadNO — 无 unload API
12部分 registry 暴露给 agentPARTIAL — registry 是 host+per-scope layered(nearest layer wins),但无 per-agent allowlist;可见性 = preset 是否 mount tool-skill + provider 注册在哪层
13per-agent skill allowlistNO — DSH 无此概念(scope 是 preset 层,不是 agent 层)
14搜索能力NO — 只有 list() 全量 + get(name) 按名
15若搜索,搜什么N/A(无搜索)
16最接近的扩展 seam自建 provider(registerProvider)+ 自建 search tool(pentester 自己做语义搜索,用 curation 数据,DSH 只负责 get() 加载)

关键格式差异(CONFIRMED FROM API)

  • DSH local provider 格式<name>/SKILL.md(目录名 = name,kebab-case)或 <name>.md(flat)。不支持 .skill 后缀,不支持递归嵌套 **/SKILL.md
  • Pentester 格式<name>.skill/SKILL.md(identity = frontmatter.name,目录名可不同)。
  • name 契约:DSH SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/(kebab-case)。pentester 961 skill 中 959 合规2 个含下划线security_reporter(被 reporting agent 引用)、hello_js_reverse_skill(standalone)。这 2 个会触发 DSH register()invalid skill name

9. Worker Tool / Trusted Caller Model

工具注册与 allowlist

  • Worker toolFilter:src/dsh.ts WORKER_TOOL_FILTER = { deny: [...] }deny-list:deny 5 个 Root 工具 + ask_user_question)。
  • Worker 继承 pentester preset standing scope → 拥有 pentester_container_execworker-tools.ts 注册)。
  • Skill tool 不在 allowlist(preset 未 mount)。
  • 新增 pentester_skill_search 的注册位置:应仿照 registerContainerExecToolrun-state.mjsctx.pentester.registerContainerExecTool(ctx.tools) 旁注册(preset scope 内),Worker 经 deny-list 自动继承,Root 经 ROOT_DENY 隐藏。

Trusted Caller seam(src/dsh.ts trustedCallerFromExec

interface TrustedCaller {
  sessionId: string
  agentPreset?: string
  isSubagent: boolean
  isPentesterRoot: boolean
  cwd?: string
}

exec.agent.session(host-injected,非 model-supplied)提取。可进一步经已有链路反查:

目标字段可得?链路
sessionIdYEScaller.sessionId
agentIdYESresolveWorkerTargetContext(cwd, sessionId)run.delegations.find(d => d.sessionId===sessionId).agentId
taskIdYESdelegation.id(D-001)
agentRunIdNO(pentester 无 AgentRun 概念)
engagementIdYESrun.id / ctx.targetId

结论:host 已有可靠 trusted caller seam(worker-tools.tsmakeContainerExecExecutor 完整示范了 sessionId → delegation → agent 的链路)。Model 无需传 agentId/taskId,宿主从 sessionId 反查,可信。


10. Settings / Config Behavior

  • 用户配置 Agent Skill 的方式:Settings → Agent Library → Agents(新建 custom agent 时填 skills 逗号分隔文本,或 Use as Base 继承 builtin)。
  • UI 列所有 skill:YES(Skills tab 按 collection 分组列出全部,builtin 🔒 / custom 可编辑)。
  • 搜索:NO(无 search 输入框,全部列出)。
  • Add/Remove:PARTIAL — 编辑 agent 时用逗号分隔文本手动改 skills 列表;skill 本身可 create/update/delete。
  • 区分 builtin/custom:YES(source 字段 + 🔒 标记)。
  • 显示来源:YES(source)。
  • Bundle 支持:NO(UI 无 bundle 分配,只有 collection 分组展示)。
  • 树形目录:NO(collection 分组是扁平列表,非树)。
  • Builtin 直接编辑:NO(锁定)。
  • Custom 创建/继承:YES(AgentForm 支持 extends = Use as Base)。
  • Settings 保存生效:立即(live store 内存更新 + notify;watcher 200ms debounce + 5s 轮询兜底 rescan)。
  • 需 reopen/recovery:NO(custom agent/skill 文件变化经 watcher 自动 rescan;stageAgents 经 live store 立即生效)。
  • Stage policy vs Agent config 生效机制:不同 — stageAgents 走 settings.json(live store);agent/skill 定义走文件系统 + watcher。

11. Curation Data Runtime Readiness

真实存在的文件(analysis/skill-curation/):

文件内容规模
global-index.json961 canonical index(skill_index S0001-S0961, skill_id, source_path, collection)961
runs/2026-08-30-step1-full/cards/*.jsonSkill Cards(primary_domain, domain_tags, capability_tags, technique_tags, tools, target_types, role, workflow_focus, summary 等 16 字段)961
final/skill-family-membership.jsonFamily membership961 entries
final/skill-relations.jsonRelation graph890 edges
final/families.jsonFamilies379
final/decision-ledger.json决策台账961
final/related-skills.json相关 skill961
step2/, step3/, step4/, step4.1/中间产物

回答 J 部分:

  1. 真实存在:见上表。
  2. 961 canonical index:global-index.json
  3. 含 primary_domain/domain_tags/capability_tags/technique_tags/tools/target_types/role/workflow_focus/summary:Skill Cardsruns/.../cards/S0001.json 等 961 张)。
  4. Family membership:final/skill-family-membership.json
  5. Relation graph:final/skill-relations.json
  6. canonical ID 与 runtime name 一致:100% 一致(运行验证:global-index 961 id ↔ runtime 961 name,0 stale / 0 missing 双向)。
  7. stale ID:0
  8. 961 全映射回真实 skill:YES(0 stale / 0 missing)。
  9. 适合做 search 静态索引:YES — Card 的 summary/capability_tags/technique_tags/tools/role/workflow_focus 是理想检索字段。
  10. 应 runtime 直接读 / build-time 生成 / package 生成:build-time 生成(见下)。
  11. npm 发布含 analysis/:NOpackage.json files = ['lib','presets','agents','skills','docker',...]不含 analysis/
  12. 最合理生成位置:build-time 把精简 runtime index 生成到 lib/(或 skills/ 旁),例如 lib/skill-search-index.json,随 npm 发布;或 package 时把 analysis/skill-curation/final/ 的搜索所需字段(global-index + card 精简版 + families)合并成单个 lib/skill-search-index.json

12. Proposed Integration Seams

Bundle Resolver(推荐位置)

  • 推荐文件src/agent-library/profile-resolver.ts(或新增 src/agent-library/bundle-resolver.ts)。
  • 推荐函数resolveSkillGrants(profile.skills, library) → { allowedSkillIds: string[], grants: Grant[] }
  • 输入readonly string[](可能含 leaf id 或 .skills bundle id)。
  • 输出{ allowedSkillIds: string[], grants: { skillId, grantedBy: [{type:'builtin-bundle'|'user-explicit', id}] }[] }
  • 为什么resolveAgentProfile 已产出 skills,Bundle 展开是它下游的纯函数;AgentLibrary.listSkills() 已有 collection: string[],bundle → leaf 的映射 = 遍历所有 skill 找 collection 前缀匹配(如 bundle pentest-skills → 所有 collection'pentest-skills' 的 skill)。不需要新建 .skills registry 实体 —— collection 数组已够展开。

User Overlay(skillsAdd/skillsRemove)

  • 已存在resolveAgentProfile() 已实现 skills 完整替换 / inherited - skillsRemove + skillsAdd
  • 最小缺口skills + skillsAdd 共存时 skillsAdd 被忽略(当前语义 skills 完整替换)。若目标要求 "builtin 默认 + skillsAdd 追加",需决定:保留 "skills 完整替换" 还是改为 "skills 也是追加"。建议保持现状(skills = 完整替换是清晰的显式语义),custom agent 用 extends + skillsAdd 实现追加。

Provenance

  • 当前无 provenance 字段
  • 判断resolve-time derived 即可(无需持久化)—— 每次 resolve 时从 bundle 展开 + 用户显式 skillsAdd 计算 grantedBy,供 search 排序 boost。持久化到 run.json 会引入不必要的 schema 演进。

Search Scope source of truth

  • 推荐Worker launch spec / snapshot(即 agent-profile.json 已持久化的 skills 列表 + delegation 目录 input/skills/)。
  • 理由:原则 "正在运行的 Worker 不应因用户后来改 Profile 而静默改变能力边界"。agent-profile.json 在 dispatch 时写死(immutable),是 Worker 能力边界的权威快照。pentester_skill_search 应读取该 snapshot 的 allowedSkillIds,而非重新 resolve 当前 config。
  • 次选:run.json 的 Delegation(但当前只存 agentId,需扩展存 skillIds)。

13. Test Coverage Matrix

行为测试文件覆盖缺口
nested .skills scanagent-library.test.ts:163
leaf .skill scanagent-library.test.ts:163
.skill 内不递归agent-library.test.ts:184
duplicate canonical nameagent-library.test.ts:404
builtin/custom conflictagent-library.test.ts:416
agent inheritanceagent-library.test.ts:64
skillsAdddelegations.test.ts:439 (Phase 45)
skillsRemoveagent-library.test.ts:86
worker snapshot contains skill IDsdelegations.test.ts:389, 439agent-profile.json skills[]
continue configuration matching无 continue 重校验测试
config changes don't mutate running Worker⚠️ 隐含代码保证(snapshot),无显式测试
Skill runtime actual loadNOT_IMPLEMENTED(无 DSH skill tool)
Custom Skill loaddelegations.test.ts:439
invalid Skill diagnosticsagent-library.test.ts:194, 221, 235, 380, 394
bundle assignmentNOT_IMPLEMENTED

14. Gap Analysis

按目标架构:

Bundle + Leaf Assignment        → Needs new code(Bundle Resolver,纯函数)
Agent Skill Grant               → Needs new code(grant 展开 + provenance derived)
Worker immutable allowedSkillIds → Reusable with small change(agent-profile.json 已存,需扩展为 allowedSkillIds 数组)
Subagent runtime search         → Needs new code(pentester_skill_search + curation runtime index)
multiple Skill results          → Needs new code(search 返回 Top N)
dynamic Skill load              → Reusable(DSH skill tool + ctx.skills registry)
能力状态说明
Skill scan + frontmatter 解析Already Existsskill-loader.ts
Skill registry(内存)Already ExistsAgentLibrary
skillsAdd / skillsRemoveAlready Existsprofile-resolver.ts
Bundle AssignmentNeeds new code纯函数展开 collection → leaf
Skill Grant provenanceNeeds new code(resolve-time)不需要持久化
Immutable Worker allowedSkillIdsReusable with small change扩展 agent-profile.json
Runtime Skill searchNeeds new codepentester_skill_search + build-time index
Dynamic Skill loadReusable(DSH)ctx.skills + tool-skill,需 mount + register + 格式适配
Per-agent allowlistNeeds new code(pentester 层)DSH 无此概念,search 层过滤
Semantic searchNeeds new code(pentester 层)DSH 无 search,用 curation Card
Full-text/name searchNeeds new code同上
2 个非 kebab-case nameShould not be in DSH registrysecurity_reporter / hello_js_reverse_skill 需改名或 wrapper
DSH registry 是否在 pentester 进程可 injectUnknown until verified需确认 out-of-tree plugin 能否 inject: ['skills']

  1. Bundle Resolverprofile-resolver.ts 纯函数)—— 展开 collection → leaf,产出 allowedSkillIds + grants
  2. Worker snapshot 扩展 —— agent-profile.json 增加 allowedSkillIds(resolve 后的最终列表),保持 immutable。
  3. Build-time runtime index —— 把 curation global-index.json + 961 Card 精简字段(summary/capability_tags/technique_tags/tools/role/workflow_focus)生成 lib/skill-search-index.json(进 npm files)。
  4. pentester_skill_search 工具 —— 仿 worker-tools.ts 注册,trusted caller 反查 delegation → allowedSkillIds → 过滤 + 排序(用户显式 grant boost)→ 返回 Top N。
  5. DSH Skill Runtime 集成(最后,风险最高)—— 先验证 ctx.skills 可 inject;mount tool-skill + 自定义 provider 注册 961 skills;处理 2 个 kebab-case name。

16. Open Questions

  1. DSH ctx.skills 在 out-of-tree plugin 中是否可 inject 需验证 pentester 作为 repository plugin 能否 mount @deepseek-ai/dsh-skill(权威文档显示 host+per-scope layered,repository plugin 落在 global layer —— 但 pentester 当前 inject 列表只有 ['tools','subagents','typert'])。
  2. per-agent allowlist 如何映射 DSH layer? DSH registry 无 per-agent allowlist;若 DSH skill 工具对 Worker 可见,会暴露全部注册的 961 skills,而非 Worker 的 allowed subset。方案pentester_skill_search 在 pentester 层过滤(不依赖 DSH 限制),DSH skill 工具只负责加载已被 search 确认的 skill;或每个 Worker 用 ctx.skills.register() 注册自己 allowed 的 skill(但这会污染 global layer)。
  3. 2 个非 kebab-case name 的处理方式(改名 vs 排除 vs wrapper provider 做 name 映射)。
  4. skills 完整替换 vs 追加 的最终语义(当前实现 = 完整替换;目标 §3 描述 = 默认 + skillsAdd)。
  5. search 索引是否含 body(当前 Card 无 body,body 需从 SKILL.md 或 snapshot 加载)。

最终数字(运行验证,非手写)

Total Leaf Skills:                     961
Total top-level *.skills Bundles:        6
Total standalone top-level *.skill:      5
Total Builtin Agents:                    7
Agents with Skill assignments:           7
Agents without Skill assignments:        0
Maximum resolved Skills on one Agent:   48  (web)
Minimum:                                 2  (threat-model)
Median:                                  9
Raw assignment count:                   98  (全部 leaf,无 bundle)
Resolved leaf count:                    98  (= raw,无 bundle 可展开)

SKILL_RUNTIME_INVESTIGATION_COMPLETE

Current architecture:
自研文件系统快照机制:scan → 内存 registry → dispatch 时复制 bundle → Worker 经 shell cat SKILL.md(不接 DSH 原生 skill runtime)

Bundle assignment support:
NO(profile.skills 全是 leaf ID;.skills 仅 UI 分组)

Leaf assignment support:
YES(resolveValidated 校验 leaf id 存在;dispatch 复制 bundle)

Builtin Agent defaults:
YES(7 builtin 各带 leaf skill 列表,共 98 引用全部有效)

skillsAdd / skillsRemove:
YES(profile-resolver 单继承,skills 完整替换 / else -remove +add)

Immutable Worker skill grant:
PARTIAL(dispatch 时磁盘快照 + persona 固定;但 run.json 不存 skillIds,continue 不重校验)

Runtime Skill search:
NO(无 pentester_skill_search;curation 数据未接入 runtime)

Dynamic Skill loading:
NO(pentester 无 DSH skill tool;DSH 原生支持但未 mount)

DSH native Skill runtime reusable:
PARTIAL(ctx.skills + tool-skill 完整;但 2 个 name 非 kebab-case、格式差异、per-agent allowlist 缺失、ctx.skills inject 未验证)

Recommended implementation entrypoint:
src/agent-library/profile-resolver.ts(Bundle Resolver)+ src/worker-tools.ts(pentester_skill_search)

Highest-risk unknown:
out-of-tree plugin 能否 inject DSH ctx.skills 并按 per-agent 暴露 subset(DSH registry 无 per-agent allowlist)

Ready for implementation design:
YES