π§© Skill Ledger
August 23, 2026 Β· View on GitHub
Skills you can see, switch, and analyze. Per-reply skill badges + a non-blocking alternative-skill hint + a session-wide Skills tab (ledger with capability/quality analysis).
A DeepSeek Harness (DSH) plugin that turns skills into a first-class observable surface. Three pillars:
β¨ Features
A β Per-reply badges, on every turn (dual-seat)
Beneath every completed reply, the skills the turn triggered (π§ ) and the tools it actually used (β) or failed (β), aggregated with counts:
$ π§© \text{Skills} [π§ \text{ak}-\text{hk}-\text{stock}-\text{dd} β] [π§ \text{serenity}-\text{skill} β] π§© \text{Tools} [\text{read} \times 3 β] [\text{bash} β] $
The badges render through two cooperating seats, because the turn-tail slot is a chain with single-winner semantics β the shipped deliverables card claims every turn that produced files, and a chain elects exactly one entry:
conversation.chat.turnTail(chain seat) β the full badge block + hint row, rendered directly on turns the chain reaches (no files produced);conversation.chat.assistant-actions(list seat) β a compactπ§© N skills/M toolschip on EVERY completed turn's action row (list slots render all entries, so it coexists with the deliverables card). Click it to expand a popover with the same full badge block + hint row.
Result: badges are reachable under every reply, file-producing or not, without ever shadowing the shipped deliverables UI.
B β Non-blocking alternative-skill hint
When a turn used at least one skill, the hint row appears:
π§© Not happy? Try [serenity-skill] [dcf-valuation] [ak-financial-analyst] [Re-run with selected]
Multi-select candidate chips β the button writes a re-run prompt draft into the composer. Nothing is ever intercepted, popped, or timed out β you press Enter, or you don't. (The philosophical opposite of a blocking "skill gate".)
C β The Skills tab (conversation.view, id skills)
A session-scoped tab beside chat/trajectory: per-skill ledger stats (calls /
ok / err / turns / last-failing turn) backed by the skillLedger session
projection (whole session, including out-of-window turns and cold-start
backfill), plus two analysis affordances per skill:
- β capability/quality analysis β instant structural report via
POST /api/skill-ledger/analyze(folded stats + catalog definition digest); - π¬ deep analysis β writes a prompt draft that dispatches a
skill-analysisskill for a source-level capability/quality review.
Why the projection is trustworthy
The host half registers skillLedger with the runtime's real contract
(stateSchema + wire: { viewSchema, view } β the same shape
@deepseek-ai/dsh-goal uses). Earlier community plugins registered with a
schema/view top-level shape the projection registry never reads; those
units silently degrade to host-only (no client value, cache rows that force
full-log refolds on every cold read). This package is the reference
implementation of the correct wiring.
The fold also settles errors correctly: the committed tool/result event
carries isError on its message.content[] block β not on data.error
β and pairing happens through the block's toolCallId. Both facts were
verified against real session logs.
Install
Path A β Host composition (always-on, every session & preset)
Add to the profile's package.json:
"dsh": { "profile": { "bundles": ["@bryan-cmf/dsh-skill-ledger"] } }
then install (dsh plugin --profile <name> install) and restart DSH.
Path B β Agent preset row
- id: skill-ledger
name: '@bryan-cmf/dsh-skill-ledger'
inject: [sessionProjections, webServer, skills, sessions]
β οΈ Keep
injectin the row (or bundle patch): the DSH loader reads the dependency list from row options, not from the module export. The module-export form is howdsh-skill-gatesilently never activated; the missing form is howdsh-ops-view0.2.0 failed the whole tree.
Requirements
- DSH
>= 0.1.0-rc.6β servicessessionProjections,webServer,skills,sessions(all shipped in thedsh-basebundle) - Browser UI needs the
conversation.chat.turnTailchain,conversation.chat.assistant-actionslist and theconversation.viewring (shipped chat view)
Configuration
None in v0.1 β knobs are intentionally minimal. Caps are compile-time
constants (MAX_TURNS = 500, MAX_CALLS_PER_TURN = 60, candidates served
12 β client narrows to 6).
HTTP surface
| Route | Method | Purpose |
|---|---|---|
/api/skill-ledger/candidates | GET | Catalog skills (name + 120-char description) |
/api/skill-ledger/analyze | POST {sessionId, skill} | Folded stats + definition digest |
Both are fail-soft: errors return {error} with HTTP 200, never 5xx.
Design document
docs/design.md records the full review that led here β
including the autopsy of the two predecessors this package replaces
(dsh-skill-trail, dsh-skill-gate), the hot-plugin validation round, and
the dual-seat fix for the chain/deliverables conflict.
Development
pnpm install
pnpm build # tsc declarations β lib/types + tsdown β lib/{index,client}.js
License
MIT