Skill Runtime V2 Implementation Report

September 4, 2026 · View on GitHub

superseded(Host-private isolation):本报告描述的 input/skills/ 快照 + Worker 经 shell cat SKILL.md 已被 Host-private frozen snapshot 取代(完整 Skill Body 唯一入口 = pentester_skill_load,不进 /workspace)。当前权威实现见 src/skill-runtime.ts / src/delegations.ts / src/targets.ts

破坏性重构:Bundle / Leaf Capability Assignment → Resolved Agent Skill Grant → Immutable Delegation Snapshot → Subagent runtime search → batch Skill load。


1. Architecture Before / After

Before(V1)

scanSkillRoot()         递归扫描 *.skill,*.skills 仅作 UI 分组(collection[])
  → AgentLibrary        内存 Map(builtin/custom skills)
  → profile.skills      字符串数组(leaf-only),skills=完整替换 / skillsAdd 追加
  → dispatch()          snapshotAssignedSkills() 复制 bundle + buildSkillCatalog() 注入 persona
  → Worker              persona 列全部 Skill name+description+路径,经 shell cat SKILL.md
  • Bundle 无实体、无 Bundle Assignment、无 search、无 load、无 provenance。
  • 旧 UI:逗号分隔 skills 输入 + 961 行扁平列表 + collection 分组。

After(V2)

scanSkillRoot()         顶层 *.skills → SkillBundleDefinition(leafSkillIds 递归收集)
  → AgentLibrary         skills Map + bundles Map + overrides
  → profile.skills       SkillGrantRef[]({type:bundle|skill,id}),skills=Grant/Add
  → resolveAgentProfile  ResolvedAgentSkillGrant(allowedSkillIds + provenance)
  → dispatch()           snapshotAllowedSkills() + agent-profile.json v2(allowedSkillIds 冻结)
  → Worker               Loaded Skills = [],persona 只保留一句 on-demand 规则
  → pentester_skill_search   只搜 allowed,deterministic ranking + relation diversification
  → pentester_skill_load     批量加载 frozen SKILL.md(≤6),去重 + 授权

2. Breaking Changes

V1V2
SkillDefinition.collection: string[]父路径数组删除parentBundleId?: string
AgentProfileDefinition.skillsstring[],完整替换SkillGrantRef[],Grant/Add
skillsAdd存在删除
skillsRemovestring[]SkillGrantRef[](bundle/leaf 展开后减除)
ResolvedAgentProfile.skillsstring[]删除skillGrant: ResolvedAgentSkillGrant
agent-profile.jsonschemaVersion 1(skills: [{id,revision}])schemaVersion 2(skillGrant.allowedSkillIds 冻结)
Worker persona注入 "## Assigned Skills" catalog删除,只留 on-demand 规则
旧 custom profile skills: - sql-injection字符串deterministic 迁移 → {type:skill,id}(不猜 bundle/skill)

3. Skill Registry V2

新增 SkillBundleDefinition 一等实体:

interface SkillBundleDefinition {
  id: string        // 顶层目录名去 .skills(pentest-skills)
  name: string
  source: 'builtin' | 'custom'
  bundlePath: string
  leafSkillIds: readonly string[]  // 递归收集,deterministic 排序
}

interface SkillDefinition {
  id: string        // === name(frontmatter)
  name: string
  description: string
  source: AgentSource
  bundlePath: string
  parentBundleId?: string  // 顶层 bundle;standalone 无
  revision: string
}

AgentLibrary API:

getSkill(id) / getBundle(id) / listSkills() / listBundles() / resolveBundleLeaves(bundleId)
  • 顶层 *.skills/ → Bundle;嵌套 *.skills/nested_skills_bundle warning(不创造新 Bundle)。
  • 实际扫描:6 bundles(anthropic-cybersecurity 817 / hack-skills 102 / ctf-skills 11 / pentest-skills 9 / strix 9 / reverse-skills 8)+ 5 standalone = 961 leaves

