@klhapp/skillmux 1.5.2 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/schema.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://klh.app/schemas/skillmux/router-core.json",
4
4
  "title": "Skillmux — router-core contract",
5
- "description": "Typed contract for the skillmux MCP server: tool inputs/outputs, config.toml, and audit-log rows. The SKILL.md body must appear exactly once on the wire.",
5
+ "description": "Typed contract for the skillmux MCP server: tool inputs/outputs and config.toml. The SKILL.md body must appear exactly once on the wire.",
6
6
  "$defs": {
7
7
  "SkillId": {
8
8
  "type": "string",
@@ -33,15 +33,26 @@
33
33
  "reranker_protocol_error"
34
34
  ]
35
35
  },
36
- "Candidate": {
36
+ "RankedCandidate": {
37
37
  "type": "object",
38
- "description": "One shortlist entry in an ambiguous result.",
38
+ "description": "One ranked candidate in the resolution shortlist.",
39
39
  "properties": {
40
+ "rank": {
41
+ "type": "integer",
42
+ "minimum": 1,
43
+ "description": "One-based position in the returned ranked list."
44
+ },
40
45
  "skill_id": { "$ref": "#/$defs/SkillId" },
41
- "title": { "type": "string", "minLength": 1, "maxLength": 512 },
42
- "description": { "type": "string", "minLength": 1 }
46
+ "description": {
47
+ "type": "string",
48
+ "description": "Compact skill description used by the caller to choose."
49
+ },
50
+ "score": {
51
+ "type": ["number", "null"],
52
+ "description": "Final ranking score (null when score values are unavailable for the retrieval lane)."
53
+ }
43
54
  },
44
- "required": ["skill_id", "title", "description"],
55
+ "required": ["rank", "skill_id", "description", "score"],
45
56
  "additionalProperties": false
46
57
  },
47
58
  "ResolveSkillInput": {
@@ -51,89 +62,32 @@
51
62
  "type": "string",
52
63
  "description": "Natural-language task description to route. Normalized (NFC) by the server; never semantically rewritten.",
53
64
  "minLength": 1
54
- }
55
- },
56
- "required": ["query"],
57
- "additionalProperties": false
58
- },
59
- "MatchedResult": {
60
- "type": "object",
61
- "description": "One reranked candidate clearly dominates. Full SKILL.md is delivered inline.",
62
- "properties": {
63
- "outcome": { "const": "matched" },
64
- "retrieval": { "enum": ["exact", "reranked"] },
65
- "degraded_from": { "enum": ["reranked", "hybrid"] },
66
- "degradation_reason": { "$ref": "#/$defs/DegradationReason" },
67
- "skill_id": { "$ref": "#/$defs/SkillId" },
68
- "title": { "type": "string", "minLength": 1, "maxLength": 512 },
69
- "content_sha256": { "$ref": "#/$defs/Sha256Hex" },
70
- "score": {
71
- "type": "number",
72
- "description": "Top configured-reranker score. Satisfies score >= config thresholds.match_score."
73
- },
74
- "margin": {
75
- "type": "number",
76
- "description": "Top minus second configured-reranker score. Equals score when there is exactly one candidate.",
77
- "minimum": 0
78
- },
79
- "body": {
80
- "type": "string",
81
- "description": "Verbatim UTF-8 content of SKILL.md. sha256(body bytes) == content_sha256 == hash of the on-disk file read at delivery time.",
82
- "minLength": 1
83
65
  },
84
- "files": {
85
- "type": "array",
86
- "description": "Relative paths of the skill's supporting files (contents NOT included). Same semantics as FetchSkillResult.files.",
87
- "items": { "$ref": "#/$defs/RelativePath" },
88
- "uniqueItems": true
66
+ "top_k": {
67
+ "type": "integer",
68
+ "minimum": 1,
69
+ "description": "Optional maximum number of ranked candidates to return, bounded by server output.max_top_k."
89
70
  }
90
71
  },
91
- "required": ["outcome", "retrieval", "skill_id", "title", "content_sha256", "score", "margin", "body", "files"],
72
+ "required": ["query"],
92
73
  "additionalProperties": false
93
74
  },
94
- "AmbiguousResult": {
75
+ "ResolveSkillResult": {
95
76
  "type": "object",
96
- "description": "Several plausible candidates; the calling LLM picks, then calls fetch_skill.",
77
+ "description": "Ranked candidates response from resolve_skill.",
97
78
  "properties": {
98
- "outcome": { "const": "ambiguous" },
99
79
  "retrieval": { "enum": ["exact", "reranked", "hybrid", "lexical"] },
100
80
  "degraded_from": { "enum": ["reranked", "hybrid"] },
101
81
  "degradation_reason": { "$ref": "#/$defs/DegradationReason" },
102
82
  "candidates": {
103
83
  "type": "array",
104
- "items": { "$ref": "#/$defs/Candidate" },
105
- "minItems": 1,
106
- "maxItems": 5
84
+ "items": { "$ref": "#/$defs/RankedCandidate" },
85
+ "description": "Shortlist of candidates in descending score order with contiguous 1-based ranks."
107
86
  }
108
87
  },
109
- "required": ["outcome", "retrieval", "candidates"],
88
+ "required": ["retrieval", "candidates"],
110
89
  "additionalProperties": false
111
90
  },
112
- "NoMatchResult": {
113
- "type": "object",
114
- "description": "AC4: no skill passed the relevance floor. Caller proceeds under its normal workflow; must not load an unrelated skill.",
115
- "properties": {
116
- "outcome": { "const": "no_match" },
117
- "retrieval": { "enum": ["exact", "reranked", "hybrid", "lexical"] },
118
- "degraded_from": { "enum": ["reranked", "hybrid"] },
119
- "degradation_reason": { "$ref": "#/$defs/DegradationReason" },
120
- "message": {
121
- "type": "string",
122
- "description": "Fixed guidance string telling the caller to proceed normally.",
123
- "minLength": 1
124
- }
125
- },
126
- "required": ["outcome", "retrieval", "message"],
127
- "additionalProperties": false
128
- },
129
- "ResolveSkillResult": {
130
- "description": "AC1: exactly three outcomes, discriminated by 'outcome'.",
131
- "oneOf": [
132
- { "$ref": "#/$defs/MatchedResult" },
133
- { "$ref": "#/$defs/AmbiguousResult" },
134
- { "$ref": "#/$defs/NoMatchResult" }
135
- ]
136
- },
137
91
  "FetchSkillInput": {
138
92
  "type": "object",
139
93
  "properties": {
@@ -144,7 +98,7 @@
144
98
  },
145
99
  "FetchSkillResult": {
146
100
  "type": "object",
147
- "description": "AC5: verbatim delivery independent of any prior resolve outcome. Unknown skill_id raises ToolError with code SKILL_NOT_FOUND instead of returning this shape.",
101
+ "description": "AC5: verbatim delivery independent of any prior resolve_skill call. Unknown skill_id raises ToolError with code SKILL_NOT_FOUND instead of returning this shape.",
148
102
  "properties": {
149
103
  "skill_id": { "$ref": "#/$defs/SkillId" },
150
104
  "title": { "type": "string", "minLength": 1, "maxLength": 512 },
@@ -208,34 +162,26 @@
208
162
  "output": {
209
163
  "type": "object",
210
164
  "properties": {
211
- "ambiguous_candidate_limit": {
165
+ "top_k": {
212
166
  "type": "integer",
213
167
  "minimum": 1,
214
- "maximum": 50,
215
- "default": 5,
216
- "description": "Maximum number of candidates returned on ambiguous outcomes."
217
- }
218
- },
219
- "additionalProperties": false
220
- },
221
- "thresholds": {
222
- "type": "object",
223
- "description": "Caller-facing shortlist policy (deprecated in favor of output.ambiguous_candidate_limit). Reranker thresholds live under inference.thresholds.",
224
- "properties": {
225
- "candidate_limit": {
168
+ "default": 10,
169
+ "description": "Default maximum number of candidates returned by resolve_skill."
170
+ },
171
+ "max_top_k": {
226
172
  "type": "integer",
227
173
  "minimum": 1,
228
- "maximum": 50,
229
- "default": 5,
230
- "description": "Deprecated: use output.ambiguous_candidate_limit instead."
174
+ "default": 50,
175
+ "description": "Upper bound for per-request top_k override."
231
176
  }
232
177
  },
178
+ "required": ["top_k", "max_top_k"],
233
179
  "additionalProperties": false
234
180
  },
235
181
  "inference": { "$ref": "#/$defs/InferenceConfig" },
236
182
  "server": { "$ref": "#/$defs/ServerConfig" }
237
183
  },
238
- "required": ["vault_path", "state_dir", "recall", "inference"],
184
+ "required": ["vault_path", "state_dir", "recall", "output", "inference"],
239
185
  "additionalProperties": false
240
186
  },
241
187
  "InferenceConfig": {
@@ -296,17 +242,6 @@
296
242
  },
297
243
  "required": ["adapter", "endpoint", "model"],
298
244
  "additionalProperties": false
299
- },
300
- "thresholds": {
301
- "type": "object",
302
- "description": "Model-specific calibrated values; required when reranker is configured.",
303
- "properties": {
304
- "match_score": { "type": "number" },
305
- "match_margin": { "type": "number", "minimum": 0 },
306
- "candidate_floor": { "type": "number" }
307
- },
308
- "required": ["match_score", "match_margin", "candidate_floor"],
309
- "additionalProperties": false
310
245
  }
311
246
  },
312
247
  "required": ["mode", "timeout_ms", "embedding"],
@@ -332,43 +267,6 @@
332
267
  },
333
268
  "required": ["auth_enabled", "auth_token_env", "allowed_origins"],
334
269
  "additionalProperties": false
335
- },
336
- "AuditRow": {
337
- "type": "object",
338
- "description": "AC10: one row per resolve_skill call, appended to the audit table in the state-dir SQLite. Raw query stored deliberately (single-user private homelab).",
339
- "properties": {
340
- "id": { "type": "integer", "minimum": 1 },
341
- "ts": { "type": "string", "format": "date-time" },
342
- "query": { "type": "string" },
343
- "outcome": { "type": "string", "enum": ["matched", "ambiguous", "no_match"] },
344
- "retrieval": { "enum": ["exact", "reranked", "hybrid", "lexical"] },
345
- "degraded_from": {
346
- "oneOf": [{ "enum": ["reranked", "hybrid"] }, { "type": "null" }]
347
- },
348
- "degradation_reason": {
349
- "oneOf": [{ "$ref": "#/$defs/DegradationReason" }, { "type": "null" }]
350
- },
351
- "candidates": {
352
- "type": "array",
353
- "description": "skill_id + score of every reranked (or FTS5-ranked) candidate considered, best first.",
354
- "items": {
355
- "type": "object",
356
- "properties": {
357
- "skill_id": { "$ref": "#/$defs/SkillId" },
358
- "score": { "type": ["number", "null"] }
359
- },
360
- "required": ["skill_id", "score"],
361
- "additionalProperties": false
362
- }
363
- },
364
- "selected_skill_id": {
365
- "oneOf": [{ "$ref": "#/$defs/SkillId" }, { "type": "null" }],
366
- "description": "Set iff outcome=matched."
367
- },
368
- "latency_ms": { "type": "integer", "minimum": 0 }
369
- },
370
- "required": ["id", "ts", "query", "outcome", "retrieval", "candidates", "selected_skill_id", "latency_ms"],
371
- "additionalProperties": false
372
270
  }
