6.5 KiB
P5 — D4 Skill-Write Governance · Build Plan
Phase sequence (open-questions.md:574): P1=D3 ✅ · P2=verify+J08 ✅ · P3=D2 ✅ · P4=D11/D12 ✅ · P5=D4 ← here · P7=D15 (card risk-taxonomy alignment, deferred). Decision text:
docs/ux-refactor/deltas/open-questions.md§D4 (lines 537–543).
What D4 ratified (four bindings)
- (i) Policy.
create_skill— normal = ask (in-chat approval card), trusted/yolo = auto-execute.delete_skill— always ask, every autonomy level.read_skillungated. Always-audit all three withinitiator:'agent'. - (ii) Surface. In-chat SSE approval card is canonical. Card↔
ui/approval-modal.tsxrisk-taxonomy alignment is D15/P7 — explicitly deferred, NOT P5. - (iii) One write path. Do NOT route the agent tool through HTTP. Extract a shared
skill-write service module (create/update/delete + redaction + audit inside the
service), consumed by both
routes/skills.tsandskill-tools.ts. One module, one audit trail, two callers. - (iv) Endpoint consolidation (absorbs D14).
POST /api/skillsdelegates to the audited service.PUT/DELETE /api/skills/:nameenter the audit trail. Provenance: frontmatterinitiator/source+GET /api/skillsreturns it + Skills Hub badge ("created by agent — review"), replacing the name-heuristic "Custom" classification. - Persona exception (already shipped): read-only personas keep losing
create_skill/delete_skill, retainread_skill. - PM residual (no code): skill creation stays FREE at launch — tier gate unchanged.
Recon — current seams (verified 2026-06-12)
| Seam | File | State |
|---|---|---|
| Agent tool | packages/agent/src/skill-tools.ts |
create/delete write to disk + redact; no audit, no provenance, no gate |
| HTTP routes | packages/server/src/local/routes/skills.ts |
POST /api/skills, PUT/DELETE /:name redact but don't audit; POST /api/skills/create (structured) DOES audit action:'installed' |
| Autonomy gate | packages/agent/src/confirmation.ts |
declarative sets; chat.ts:900 calls needsConfirmationWithAutonomy |
| Frontmatter | packages/agent/src/skill-frontmatter.ts |
parser+serializer; has name/description/scope/promoted_from/permissions — no initiator/source |
| Audit store | packages/core/src/install-audit.ts |
record(); action enum lacks a delete value |
| Skills Hub UI | apps/web/src/components/os/apps/skills/SkillRow.tsx, routes/SkillsRoute.tsx |
custom badge driven by name-heuristic |
Decisions
DEC-1 — Audit action for delete (the one real fork)
AuditAction = 'proposed'|'approved'|'installed'|'rejected'|'failed'|'blocked' — no
delete/uninstall value. The CHECK is duplicated in packages/core/src/install-audit.ts
and packages/hive-mind-core/src/mind/schema.ts (OSS-synced, §7.5). SQLite CHECK
can't be ALTERed → widening needs a table-rebuild migration on existing installs.
- create / update →
action:'installed'(established convention —routes/skills.tsstructured-create already does this; a skill on disk = installed). - delete → needs a decision (see status note). Options:
- A (recommended): add
'uninstalled'toAuditAction+ both schema CHECKs + a rebuild migration. Fills a genuine pre-existing gap (no uninstall of ANY capability is auditable today); additive/backward-compatible; honest trail. Cost: migration in 2 files + OSS drift check. - B: reuse
'rejected'withdetail:'deleted by agent'. Zero migration, but semantically muddy and pollutes rejection queries.
- A (recommended): add
DEC-2 — Shared service location
packages/agent/src/skill-write-service.ts. The agent package is already a server
dependency (@waggle/agent imported by routes/skills.ts), so one module serves both
callers. Signature (pure, deps injected — no hidden globals):
createSkillWrite({ skillsDir, name, content, initiator, source, auditStore?,
skillHashStore?, onChange? }) → { path, redactions, action }
updateSkillWrite({ … }) ; deleteSkillWrite({ skillsDir, name, initiator, auditStore?, … })
Redaction (redactSkillContent) + provenance frontmatter stamp + audit record() all
live inside the service. Callers stop doing these by hand.
DEC-3 — Provenance frontmatter
Add initiator?: 'agent'|'user' and source?: string to SkillFrontmatter + parser +
serializeFrontmatter. The service stamps them on create (preserves on update). Legacy
skills with no frontmatter → treated as initiator:'user' (no badge). Name-heuristic
"Custom" classification retired in favor of initiator==='agent'.
Build order (TDD, commit per step)
- ✅ D4(i) gating —
confirmation.tssets + tests. (73f2ed5) - ✅ DEC-1 audit enum — founder picked Option A:
'uninstalled'+ widen-CHECK migration in coreinstall-audit.ts(belt-and-suspenders rebuild) AND hive-mind-coredb.tsrunMigrations sentinel +schema.tsDDL. (658884f) - ✅ DEC-3 frontmatter —
initiator/sourcein parser/serializer. (f3bda5b) - ✅ DEC-2 service —
skill-write-service.ts(lossless provenance stamp). (2b56b78) - ✅ Rewire agent —
skill-tools.tscreate/delete via service. (2b56b78) - ✅ Rewire HTTP —
routes/skills.tsPOST/create/PUT/DELETE via service;GET /api/skillsreturns provenance. (29313a9) - ✅ UI badge —
SkillRow.tsx"agent · review" off provenance. (48292ef) - ⏳ Adversarial review workflow → record → final gates. (pending — needs opt-in)
Status — implementation COMPLETE (2026-06-12)
All four D4 bindings shipped in 7 commits (73f2ed5→48292ef), NOT pushed.
Gates: tsc 0 across shared/hive-mind-core/core/agent/server/apps-web · 147 P5 tests
green (agent 96, core 22, server 20, FE 9) · lint 0.
Remaining for P5 closeout:
- OSS re-split (§7.5):
hive-mind-core/src/mind/{schema.ts,db.ts}changed here first (the'uninstalled'CHECK + migration sentinel). Themarolinik/hive-mindmirror must be regenerated via subtree-split before the next OSS release. - Adversarial review (step 8) — every prior P-phase ran a multi-agent review workflow; deferred pending explicit opt-in.
- Decision-log + open-questions phase-line update marking P5 done.
- D4(ii) card↔modal risk-taxonomy alignment stays P7/D15 (out of P5 scope).
Out of scope (ledgered)
- D4(ii) card↔modal risk-taxonomy alignment → P7/D15.
- Marketplace install PRO-gate (unchanged).
promote_skill/auto_extract_skills/retire_skillsaudit (not named by D4; leave).