@malloy-publisher/skills 0.1.38 → 0.1.39
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/package.json +1 -1
- package/skills/eval-answer/SKILL.md +1 -1
- package/skills/eval-answer/reference/ledger-schema.md +4 -3
- package/skills/eval-answer/scripts/ledger.py +5 -1
- package/skills/eval-answer/scripts/publisher_rest.py +39 -0
- package/skills/eval-diagnose/SKILL.md +43 -18
- package/skills/eval-diagnose/scripts/cluster_failures.py +2 -0
- package/skills/eval-diagnose/scripts/diagnose.py +11 -4
- package/skills/eval-import/SKILL.md +1 -1
- package/skills/eval-improve/SKILL.md +46 -6
- package/skills/eval-improve/scripts/improve.py +45 -16
- package/skills/eval-improve/scripts/improve_test.py +35 -0
- package/skills/eval-judge/SKILL.md +1 -1
- package/skills/eval-loop/SKILL.md +3 -2
- package/skills/eval-loop/reference/acceptance-check.md +12 -0
- package/skills/eval-loop/scripts/agent_harness.py +38 -1
- package/skills/eval-loop/scripts/agent_harness_test.py +12 -1
- package/skills/eval-loop/scripts/package_skills_test.py +263 -0
- package/skills/eval-loop/scripts/run_baseline.py +185 -16
- package/skills/eval-report/SKILL.md +1 -1
- package/skills/malloy-analysis/SKILL.md +1 -1
- package/skills/malloy-analysis-report/SKILL.md +1 -1
- package/skills/malloy-charts/SKILL.md +1 -1
- package/skills/malloy-dashboards/SKILL.md +1 -1
- package/skills/malloy-define/SKILL.md +1 -1
- package/skills/malloy-discover/SKILL.md +1 -1
- package/skills/malloy-document/SKILL.md +1 -1
- package/skills/malloy-getting-started/SKILL.md +2 -2
- package/skills/malloy-gotchas-modeling/SKILL.md +1 -1
- package/skills/malloy-html-data-apps/SKILL.md +1 -1
- package/skills/malloy-lookml-review/SKILL.md +1 -1
- package/skills/malloy-materialization/SKILL.md +1 -1
- package/skills/malloy-model/SKILL.md +1 -1
- package/skills/malloy-model-as-you-go/SKILL.md +1 -1
- package/skills/malloy-modeling/SKILL.md +1 -1
- package/skills/malloy-notebooks/SKILL.md +1 -1
- package/skills/malloy-phrase-detection/SKILL.md +1 -1
- package/skills/malloy-powerbi-review/SKILL.md +1 -1
- package/skills/malloy-publish/SKILL.md +1 -1
- package/skills/malloy-queries/SKILL.md +1 -1
- package/skills/malloy-reports/SKILL.md +1 -1
- package/skills/malloy-review/SKILL.md +1 -1
- package/skills/malloy-run-agent/SKILL.md +58 -0
- package/skills/malloy-source-unreachable/SKILL.md +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: eval-answer
|
|
3
3
|
description: 'Score one analytical answer against a verified golden, and score which of the entities the golden depends on retrieval delivered to the answerer. Run the contamination checklist, re-execute the submitted query yourself, then spawn a judge subagent per skill:eval-judge. Append attempt, tool_call, score, and retrieval_score events to the file ledger (reference/ledger-schema.md). Never explain the failure (eval-diagnose) or edit the model (eval-improve). Use when asked whether an answer was correct, to score a run, or to baseline a model.'
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.39
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Evaluate One Answer
|
|
@@ -153,7 +153,8 @@ on that axis.
|
|
|
153
153
|
| `goldenCheck` | What `verify_goldens.py` said before the run started: `N ok, M drifted, K other finding(s)`, or why it did not run (no truth server on a platform target; `--skip-golden-check`; rebuild). A run that started on a drifted set says so here rather than pretending its verdicts mean something. `run_baseline` calls `verify()` without a model, so the set-name audit (check 5) does not run in-arm; the trailing `N stale entity name(s)` is the in-arm lint's own count, and `entity-name lint not run` says there was no model text to lint against. Without it the field read `0 other finding(s)` for a check that never happened. |
|
|
154
154
|
| `staleEntityNames` | The `expectedEntities` ids the compiled served model does not declare, from the in-arm lint (bare names from a search of the model text when the compiled model could not be read). Each one depresses the run's own recall on every case listing it, so a stale set reads as a retrieval failure until someone checks by hand. `null` when there was no model text to lint against (a platform target), which is a different fact from `[]`. |
|
|
155
155
|
| `status` | `complete`; `aborted` when four consecutive attempts errored or found the server dead and the harness stopped rather than spend the rest of the budget on attempts nobody will trust; or `incomplete` when the arm ran to the end and still cannot report a rate, because `truncated` or `contaminated` is non-empty. `incomplete` is a third state, not a soft `complete`: the arm did everything asked of it and the number is still unearned. It does NOT stop the run being compared: `flip_table.py` scores the cases both arms did, and names the ones this arm left unscored -- an excluded case carries no verdict, so it can never be counted as a flip. |
|
|
156
|
-
| `packageSha` / `servedRevision` *(pins)* | Taken from the server, not recomputed: `sourceContentSha` is a content hash over EVERY model path in the package, so an edit to an imported file moves it where a sha of the one `--model-path` does not; `servedRevision` is minted per load, so it identifies a load rather than content and is a poor pin alone. Measured: `publisher.json`'s `version` moves neither, and nothing in Publisher reads it -- it is not in the Package API schema and never returned, so it pins nothing. |
|
|
156
|
+
| `packageSha` / `servedRevision` *(pins)* | Taken from the server, not recomputed: `sourceContentSha` is a content hash over EVERY model path in the package AND its `skills/` files AND its declared agents (their files and the `publisher.json` declaration), so an edit to an imported file or to a skill moves it where a sha of the one `--model-path` does not; `servedRevision` is minted per load, so it identifies a load rather than content and is a poor pin alone. Measured: `publisher.json`'s `version` moves neither, and nothing in Publisher reads it -- it is not in the Package API schema and never returned, so it pins nothing. |
|
|
157
|
+
| `packageSkillsMode` / `packageSkillsSha` *(pins)* | How the package's own skills reached the answerer (`install` copied them into its workspace, `tool` left it to fetch them with `get_skill`, `off` withheld them, which is also what a package that ships none is recorded as), and a content hash of that `skills/` tree. A run that spawns no answerer (`--rebuild`, `--rejudge`, `--from`) carries the prior run's values, and a platform run records null. Two runs can differ only in guidance; without these the ledger cannot tell them apart, and a flip table between them would look like noise. |
|
|
157
158
|
| `datasetSha` *(pin)* | Content hash of `set.json` + `cases.jsonl`. Deliberately SEPARATE from the model's sha: a golden repair is not a model change, and one pin covering both would make every answer-key fix read as an edit to the model, which is the distinction an A/B rests on. Automatic, so nobody has to remember it; `datasetVersion` stays beside it as the human-readable sequence. **Local targets only.** A hosted target that publishes IMMUTABLE versions needs none of this: the set rides inside the version, and immutability -- not hashing -- is what makes a pin trustworthy. There, `targetVersion` alone identifies model and set together. |
|
|
158
159
|
| `doubtedGoldens` | The cases whose golden is not believed: `qid`, `gold_status` (`suspect` or `verified_wrong`), `gold_note`, and `declaredBy` (`judge` or `set`). A status the SET declared is a key somebody already settled, not an opinion the judge formed this run, and attributing both to the judge claimed a judgement that never happened. **Read this before diagnose.** These are dataset issues, not model failures, and they go through the golden side door in `skill:eval-loop`. Empty list when no key is in doubt from either source. Written from the same scan that prints the end-of-run warning, because a warning that lives only in console text is one scrollback away from sending a modelling agent at a model that is already right. |
|
|
159
160
|
| `mode` / `setName` / `serverVersion` / `traceMode` / `callBudget` | Defined and accepted, **not yet written by any harness** -- kept in the schema for the platform target and the conductor, which need them. (`targetVersion` and `status` were in this list and are both written now.) |
|
|
@@ -357,7 +358,7 @@ rejected direction keeps its record.
|
|
|
357
358
|
| `issue_ids` | |
|
|
358
359
|
| `files` | Paths the edit touched. |
|
|
359
360
|
| `diffSummary` | One line per file. |
|
|
360
|
-
| `probes` | Query and result for each factual claim. |
|
|
361
|
+
| `probes` | Query and result for each factual claim. May be empty for a `class: skill` candidate that asserts nothing about the data: a guide making no factual claim has nothing to probe. Empty is not a missing receipt there; a guide that DOES quote a number still needs one. |
|
|
361
362
|
| `meaningChanged` | Entities whose *meaning* the edit changed; `[]` for a docs-only edit. |
|
|
362
363
|
| `goldenSuspect` | Each `{qid, entity, stored, rederived}`: a golden this edit may have invalidated. Reported by the improver, never repaired by it. **Non-empty halts the acceptance check** until adjudicated through the golden side door. |
|
|
363
364
|
| `goldenAudit` | The set's `verify_goldens.py` run against the edited model: `{ran, clean, model, tail}`. Catches drift and rubric-vs-model contradictions only; a golden whose value silently moved is invisible to it, which is why `goldenSuspect` exists alongside. |
|
|
@@ -373,7 +374,7 @@ Written by `eval-loop`, one per acceptance check decision, BEFORE any checkpoint
|
|
|
373
374
|
|---|---|
|
|
374
375
|
| `issue_ids` | |
|
|
375
376
|
| `decision` | `accepted` / `rejected`. |
|
|
376
|
-
| `class` | `docs` / `definition` / `retrieval` / `justified`. |
|
|
377
|
+
| `class` | `docs` / `definition` / `skill` / `retrieval` / `justified`. `skill` is an edit to a package skill under the package's `skills/`; it never takes the documentation shortcut in the acceptance check. |
|
|
377
378
|
| `baselineRunId` / `finalRunIds` | Plural: acceptance needs two independent runs. |
|
|
378
379
|
| `regressions` | Case ids whose verdict got worse vs baseline. Must be empty to accept. |
|
|
379
380
|
| `holdoutDelta` | Confident-verdict delta on the holdout slice. |
|
|
@@ -217,7 +217,11 @@ RUN_OPTIONAL = {"label", "effort", "environment", "package", "modelPath",
|
|
|
217
217
|
"staleEntityNames",
|
|
218
218
|
"retrievalMode", "retrievalCalls", "retrievalGate",
|
|
219
219
|
"reExecution", "coverageReport",
|
|
220
|
-
"modelRepo"
|
|
220
|
+
"modelRepo",
|
|
221
|
+
# How the package's own skills reached the answerer, and a hash
|
|
222
|
+
# of the tree they came from. Two runs can differ ONLY in
|
|
223
|
+
# guidance, and without these the ledger cannot tell them apart.
|
|
224
|
+
"packageSkillsMode", "packageSkillsSha"} | RUN_RECOMMENDED
|
|
221
225
|
|
|
222
226
|
|
|
223
227
|
def dataset_sha(set_dir: pathlib.Path) -> str | None:
|
|
@@ -148,6 +148,45 @@ def get_json(base: str, path: str, timeout: int = 30) -> Any:
|
|
|
148
148
|
return json.loads(resp.read())
|
|
149
149
|
|
|
150
150
|
|
|
151
|
+
def served_package_dir(base: str, environment: str,
|
|
152
|
+
package: str) -> pathlib.Path | None:
|
|
153
|
+
"""The directory the running Publisher serves this package FROM, or None.
|
|
154
|
+
|
|
155
|
+
A host commonly serves a copy rather than your working tree, so this is the
|
|
156
|
+
only path whose bytes are the ones answering queries.
|
|
157
|
+
"""
|
|
158
|
+
try:
|
|
159
|
+
for proj in get_json(base, "api/v0/projects"):
|
|
160
|
+
if proj.get("name") == environment and proj.get("location"):
|
|
161
|
+
p = pathlib.Path(proj["location"]) / package
|
|
162
|
+
return p if p.is_dir() else None
|
|
163
|
+
except (OSError, ValueError): # unreachable or non-JSON: absence is reported
|
|
164
|
+
return None
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def package_skill_names(base: str, environment: str, package: str) -> list[str]:
|
|
169
|
+
"""Skills the server says this package ships, or [] if it ships none.
|
|
170
|
+
|
|
171
|
+
Read from the server rather than from the working tree, because those are
|
|
172
|
+
two different questions: a host serves a COPY of the package. What the
|
|
173
|
+
answerer can actually reach is what this returns.
|
|
174
|
+
|
|
175
|
+
An unreachable or older server returns [] rather than raising: the caller
|
|
176
|
+
decides whether "the server serves none" contradicts what it asked for.
|
|
177
|
+
"""
|
|
178
|
+
try:
|
|
179
|
+
rows = get_json(
|
|
180
|
+
base,
|
|
181
|
+
f"api/v0/environments/{urllib.parse.quote(environment)}"
|
|
182
|
+
f"/packages/{urllib.parse.quote(package)}/skills")
|
|
183
|
+
except (OSError, ValueError): # unreachable or non-JSON
|
|
184
|
+
return []
|
|
185
|
+
return sorted(r["name"] for r in rows
|
|
186
|
+
if isinstance(r, dict) and r.get("origin") == "package"
|
|
187
|
+
and isinstance(r.get("name"), str))
|
|
188
|
+
|
|
189
|
+
|
|
151
190
|
def served_model_path(base: str, environment: str, package: str,
|
|
152
191
|
model_path: str) -> pathlib.Path | None:
|
|
153
192
|
"""Where the running Publisher reads this model from, or None.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: eval-diagnose
|
|
3
3
|
description: 'Diagnose why a scored answer failed and who owns the fix, then cluster the failures by shared root cause. Walk dataset, agent-call, get_context/model, get_context/retrieval, construction, then model-definition. Append issue events to the file ledger, linked by traceId, one per cluster. Use after eval-answer, when triaging a run, or before changing a model. Does not edit the model (eval-improve).'
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.39
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Diagnose One Answer
|
|
@@ -68,9 +68,19 @@ never "C1" / "C2" / "C3":
|
|
|
68
68
|
| `construction` | Did sufficient context arrive, and the agent still built the wrong query? |
|
|
69
69
|
| `model-definition` | Is a measure, join, filter convention, or source semantically wrong? |
|
|
70
70
|
|
|
71
|
-
`owner` is separate: `model`, `retrieval`, `agent-skill`, or
|
|
72
|
-
is no environment owner: an environment failure stops the run
|
|
73
|
-
diagnosis (see the boundary above), so no issue can carry it.
|
|
71
|
+
`owner` is separate: `model`, `package-skill`, `retrieval`, `agent-skill`, or
|
|
72
|
+
`dataset`. There is no environment owner: an environment failure stops the run
|
|
73
|
+
before diagnosis (see the boundary above), so no issue can carry it.
|
|
74
|
+
|
|
75
|
+
`package-skill` and `agent-skill` are both "the guidance was wrong", split by
|
|
76
|
+
who can fix it here. A package can ship its own guides under `skills/`, and one
|
|
77
|
+
whose name matches a built-in REPLACES it for that package, so a fix scoped to
|
|
78
|
+
this package is a file in the model repo and lands in the same commit as a model
|
|
79
|
+
edit. `agent-skill` is the shared skill, outside this repo and outside this
|
|
80
|
+
loop. Choose `package-skill` only when the fix is genuinely specific to this
|
|
81
|
+
package: this customer's vocabulary, this model's conventions, a trap only these
|
|
82
|
+
sources have. A gap every package would hit is `agent-skill`, and widening a
|
|
83
|
+
shared skill by editing one package's copy hides it from every other package.
|
|
74
84
|
|
|
75
85
|
### Assigning owner: one question
|
|
76
86
|
|
|
@@ -202,14 +212,20 @@ Prior `score` events are not rewritten. They keep the old `golden_revision`.
|
|
|
202
212
|
| Code | When | Owner |
|
|
203
213
|
|---|---|---|
|
|
204
214
|
| `NEVER-ASKED` | no utterance targeted a needed concept | agent-skill, and model if nothing would have prompted the ask |
|
|
205
|
-
| `VAGUE` | compound or generic utterances, so nothing could rank | agent-skill |
|
|
206
|
-
| `QUESTION-VOCAB` | utterances parroted the question where the data uses other words |
|
|
207
|
-
| `RULE_UNWRITTEN` | the data is present and the model does not encode the rule for combining or filtering it, so whoever answers invents one. Qualified `(guessable)` when the data forces the rule -- list price minus sale price -- and `(arbitrary)` when it is a business decision nobody could derive, such as a season window or whether revenue is net of tax. Only the stated conventions tell those apart: from the model alone the two look identical, and an unqualified reading defaults to the harmless one | model: write the rule down |
|
|
215
|
+
| `VAGUE` | compound or generic utterances, so nothing could rank | agent-skill: decomposition is a general skill. package-skill only if this package's concepts are what defeated it |
|
|
216
|
+
| `QUESTION-VOCAB` | utterances parroted the question where the data uses other words | package-skill: map this business's words to the model's. model if the vocabulary belongs on the entity itself |
|
|
217
|
+
| `RULE_UNWRITTEN` | the data is present and the model does not encode the rule for combining or filtering it, so whoever answers invents one. Qualified `(guessable)` when the data forces the rule -- list price minus sale price -- and `(arbitrary)` when it is a business decision nobody could derive, such as a season window or whether revenue is net of tax. Only the stated conventions tell those apart: from the model alone the two look identical, and an unqualified reading defaults to the harmless one | model: write the rule down, or expose a named measure. package-skill when no measure or doc can carry it, e.g. which population to default to |
|
|
208
218
|
| `ASSUMED` | assumed a scope or convention instead of checking | model if nothing warned; agent-skill otherwise |
|
|
209
219
|
| `WRONG-TYPE-OR-SCOPE` | asked, but with the wrong target type or an empty/wrong scope | agent-skill |
|
|
210
220
|
|
|
211
221
|
If the agent could not reasonably have known to ask, that is a model gap.
|
|
212
222
|
|
|
223
|
+
Between `package-skill` and `model` on any of these rows: if the fact belongs to
|
|
224
|
+
one entity, put it on that entity's `#(doc)`, because a doc reaches every agent
|
|
225
|
+
whether or not it loaded a guide. Reach for `package-skill` when the fact spans
|
|
226
|
+
entities or is a rule rather than a property: which source to prefer, what the
|
|
227
|
+
house default population is, what this business calls things.
|
|
228
|
+
|
|
213
229
|
### get_context / model
|
|
214
230
|
|
|
215
231
|
| Code | When | Owner |
|
|
@@ -218,8 +234,8 @@ If the agent could not reasonably have known to ask, that is a model gap.
|
|
|
218
234
|
| `NOT-RETURNED` | it exists, the ask was on target, it never came back | model: labels, docs, synonyms, index |
|
|
219
235
|
| `LOW-RANK` | returned, buried under noise the agent reasonably skipped | model |
|
|
220
236
|
| `AMBIGUOUS` | several candidates and nothing says which this question means. Judged over the entities AND their docs: a `#(doc)` that resolves the choice makes it `MODELLED`, which is why a sentence of documentation is usually the whole fix | model: "use X for …, Y when …" |
|
|
221
|
-
| `GUIDANCE-NOT-RETRIEVED` | entities came back, governing guidance did not | model: put guidance on the entities agents search for |
|
|
222
|
-
| `GUIDANCE-DECLINED` | guidance was retrieved and judged inapplicable | model: state the business default, not a caveat |
|
|
237
|
+
| `GUIDANCE-NOT-RETRIEVED` | entities came back, governing guidance did not | model: put guidance on the entities agents search for. package-skill when it governs several sources and belongs on none |
|
|
238
|
+
| `GUIDANCE-DECLINED` | guidance was retrieved and judged inapplicable | model: state the business default, not a caveat. package-skill if the default is a house rule rather than a property of one field |
|
|
223
239
|
|
|
224
240
|
**Before diagnosing a `NOT-RETURNED`, check the call actually returned
|
|
225
241
|
nothing.** The `tool_call` event carries `retrieval_mode` and `rankedSummary`.
|
|
@@ -254,7 +270,7 @@ with both queries. Otherwise it is still `NOT-RETURNED` / `LOW-RANK`.
|
|
|
254
270
|
|
|
255
271
|
| Code | When | Owner |
|
|
256
272
|
|---|---|---|
|
|
257
|
-
| `WRONG-PICK` | needed entity returned, used a different one | model if indistinguishable; agent-skill if docs distinguished them |
|
|
273
|
+
| `WRONG-PICK` | needed entity returned, used a different one | model if indistinguishable; package-skill if choosing between them is a rule of this package; agent-skill if the docs already distinguished them |
|
|
258
274
|
| `SCOPE` | right entities, wrong population | model if the scope rule was undocumented |
|
|
259
275
|
| `GRAIN` | right entities, wrong grain | model or agent-skill |
|
|
260
276
|
| `FILTER-LITERAL` | filter literal did not match stored values | model (document the stored form) and agent-skill |
|
|
@@ -352,9 +368,10 @@ convention explains both. Same `owner` and same `component` is a hint, never a
|
|
|
352
368
|
criterion: two `MISSING` issues about different missing entities are two
|
|
353
369
|
clusters, and merging them produces an edit that fixes neither cleanly.
|
|
354
370
|
|
|
355
|
-
Order clusters by how many cases they would fix. Cluster
|
|
356
|
-
|
|
357
|
-
|
|
371
|
+
Order clusters by how many cases they would fix. Cluster every owner in its own
|
|
372
|
+
clusters, so nothing is lost on the way to the backlog, and keep the owners
|
|
373
|
+
separate: `model` and `package-skill` each license a different edit, and the
|
|
374
|
+
rest license none here.
|
|
358
375
|
|
|
359
376
|
Say what you considered merging and chose not to. A cluster is a claim that one
|
|
360
377
|
change fixes N cases, and the near-misses are what a reviewer needs to falsify
|
|
@@ -381,13 +398,20 @@ can design the edit is the whole job here; the edit itself is
|
|
|
381
398
|
`skill:eval-improve`, working from this. A cluster carrying an honest open
|
|
382
399
|
question is more useful than one carrying a remedy nobody probed.
|
|
383
400
|
|
|
384
|
-
|
|
385
|
-
the
|
|
401
|
+
**`owner: model` and `owner: package-skill` proceed to `eval-improve`.** Both
|
|
402
|
+
are fixable in the model repo, so both are checkpointed and rolled back by the
|
|
403
|
+
same git commit. They are still separate owners, because the edit they license
|
|
404
|
+
is different and the acceptance check treats them differently: a model edit
|
|
405
|
+
changes what the data means, a package-skill edit changes what the agent is told.
|
|
406
|
+
|
|
407
|
+
`owner: agent-skill` findings go back into the shared analysis or
|
|
408
|
+
phrase-detection skill, outside this loop. Retrieval findings go to the tool.
|
|
386
409
|
`BAD-REFERENCE` and `AMBIGUOUS-REFERENCE` go to the golden side door in
|
|
387
410
|
`skill:eval-loop` (repair or hold). Do not send them to improve. Other dataset
|
|
388
411
|
findings (a bad question, a case worth excluding) go back to the case in
|
|
389
|
-
`cases.jsonl` via the conductor. Routing a
|
|
390
|
-
|
|
412
|
+
`cases.jsonl` via the conductor. Routing a guidance bug into the model is how
|
|
413
|
+
models accumulate scar tissue: if the entity was there, correctly defined, and
|
|
414
|
+
the agent still misused it, the fix is guidance, not a new field.
|
|
391
415
|
|
|
392
416
|
## Anti-patterns
|
|
393
417
|
|
|
@@ -401,5 +425,6 @@ how models accumulate scar tissue.
|
|
|
401
425
|
## Related skills
|
|
402
426
|
|
|
403
427
|
- `skill:eval-answer`: the score this consumes.
|
|
404
|
-
- `skill:eval-improve`: smallest
|
|
428
|
+
- `skill:eval-improve`: the smallest edit, for `owner: model` and
|
|
429
|
+
`owner: package-skill`.
|
|
405
430
|
- `skill:eval-loop`: golden hold/repair, the acceptance check, and checkpoint.
|
|
@@ -76,6 +76,7 @@ WHERE_BY_OWNER = {
|
|
|
76
76
|
"retrieval": NOT_RETURNED[2],
|
|
77
77
|
"agent-skill": DELIVERED[2],
|
|
78
78
|
"dataset": "dataset",
|
|
79
|
+
"package-skill": "package guidance",
|
|
79
80
|
}
|
|
80
81
|
# Which artifact the edit lands in. An owner nobody has named yet -- eval-
|
|
81
82
|
# diagnose has not run, or it ran and declined (`undecided` for a
|
|
@@ -87,6 +88,7 @@ LEVER_BY_OWNER = {
|
|
|
87
88
|
"retrieval": "retrieval",
|
|
88
89
|
"agent-skill": "skill",
|
|
89
90
|
"dataset": "dataset",
|
|
91
|
+
"package-skill": "package-skill",
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
|
|
@@ -94,7 +94,7 @@ READ_ONLY = (*NO_EDITS, *NO_SHELL)
|
|
|
94
94
|
CODE_IN_TABLE = re.compile(r"^\|\s*`([A-Z][A-Z_-]+)`\s*\|", re.M)
|
|
95
95
|
COMPONENTS = ("dataset", "agent-call", "get_context/model",
|
|
96
96
|
"get_context/retrieval", "construction", "model-definition")
|
|
97
|
-
OWNERS = ("model", "retrieval", "agent-skill", "dataset")
|
|
97
|
+
OWNERS = ("model", "package-skill", "retrieval", "agent-skill", "dataset")
|
|
98
98
|
SUFFICIENCY = ("sufficient", "insufficient", "unknown")
|
|
99
99
|
SEVERITY = ("low", "medium", "high")
|
|
100
100
|
|
|
@@ -1108,11 +1108,18 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1108
1108
|
if invalid:
|
|
1109
1109
|
print(f"{len(invalid)} broke the skill's vocabulary "
|
|
1110
1110
|
f"(see `_invalid` in diagnoses.jsonl)")
|
|
1111
|
+
# Both owners are fixable in the model repo, so both are what improve may
|
|
1112
|
+
# touch. Counted separately because they license different edits and the
|
|
1113
|
+
# acceptance check treats them differently.
|
|
1114
|
+
fixable = ("model", "package-skill")
|
|
1111
1115
|
actionable = sum(len(c.get("qids", []))
|
|
1112
1116
|
for c in clusters.get("clusters", [])
|
|
1113
|
-
if c.get("owner")
|
|
1114
|
-
|
|
1115
|
-
|
|
1117
|
+
if c.get("owner") in fixable)
|
|
1118
|
+
by_owner = ", ".join(
|
|
1119
|
+
f"{o}: {sum(len(c.get('qids', [])) for c in clusters.get('clusters', []) if c.get('owner') == o)}"
|
|
1120
|
+
for o in fixable)
|
|
1121
|
+
print(f"{actionable} cases sit behind a cluster eval-improve may touch "
|
|
1122
|
+
f"({by_owner})")
|
|
1116
1123
|
print(f"cost ${spend:.2f}")
|
|
1117
1124
|
return 0
|
|
1118
1125
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: eval-import
|
|
3
3
|
description: 'Turn a list of questions into an eval set, whatever shape it arrived in: a JSONL a customer sent, a CSV, a spreadsheet export, a markdown doc, an email thread, or a pull from production logs. Classify each item by what came WITH the question (a query, a number, prose criteria, or nothing), write cases.jsonl and set.json per reference/case-format.md, keep the file as it arrived, and seal each question so a later edit is detectable. Never marks a value-bearing golden verified and never invents a question. Use when questions arrive from outside and need to become a set, or before the first run against a set nobody here authored.'
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.39
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Import questions into an eval set
|
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: eval-improve
|
|
3
|
-
description: 'Make the smallest safe
|
|
4
|
-
version: 0.1.
|
|
3
|
+
description: 'Make the smallest safe edit that closes a diagnosed gap the model repo owns: a Malloy model edit, or a package skill under skills/. Every factual claim carries a probe receipt. Use after eval-diagnose, or when asked to fix a model so an agent can discover the right answer. Never accepts its own edit; the acceptance check belongs to eval-loop. Does not decide whether an answer was wrong (eval-answer) or why (eval-diagnose).'
|
|
4
|
+
version: 0.1.39
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Improve the Model
|
|
8
8
|
|
|
9
|
-
Takes an issue with `owner: model` and produces **one
|
|
10
|
-
the gap. Every factual claim is backed by a query
|
|
9
|
+
Takes an issue with `owner: model` or `owner: package-skill` and produces **one
|
|
10
|
+
smallest edit** that closes the gap. Every factual claim is backed by a query
|
|
11
|
+
you ran.
|
|
12
|
+
|
|
13
|
+
The two owners license different edits and are never interchangeable:
|
|
14
|
+
|
|
15
|
+
- **`model`** changes what the data means or how it is described:
|
|
16
|
+
a `.malloy` file.
|
|
17
|
+
- **`package-skill`** changes what the agent is told about using this package:
|
|
18
|
+
a file under the package's `skills/`. Use it for a rule that spans entities or
|
|
19
|
+
is not a property of any one of them. A fact about a single field belongs in
|
|
20
|
+
that field's `#(doc)`, which reaches every agent whether or not it loaded a
|
|
21
|
+
guide.
|
|
22
|
+
|
|
23
|
+
Both live in the model repo, so one git commit checkpoints and rolls back
|
|
24
|
+
either.
|
|
11
25
|
|
|
12
26
|
**Two hard boundaries:**
|
|
13
27
|
|
|
@@ -30,11 +44,18 @@ the gap. Every factual claim is backed by a query you ran.
|
|
|
30
44
|
| Doubt only, or retrieval-only (no verdict) | Docs, labels, index only, and only where the transcript shows a concrete confusion. |
|
|
31
45
|
| Silence | **No edit.** |
|
|
32
46
|
|
|
47
|
+
A `package-skill` edit sits at the docs tier throughout: it is permitted
|
|
48
|
+
wherever the table permits docs, and never on silence. It is the easiest artifact
|
|
49
|
+
in this loop to overfit, because it is prompt text sitting next to the question
|
|
50
|
+
it just failed, so the expert test in Step 1 applies to it word for word.
|
|
51
|
+
|
|
33
52
|
Do not edit for `BAD-REFERENCE` or `AMBIGUOUS-REFERENCE`. Those are the
|
|
34
53
|
golden side door in `skill:eval-loop`: repair or hold the golden, bump
|
|
35
54
|
`goldenRevision` on the case, and open a new baseline run. Being right and unmatched
|
|
36
|
-
beats encoding a defect or an unsettled key. Do not edit for
|
|
37
|
-
retrieval, or dataset owner
|
|
55
|
+
beats encoding a defect or an unsettled key. Do not edit for an `agent-skill`,
|
|
56
|
+
retrieval, or dataset owner: `agent-skill` means the shared skill, which is not
|
|
57
|
+
in this repo, and widening it by editing one package's copy hides the gap from
|
|
58
|
+
every other package.
|
|
38
59
|
|
|
39
60
|
## Step 1: What a correct answer may teach
|
|
40
61
|
|
|
@@ -66,6 +87,20 @@ A false `primary_key` compiles and silently corrupts every aggregate. Of one
|
|
|
66
87
|
pilot's 11 accepted edits, 4 of 5 wrong ones died to a single
|
|
67
88
|
`COUNT(*)` vs `COUNT(DISTINCT …)` probe that was never run.
|
|
68
89
|
|
|
90
|
+
**A package-skill edit still needs probes, for a different reason.** It makes no
|
|
91
|
+
claim about a join key, so there is nothing structural to verify, and the
|
|
92
|
+
`probes` array can legitimately be short. But a guide that states a fact about
|
|
93
|
+
the data is asserting it to every agent that reads it, with no compiler and no
|
|
94
|
+
query to contradict it. Probe every number and every claim about what a measure
|
|
95
|
+
counts before writing it into a guide, exactly as you would before writing it
|
|
96
|
+
into a `#(doc)`. A guide that quotes a stale figure is worse than one that
|
|
97
|
+
quotes none: it is confidently wrong at the moment of use.
|
|
98
|
+
|
|
99
|
+
Its reload receipt is the ordinary one. Skill files are part of the package's
|
|
100
|
+
content hash, so a skill-only edit moves `sourceContentSha` and an unchanged sha
|
|
101
|
+
means the edit did not reach what is served. After the reload, read the guide
|
|
102
|
+
back from the server and confirm it is your text.
|
|
103
|
+
|
|
69
104
|
Compile-check the edit before saving (scope `file` for an edit), then reload
|
|
70
105
|
the package. Confirm it is not serving a stale model.
|
|
71
106
|
|
|
@@ -98,6 +133,11 @@ displace answers that already worked.
|
|
|
98
133
|
| 4 | Declared join on a *probed* key |
|
|
99
134
|
| 5 | A new source: last resort, at most one |
|
|
100
135
|
|
|
136
|
+
For a `package-skill` issue the ranking is the same shape, and rung 1 is still
|
|
137
|
+
the model: if the fact fits on one entity's `#(doc)`, put it there instead. A
|
|
138
|
+
guide is the right home only for what a doc cannot hold, and it reaches only the
|
|
139
|
+
agents that read it.
|
|
140
|
+
|
|
101
141
|
### Four ways a doc edit backfires, each measured
|
|
102
142
|
|
|
103
143
|
**Do not put an emphatic claim next to an unsettled one.** A dimension doc was
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
python improve.py --run <workdir>/runs/<label> --set <set-dir> --watch-mode
|
|
5
5
|
# the model directory comes from [model] repo in the set's eval.toml
|
|
6
6
|
|
|
7
|
-
One agent per `owner: model`
|
|
8
|
-
producing at most one smallest edit with
|
|
9
|
-
event per cluster to the run's
|
|
7
|
+
One agent per fixable cluster (`owner: model` or `owner: package-skill`), each
|
|
8
|
+
holding `skill:eval-improve`, each producing at most one smallest edit with
|
|
9
|
+
probe receipts. Appends one `candidate` event per cluster to the run's
|
|
10
|
+
`events.jsonl`.
|
|
10
11
|
|
|
11
12
|
THIS SCRIPT CONTAINS NO MODELING DOCTRINE
|
|
12
13
|
|
|
@@ -71,13 +72,13 @@ IMPROVE_TOOLS = ("mcp__publisher__get_context",
|
|
|
71
72
|
"mcp__publisher__execute_query",
|
|
72
73
|
"mcp__publisher__compile_model",
|
|
73
74
|
"mcp__publisher__reload_package",
|
|
75
|
+
"mcp__publisher__get_skill",
|
|
76
|
+
"mcp__publisher__get_status",
|
|
74
77
|
"Read", "Edit", "Write", "Grep", "Glob",
|
|
75
78
|
"Bash(bash ./sync_and_reload.sh)")
|
|
76
79
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
-H "Accept: application/json, text/event-stream" \\
|
|
80
|
-
-d '{{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{{"name":"reload_package","arguments":{{"environmentName":"{environment}","packageName":"{package}"}}}}}}' \\
|
|
80
|
+
# REST, not MCP: an MCP reply escapes its inner JSON, so grepping it for the sha matches nothing.
|
|
81
|
+
RELOAD = """curl -s -m 60 "{rest_url}/api/v0/environments/{environment}/packages/{package}?reload=true" \\
|
|
81
82
|
| grep -o '"sourceContentSha":"[^"]*"' | head -1"""
|
|
82
83
|
|
|
83
84
|
SYNC_SCRIPT = """#!/bin/bash
|
|
@@ -87,11 +88,31 @@ echo "reloaded; if sourceContentSha did not change, the edit did not reach the s
|
|
|
87
88
|
"""
|
|
88
89
|
|
|
89
90
|
|
|
91
|
+
SYNC_HELPER = "sync_and_reload.sh"
|
|
92
|
+
|
|
93
|
+
|
|
90
94
|
def git(args: list[str], cwd: pathlib.Path) -> str:
|
|
91
95
|
return subprocess.run(["git", *args], cwd=cwd, capture_output=True,
|
|
92
96
|
text=True).stdout
|
|
93
97
|
|
|
94
98
|
|
|
99
|
+
def candidate_diff(model_dir: pathlib.Path, extra: tuple[str, ...] = ()) -> str:
|
|
100
|
+
"""The agent's edit: tracked changes plus new files, minus our own helper.
|
|
101
|
+
|
|
102
|
+
`git diff` alone omits untracked files, so a new skills/ file left an empty
|
|
103
|
+
patch that `git clean` then deleted. `--no-index` diffs each against
|
|
104
|
+
/dev/null without touching the index.
|
|
105
|
+
"""
|
|
106
|
+
out = git(["diff", *extra, "--", "."], model_dir)
|
|
107
|
+
new = git(["ls-files", "--others", "--exclude-standard", "--", "."],
|
|
108
|
+
model_dir).splitlines()
|
|
109
|
+
for f in new:
|
|
110
|
+
if f != SYNC_HELPER:
|
|
111
|
+
out += git(["diff", "--no-index", *extra, "--", "/dev/null", f],
|
|
112
|
+
model_dir)
|
|
113
|
+
return out
|
|
114
|
+
|
|
115
|
+
|
|
95
116
|
# Thin on purpose: the cluster, the cases, the one operational fact the skill
|
|
96
117
|
# cannot know (how to reach the server), and the output shape. Everything about
|
|
97
118
|
# how to choose an edit is in the loaded skill.
|
|
@@ -111,7 +132,8 @@ MAKING YOUR EDIT REACH THE SERVER
|
|
|
111
132
|
|
|
112
133
|
After each edit run `bash ./sync_and_reload.sh`. It prints `sourceContentSha`.
|
|
113
134
|
If that value does not change, your edit did NOT reach the server and every
|
|
114
|
-
probe you run afterwards is testing the old model.
|
|
135
|
+
probe you run afterwards is testing the old model. This covers a package skill
|
|
136
|
+
under skills/ as well as a .malloy file: both are part of the hash.
|
|
115
137
|
|
|
116
138
|
One edit for the cluster's shared root cause, not one per case.
|
|
117
139
|
|
|
@@ -259,7 +281,8 @@ def improve_cluster(issue: dict[str, Any], cases: dict[str, Any],
|
|
|
259
281
|
return {**json.loads(out.read_text()), "_cached": True}
|
|
260
282
|
d.mkdir(parents=True, exist_ok=True)
|
|
261
283
|
|
|
262
|
-
reload_cmd = RELOAD.format(
|
|
284
|
+
reload_cmd = RELOAD.format(rest_url=a.rest_url.rstrip("/"),
|
|
285
|
+
environment=a.environment,
|
|
263
286
|
package=a.package)
|
|
264
287
|
sync = ""
|
|
265
288
|
if not a.watch_mode:
|
|
@@ -268,7 +291,7 @@ def improve_cluster(issue: dict[str, Any], cases: dict[str, Any],
|
|
|
268
291
|
if served:
|
|
269
292
|
sync = (f'rsync -a --delete --exclude .git '
|
|
270
293
|
f'"{a.model_dir.resolve()}/" "{served.resolve()}/"\n')
|
|
271
|
-
helper = a.model_dir /
|
|
294
|
+
helper = a.model_dir / SYNC_HELPER
|
|
272
295
|
helper.write_text(SYNC_SCRIPT.format(sync=sync, reload=reload_cmd))
|
|
273
296
|
helper.chmod(0o755)
|
|
274
297
|
|
|
@@ -298,9 +321,9 @@ def improve_cluster(issue: dict[str, Any], cases: dict[str, Any],
|
|
|
298
321
|
save_transcript=d / "improver.jsonl")
|
|
299
322
|
(d / "improver.md").write_text(r.text)
|
|
300
323
|
|
|
301
|
-
diff =
|
|
324
|
+
diff = candidate_diff(a.model_dir)
|
|
302
325
|
(d / "edit.patch").write_text(diff)
|
|
303
|
-
stat =
|
|
326
|
+
stat = candidate_diff(a.model_dir, ("--stat",)).strip()
|
|
304
327
|
|
|
305
328
|
# While the edit is still in the tree: the mechanical half of Step 4. Only
|
|
306
329
|
# catches drift and rubric sentences that contradict the model -- a golden
|
|
@@ -347,6 +370,10 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
347
370
|
ap.add_argument("--environment", default=None)
|
|
348
371
|
ap.add_argument("--package", default=None)
|
|
349
372
|
ap.add_argument("--mcp-url", default=None)
|
|
373
|
+
ap.add_argument("--rest-url", default=None,
|
|
374
|
+
help="REST base; the reload receipt is read from here, "
|
|
375
|
+
"because the MCP reply double-encodes its JSON. "
|
|
376
|
+
"Default: the [model] server in the set's eval.toml")
|
|
350
377
|
# No built-in port: a fixed default once named the server holding the
|
|
351
378
|
# model under test, which cannot verify its own goldens. The fallback is
|
|
352
379
|
# the [truth] section of the set's eval.toml. With neither, the value
|
|
@@ -406,6 +433,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
406
433
|
"--environment")
|
|
407
434
|
a.package = cfg.need(a.package, "model", "package", "--package")
|
|
408
435
|
a.mcp_url = a.mcp_url or cfg.model_mcp_url()
|
|
436
|
+
a.rest_url = a.rest_url or cfg.model_publisher()
|
|
409
437
|
a.truth_publisher = a.truth_publisher or cfg.truth_publisher()
|
|
410
438
|
a.truth_environment = a.truth_environment or cfg.get("truth", "environment")
|
|
411
439
|
a.skills_root = a.skills_root or cfg.get("paths", "skills_root")
|
|
@@ -439,7 +467,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
439
467
|
if e.get("kind") == "issue_status":
|
|
440
468
|
status[e["issue_id"]] = e["status"]
|
|
441
469
|
issues = [e for e in events if e.get("kind") == "issue"
|
|
442
|
-
and e.get("owner")
|
|
470
|
+
and e.get("owner") in ("model", "package-skill")
|
|
443
471
|
and status.get(e["issue_id"]) == "open"]
|
|
444
472
|
if a.only:
|
|
445
473
|
want = {x.strip() for x in a.only.split(",")}
|
|
@@ -450,13 +478,14 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
450
478
|
if a.limit is not None:
|
|
451
479
|
issues = issues[:a.limit]
|
|
452
480
|
if not issues:
|
|
453
|
-
print("no open model-owned issues; run diagnose.py
|
|
454
|
-
"cluster is owned by retrieval, agent-skill,
|
|
481
|
+
print("no open model- or package-skill-owned issues; run diagnose.py "
|
|
482
|
+
"first, or every cluster is owned by retrieval, agent-skill, "
|
|
483
|
+
"or dataset")
|
|
455
484
|
return 0
|
|
456
485
|
|
|
457
486
|
art = a.run / "artifacts"
|
|
458
487
|
art.mkdir(parents=True, exist_ok=True)
|
|
459
|
-
print(f"{len(issues)} model-
|
|
488
|
+
print(f"{len(issues)} model/package-skill clusters, agent {a.model}, "
|
|
460
489
|
f"{'isolated' if a.isolate else 'cumulative'}, "
|
|
461
490
|
f"{'watch mode' if a.watch_mode else 'snapshot sync'}")
|
|
462
491
|
|
|
@@ -247,5 +247,40 @@ class ClusterMembers(unittest.TestCase):
|
|
|
247
247
|
"golden": [{"region": "West", "n": 3}],
|
|
248
248
|
"rubric": "West has 3."}])
|
|
249
249
|
|
|
250
|
+
class CandidateDiff(unittest.TestCase):
|
|
251
|
+
"""A new file is part of the edit, and our own helper is not."""
|
|
252
|
+
|
|
253
|
+
def setUp(self):
|
|
254
|
+
self.repo = pathlib.Path(tempfile.mkdtemp())
|
|
255
|
+
self.addCleanup(shutil.rmtree, self.repo, True)
|
|
256
|
+
for cmd in (["init", "-q"], ["config", "user.email", "t@t"],
|
|
257
|
+
["config", "user.name", "t"]):
|
|
258
|
+
subprocess.run(["git", *cmd], cwd=self.repo, check=True)
|
|
259
|
+
(self.repo / "m.malloy").write_text("source: a is x\n")
|
|
260
|
+
subprocess.run(["git", "add", "."], cwd=self.repo, check=True)
|
|
261
|
+
subprocess.run(["git", "commit", "-qm", "init"], cwd=self.repo, check=True)
|
|
262
|
+
|
|
263
|
+
def test_a_new_skills_file_is_in_the_patch_and_the_helper_is_not(self):
|
|
264
|
+
(self.repo / "skills" / "s").mkdir(parents=True)
|
|
265
|
+
(self.repo / "skills" / "s" / "SKILL.md").write_text("NEW GUIDE\n")
|
|
266
|
+
(self.repo / improve.SYNC_HELPER).write_text("#!/bin/bash\n")
|
|
267
|
+
(self.repo / "m.malloy").write_text("source: a is y\n")
|
|
268
|
+
diff = improve.candidate_diff(self.repo)
|
|
269
|
+
self.assertIn("NEW GUIDE", diff)
|
|
270
|
+
self.assertIn("source: a is y", diff)
|
|
271
|
+
self.assertNotIn(improve.SYNC_HELPER, diff)
|
|
272
|
+
self.assertIn("SKILL.md", improve.candidate_diff(self.repo, ("--stat",)))
|
|
273
|
+
|
|
274
|
+
def test_only_the_helper_is_an_empty_diff(self):
|
|
275
|
+
(self.repo / improve.SYNC_HELPER).write_text("#!/bin/bash\n")
|
|
276
|
+
self.assertEqual(improve.candidate_diff(self.repo).strip(), "")
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class Grants(unittest.TestCase):
|
|
280
|
+
def test_the_tools_the_skill_names_are_granted(self):
|
|
281
|
+
for t in ("get_skill", "get_status"):
|
|
282
|
+
self.assertIn(f"mcp__publisher__{t}", improve.IMPROVE_TOOLS)
|
|
283
|
+
|
|
284
|
+
|
|
250
285
|
if __name__ == "__main__":
|
|
251
286
|
unittest.main()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: eval-judge
|
|
3
3
|
description: 'Decide whether ONE answer matches its golden, and say whether you believe the golden. Read this before emitting any verdict. Covers containment, column pairing, near_match, refusals, and the gold_status judgement. Use when scoring an attempt in an evaluation run; never to conduct a run (eval-loop), diagnose a failure (eval-diagnose) or edit a model (eval-improve).'
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.39
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# The judge
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: eval-loop
|
|
3
3
|
description: 'Conduct a local Publisher evaluation loop in five steps: scrape/run, eval, diagnose, improve, checkpoint. You are the conductor: import cases into the file ledger, spawn a blind answerer, then run eval-answer, eval-diagnose, and eval-improve. Persistence is plain files: the set in the model package''s evals/ directory, runs in the set''s workdir; checkpoints are git commits of the model repo. `eval.py` runs each step from the set''s eval.toml. Use to score a model, diagnose failures, improve behind an acceptance check, or roll back a bad direction.'
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.39
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# The Evaluation Loop
|
|
@@ -523,7 +523,8 @@ conductor. Do not:
|
|
|
523
523
|
|
|
524
524
|
## Prime directives
|
|
525
525
|
|
|
526
|
-
-
|
|
526
|
+
- Improve edits the model repo and nothing else: a `.malloy` file, or a package
|
|
527
|
+
skill under the package's `skills/`. No question text, qids, or
|
|
527
528
|
expected values in any name, doc, or comment.
|
|
528
529
|
- **You are measuring a model, not reviewing this harness.** Read a script when
|
|
529
530
|
a number you have to report cannot be explained otherwise, and stop there. Do
|
|
@@ -50,6 +50,18 @@ Acceptance rules (replacing any vague "results improve"):
|
|
|
50
50
|
- Independent deterministic justification (a probed-wrong definition
|
|
51
51
|
corrected) may accept without a measured win. Record that as the acceptance check
|
|
52
52
|
`reason`.
|
|
53
|
+
- **Package-skill edits (`class: skill`) get the full rules, never the
|
|
54
|
+
documentation shortcut.** A `get_context` probe proves an entity is
|
|
55
|
+
discoverable. Nothing deterministic proves an agent read a guide and behaved
|
|
56
|
+
differently, so the only evidence a skill edit has is a fresh blind
|
|
57
|
+
re-answer: both splits, twice, clearing the flip-count bar. Treat the
|
|
58
|
+
documentation shortcut as unavailable here even when the edit reads like
|
|
59
|
+
documentation, because a guide is prompt text written next to the case it
|
|
60
|
+
failed and is the easiest artifact in this loop to overfit.
|
|
61
|
+
|
|
62
|
+
Read the diff before measuring it, too. A guide that names the case, quotes
|
|
63
|
+
its expected number, or encodes the shape of one question fails the expert
|
|
64
|
+
test in `skill:eval-improve` and is rejected without a run.
|
|
53
65
|
|
|
54
66
|
Write the `acceptance_check` event (decision, class, baseline and final run ids,
|
|
55
67
|
regressions, holdout delta, reason) BEFORE any commit, so a rejected
|