Skill Runtime V2 Implementation Report
September 4, 2026 · View on GitHub
superseded(Host-private isolation):本报告描述的
input/skills/快照 + Worker 经 shellcat 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
| 项 | V1 | V2 |
|---|---|---|
SkillDefinition.collection: string[] | 父路径数组 | 删除 → parentBundleId?: string |
AgentProfileDefinition.skills | string[],完整替换 | SkillGrantRef[],Grant/Add |
skillsAdd | 存在 | 删除 |
skillsRemove | string[] | SkillGrantRef[](bundle/leaf 展开后减除) |
ResolvedAgentProfile.skills | string[] | 删除 → skillGrant: ResolvedAgentSkillGrant |
agent-profile.json | schemaVersion 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_bundlewarning(不创造新 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 引用,全部有效):
| Agent | resolved allowedSkillIds |
|---|---|
| web | 48 |
| impact | 16 |
| recon | 11 |
| validation | 9 |
| vulnerability | 8 |
| reporting | 4 |
| threat-model | 2 |
第一阶段未强行把几十个 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 index:
scripts/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(与 immutableinput/分离)。 SkillGrantSnapshot(immutable)≠SkillRuntimeState(mutable)概念严格分开。
13. Settings UI Architecture
- 完整 Pentester Settings 页面继续用官方
settings.sectionslot(plugin.tsx已用,未造独立 Shell)。 - 官方 primitives 复用(实际 exports 验证后):
Button/Input/Pill/Modal/StateDot。 - CSS Modules + tokens:
agent-library-section.module.css用--dsw-alias-*/--dsw-static-*semantic tokens;删除旧硬编码颜色(#1e90ff等);build-client.mjsloader 切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)。
- Agents:Builtin(Customize / Reset Overrides / Create Profile From This)+ Custom(Edit/Delete/New);
Capabilities 显示
14. Official Harness Components Reused
| 组件 | 用途 |
|---|---|
settings.section slot | Pentester Settings 页面挂载(已有,保留) |
Button / Input / Pill / Modal / StateDot | 全部官方 primitives |
--dsw-alias-* / --dsw-static-* tokens | CSS 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.collection | types / 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.ts | bundle registry 测试 + legacy 字符串迁移 + typed grant + override(3 个新测试) |
test/delegations.test.ts | snapshot 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.ts | skillGrant.allowedSkillIds / ROOT_DENY 三工具 |
test/agent-library-testkit.ts | agentOverridesRoot + 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
- Bundle grant 优化未做:Manage Skills 提交用 leaf grant(取消一个 leaf 只写一条 skillsRemove,
满足 §29 核心诉求);但"整包新增"尚未折叠成单个
{type:bundle}(当前 builtin 全是 leaf grant, 无 bundle grant,影响有限;未来 builtin 迁 bundle 后再优化)。 - DSH 原生 tool-skill 未 mount(§36 明确本 Wave 不强接):
pentester_skill_search/load是 Worker-facing contract;DSH 提供 per-agent registry scope 后再评估底层切换。 - 2 个非 kebab-case skill name(
security_reporter、hello_js_reverse_skill):不影响当前 pentester 自有 runtime(identity 用 frontmatter.name);仅在未来接 DSHctx.skills.register时需处理。 - Custom Skill 无 curation Card:搜索用 name/description/body 词法 fallback(无 technique/tool 标签)。
search历史 +loadedSkillIds无 UI 展示(§33 允许:无合适 Slot 先不做)。
19. Follow-up Recommendations
- Builtin Agent 默认逐步从 leaf grant 迁到 Bundle grant(web → pentest-skills 等),让 Manage Skills 三态真正体现 Bundle 语义。
- 为 Custom Skill 提供 curation Card 编辑入口(复用 Step 1 card 字段)。
- 评估 DSH
ctx.skillsper-agent scope 能力,若成熟则把pentester_skill_load底层切到 DSH runtime。 - 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