373
271
  }
374
272
  }
@@ -170,8 +170,13 @@ skillmux report --since 7d
170
170
  skillmux report --server http://host:3000 --since 7d
171
171
  ```
172
172
 
173
- Repeatedly matched skills may belong in core or a project group. Repeated
174
- `no_match` queries point to missing skills or weak skill descriptions.
173
+ `skillmux report` aggregates total requests, empty shortlist count and rate,
174
+ retrieval totals across lanes (`exact`, `reranked`, `hybrid`, `lexical`), degraded
175
+ request counts, average latency in milliseconds, per-skill `candidate_count`, and
176
+ `top_empty_shortlist_queries`.
177
+
178
+ Skills with high candidate counts may belong in `[core]` or a project group.
179
+ Top empty shortlist queries point to missing skills or weak skill descriptions.
175
180
 
176
181
  `--since` accepts windows such as `1h`, `7d`, and `1m`, plus absolute dates and
177
182
  timestamps.
@@ -17,7 +17,7 @@ file or an existing `~/.config/skillmux` directory. If you need a specific
17
17
  vault, remote inference, or HTTP policy, create a config with `skillmux config
18
18
  init --vault ~/skills --yes`; otherwise the server uses its defaults. A missing
19
19
  optional config parent disables live reload until the next server start. For a
20
- malformed watched config, check the reported reload errorthe running server
20
+ malformed watched config, check the reported reload error: the running server
21
21
  continues with its last known good configuration.
22
22
 
23
23
  ## Docker rejected a CLI command
@@ -25,7 +25,7 @@ continues with its last known good configuration.
25
25
  The server image can inspect and serve a mounted vault, but it cannot manage
26
26
  agent directories or mutate host configuration. Its help lists the supported
27
27
  server commands. For `init`, `sync`, install or pin management, model
28
- downloads, contexts, calibration, evaluation, project/target/local-vault
28
+ downloads, contexts, evaluation, project/target/local-vault
29
29
  management, or `config init`/`config set`, install and use the host CLI:
30
30
 
31
31
  ```sh
