@kontextmind/kxm 0.7.189 → 0.7.191

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.
Files changed (34) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/docs/README.md +1 -0
  3. package/docs/evals/planner-bakeoff-2026-10/judge/argon.json +18 -0
  4. package/docs/evals/planner-bakeoff-2026-10/judge/journal.json +18 -0
  5. package/docs/evals/planner-bakeoff-2026-10/judge/reviewer.json +18 -0
  6. package/docs/evals/planner-bakeoff-2026-10/judge/weekly.json +18 -0
  7. package/docs/evals/planner-bakeoff-2026-10/judge/wiki.json +18 -0
  8. package/docs/evals/planner-bakeoff-2026-10/judge/writer2.json +18 -0
  9. package/docs/evals/planner-bakeoff-2026-10/prompts/argon.txt +30 -0
  10. package/docs/evals/planner-bakeoff-2026-10/prompts/journal.txt +22 -0
  11. package/docs/evals/planner-bakeoff-2026-10/prompts/reviewer.txt +28 -0
  12. package/docs/evals/planner-bakeoff-2026-10/prompts/shared.txt +20 -0
  13. package/docs/evals/planner-bakeoff-2026-10/prompts/weekly.txt +23 -0
  14. package/docs/evals/planner-bakeoff-2026-10/prompts/wiki.txt +27 -0
  15. package/docs/evals/planner-bakeoff-2026-10/prompts/writer2.txt +40 -0
  16. package/docs/evals/planner-bakeoff-2026-10/raw/argon-fable.txt +68 -0
  17. package/docs/evals/planner-bakeoff-2026-10/raw/argon-sonnet.txt +69 -0
  18. package/docs/evals/planner-bakeoff-2026-10/raw/journal-fable.txt +60 -0
  19. package/docs/evals/planner-bakeoff-2026-10/raw/journal-sonnet.txt +79 -0
  20. package/docs/evals/planner-bakeoff-2026-10/raw/reviewer-fable.txt +74 -0
  21. package/docs/evals/planner-bakeoff-2026-10/raw/reviewer-sonnet.txt +95 -0
  22. package/docs/evals/planner-bakeoff-2026-10/raw/weekly-fable.txt +63 -0
  23. package/docs/evals/planner-bakeoff-2026-10/raw/weekly-sonnet.txt +66 -0
  24. package/docs/evals/planner-bakeoff-2026-10/raw/wiki-fable.txt +72 -0
  25. package/docs/evals/planner-bakeoff-2026-10/raw/wiki-sonnet.txt +79 -0
  26. package/docs/evals/planner-bakeoff-2026-10/raw/writer2-fable.txt +73 -0
  27. package/docs/evals/planner-bakeoff-2026-10/raw/writer2-sonnet.txt +79 -0
  28. package/docs/evals/planner-bakeoff-2026-10/score.py +304 -0
  29. package/docs/evals/planner-bakeoff-2026-10.md +164 -0
  30. package/package.json +1 -1
  31. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  32. package/plugins/kxm/dist/mcp-server.js +1 -1
  33. package/plugins/kxm/package.json +1 -1
  34. package/plugins/kxm/src/mcp-server.ts +1 -1