4. Bundle Semantics

skills/pentest-skills.skills/web/sql-injection.skill/SKILL.md
  → Bundle "pentest-skills"(可整体分配)
  → Leaf "sql-injection"(parentBundleId="pentest-skills",可独立分配)
  • Bundle ID = 顶层目录名去 .skills 后缀;Bundle 内普通目录仅作 UI 树,不创建 assignable ID。
  • 嵌套 .skills 产生 diagnostic,不默默创造 Bundle 语义。
  • resolveBundleLeaves(bundleId) deterministic(scanner 构建时已建立 Bundle → Leaf 映射,resolver 不猜路径)。

5. Agent Skill Grant Semantics

type SkillGrantRef = { type: 'bundle'; id: string } | { type: 'skill'; id: string }

interface ResolvedAgentSkillGrant {
  allowedSkillIds: readonly string[]
  provenance: ReadonlyMap<string, readonly SkillGrantReason[]>
}

type SkillGrantReason =
  | { source: 'builtin-default'; via: 'bundle' | 'skill'; id: string; profileId: string }
  | { source: 'user-explicit'; via: 'bundle' | 'skill'; id: string; profileId: string }

合并顺序(§4):

Resolve parent → Expand skills refs → Union → Expand skillsRemove refs → Subtract → Dedup
  • skills 始终 Grant/Add(不再"完整替换 inherited")。
  • skillsRemove 在 leaf 集合层减除(bundle remove 展开后减除全部 leaf)。
  • Bundle 展开 deterministic,provenance 保留每个 skill 的授予原因(user-explicit 供 ranking boost)。

6. Builtin Defaults

7 个 Builtin Agent 全部迁移为新 typed schema(skills: [{type:skill,id}],共 98 引用,全部有效):

Agentresolved allowedSkillIds
web48
impact16
recon11
validation9
vulnerability8
reporting4
threat-model2

第一阶段未强行把几十个 Leaf 改成大 Bundle(Allowed 多 ≠ Loaded 多)。


7. Builtin User Overrides

新增 $DSH_HOME/dsh-pentester/agent-overrides/<id>.yml

skills:
  - type: skill
    id: company-api
skillsRemove:
  - type: skill
    id: unwanted-skill
  • resolve:Builtin defaults + User Overlay → Effective Builtin(override 的 grant 标记 user-explicit)。
  • agent.override(写 override)+ agent.resetOverride(删除 = Reset to Defaults)RPC。
  • watcher 监视 agent-overrides/ 目录,修改后自动 rescan。
  • 长期定制仍推荐 "Create Profile From This"(extends builtin)。

8. Delegation Snapshot(Immutable)

input/agent-profile.json(schemaVersion 2):

{
  "schemaVersion": 2,
  "agent": { "id": "web", "name": "Web Agent", "source": "builtin", "extendsChain": ["web"] },
  "skillGrant": {
    "allowedSkillIds": ["sql-injection", "xss", "..."],
    "userExplicitIds": ["..."],
    "skills": [{ "id": "sql-injection", "revision": "..." }]
  }
}
  • allowedSkillIds 冻结(Worker 能力边界的 source of truth,绝不重新 resolve 当前 Profile)。
  • snapshotAllowedSkills() 复制 frozen bundle 到 input/skills/<name>.skill/
  • 存在 ≠ 已加载:只是 immutable capability filesystem。
  • spawn 后修改 Profile / Custom Skill 不影响已运行 Worker(测试锁定)。

9. Search Architecture

trustedCallerFromExec → sessionId → Delegation → agent-profile.json
  → allowedSkillIds → Search Index filter → ranking → relation diversification
  • Build-time indexscripts/build-skill-search-index.mjs 从 curation Cards + families + relations 生成 lib/generated/skill-search-index.json(961 skills + 890 relations,随 npm 发布)。
  • Custom fallback:rescan 时按需读 custom SKILL.md body 建立词法索引(≠ 注入模型)。
  • pentester_skill_search(Worker tool):{ query, limit? },default 6 / max 10,Model 不传任何身份参数
  • 只搜 allowed:绝对不搜全 961(除非 agent 真被授权 961)。

