@malloy-publisher/skills 0.1.19 → 0.1.21

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 (79) hide show
  1. package/package.json +1 -1
  2. package/skills/eval-answer/SKILL.md +45 -5
  3. package/skills/eval-answer/reference/definition-ledger.md +305 -0
  4. package/skills/eval-answer/reference/ledger-schema.md +36 -7
  5. package/skills/eval-answer/scripts/check_coverage.py +12 -21
  6. package/skills/eval-answer/scripts/check_coverage_test.py +22 -0
  7. package/skills/eval-answer/scripts/check_findable.py +386 -0
  8. package/skills/eval-answer/scripts/check_findable_test.py +326 -0
  9. package/skills/eval-answer/scripts/json_scan.py +43 -0
  10. package/skills/eval-answer/scripts/ledger.py +63 -7
  11. package/skills/eval-answer/scripts/ledger_test.py +7 -0
  12. package/skills/eval-answer/scripts/mcp_payload.py +111 -2
  13. package/skills/eval-answer/scripts/mcp_payload_test.py +73 -2
  14. package/skills/eval-answer/scripts/publisher_rest.py +10 -4
  15. package/skills/eval-answer/scripts/publisher_rest_test.py +56 -0
  16. package/skills/eval-answer/scripts/score_retrieval.py +326 -41
  17. package/skills/eval-answer/scripts/score_retrieval_test.py +537 -15
  18. package/skills/eval-answer/scripts/verify_definitions.py +752 -0
  19. package/skills/eval-answer/scripts/verify_definitions_test.py +820 -0
  20. package/skills/eval-answer/scripts/verify_goldens.py +336 -22
  21. package/skills/eval-answer/scripts/verify_goldens_test.py +261 -2
  22. package/skills/eval-diagnose/SKILL.md +47 -2
  23. package/skills/eval-diagnose/scripts/cluster_failures.py +57 -14
  24. package/skills/eval-diagnose/scripts/cluster_failures_test.py +127 -0
  25. package/skills/eval-diagnose/scripts/diagnose.py +471 -42
  26. package/skills/eval-diagnose/scripts/diagnose_test.py +421 -0
  27. package/skills/eval-import/SKILL.md +180 -1
  28. package/skills/eval-import/reference/case-format.md +1 -1
  29. package/skills/eval-improve/SKILL.md +1 -1
  30. package/skills/eval-improve/scripts/improve.py +29 -3
  31. package/skills/eval-improve/scripts/improve_test.py +51 -0
  32. package/skills/eval-judge/SKILL.md +1 -1
  33. package/skills/eval-loop/SKILL.md +112 -3
  34. package/skills/eval-loop/reference/measurement.md +44 -4
  35. package/skills/eval-loop/reference/running-a-run.md +5 -1
  36. package/skills/eval-loop/scripts/agent_harness.py +21 -20
  37. package/skills/eval-loop/scripts/agent_harness_test.py +136 -0
  38. package/skills/eval-loop/scripts/build_run_package.py +9 -3
  39. package/skills/eval-loop/scripts/flip_table.py +107 -2
  40. package/skills/eval-loop/scripts/flip_table_test.py +165 -0
  41. package/skills/eval-loop/scripts/run_baseline.py +956 -97
  42. package/skills/eval-loop/scripts/run_baseline_test.py +799 -1
  43. package/skills/eval-loop/templates/eval-run-package/README.md +19 -5
  44. package/skills/eval-loop/templates/eval-run-package/eval_run.malloy +48 -19
  45. package/skills/eval-loop/templates/eval-run-package/eval_run.malloynb +11 -4
  46. package/skills/eval-loop/templates/eval-run-package/public/app.js +12 -4
  47. package/skills/eval-report/SKILL.md +362 -0
  48. package/skills/malloy/SKILL.md +6 -7
  49. package/skills/malloy-analysis/SKILL.md +22 -45
  50. package/skills/malloy-analysis-pitfalls/SKILL.md +21 -6
  51. package/skills/malloy-analysis-report/SKILL.md +1 -1
  52. package/skills/malloy-analyze/SKILL.md +1 -1
  53. package/skills/malloy-charts/SKILL.md +2 -2
  54. package/skills/malloy-dashboards/SKILL.md +1 -1
  55. package/skills/malloy-debug/SKILL.md +1 -1
  56. package/skills/malloy-define/SKILL.md +1 -1
  57. package/skills/malloy-discover/SKILL.md +1 -1
  58. package/skills/malloy-document/SKILL.md +1 -1
  59. package/skills/malloy-getting-started/SKILL.md +1 -1
  60. package/skills/malloy-gotchas-modeling/SKILL.md +2 -2
  61. package/skills/malloy-gotchas-queries/SKILL.md +2 -2
  62. package/skills/malloy-gotchas-rendering/SKILL.md +2 -2
  63. package/skills/malloy-html-data-app-embedding/SKILL.md +1 -1
  64. package/skills/malloy-html-data-app-runtime/SKILL.md +2 -2
  65. package/skills/malloy-html-data-apps/SKILL.md +1 -1
  66. package/skills/malloy-lookml-review/SKILL.md +1 -1
  67. package/skills/malloy-materialization/SKILL.md +1 -1
  68. package/skills/malloy-materialization-tuning/SKILL.md +1 -1
  69. package/skills/malloy-model/SKILL.md +1 -1
  70. package/skills/malloy-model-as-you-go/SKILL.md +1 -1
  71. package/skills/malloy-modeling/SKILL.md +1 -1
  72. package/skills/malloy-notebook-chat/SKILL.md +1 -1
  73. package/skills/malloy-notebooks/SKILL.md +1 -1
  74. package/skills/malloy-patterns/SKILL.md +2 -2
  75. package/skills/malloy-phrase-detection/SKILL.md +12 -7
  76. package/skills/malloy-publish/SKILL.md +1 -1
  77. package/skills/malloy-queries/SKILL.md +2 -2
  78. package/skills/malloy-review/SKILL.md +1 -1
  79. package/skills/malloy-scope/SKILL.md +7 -4