@@ -0,0 +1,304 @@
1
+ #!/usr/bin/env python3
2
+ """Score the committed planner bake-off, or refuse to invent a new one.
3
+
4
+ Scoring reads raw plans, blinded judge JSON, and the price catalogs.
5
+ It does not call a model.
6
+
7
+ ``--run`` is the regeneration path. It does not run unless the native
8
+ ``claude`` binary is on PATH and ``claude auth status`` succeeds, or
9
+ ``ANTHROPIC_API_KEY`` is set. This checkout had neither, so the committed
10
+ plans came from Cursor subagents. See the report for that transport.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import argparse
16
+ import json
17
+ import math
18
+ import re
19
+ import shutil
20
+ import sys
21
+ from pathlib import Path
22
+
23
+ ROOT = Path(__file__).resolve().parent
24
+ REPO = ROOT.parents[2]
25
+ RAW = ROOT / "raw"
26
+ JUDGE = ROOT / "judge"
27
+ PROMPTS = ROOT / "prompts"
28
+
29
+ TASKS = ("argon", "reviewer", "writer2", "weekly", "wiki", "journal")
30
+ MODELS = ("fable", "sonnet")
31
+ DIMS = ("correctness", "completeness", "risk", "sequencing", "conciseness")
32
+
33
+ # Blind labels from random.seed(20261017), shuffled per task.
34
+ LABELS = {
35
+ "argon": {"A": "fable", "B": "sonnet"},
36
+ "reviewer": {"A": "sonnet", "B": "fable"},
37
+ "writer2": {"A": "fable", "B": "sonnet"},
38
+ "weekly": {"A": "sonnet", "B": "fable"},
39
+ "wiki": {"A": "sonnet", "B": "fable"},
40
+ "journal": {"A": "fable", "B": "sonnet"},
41
+ }
42
+
43
+ # Checklist over the full raw plan. A hit is a case-insensitive regex.
44
+ # This is a scripted cross-check, not the official score. The official
45
+ # score is the blinded gpt-5.6-sol judge.
46
+ CHECKS: dict[str, list[tuple[str, str]]] = {
47
+ "argon": [
48
+ ("argon high on the roster", r"gemini-4-argon-high|argon-high"),
49
+ ("argon medium on the roster", r"gemini-4-argon-medium|argon-medium"),
50
+ ("argon tried before flash", r"argon-medium[\s\S]{0,80}flash|argon-high[\s\S]{0,120}flash"),
51
+ ("grok stays primary", r"grok-grok-4-7"),
52
+ ("flash medium stays", r"flash-medium|gemini-3-8-flash-medium"),
53
+ ("flash high stays", r"flash-high|gemini-3-8-flash-high"),
54
+ ("no OpenRouter Google route", r"openrouter"),
55
+ ("list price 2 and 10", r"\$2|inputPerMillion: 2|2 / 10|\$2 and \$10"),
56
+ ("sha256 pin", r"sha256"),
57
+ ("planner stays fable", r"claude-fable"),
58
+ ("harness-routing doc", r"harness-routing"),
59
+ ("tracking updated", r"implementation-plan|Tracking"),
60
+ ],
61
+ "reviewer": [
62
+ ("new primary id", r"codex-gpt-6-1-sol"),
63
+ ("5.6 stays last", r"codex-gpt-5-6-sol"),
64
+ ("pi fallback stays", r"pi-qwen3-8-flash"),
65
+ ("pro is an alias", r"alias"),
66
+ ("tier boundary", r"272000"),
67
+ ("sha256 pin", r"sha256"),
68
+ ("arch critic unchanged", r"reviewer-arch"),
69
+ ("native brake", r"openai-codex|openrouter"),
70
+ ("agents table", r"AGENTS\.md"),
71
+ ("harness-run scripts", r"harness-run"),
72
+ ],
73
+ "writer2": [
74
+ ("old route retired", r"retired"),
75
+ ("old price row kept", r"qwen3-coder-plus"),
76
+ ("moonshot refused", r"moonshot|kimi"),
77
+ ("evidence note", r"plans/evidence"),
78
+ ("sha256 pin", r"sha256"),
79
+ ("argon order untouched", r"[Aa]rgon"),
80
+ ("tool gate", r"tool"),
81
+ ("inventory before admit", r"inventory"),
82
+ ("qwen3.7-plus considered", r"qwen3\.7-plus"),
83
+ ("meta not auto-admitted", r"meta|muse"),
84
+ ],
85
+ "weekly": [
86
+ ("workflow file", r"\.github/workflows"),
87
+ ("routes file untouched", r"routes\.yaml"),
88
+ ("auth failure fails closed", r"401|403"),
89
+ ("date and hash together", r"sha256"),
90
+ ("cli-only rows kept", r"CLI-only|pi`, `grok`"),
91
+ ("pr body lists ids", r"added"),
92
+ ("no auto-merge", r"auto-merge|gh pr merge"),
93
+ ("unit tests", r"[Tt]est"),
94
+ ("harness-routing doc", r"harness-routing"),
95
+ ("changelog", r"CHANGELOG"),
96
+ ],
97
+ "wiki": [
98
+ ("under .kxm/knowledge", r"\.kxm/knowledge"),
99
+ ("compile stays off", r"wiki-compile|ingest"),
100
+ ("architecture page", r"architecture"),
101
+ ("glossary link", r"glossary"),
102
+ ("ci pattern", r"CI / required|control-plane"),
103
+ ("runbooks", r"runbook"),
104
+ ("no new npm script", r"npm script"),
105
+ ("doc links", r"docs/README"),
106
+ ("not inside compiler wiki/", r"wiki/"),
107
+ ("no secrets", r"secret"),
108
+ ],
109
+ "journal": [
110
+ ("no new store", r"lands a store|no store|does not land a store|not land a store|No changes to store"),
111
+ ("workflow_not_found locked", r"workflow_not_found"),
112
+ ("no relabel", r"relabel"),
113
+ ("design note", r"design note|runtime-journal"),
114
+ ("no invented version bump", r"version"),
115
+ ("tracking", r"Tracking|implementation-plan"),
116
+ ("no auto-promote", r"auto-promot"),
117
+ ("refusal test", r"[Tt]est"),
118
+ ],
119
+ }
120
+
121
+
122
+ def estimate_tokens(text: str) -> int:
123
+ """Ceiling of characters divided by 4. Not a provider tokenizer."""
124
+ return math.ceil(len(text) / 4)
125
+
126
+
127
+ def load_fable_rates() -> tuple[float, float]:
128
+ text = (REPO / ".kxm" / "prices.yaml").read_text()
129
+ match = re.search(
130
+ r"id: anthropic/fable\b.*?inputPerMillion:\s*([0-9.]+)\s*\n\s*outputPerMillion:\s*([0-9.]+)",
131
+ text,
132
+ re.S,
133
+ )
134
+ if not match:
135
+ raise SystemExit("prices.yaml has no anthropic/fable tier")
136
+ return float(match.group(1)), float(match.group(2))
137
+
138
+
139
+ def load_sonnet_rates() -> tuple[float, float, str]:
140
+ text = (REPO / ".kxm" / "prices.yaml").read_text()
141
+ if re.search(r"id: anthropic/claude-sonnet-5\.5\b", text):
142
+ match = re.search(
143
+ r"id: anthropic/claude-sonnet-5\.5\b.*?inputPerMillion:\s*([0-9.]+)\s*\n\s*outputPerMillion:\s*([0-9.]+)",
144
+ text,
145
+ re.S,
146
+ )
147
+ if not match:
148
+ raise SystemExit("sonnet row in prices.yaml has no tier")
149
+ return float(match.group(1)), float(match.group(2)), "prices.yaml"
150
+ inventory = (REPO / ".kxm" / "models" / "inventory.yaml").read_text()
151
+ match = re.search(
152
+ r"id: anthropic/claude-sonnet-5\.5\n(?:.*\n){0,20}?\s*standard:\n\s*inputPerMillion:\s*([0-9.]+)\n\s*outputPerMillion:\s*([0-9.]+)",
153
+ inventory,
154
+ )
155
+ if not match:
156
+ raise SystemExit("inventory.yaml has no anthropic/claude-sonnet-5.5 standard tier")
157
+ return float(match.group(1)), float(match.group(2)), "inventory.yaml"
158
+
159
+
160
+ def cost(tokens_in: int, tokens_out: int, in_rate: float, out_rate: float) -> float:
161
+ return (tokens_in * in_rate + tokens_out * out_rate) / 1_000_000
162
+
163
+
164
+ def prompt_text(task: str) -> str:
165
+ shared = (PROMPTS / "shared.txt").read_text()
166
+ packet = (PROMPTS / f"{task}.txt").read_text()
167
+ return shared + "\n" + packet
168
+
169
+
170
+ def checklist(task: str, text: str) -> tuple[int, int, list[str]]:
171
+ hits = []
172
+ missed = []
173
+ for name, pattern in CHECKS[task]:
174
+ if re.search(pattern, text, re.I):
175
+ hits.append(name)
176
+ else:
177
+ missed.append(name)
178
+ return len(hits), len(CHECKS[task]), missed
179
+
180
+
181
+ def unblind(task: str) -> dict[str, dict]:
182
+ payload = json.loads((JUDGE / f"{task}.json").read_text())
183
+ labels = LABELS[task]
184
+ out = {}
185
+ for label, model in labels.items():
186
+ key = "planA" if label == "A" else "planB"
187
+ scores = payload[key]
188
+ out[model] = {
189
+ "label": label,
190
+ "scores": {dim: scores[dim] for dim in DIMS},
191
+ "total": sum(scores[dim] for dim in DIMS),
192
+ }
193
+ out["winner_label"] = payload["winner"]
194
+ winner = {"A": labels["A"], "B": labels["B"], "tie": "tie"}[payload["winner"]]
195
+ out["winner"] = winner
196
+ out["rationale"] = payload["rationale"]
197
+ return out
198
+
199
+
200
+ def score() -> dict:
201
+ fable_in, fable_out = load_fable_rates()
202
+ sonnet_in, sonnet_out, sonnet_source = load_sonnet_rates()
203
+ rates = {
204
+ "fable": (fable_in, fable_out, "prices.yaml anthropic/fable"),
205
+ "sonnet": (sonnet_in, sonnet_out, sonnet_source + " anthropic/claude-sonnet-5.5"),
206
+ }
207
+ tasks = []
208
+ totals = {model: {"judge": 0, "tokens_in": 0, "tokens_out": 0, "cost": 0.0, "wins": 0} for model in MODELS}
209
+ for task in TASKS:
210
+ judged = unblind(task)
211
+ row = {"task": task, "winner": judged["winner"], "rationale": judged["rationale"], "models": {}}
212
+ for model in MODELS:
213
+ text = (RAW / f"{task}-{model}.txt").read_text()
214
+ prompt = prompt_text(task)
215
+ tokens_in = estimate_tokens(prompt)
216
+ tokens_out = estimate_tokens(text)
217
+ in_rate, out_rate, source = rates[model]
218
+ price = cost(tokens_in, tokens_out, in_rate, out_rate)
219
+ got, total, missed = checklist(task, text)
220
+ entry = {
221
+ "judge": judged[model],
222
+ "words": len(text.split()),
223
+ "tokens_in_estimate": tokens_in,
224
+ "tokens_out_estimate": tokens_out,
225
+ "list_cost_usd": round(price, 6),
226
+ "price_source": source,
227
+ "checklist_hits": got,
228
+ "checklist_total": total,
229
+ "checklist_missed": missed,
230
+ }
231
+ row["models"][model] = entry
232
+ totals[model]["judge"] += judged[model]["total"]
233
+ totals[model]["tokens_in"] += tokens_in
234
+ totals[model]["tokens_out"] += tokens_out
235
+ totals[model]["cost"] += price
236
+ if judged["winner"] in totals:
237
+ totals[judged["winner"]]["wins"] += 1
238
+ tasks.append(row)
239
+ for model in MODELS:
240
+ totals[model]["list_cost_usd"] = round(totals[model]["cost"], 6)
241
+ return {
242
+ "token_method": "ceil(characters/4) on prompts/<task>.txt plus prompts/shared.txt, and on raw/<task>-<model>.txt. Not provider usage.",
243
+ "max_judge_per_task": 25,
244
+ "tasks": tasks,
245
+ "totals": totals,
246
+ "rates": {
247
+ "fable_input": fable_in,
248
+ "fable_output": fable_out,
249
+ "sonnet_input": sonnet_in,
250
+ "sonnet_output": sonnet_out,
251
+ "sonnet_source": sonnet_source,
252
+ },
253
+ }
254
+
255
+
256
+ def credentials_present() -> list[str]:
257
+ missing = []
258
+ if shutil.which("claude") is None:
259
+ missing.append("claude CLI is not on PATH (kxm's planner one-shot spawns claude -p)")
260
+ if not (Path.home() / ".claude").exists() and not (Path.home() / ".config" / "claude").exists():
261
+ missing.append("no ~/.claude or ~/.config/claude auth directory")
262
+ # Names only. Do not print values.
263
+ import os
264
+ if not os.environ.get("ANTHROPIC_API_KEY"):
265
+ missing.append("ANTHROPIC_API_KEY is unset")
266
+ return missing
267
+
268
+
269
+ def run_refused() -> int:
270
+ missing = credentials_present()
271
+ sys.stderr.write(
272
+ "planner bake-off regeneration refused.\n"
273
+ "kxm's planner one-shot is: claude -p --model <model> --effort medium "
274
+ "--output-format json, plus the read-only permission arguments in "
275
+ "plugins/kxm/src/harness.ts (oneShotReadOnlyArgs claude).\n"
276
+ "There is no kxm flag that overrides the planner model without editing "
277
+ ".kxm/roles/planner.yaml and admitting the route. This bake-off must not do that.\n"
278
+ "kxm routing benchmark prints placeholder numbers and does not call a model.\n"
279
+ )
280
+ if missing:
281
+ sys.stderr.write("Missing:\n")
282
+ for item in missing:
283
+ sys.stderr.write(f"- {item}\n")
284
+ sys.stderr.write("Refusing to simulate plans.\n")
285
+ return 2
286
+ sys.stderr.write("Credentials appear present. This script still does not dispatch; use the native claude one-shot with prompts/*.txt.\n")
287
+ return 0
288
+
289
+
290
+ def main(argv: list[str]) -> int:
291
+ parser = argparse.ArgumentParser()
292
+ parser.add_argument("--run", action="store_true", help="attempt a live regeneration; fail closed without credentials")
293
+ parser.add_argument("--score", action="store_true", help="score the committed raw plans")
294
+ args = parser.parse_args(argv)
295
+ if args.run:
296
+ return run_refused()
297
+ payload = score()
298
+ json.dump(payload, sys.stdout, indent=2)
299
+ sys.stdout.write("\n")
300
+ return 0
301
+
302
+
303
+ if __name__ == "__main__":
304
+ raise SystemExit(main(sys.argv[1:]))
@@ -0,0 +1,164 @@
1
+ # Planner bake-off, October 2026
2
+
3
+ This page records a scored comparison of Claude Fable 5.1 and Claude Sonnet 5.5 on six KXM planning tasks. It is for maintainers deciding whether the planner primary should stay Fable. Routing is unchanged.
4
+
5
+ ## Recommendation
6
+
7
+ Keep Fable as the planner primary. Treat Sonnet 5.5 as a fallback candidate, not a replacement.
8
+
9
+ Sonnet scored 126 and Fable scored 110, out of 150, on a blinded judge. Sonnet won four tasks and Fable won two. The two Fable wins include the Argon roster, which is the task closest to an admission decision, and Sonnet's Argon plan admitted only the medium pin and placed it after Flash. Six tasks, one judge, and a transport that is not the KXM planner route are not enough to replace the primary. List price favors Sonnet by about five to one, and a native Claude subscription records both as unmetered, so the invoice may not move.
10
+
11
+ ## How this run was scored
12
+
13
+ The planner roster in `.kxm/roles/planner.yaml` is `claude-fable` at effort medium, then `pi-qwen3-8-flash-openrouter`. The native one-shot is `claude -p --model <model> --effort medium --output-format json` with the read-only arguments in `oneShotReadOnlyArgs` for `claude`. There is no command flag that swaps the planner model without editing the role and admitting a route. `kxm routing benchmark` prints placeholder numbers and does not call a model.
14
+
15
+ This machine has no `claude` binary, no Claude auth directory, and no `ANTHROPIC_API_KEY`. The committed plans are from Cursor subagents pinned to `claude-fable-5-1-thinking-medium` and `claude-sonnet-5-5-medium`. Both received the same prompt. They were told not to read the checkout, because the landed change would leak the answer. The Argon plans still name `pi-qwen3-coder-plus-openrouter` as writer number two, which the current tree no longer has, so they do not look like a read of the solution.
16
+
17
+ The judge is `gpt-5.6-sol-medium`. Labels were shuffled with seed `20261017` and stored in `score.py`. The judge scored a section-level condensation of each plan, the same sections for both arms, not the raw file byte for byte. Full text is under `raw/`. A keyword checklist in `score.py` scores the full text and does not pick the winner.
18
+
19
+ Rubric, 0 to 5 each: correctness against what shipped (or, for the open journal gap, against the written constraints), completeness of files, tests, and docs, risk and fail-closed guards, sequencing, and conciseness. The cap in the prompt was 700 words.
20
+
21
+ Tokens are `ceil(characters / 4)` on `prompts/` and `raw/`. That is not provider usage. Cache tokens were not observed, so cache rates are not applied. Fable uses the flat tier in `.kxm/prices.yaml` for `anthropic/fable`: $10 input and $50 output per 1M tokens. Sonnet 5.5 has no row in `.kxm/prices.yaml`. Its list estimate uses the standard tier in `.kxm/models/inventory.yaml` for `anthropic/claude-sonnet-5.5`: $2 and $10. `score.py` reads both files.
22
+
23
+ Twelve plans ran in one parallel wave from 2026-10-17T17:48:29Z to 2026-10-17T17:51:03Z, 154 seconds. The transport did not return per-plan latency, so the table does not invent one.
24
+
25
+ Regenerate scores with `python3 docs/evals/planner-bakeoff-2026-10/score.py --score`. `score.py --run` exits 2 until the Claude CLI or `ANTHROPIC_API_KEY` is present, and it does not invent plans.
26
+
27
+ ## Totals
28
+
29
+ | Task | Fable | Sonnet | Winner |
30
+ | --- | --- | --- | --- |
31
+ | Argon writer roster | 19 | 16 | Fable |
32
+ | CLI reviewer switch | 18 | 23 | Sonnet |
33
+ | Writer number two | 13 | 21 | Sonnet |
34
+ | Weekly catalog refresh | 22 | 21 | Fable |
35
+ | Knowledge wiki seed | 18 | 21 | Sonnet |
36
+ | Runtime journal gap | 20 | 24 | Sonnet |
37
+ | Total (max 150) | 110 | 126 | Sonnet, 4 of 6 |
38
+
39
+ | Task | Input tokens | Fable output | Sonnet output |
40
+ | --- | --- | --- | --- |
41
+ | Argon writer roster | 895 | 1501 | 1359 |
42
+ | CLI reviewer switch | 924 | 1361 | 1306 |
43
+ | Writer number two | 1002 | 1318 | 1344 |
44
+ | Weekly catalog refresh | 800 | 1400 | 1246 |
45
+ | Knowledge wiki seed | 940 | 1295 | 1252 |
46
+ | Runtime journal gap | 661 | 1279 | 1349 |
47
+ | Total | 5222 | 8154 | 7856 |
48
+
49
+ | Model | List rate per 1M | List cost | Wins |
50
+ | --- | --- | --- | --- |
51
+ | Fable 5.1 | $10 / $50, from `.kxm/prices.yaml` | $0.460 | 2 |
52
+ | Sonnet 5.5 | $2 / $10, from inventory, not `.kxm/prices.yaml` | $0.089 | 4 |
53
+
54
+ Input tokens are identical because the prompts were identical. Wall time for the wave is 154 seconds, shared.
55
+
56
+ The keyword checklist matched every required topic on eleven of the twelve plans. Sonnet's Argon plan missed "argon high on the roster" and "argon tried before flash". The checklist does not encode the shipped writer-number-two model, so a perfect checklist there does not mean the plan matched what shipped.
57
+
58
+ ## Argon writer roster
59
+
60
+ Shipped in pull request 401: Argon high and medium on the writer roster, ahead of Flash medium (the safety net) and Flash high (last, so the file stays on a roster). Low was inventoried and not rostered. Planner primary stayed Fable.
61
+
62
+ | Dimension | Fable | Sonnet |
63
+ | --- | --- | --- |
64
+ | Correctness | 3 | 1 |
65
+ | Completeness | 3 | 2 |
66
+ | Risk | 5 | 5 |
67
+ | Sequencing | 3 | 3 |
68
+ | Conciseness | 5 | 5 |
69
+ | Total | 19 | 16 |
70
+
71
+ Fable admitted high and medium and put them ahead of Flash, but swapped Flash medium and high and described the roster as living in `.kxm/routes.yaml`. Sonnet admitted only medium and appended it after both Flash entries. Both refused an OpenRouter Google route. List cost $0.084 versus $0.015.
72
+
73
+ ## CLI reviewer switch
74
+
75
+ Shipped in pull request 402: `codex-gpt-6-1-sol` at effort low, then `pi-qwen3-8-flash-openrouter`, then `codex-gpt-5-6-sol`. Pro is an alias on the price row. Architecture review stayed Fable.
76
+
77
+ | Dimension | Fable | Sonnet |
78
+ | --- | --- | --- |
79
+ | Correctness | 3 | 5 |
80
+ | Completeness | 3 | 4 |
81
+ | Risk | 4 | 5 |
82
+ | Sequencing | 3 | 5 |
83
+ | Conciseness | 5 | 4 |
84
+ | Total | 18 | 23 |
85
+
86
+ Both named the shipped order, the pro alias, the native-vendor brake, and the doc set. The judge marked Sonnet higher because Fable placed the roster reorder inside `.kxm/routes.yaml`. Neither named `workforce-names.mjs`, the review template, or the generated bundles. List cost $0.077 versus $0.015.
87
+
88
+ ## Writer number two
89
+
90
+ Shipped in pull request 403: `pi-qwen3-7-plus-openrouter`, after a live check that the model lists tools. The old coder-plus route was retired and its price row kept. The packet withheld tool support and told the planner to fail closed.
91
+
92
+ | Dimension | Fable | Sonnet |
93
+ | --- | --- | --- |
94
+ | Correctness | 2 | 3 |
95
+ | Completeness | 4 | 3 |
96
+ | Risk | 2 | 5 |
97
+ | Sequencing | 2 | 5 |
98
+ | Conciseness | 3 | 5 |
99
+ | Total | 13 | 21 |
100
+
101
+ Both defaulted to `qwen3-coder-next`, which is not what shipped. Sonnet refused to admit on that assumption and named `qwen3.7-plus` as the fallback if tools were missing. Fable scheduled the tool check after the route edit and moved the old alias onto the new route. List cost $0.076 versus $0.015.
102
+
103
+ ## Weekly catalog refresh
104
+
105
+ Shipped in pull request 404: `.github/workflows/refresh-models.yml`, a catalog module, a script, and a unit test. Monday 13:00 UTC. One pull request when the catalog changes. Routes are not edited.
106
+
107
+ | Dimension | Fable | Sonnet |
108
+ | --- | --- | --- |
109
+ | Correctness | 5 | 4 |
110
+ | Completeness | 4 | 4 |
111
+ | Risk | 5 | 5 |
112
+ | Sequencing | 4 | 5 |
113
+ | Conciseness | 4 | 3 |
114
+ | Total | 22 | 21 |
115
+
116
+ Both chose Monday 06:00 UTC and marked it as a choice, which the packet allowed. Both kept CLI-only rows, refused a token, refused route edits, and refused auto-merge. The one-point edge is not a decision by itself. List cost $0.078 versus $0.014.
117
+
118
+ ## Knowledge wiki seed
119
+
120
+ Shipped in pull request 405: hand-written pages under `.kxm/knowledge/`, not under `wiki/` and not under a handbook directory. Compile and ingest stayed unselected. No new test, script, or route edit.
121
+
122
+ | Dimension | Fable | Sonnet |
123
+ | --- | --- | --- |
124
+ | Correctness | 3 | 4 |
125
+ | Completeness | 3 | 3 |
126
+ | Risk | 4 | 5 |
127
+ | Sequencing | 4 | 5 |
128
+ | Conciseness | 4 | 4 |
129
+ | Total | 18 | 21 |
130
+
131
+ Sonnet put pages at `.kxm/knowledge/` and did not invent a browser flag. Fable used `.kxm/knowledge/handbook/` and named `KXM_BROWSER=steel`, which the packet said not to invent. Both refused ingest and a second glossary. List cost $0.074 versus $0.014.
132
+
133
+ ## Runtime journal gap
134
+
135
+ Nothing has shipped. The constraint is: no event-store or hub-store schema change while S5 is open, do not relabel a Runtime run as a webhook run, and prefer a design note plus tests that lock `workflow_not_found`.
136
+
137
+ | Dimension | Fable | Sonnet |
138
+ | --- | --- | --- |
139
+ | Correctness | 4 | 5 |
140
+ | Completeness | 5 | 5 |
141
+ | Risk | 4 | 5 |
142
+ | Sequencing | 4 | 5 |
143
+ | Conciseness | 3 | 4 |
144
+ | Total | 20 | 24 |
145
+
146
+ Both refused a store and refused invented schema versions. Sonnet stopped at characterization tests and a design note. Fable also changed the CLI message, which is more than the smallest step and still not a schema change. List cost $0.071 versus $0.015.
147
+
148
+ ## Caveats
149
+
150
+ - Six tasks is a small sample. One task is still open, so its correctness score is against constraints, not a diff.
151
+ - The judge saw condensations. Weekly is a one-point result and could flip. Argon, the reviewer switch, writer number two, and the journal gap are wider.
152
+ - The judge is one model, GPT-5.6 Sol, which is the CLI critic family. It was blind to the author and not blind to the shipped outcome.
153
+ - Token counts and list cost are estimates. Provider usage was not returned. A Claude subscription would record `costBasis: unmetered` for both models.
154
+ - Sonnet's price is not in `.kxm/prices.yaml`. Quoting it from the inventory is a list estimate, and the price loader would mark that row missing.
155
+ - Cursor subagents are not `claude -p` and not the KXM planner route. Effort was medium on both, matching the roster.
156
+ - Pull request 398, the plan-link repair, was not run. The six tasks are the five roster and docs slices from 401 through 405, plus the open Runtime journal gap.
157
+ - Plans were instructed not to use tools. A subagent that ignored that instruction could have read the landed diff. The Argon plans still describe the pre-change writer number two, which argues against that leak, and it is not proof.
158
+
159
+ ## Related
160
+
161
+ - [Harness routing](../reference/harness-routing.md)
162
+ - [Configuration reference](../reference/config-reference.md)
163
+ - [CLI reference](../reference/cli-reference.md)
164
+ - Raw plans, prompts, judge JSON, and `score.py` live in `docs/evals/planner-bakeoff-2026-10/`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kontextmind/kxm",
3
- "version": "0.7.189",
3
+ "version": "0.7.191",
4
4
  "description": "KXM local-first multi-agent orchestration and operator dashboard",
5
5
  "type": "module",
6
6
  "author": "KontextMind",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "kxm",
4
4
  "displayName": "KXM",
5
- "version": "0.7.189",
5
+ "version": "0.7.191",
6
6
  "description": "Headless multi-agent orchestration, durable workflows, and a live operator dashboard for Pi and Claude Code",
7
7
  "author": {
8
8
  "name": "KontextMind",
@@ -25462,7 +25462,7 @@ function sessionTokenFixHint(policy) {
25462
25462
  }
25463
25463
 
25464
25464
  // plugins/kxm/src/mcp-server.ts
25465
- var VERSION = "0.7.189";
25465
+ var VERSION = "0.7.191";
25466
25466
  var CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
25467
25467
  var inbox = /* @__PURE__ */ new Map();
25468
25468
  var notifiedInbox = /* @__PURE__ */ new Set();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-plugin",
3
- "version": "0.7.189",
3
+ "version": "0.7.191",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "engines": {
@@ -11,7 +11,7 @@ import { deliverInboxNotification } from "./inbox.ts";
11
11
  import type { HubEvent, MessageRecord } from "./protocol.ts";
12
12
  import { sessionTokenFixHint } from "./session-token-hint.ts";
13
13
 
14
- const VERSION = "0.7.189";
14
+ const VERSION = "0.7.191";
15
15
  const CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
16
16
  const inbox = new Map<string, MessageRecord>();
17
17
  const notifiedInbox = new Set<string>();