10. Search Ranking(deterministic,不调 LLM)

exact id/name match  +10
technique tag        ×3
tool                 ×2
capability           ×2
family               ×1.5
target               ×1
summary lexical      ×2(命中比例)
body lexical fallback ×1(custom)
user-explicit boost  +0.5(轻微,不盖过相关性)
  • Relation-aware diversification:邻接关系(complementary/specializes/generalizes)轻微加分, 避免全同 family 的 Top N,优先 general + specialist + tool-specific 组合。
  • 响应含 score / source / bundleId / role / summary / reason[] / userPreferred / loaded, 及 recommendedLoad(不含已加载)。

11. Batch Skill Loading

pentester_skill_load{ ids: string[] }(min 1 / max 6)。

trusted caller → delegation snapshot → validate id ∈ allowedSkillIds
  → dedup → read frozen input/skills/<id>.skill/SKILL.md → return multiple contents

返回:

{ loaded: [{ id, content, resourceBase }], alreadyLoaded: string[], rejected: string[] }
  • 重复加载 → alreadyLoaded(不重复返回全文)。
  • 未授权 / 快照缺失 → rejected
  • 参考 DSH progressive disclosure 输出(每个 skill 保留边界)。

12. Worker Runtime State(mutable)

interface WorkerSkillRuntimeState {
  loadedSkillIds: string[]
  searches: Array<{ query, resultIds, timestamp }>
}
  • 存于 delegation 目录 skill-state.json(与 immutable input/ 分离)。
  • SkillGrantSnapshot(immutable)≠ SkillRuntimeState(mutable)概念严格分开。