@@ -89,11 +89,25 @@ Comparing retrieval itself across engine versions is not this package's job; a c
89
89
 
90
90
  ## Every failure is placed
91
91
 
92
- `where_to_fix` is one of *query construction*, *retrieval ranking*, *model
93
- coverage* or *refusal behaviour*, and every scored failure has exactly one. The
94
- counts in `failures_by_where_to_fix` therefore sum to the failure count in
95
- `run_summary`. If they ever do not, attribution has a hole -- that exact bug is why
96
- the tables are cross-checked rather than trusted.
92
+ `where_to_fix` is one of *model coverage*, *never asked*, *not retrieved*,
93
+ *delivered, wrong*, *refusal behaviour* or *coverage not measured*, and every
94
+ scored failure has exactly one. Only two name an owner.
95
+
96
+ *never asked* is the agent's, and it is mechanical: no search asked for the
97
+ missing entity's kind, so nothing of that kind could come back. *model coverage*
98
+ is the model's. *not retrieved* means the entity exists, a search of the right
99
+ kind was issued, and it still did not come back -- the docs may not say what the
100
+ question asks, or the search wording may be off, and eval-diagnose separates
101
+ NOT-RETURNED from QUESTION-VOCAB. *delivered, wrong* means everything arrived and
102
+ the answer is still wrong: the agent misused it, or the docs never said how.
103
+ *coverage not measured* is a miss whose case carries no measured coverage label.
104
+
105
+ Two of these are deliberately ownerless. A label that asserted *documentation* on
106
+ every retrieval miss was wrong on the first real run, where the agent had searched
107
+ only for a source and a dimension and the measure it needed was blamed on docs
108
+ that described it perfectly well. The counts in `failures_by_where_to_fix` therefore sum
109
+ to the failure count in `run_summary`. If they ever do not, attribution has a
110
+ hole -- that exact bug is why the tables are cross-checked rather than trusted.
97
111
 
98
112
  `needs_human` is neither a pass nor a failure and is attributed to nothing.
99
113
 