@@ -149,20 +149,15 @@ overlay.
149
149
 
150
150
  ## Retrieval failures
151
151
 
152
- ### `resolve_skill` returns `ambiguous`
152
+ ### Candidates are missing or irrelevant
153
153
 
154
- Ambiguity is the expected result without calibrated reranker thresholds. The
155
- calling model should select a candidate and call `fetch_skill`.
154
+ `resolve_skill` returns ranked candidate summaries for the calling model to
155
+ review. If the shortlist is empty (`candidates: []`) or missing an expected skill:
156
156
 
157
- Improve a weak shortlist by:
158
-
159
- - writing a concrete skill description with task vocabulary;
160
- - enabling embeddings;
161
- - increasing recall depth when the relevant skill falls outside the fused
162
- candidate set.
163
-
164
- Use a labelled dataset and [Policy calibration](calibration.md) before enabling
165
- automatic matches.
157
+ - write a concrete skill description with specific task vocabulary and user intents in `SKILL.md`;
158
+ - verify embeddings are active with `skillmux doctor`;
159
+ - increase candidate recall depth in `[recall]` (`k_lexical`, `k_vector`, or `k_rerank`);
160
+ - evaluate ranking quality against a labelled dataset with `skillmux eval` (see [Ranked-shortlist migration](ranked-shortlist-migration.md)).
166
161
 
167
162
  ### Server reports lexical mode
168
163
 
@@ -256,6 +251,9 @@ checking for a retry loop or shared-token traffic.
256
251
 
257
252
  Skillmux rejects removed fields with migration guidance:
258
253
 
254
+ - replace obsolete `[thresholds]` table and `inference.thresholds` with `[output]` and `top_k`;
255
+ - replace obsolete `output.ambiguous_candidate_limit` with `output.top_k`;
256
+ - remove obsolete `inference.calibration` and use `skillmux eval` for ranking evaluation;
259
257
  - replace `[targets.<name>].project` with `project_groups = [...]`;
260
258
  - rename `[project.<group>].repos` to `paths`;
261
259
  - use `skillmux core pin|unpin` instead of removed `manifest pin|unpin`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@klhapp/skillmux",
3
- "version": "1.5.2",
3
+ "version": "1.7.0",
4
4
  "description": "Skill management and retrieval for AI agents: sync native skills across clients and route the long tail over MCP",
5
5
  "type": "module",
6
6
  "private": false,