13. Settings UI Architecture

  • 完整 Pentester Settings 页面继续用官方 settings.section slot(plugin.tsx 已用,未造独立 Shell)。
  • 官方 primitives 复用(实际 exports 验证后):Button / Input / Pill / Modal / StateDot
  • CSS Modules + tokensagent-library-section.module.css--dsw-alias-* / --dsw-static-* semantic tokens;删除旧硬编码颜色(#1e90ff 等);build-client.mjs loader 切 local-css
  • 三个子页:
    • Agents:Builtin(Customize / Reset Overrides / Create Profile From This)+ Custom(Edit/Delete/New); Capabilities 显示 N Skills · M inherited · K direct;Stage availability。
    • Manage Skills Modal:Bundle tri-state(checked/partial/unchecked,indeterminate)+ Leaf 选择 + search; 提交按"继承基线 diff"生成 skills / skillsRemove(取消一个 leaf 不写全量 leaf)。
    • Skills:Library Browser(Bundles 展开 + Standalone + search),Skill 详情(Canonical ID / Bundle / Revision / Used by Agents);不再是 961 行扁平列表。
    • Stage Assignment:保持(allowlist + Reset)。

14. Official Harness Components Reused

组件用途
settings.section slotPentester Settings 页面挂载(已有,保留)
Button / Input / Pill / Modal / StateDot全部官方 primitives
--dsw-alias-* / --dsw-static-* tokensCSS Modules 主题
trustedCallerFromExec(已有)search/load 的 caller 授权 seam
ctx.subagents.startContinuable(已有)Worker spawn(不变)

未引入 shadcn / MUI / Tailwind / 第三方 UI Kit(§23 禁止)。


15. Deleted Legacy Code

删除项位置
skillsAdd 字段types / profile-loader / profile-resolver / library / invocations / rpc / UI
skills 完整替换 inherited 语义profile-resolver
SkillDefinition.collectiontypes / skill-loader / library / UI
buildSkillCatalog() persona 注入delegations.ts
逗号分隔 skills 输入 + 扁平 961 列表 + collection 分组agent-library-section.tsx(整文件重写)
手写 profile YAML 解析器(含 block-scalar 子集)profile-loader.ts(换 yaml package)
硬编码 UI 颜色agent-library-section.tsx → CSS Modules

16. Tests Added / Updated

文件变更
test/agent-library.test.tsbundle registry 测试 + legacy 字符串迁移 + typed grant + override(3 个新测试)
test/delegations.test.tssnapshot v2 断言 + catalog 移除断言 + config-changes-don't-mutate-worker
test/skill-runtime.test.ts新增:search 只搜 allowed / 多个结果 / user boost / loaded 标记;load 多 skill / 拒绝 / 去重 / runtime state
test/model.test.ts / test/run-state.test.ts / test/tools.test.tsskillGrant.allowedSkillIds / ROOT_DENY 三工具
test/agent-library-testkit.tsagentOverridesRoot + bundleId 支持

覆盖矩阵关键项:nested .skills scan、leaf scan、duplicate name、builtin/custom conflict、 inheritance、bundle assignment、leaf assignment、remove inherited leaf、remove whole bundle、 builtin override、reset override、allowedSkillIds frozen、search restricted、multiple results、 user boost、multiple load、reject unassigned、skip already-loaded。


17. Validation Results

pnpm test             → 381 pass / 0 fail
pnpm run typecheck    → PASS
pnpm run validate:skills → OK(961 skills loaded, 2 warnings)
pnpm run build        → OK(lib/index.js + lib/client.js + lib/client.css + skill-search-index.json + BUILD_ID verified)
git diff --check      → OK

18. Known Limitations

  1. Bundle grant 优化未做:Manage Skills 提交用 leaf grant(取消一个 leaf 只写一条 skillsRemove, 满足 §29 核心诉求);但"整包新增"尚未折叠成单个 {type:bundle}(当前 builtin 全是 leaf grant, 无 bundle grant,影响有限;未来 builtin 迁 bundle 后再优化)。
  2. DSH 原生 tool-skill 未 mount(§36 明确本 Wave 不强接):pentester_skill_search/load 是 Worker-facing contract;DSH 提供 per-agent registry scope 后再评估底层切换。
  3. 2 个非 kebab-case skill namesecurity_reporterhello_js_reverse_skill):不影响当前 pentester 自有 runtime(identity 用 frontmatter.name);仅在未来接 DSH ctx.skills.register 时需处理。
  4. Custom Skill 无 curation Card:搜索用 name/description/body 词法 fallback(无 technique/tool 标签)。
  5. search 历史 + loadedSkillIds 无 UI 展示(§33 允许:无合适 Slot 先不做)。

19. Follow-up Recommendations

  1. Builtin Agent 默认逐步从 leaf grant 迁到 Bundle grant(web → pentest-skills 等),让 Manage Skills 三态真正体现 Bundle 语义。
  2. 为 Custom Skill 提供 curation Card 编辑入口(复用 Step 1 card 字段)。
  3. 评估 DSH ctx.skills per-agent scope 能力,若成熟则把 pentester_skill_load 底层切到 DSH runtime。
  4. Worker 执行页增加轻量 "Loaded Skills (N)" 展示(有合适 conversation.view Slot 后)。

SKILL_RUNTIME_V2_COMPLETE

Bundle registry:
YES

Bundle assignment:
YES

Direct leaf assignment:
YES

Builtin default grants:
YES

Builtin user overrides:
YES

Derived custom profiles:
YES

Immutable allowedSkillIds:
YES

Catalog removed from worker prompt:
YES

pentester_skill_search:
YES

Search restricted to allowed skills:
YES

Multiple search results:
YES

pentester_skill_load:
YES

Multiple skill load:
YES

Settings UI uses official Harness primitives:
YES

Legacy runtime removed:
YES

Tests:
381/381

Typecheck:
PASS

Ready for real PTES run:
YES