@@ -128,10 +128,16 @@ include {
128
128
  public: final_query_source
129
129
  #(doc) The answer text the judge scored. Readable without opening the transcript.
130
130
  public: answer_text
131
- #(doc) Answerer input tokens.
131
+ #(doc) Answerer input tokens AFTER the last cache breakpoint only. Not the context volume: one
132
+ #(doc) run recorded 224 here for 24 questions beside 2.4M cache reads. See cache_read_tokens.
132
133
  public: input_tokens
133
134
  #(doc) Answerer output tokens.
134
135
  public: output_tokens
136
+ #(doc) Tokens served from the prompt cache. The bulk of an answerer's input.
137
+ public: cache_read_tokens
138
+ #(doc) Tokens written into the prompt cache. Priced above input; on one analysed run the single
139
+ #(doc) largest cost line, and never recorded before this column existed.
140
+ public: cache_write_tokens
135
141
  #(doc) Answerer cost in USD for this attempt.
136
142
  public: cost_usd
137
143
  #(doc) Assistant turns the attempt took.
@@ -200,12 +206,18 @@ extend {
200
206
  total_cost_usd is sum(cost_usd::number)
201
207
  #(doc) Mean cost per attempt. Compare across arms only at equal case counts.
202
208
  avg_cost_usd is avg(cost_usd::number)
203
- #(doc) Total tokens the answerer consumed.
209
+ #(doc) Post-breakpoint input plus output. NOT the context volume: cache reads are excluded,
210
+ #(doc) and they are the bulk of it. Kept under its old name for continuity; read
211
+ #(doc) context_tokens for size and cost_usd for money.
204
212
  -- Cast, like cases_covered below and for the same reason: a column that is
205
213
  -- empty in every row types as VARCHAR, and a platform run reports no token
206
214
  -- usage at all. Untyped, `sum()` then fails to compile and takes the whole
207
215
  -- package with it, so the run that most needs reading shows nothing.
208
216
  total_tokens is sum(input_tokens::number) + sum(output_tokens::number)
217
+ #(doc) Every input token the model saw: post-breakpoint input, cache reads and cache writes.
218
+ #(doc) This is the number that matches the CLI's cost accounting; total_tokens is not.
219
+ context_tokens is sum(input_tokens::number) + sum(cache_read_tokens::number)
220
+ + sum(cache_write_tokens::number)
209
221
  #(doc) Mean turns per attempt. Rises when the agent is groping rather than reading.
210
222
  avg_turns is avg(num_turns::number)
211
223
  #(doc) Mean wall-clock seconds per attempt.
@@ -237,12 +249,19 @@ include {
237
249
  public: precision
238
250
  #(doc) How many distinct entities get_context returned across the attempt.
239
251
  public: n_returned
240
- #(doc) construction, get_context/retrieval or get_context/model. Empty when the attempt passed.
252
+ #(doc) Which stage the failure sits in: construction, get_context,
253
+ #(doc) get_context/agent-call or get_context/model. Empty when the attempt passed.
241
254
  public: component
242
- #(doc) agent-skill, retrieval or model. Kept to match eval-diagnose's taxonomy; for reading, use where_to_fix.
255
+ #(doc) model, agent-skill, or undecided when the run cannot tell and eval-diagnose
256
+ #(doc) decides, or unknown when coverage was never measured. Kept to match
257
+ #(doc) eval-diagnose's taxonomy; for reading, use where_to_fix.
243
258
  public: owner
244
- #(doc) Where to go and fix it: query construction, retrieval ranking, model coverage or refusal behaviour.
245
- #(doc) Empty unless the attempt failed. Every failure has one, so this ties to the failure count.
259
+ #(doc) Where to go and fix it: model coverage, not retrieved, never asked, delivered wrong,
260
+ #(doc) refusal behaviour, or coverage not measured. Empty unless the attempt failed. Every failure
261
+ #(doc) has one, so this ties to the failure count. Only two of them name an owner. `never asked`
262
+ #(doc) is the agent's: no search asked for the missing entity's kind, so nothing of that kind
263
+ #(doc) could return. `model coverage` is the model's. `not retrieved` and `delivered, wrong` name
264
+ #(doc) no owner on purpose -- the docs or the agent, and eval-diagnose decides.
246
265
  public: where_to_fix
247
266
  #(doc) One sentence on why it landed there.
248
267
  public: why
@@ -279,14 +298,21 @@ extend {
279
298
  #(doc) Scored failures. Every one of these carries a where_to_fix.
280
299
  -- String comparison for the reason given on submitted_count.
281
300
  failure_count is count() { where: failed = 'true' }
282
- #(doc) Failures where retrieval delivered everything, so the fix is skills, not search.
283
- construction_failures is count() { where: where_to_fix = 'query construction' }
284
- #(doc) Failures where the entity existed and search missed it.
285
- retrieval_failures is count() { where: where_to_fix = 'retrieval ranking' }
301
+ #(doc) Failures where retrieval delivered everything and the answer is still wrong. The agent's
302
+ #(doc) or the docs'; eval-diagnose decides, sufficiency first. Do not read this as a skills count.
303
+ delivered_wrong_failures is count() { where: where_to_fix = 'delivered, wrong' }
304
+ #(doc) Failures where the entity existed, a search of the right kind was issued, and it did not
305
+ #(doc) come back. The docs or the search wording; eval-diagnose decides. Not a docs count.
306
+ not_retrieved_failures is count() { where: where_to_fix = 'not retrieved' }
307
+ #(doc) Failures where no search asked for the missing entity's kind at all. The agent's.
308
+ never_asked_failures is count() { where: where_to_fix = 'never asked' }
286
309
  #(doc) Failures where there was nothing to retrieve. Add the entity.
287
310
  model_failures is count() { where: where_to_fix = 'model coverage' }
288
311
  #(doc) Failures where the model cannot answer and the agent answered anyway.
289
312
  refusal_failures is count() { where: where_to_fix = 'refusal behaviour' }
313
+ #(doc) Failures on a case with no measured coverage label: retrieval's or the model's, and
314
+ #(doc) nothing says which. Not a model gap.
315
+ unmeasured_failures is count() { where: where_to_fix = 'coverage not measured' }
290
316
  }
291
317
 
292
318
  #(doc) The answer judge's verdict for one attempt. Exactly one per attempt in a scored run.
@@ -317,8 +343,10 @@ include {
317
343
  public: rubric_sha
318
344
  #(doc) Golden revision at score time.
319
345
  public: golden_revision
320
- #(doc) verified, verified_benign, suspect or verified_wrong. verified_wrong is excluded from aggregates.
346
+ #(doc) verified, verified_benign, suspect or verified_wrong.
321
347
  public: gold_status
348
+ #(doc) Whether this score belongs in the aggregates. False for a golden a person has established is WRONG (gold_status verified_wrong): the answer was scored against a key that does not hold, so its verdict says nothing about the model. Written by build_run_package.py from flip_table.counts_toward_score, the same rule run_baseline.py applies before printing a pass rate -- this column exists so the two cannot drift, which they had.
349
+ public: counts
322
350
  #(doc) Fields from golden.mustNotUse found in the final query, pipe-separated.
323
351
  #(doc) A hit forces no_match: a script decides this, never the judge.
324
352
  public: must_not_use_hits
@@ -339,18 +367,18 @@ extend {
339
367
  join_one: retrieval with attempt_key
340
368
 
341
369
  measure:
342
- #(doc) Rows with a verdict, scorable or not.
370
+ #(doc) Rows with a verdict, scorable or not. The only measure here that does NOT drop an excluded golden, so scored_count minus the others shows what left.
343
371
  scored_count is count()
344
- #(doc) Verdicts the acceptance check counts: decided ones only. near_match and needs_human are excluded, both being the judge declining to decide.
345
- confident_count is count() { where: outcome ? 'pass' | 'fail' }
372
+ #(doc) Verdicts the acceptance check counts: decided ones only. near_match and needs_human are excluded, both being the judge declining to decide. A score whose golden is verified_wrong is excluded too -- see `counts`.
373
+ confident_count is count() { where: outcome ? 'pass' | 'fail'; where: counts = 'true' }
346
374
  #(doc) Attempts the judge accepted. A near_match is NOT a pass: it means defensibly different, and an arguable verdict must not move an acceptance check.
347
- passed is count() { where: outcome = 'pass' }
375
+ passed is count() { where: outcome = 'pass'; where: counts = 'true' }
348
376
  #(doc) Attempts the judge rejected.
349
- failed is count() { where: outcome = 'fail' }
377
+ failed is count() { where: outcome = 'fail'; where: counts = 'true' }
350
378
  #(doc) Defensibly different: neither a pass nor a fail. Watch this climb - it is the leading indicator of a rubric going vague.
351
- near_matches is count() { where: verdict = 'near_match' }
379
+ near_matches is count() { where: verdict = 'near_match'; where: counts = 'true' }
352
380
  #(doc) Verdicts referred to a human because the judge was not confident.
353
- needs_human is count() { where: verdict = 'needs_human' }
381
+ needs_human is count() { where: verdict = 'needs_human'; where: counts = 'true' }
354
382
  #(doc) Passes over DECIDED verdicts. The headline number; excludes near_match and needs_human by design.
355
383
  # percent
356
384
  pass_rate is passed / confident_count
@@ -412,7 +440,8 @@ include {
412
440
  public: cluster_id
413
441
  #(doc) What the group has in common, in a phrase.
414
442
  public: label
415
- #(doc) query construction, retrieval ranking, model coverage or refusal behaviour.
443
+ #(doc) model coverage, not retrieved, never asked, delivered wrong, refusal behaviour,
444
+ #(doc) or coverage not measured.
416
445
  public: where_to_fix
417
446
  #(doc) eval-diagnose component.
418
447
  public: component
@@ -68,13 +68,20 @@ Every failure carries exactly one **where to fix**, so these counts sum to the
68
68
  run's failure count. If they ever do not, the attribution has a hole and the two
69
69
  tables below disagree with each other.
70
70
 
71
- - **query construction** -- retrieval delivered everything; the agent wrote the
72
- query wrong. Fix the skill.
73
- - **retrieval ranking** -- the entity exists and search did not return it. Fix
74
- ranking.
75
71
  - **model coverage** -- there was nothing to return. Add the entity.
72
+ - **not retrieved** -- a search of the right kind went out and the entity did
73
+ not come back. Two live causes and the run cannot separate them: the docs do
74
+ not describe it the way the question phrases it, or the search wording was
75
+ off. eval-diagnose decides which.
76
+ - **never asked** -- no search asked for that kind of entity at all, so nothing
77
+ of that kind could come back. Mechanical, and the agent's.
78
+ - **delivered, wrong** -- retrieval delivered everything and the answer is still
79
+ wrong. The agent's or the docs'; eval-diagnose decides, sufficiency first.
76
80
  - **refusal behaviour** -- the model genuinely cannot answer, and the agent
77
81
  answered anyway instead of declining.
82
+ - **coverage not measured** -- a miss on a case with no measured coverage label.
83
+ Retrieval's or the model's, and nothing says which, so it is charged to
84
+ neither.
78
85
  >>>malloy
79
86
  run: failures_by_where_to_fix
80
87
  >>>markdown
@@ -401,10 +401,18 @@ async function main() {
401
401
 
402
402
  const fixes = [...new Set(state.rows.flatMap(r => r.arms.map(a => a.where_to_fix).filter(Boolean)))].sort();
403
403
  document.getElementById('wtf').title =
404
- 'Where a failure would have to be fixed: query construction (the agent had '
405
- + 'what it needed and built the query wrong), retrieval ranking (the entity '
406
- + 'existed but was not returned), model coverage (nothing in the model '
407
- + 'answers this), or refusal behaviour.';
404
+ // No label is split across a concatenation: `score_retrieval_test.py`
405
+ // checks each one appears here verbatim, and a label broken over two
406
+ // string literals reads as absent.
407
+ 'Where a failure would have to be fixed. '
408
+ + '"model coverage": nothing in the model answers this. '
409
+ + '"not retrieved": a search of the right kind went out and the entity '
410
+ + 'did not come back. '
411
+ + '"never asked": no search of that kind went out at all. '
412
+ + '"delivered, wrong": everything arrived; the agent or the docs, '
413
+ + 'diagnose decides. '
414
+ + '"refusal behaviour": the model cannot answer and the agent did. '
415
+ + '"coverage not measured": a miss with no measured coverage label.';
408
416
  document.getElementById('wtf').innerHTML = '<option value="">Where to fix: any</option>' +
409
417
  fixes.map(f => `<option${f === state.wtf ? ' selected' : ''}>${esc(f)}</option>`).join('');
410
418
 
@@ -0,0 +1,362 @@
1
+ ---
2
+ name: eval-report
3
+ description: 'Write the summary a person reads after an evaluation run: what ran, what it scored, which failures were the MODEL and which were the EVAL itself (out of turns, contaminated, unestablished golden, unreadable judge), what was learned, and what to do next. Every claim links to the artifact behind it. Use at the end of any run, before quoting a number to anyone. Never scores an answer (eval-answer) or decides who owns a failure (eval-diagnose).'
4
+ version: 0.1.21
5
+ ---
6
+
7
+ # Report a Run
8
+
9
+ A run directory is JSONL and a console summary that scrolls away. Neither is a
10
+ result. This skill turns one into something a person can open, and it is the
11
+ last step of every run, including a run that failed.
12
+
13
+ The complaint this exists to answer, in a reviewer's words: *"eval runs and a
14
+ bunch of stuff happens and it is hard to know the actual results."*
15
+
16
+ **Scope boundary:** this skill reports what the ledger already says. It does not
17
+ score an answer (`skill:eval-answer`), decide who owns a failure
18
+ (`skill:eval-diagnose`), or edit a model (`skill:eval-improve`). If a number is
19
+ not in the ledger, do not put it in the report.
20
+
21
+ **The report is about THIS RUN, not about the harness.** Defects you find in
22
+ the eval tooling while running it are real and worth filing, and they do not
23
+ belong here: the reader wants to know what their model scored and why, not what
24
+ is wrong with the thing that measured it. File those against the harness. The
25
+ one exception is anything that qualifies THIS run's number -- a truncated
26
+ attempt, a contaminated one, an unestablished answer key -- which the "Eval
27
+ failures" section exists for.
28
+
29
+ ## Step 1: build the artifacts, before writing a word
30
+
31
+ ```bash
32
+ python3 skills/eval-loop/scripts/build_run_package.py \
33
+ --run <run-dir> --set <set-dir> --out /tmp/eval-<label>
34
+ curl -sS -X POST http://<publisher>/api/v0/environments/<env>/packages \
35
+ -H 'content-type: application/json' \
36
+ -d '{"name":"eval-<label>","location":"/tmp/eval-<label>"}'
37
+ ```
38
+
39
+ That builds a Malloy package over the run's own CSVs and registers it with no
40
+ restart. It gives you two things to link:
41
+
42
+ | Artifact | What it is | URL |
43
+ |---|---|---|
44
+ | The case matrix app | Every question, its verdict, which needed entities retrieval delivered, and a drawer per case holding the reference answer, the judge's reasoning, the re-executed rows and every query the answerer ran | `<publisher>/environments/<env>/packages/eval-<label>/` |
45
+ | `eval_run.malloynb` | The aggregate tables: pass rate, effort, cost, most-missed entities, the backlog | `<publisher>/<env>/eval-<label>/eval_run.malloynb` |
46
+
47
+ **Those two URLs are in different path spaces, and guessing costs a 404.** The
48
+ app is served by the in-package `public/` handler, which owns
49
+ `/environments/<env>/packages/<pkg>/<file>`. The notebook is a MODEL, rendered
50
+ by the Console, whose routes are `/<env>/<package>/<model path>` with no
51
+ `environments` or `packages` segment at all. Putting the notebook under the
52
+ app's prefix 404s, because the public-file handler answers there and the
53
+ notebook is not in `public/`. Verified both, on a real package, by opening
54
+ them.
55
+
56
+ Pass `--run` more than once to put two arms side by side.
57
+
58
+ ## Step 2: write the report
59
+
60
+ Put it in the repository beside the set, as `RESULTS.md`. Not in a chat log,
61
+ not in `~/Downloads`, both of which have lost a findings document before.
62
+
63
+ **Derive every number from `events.jsonl` and `run.json`, not from memory or
64
+ from the console.** A figure retyped from scrollback is a figure nobody can
65
+ check, and the console rounds.
66
+
67
+ ### The template
68
+
69
+ ```markdown
70
+ # <set> -- <label>
71
+
72
+ <one sentence: what was being measured, against what, and why now>
73
+
74
+ ## What ran
75
+
76
+ - Set `<name>` v`<datasetVersion>`, N cases (N dev, N holdout)
77
+ - Target `<env>/<package>`, model `<modelPath>`, pinned `<modelSha or targetVersion>`
78
+ - Answerer `<model>`, judge `<model>`, cap `<maxTurns>` turns
79
+ - Steps run: scrape/run, eval<, diagnose><, improve>
80
+ - Cost $X answerer + $Y judge
81
+
82
+ ## The result
83
+
84
+ **N of M decided (P%).** <-- or: **No pass rate.** See "Eval failures" below.
85
+
86
+ | qid | question | verdict |
87
+ |---|---|---|
88
+
89
+ [case matrix](<url>) - [aggregate tables](<url>)
90
+
91
+ ## Retrieval and coverage
92
+
93
+ Covered? -> Retrieved? -> Correct?, with the per-arm numbers under each.
94
+
95
+ ## Model failures
96
+
97
+ One entry per wrong answer. What it got wrong in plain words, then the
98
+ mechanism, then the link.
99
+
100
+ ## Eval failures
101
+
102
+ What went wrong with the MEASUREMENT rather than the model. Empty is a real
103
+ and good answer; say "none" rather than dropping the section.
104
+
105
+ ## Query errors
106
+
107
+ Malloy the answerer wrote that would not run, and whether it recovered.
108
+
109
+ ## What this run taught us
110
+
111
+ ## What to do next
112
+ ```
113
+
114
+ ### "What to do next" is a checklist, and most of it is derived
115
+
116
+ Do not invent this section. Most of it falls out of what the run did NOT do,
117
+ and a reader should be able to see that nothing was skipped silently. Walk
118
+ these in order and put every one that fires into the list, with its command:
119
+
120
+ | If the run shows | Then the next step is |
121
+ |---|---|
122
+ | `coverage: unmeasured` | run `check_coverage.py --set <set> --model <pkg> --out coverage.json`, then re-run with `--coverage` so it charges the failures |
123
+ | `goldenCheck: skipped` or the set names no `truthPackage` | build one with `init_truth_package.py`; until then the goldens were derived through the model under test and certify themselves |
124
+ | any golden still `provisional` | re-derive and `verify_goldens.py --promote` |
125
+ | a stale entity name warning | fix `expectedEntities`; it scores as a retrieval miss on every run until you do |
126
+ | a passing case with recall below 1.0 | check whether `required` over-specifies one path |
127
+ | `truncated` non-empty | re-run those cases at a higher cap with `--from` |
128
+ | diagnose did not run | run it, or say the failures have no owner yet |
129
+ | a cluster with `owner: model` | `skill:eval-improve`, then the acceptance check |
130
+ | a cluster with `owner: agent-skill` | **edit that skill.** This is NOT a dead end |
131
+ | a cluster with `owner: dataset` | the golden side door in `skill:eval-loop` |
132
+ | only one arm exists | note that no noise band has been measured for this set yet |
133
+
134
+ **An `agent-skill` cluster is work, not an absence of work.** `eval-improve`
135
+ may not touch it, and writing "nothing to do, the model is fine" there is how a
136
+ real defect gets closed as a non-finding. Name the skill, name the rule to add
137
+ or change, and say who owns it. The model being innocent is a statement about
138
+ the model, never about the run.
139
+
140
+ ### Write it for someone who was not there
141
+
142
+ The reader is a colleague who did not run this and does not know the loop's
143
+ vocabulary. Two rules, both learned by handing a report to one:
144
+
145
+ - **Never make a bare count carry the meaning.** "The only cluster is
146
+ `owner: agent-skill`" tells a reader nothing: what is a cluster, and what
147
+ follows from it? Write what happened and what to do: "The one wrong answer
148
+ came from the agent picking the wrong kind of field, so the fix is in the
149
+ analysis skill, not in the model."
150
+ - **Do not open with a negation of something you just reported.** A section
151
+ that says "nothing to do" directly under a section reporting a wrong answer
152
+ reads as a contradiction, and the reader stops trusting both. Lead with the
153
+ wrong answer and what closes it; put anything genuinely needing no action
154
+ after that, and say why.
155
+
156
+ ## The two failure sections, and why they are separate
157
+
158
+ This is the part most reports get wrong. A wrong answer and a broken
159
+ measurement look identical in a pass rate and have nothing else in common: one
160
+ is work for the model owner, the other is work for whoever runs the harness.
161
+ Mixing them sends a modelling agent to fix a model that is fine.
162
+
163
+ **Model failures** are cases that were fairly asked, fairly answered and got the
164
+ wrong answer. One entry each: what the answer said, what was right, and the
165
+ mechanism in one sentence. Do not write a cluster id here; write what it got
166
+ wrong.
167
+
168
+ **Eval failures** are everything that stopped the run measuring the model.
169
+ Report every one that occurred, with its count and its qids, and say plainly
170
+ that these are NOT evidence about the model:
171
+
172
+ | What happened | How it reads in the ledger | Who fixes it |
173
+ |---|---|---|
174
+ | Answerer ran out of turns | `reason: answerer_truncated`, and `run.json` `truncated` | raise `--max-turns`, re-run those cases with `--from` |
175
+ | Isolation breached | `reason: contaminated`, `contamination_reasons` on the attempt | harness config; the answerer held a tool it should not |
176
+ | Harness or server failed | `reason: environment_failure: <what>` | fix the environment, re-run the arm |
177
+ | Answer key not established | `reason: golden_provisional` / `_invalid` / `_ambiguous` / `_missing` | derive the key (`skill:eval-import`), or the golden side door |
178
+ | Judge reply unreadable | `reason: judge_unparseable` | it already retried; read `artifacts/<qid>/judge.md` |
179
+ | Judge doubted the key | `gold_status` `suspect` / `verified_wrong`, `run.json` `doubtedGoldens` | the golden side door in `skill:eval-loop`, NOT improve |
180
+ | Rubric quoted a stale figure | `verify_goldens.py` check 2 review items | repair the rubric against its own golden rows |
181
+ | Arm stopped early | `run.json` `status: aborted` | four consecutive dead attempts; fix and re-run |
182
+
183
+ **If any of the first three occurred, there is no pass rate to quote.** The
184
+ harness prints `INCOMPLETE` and withholds the percentage; the report does the
185
+ same. Give the counts and the re-run command instead of a number with a caveat,
186
+ because the number is what gets repeated and the caveat is what gets dropped.
187
+
188
+ ## Retrieval and coverage belong in every report
189
+
190
+ A pass rate says an answer was wrong. It does not say WHERE, and the three
191
+ metrics that do are already in the run summary and the notebook. Report them,
192
+ because a report that omits them makes every failure look like the model's:
193
+
194
+ **Copy the cascade the run printed. Do not re-derive it.** `cascade_lines()`
195
+ in `run_baseline.py` already renders every rung, and it carries one field a
196
+ hand-made table keeps losing: how many cases **answered correctly anyway**
197
+ after stopping on that rung. Paste its block into the report.
198
+
199
+ **A finer measurement must never revise the headline result.** This is the rule
200
+ the re-derivation breaks. Written as a funnel with a shrinking denominator --
201
+ `5 of 10 covered`, then `3 of the 5`, then `3 of the 3` -- the last rung reads
202
+ as "only 3 of 10 succeeded" on a run where **all ten answers were correct**.
203
+ That report went to a reviewer, and the objection was the right one: adding
204
+ detail about retrieval cannot turn a 10 of 10 into a 3.
205
+
206
+ Two things stop it:
207
+
208
+ - **Report every rung over all N cases**, never over the survivors of the rung
209
+ above. The rungs are three measurements of the same cases, not a narrowing of
210
+ them.
211
+ - **`delivered, right` is NOT the pass rate.** The passing cases are
212
+ `delivered, right` plus `passed_not_covered` plus `passed_not_retrieved`. A
213
+ report that omits those last two has dropped the only numbers that reconcile
214
+ the cascade with the score.
215
+
216
+ **Label a rung for what it measures, not for what a reader will assume.**
217
+ "Can the model express an answer?" is wrong for most of what lands on the `no`
218
+ side of the first rung. `CONVENTION` means the data is present and no named
219
+ measure encodes the statistical or business convention; `NO-DISAMBIG` means two
220
+ plausible candidates and no doc saying which the question means. The model can
221
+ express an answer in both -- it does not say WHICH answer is meant. So the rung
222
+ asks whether the model NAMES what the question needs.
223
+
224
+ **Give the per-entity number too, and label both.**
225
+
226
+ - per CASE, the cascade: did this case get everything it needed
227
+ - per ENTITY, recall: of the N entities the answers depended on, how many were
228
+ delivered
229
+
230
+ They answer different questions, and the entity number is the more natural read
231
+ of "how did search do". One run reported `3 of 5` cases and buried `16 required
232
+ entities, 80% delivered`, and the case number was the only thing a reader saw.
233
+
234
+ - **Covered?** Can the model express a correct answer at all? Not computed by
235
+ the run: `check_coverage.py` reads the MODEL rather than the answers, and the
236
+ run consumes its report through `--coverage`. **If it was not run, say
237
+ `unmeasured` rather than leaving the row out** -- an unmeasured coverage
238
+ label is not evidence that the model covers the question.
239
+ - **Retrieved?** Of the entities the golden answer depends on, how many did
240
+ `get_context` hand back. Quote `required_count`, `delivered_count` and how
241
+ many were ranked rather than merely mentioned in a returned source's docs.
242
+ - **Correct?** The pass rate, which is the rung the other two qualify.
243
+
244
+ Two numbers here are routinely misread, so qualify them in the report or leave
245
+ them out:
246
+
247
+ - **Entity precision is a PAYLOAD number, not a retrieval-quality one.** Report
248
+ it as what it measures: how much context the agent was handed against how
249
+ much it needed. 52 entities returned per attempt for the 2 an answer used is
250
+ a real cost, in tokens and in attention, and it is worth tracking. What it
251
+ cannot tell you is whether retrieval worked, because the denominator is
252
+ everything returned and it ignores rank: an entity that came back at rank 3
253
+ of 51 scores identically to one at rank 51.
254
+ - **For quality, report rank.** "Required entities came back at median rank 3,
255
+ 9 of 16 in the top 5" is a statement about retrieval. Precision alone reads
256
+ as an indictment of it and is mostly a statement about how many fields the
257
+ package has.
258
+ - **Report the misses split by cause, not as one recall figure.** The run
259
+ attributes each to `never asked` (no target of a type that could return it --
260
+ deterministic, because `target_type` is a hard filter on the server, so no
261
+ documentation could have delivered it) or `not retrieved` (a compatible target
262
+ was issued and it still did not come back, which is the docs or the wording,
263
+ and diagnose separates them). Those have different owners and one recall
264
+ number hides which you have.
265
+ - **A miss on a case that PASSED is still a miss, and the report says so.** The
266
+ answer was right by another route; name the route. Measured on one run, 3 of
267
+ 4 findings were on passing cases and the route was always the same: the agent
268
+ rebuilding the model's own measure inline, which held for `count()` and failed
269
+ on the one measure carrying a grain rule.
270
+ - **Read the entities that did NOT come back, and what was asked for.** That is
271
+ where the retrieval signal actually is, and the misses are rarely independent.
272
+ Measured on one run: 5 required entities never came back as ranked results,
273
+ and 4 of the 5 had the same cause -- the agent sent only `source` and
274
+ `dimension` targets, and `target_type` is a hard filter, so no measure could
275
+ be returned however well the model documents it. One defect, five symptoms,
276
+ and the same one that produced the run's only wrong answer. A per-case list of
277
+ misses beside what was asked for would have shown it in a glance.
278
+ - **A PASSING case with recall below 1.0 is usually an expectation defect**, not
279
+ a retrieval miss: the set named one path to an answer the agent reached by
280
+ another. Report the count and read it as a prompt to check `required`.
281
+
282
+ ## Query errors are worth a section of their own
283
+
284
+ Malloy the answerer wrote that would not compile or run, whether or not the
285
+ case passed. These sit on `tool_call` events as `error`, and nothing else
286
+ surfaces them: a case that errored twice and then recovered scores exactly like
287
+ one that got it right first time, so the effort disappears.
288
+
289
+ They are the sharpest available evidence on whether the skills and the docs are
290
+ leading agents astray, because each one names a specific thing the agent
291
+ believed and the language does not support. Report the count, the distinct
292
+ error kinds, and for each whether an existing skill already covers it. An error
293
+ whose fix IS documented, in a skill the answerer did not open, is a finding
294
+ about the skills rather than about the agent.
295
+
296
+ `eval-diagnose` does not see these today: it reads failing cases only, so an
297
+ error inside a passing case is invisible to it. Say so rather than implying
298
+ they were triaged.
299
+
300
+ ## Say what did not run, and why
301
+
302
+ A step that did not run is a result, and it is indistinguishable from having
303
+ forgotten unless it is written down. The common one: `improve` does not run when
304
+ every cluster came back `owner: agent-skill` or `owner: dataset`, because
305
+ `skill:eval-improve` may only touch `owner: model`. That means the model is not
306
+ at fault, which is worth a sentence rather than a silence.
307
+
308
+ Same for diagnose. If it did not run, no failure in this report has an owner
309
+ yet, and the report says so rather than implying the failures are the model's.
310
+
311
+ ## Findings and improvement ideas
312
+
313
+ Two different things, kept apart.
314
+
315
+ - **Findings** are what this run established, each with the evidence beside it.
316
+ A finding with no artifact behind it is an opinion.
317
+ - **Improvement ideas** are what somebody might do about them, tagged with who
318
+ owns each: the model, the answer key, the analysis skills, or the harness.
319
+ An idea is a proposal, not a decision, and the report does not pretend a
320
+ cluster has been triaged when it has not.
321
+
322
+ Rank by how many cases each would move, and say when the answer is "nothing":
323
+ a 90% run whose one failure is a known fan-out trap may need no action at all.
324
+
325
+ **Report facts, do not instruct the reader.** "This set has had one arm, so no
326
+ noise band exists for it" is a fact they can act on. "Do not quote 90% until X"
327
+ is a lecture about their own number, and they did not ask for one. State what
328
+ was and was not measured; what to do with it is theirs.
329
+
330
+ ## Make the links clickable
331
+
332
+ A report whose evidence cannot be opened is a report nobody checks.
333
+
334
+ - **Absolute paths**, never relative ones. A reader is not in your working
335
+ directory, and a terminal only makes an absolute path clickable.
336
+ - **The served URL** for the app and the notebook, with the real host and port
337
+ the run used, not a placeholder.
338
+ - Per case, link the FILES, not the directory: `artifacts/<qid>/answer.md`,
339
+ `artifacts/<qid>/judge.md`, `artifacts/<qid>/answerer.jsonl`. A `file://`
340
+ link to a directory opens nothing in most editors, which is a dead link that
341
+ looks live.
342
+ - **Move the run somewhere durable before you link it.** A run written under a
343
+ session scratch directory (`/tmp/...`, a host's per-session sandbox) is
344
+ deleted when the session ends, and many editors will not linkify a path there
345
+ even while it exists. Copy the run directory, artifacts included, next to the
346
+ report under the set, then link that. A report whose evidence is in a temp
347
+ directory is a report with no evidence by next week. This was caught twice by
348
+ a reader clicking and getting nothing.
349
+ - Write a local path bare, as `/abs/path/to/file`, not as `[text](file:///...)`.
350
+ Terminals and editors linkify a bare absolute path; a `file://` markdown link
351
+ is frequently inert, and an inert link is worse than the path in plain text
352
+ because the reader has nothing to copy.
353
+ - Never link a run LABEL or a qid as though it were a path. `faa-v1-baseline-01`
354
+ is a label; the path is the run directory. A backticked label that looks like
355
+ a link and resolves to nothing is worse than plain text, and it has already
356
+ wasted a reader's time.
357
+
358
+ ## Related skills
359
+
360
+ - `skill:eval-loop`: conducts the run this reports on.
361
+ - `skill:eval-answer`: the verdicts and the ledger schema this reads.
362
+ - `skill:eval-diagnose`: owners and clusters, when it ran.
@@ -1,19 +1,15 @@
1
1
  ---
2
2
  name: malloy
3
3
  description: Index of all Malloy skills. Use when user asks "malloy help", "what malloy skills are available", "how do I use malloy", or needs guidance on which Malloy skill to use.
4
- version: 0.1.19
4
+ version: 0.1.21
5
5
  ---
6
- <!--
7
- Copyright (c) Credible Data Inc.
8
- SPDX-License-Identifier: MIT
9
- -->
6
+ <!-- Copyright (c) Credible Data Inc. SPDX-License-Identifier: MIT -->
10
7
 
11
8
  # Malloy Skills Index
12
9
 
13
10
  ## First-Time Setup
14
11
 
15
- **No .malloy files in workspace?**
16
- Say "model my data" and the agent will orchestrate the full modeling workflow automatically. Make sure the Malloy Publisher MCP tools are configured first.
12
+ **No .malloy files in workspace?** Say "model my data" and the agent will orchestrate the full modeling workflow automatically. Make sure the Malloy Publisher MCP tools are configured first.
17
13
 
18
14
  ## Skill Reference
19
15
 
@@ -61,6 +57,9 @@ This table is a catalogue of what exists, not of what is loaded. A host that ins
61
57
  | `eval-answer` | Scoring one answer against a verified golden, and what retrieval delivered |
62
58
  | `eval-diagnose` | Deciding why a case failed and which artifact owns the fix |
63
59
  | `eval-improve` | The smallest model edit for a diagnosed cluster, with probe receipts |
60
+ | `eval-judge` | Scoring one attempt against its golden, and saying whether the golden itself is believed |
61
+ | `eval-import` | Turning questions that arrived from outside into a set, before the first run against it |
62
+ | `eval-report` | Turning a finished run into something a person can open, and saying what it did and did not measure |
64
63
 
65
64
  **Writing correct Malloy** (read before writing, not after failing)
66
65