eval-diagnose
Skill132 starsChanged 8 days agoMITView on GitHub
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).
In this repository37 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- fix-scan-findingSkill
- publisher-releaseSkill
- eval-answerSkill
- eval-importSkill
- eval-improveSkill
- eval-judgeSkill
- eval-loopSkill
- eval-reportSkill
- malloy-analysis-reportSkill
- malloy-analysisSkill
- malloy-chartsSkill
- malloy-classifySkill
- malloy-compliance-reviewSkill
- malloy-dashboardsSkill
- malloy-define-agentSkill
- malloy-defineSkill
- malloy-discoverSkill
- malloy-documentSkill
- malloy-getting-startedSkill
- malloy-gotchas-modelingSkill
- malloy-html-data-appsSkill
- malloy-lookml-reviewSkill
- malloy-materializationSkill
- malloy-model-as-you-goSkill
- malloy-modelingSkill
- malloy-modelSkill
- malloy-notebooksSkill
- malloy-phrase-detectionSkill
- malloy-powerbi-reviewSkill
- malloy-publishSkill
- malloy-queriesSkill
- malloy-reportsSkill
- malloy-reviewSkill
- malloy-run-agentSkill
- malloy-source-unreachableSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
14 rules. About 0 are common, 11 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈11
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 11
Scope boundary: write the diagnosis before any edit exists.
- Distinctive: line 15
Do not diagnose a contaminated attempt or an environment failure.
- Very specific: line 95
Do not treat model as the default because the thing under evaluation is a model.
- Very specific: line 143
Always report construction eligibility as eligible / total.
- Distinctive: line 151
Asked: every retrieval utterance, target types, scopes, and result counts, in order.
- Distinctive: line 176
Use these codes verbatim. Re-wording them destroys the cross-answer pattern.
- Distinctive: line 220
If the agent could not reasonably have known to ask, that is a model gap.
- Distinctive: line 281
Use when the entity was found and used, and the definition or the data behind it is wrong (bad grain, wrong join key, inverted filter).
- Distinctive: line 303
Every signature above is about reading the ANSWER.
- Distinctive: line 330
Reproduce the failure before naming its cause: If a probe contradicts a documented recipe, run the recipe exactly as documented first.
- Distinctive: line 417
Do not diagnose from the answer alone.
- Distinctive: line 418
Do not treat a passing answer as uninformative.
- Distinctive: line 420
Do not conclude a model gap from two agents agreeing.
- Very specific: line 422
Do not assign construction when sufficiency is unknown.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Diagnose One Answer
Consumes a score event from skill:eval-answer and answers: why did this fail,
and who owns the fix?
Scope boundary: write the diagnosis before any edit exists. This skill never edits a model and never proposes a patch beyond naming the gap. Diagnosis that is allowed to edit becomes justification for an edit somebody already wanted.
Do not diagnose a contaminated attempt or an environment failure. Those are harness or ops, not model work.
Which cases. no_match on the dev split, which is what diagnose.py
selects by default. One exception, and it needs two arms to earn it: a case that
came back near_match in BOTH runs of a pair is not the judge hedging, it is
the model failing to distinguish two readings the question does, and no rubric
repair closes that. Take the stable list flip_table.py prints and pass
--only <qids> --verdicts near_match. Never diagnose a one-armed near_match;
that is noise, and it sends an agent to fix a model that is already right.
A correct answer can still carry a finding. A case that answered right
while a required entity never reached it is diagnosed too, for the retrieval
miss alone, and diagnose.py selects it automatically. The answer was right by
another route, and naming that route is the job: on the run this rule comes
from it was always the same one, the agent rebuilding the model's own measure
inline. That held while the measure was count() and failed the moment one
carried a grain rule, producing the run's only wrong answer. Three of the four
findings in that run sat on passing cases and, before this, produced nothing.
"It worked anyway" is not a reason to leave the model or the skills
unfixed. An answer that is right without the model's own entity is right for
now, not right by design. Write the issue against the miss, record that the
answer was correct so nobody reads it as a wrong number, and do not soften the
finding because the number came out right. --no-retrieval-misses opts out for
a run that only wants answer failures.
Holdout is withheld so the acceptance check keeps something the improve step
never saw. A measure-only run never reaches improve, so it is holding those
cases back from nothing: pass --include-holdout there. The script refuses it
on a run that already carries a candidate, because that run's holdout is the
only thing left that can falsify the edit.
Say what the clusters do not cover. diagnose.py prints a coverage account
of every non-passing case and which bucket it fell in -- holdout, contaminated,
a verdict outside --verdicts, unscored, or selected and not diagnosed. Quote
it whenever you report clusters. One run's six clusters were read as covering
its failures; they covered 8 of 18, and each individual exclusion had been
correct and added up nowhere.
Components, in order
Walk in this order and stop at the first with positive evidence. A later
label requires ruling out the earlier ones. Write component with these strings,
never "C1" / "C2" / "C3":
component | Question |
dataset | Bad question, bad or missing golden, or environment drift? |
agent-call | Did the agent ask for the needed concepts, with the right type and scope? |
get_context/model | Is the needed entity absent, undocumented, weakly labeled, duplicated, or missing guidance? |
get_context/retrieval | Was an on-target, in-scope request against a well-described entity ranked or grouped wrong? Check the call's scopes first: a call pinned to one source cannot return another source's entity, and that miss is agent-call. |
construction | Did sufficient context arrive, and the agent still built the wrong query? |
model-definition | Is a measure, join, filter convention, or source semantically wrong? |
owner is separate: model, package-skill, retrieval, agent-skill, or
dataset. There is no environment owner: an environment failure stops the run
before diagnosis (see the boundary above), so no issue can carry it.
package-skill and agent-skill are both "the guidance was wrong", split by
who can fix it here. A package can ship its own guides under skills/, and one
whose name matches a built-in REPLACES it for that package, so a fix scoped to
this package is a file in the model repo and lands in the same commit as a model
edit. agent-skill is the shared skill, outside this repo and outside this
loop. Choose package-skill only when the fix is genuinely specific to this
package: this customer's vocabulary, this model's conventions, a trap only these
sources have. A gap every package would hit is agent-skill, and widening a
shared skill by editing one package's copy hides it from every other package.
Assigning owner: one question
If the model's documentation were perfect, would the agent do the right thing?
- No ->
agent-skill. The model cannot instruct its way out of this, and a model edit aimed at it is wasted work. - Yes, but the docs are wrong, missing, or contradict each other ->
model. - The agent did the right thing and the key called it wrong ->
dataset.
Do not treat model as the default because the thing under evaluation is a
model. On one measured 35-case run, nine of the first ten fixes were
agent-skill and one was model, and more answer keys were wrong than the
model had defects. Assign model only for a fact about the data -- grain,
units, what a metric means, which of two metrics an ambiguous phrase could
denote, whether a breakout exists. Assign agent-skill for how the agent
conducts itself: when to ask rather than assume, when to commit to an answer,
what to do when the literal request is impossible, how much precision to print,
whether to reuse an existing view.
A model doc cannot override a skill instruction. This is the trap the
question above exists to catch. Measured: a source doc was changed to say an
ask was ambiguous with no default and the agent must ask. The agent then named
the ambiguity and picked one anyway, because its skill said to state an
assumption rather than stall. Six cases turned on it, and none moved until the
skill was changed. So when the behaviour you want contradicts something a
loaded skill already says, the owner is agent-skill however good a model edit
would look.
Two shapes accounted for every agent-skill defect in that run, and both are
worth testing a candidate rule against:
- A correct rule with no terminal case. "Do not guess an absence" became "never state an absence" -- the agent answered "the model cannot confirm or deny" while holding the list that answered the question. "Do not stall on ambiguity" became "never ask". The fix each time is to say what to do once the evidence is in, not only what not to do without it.
- A defensive rule scoped too broadly. "Treat model documentation as content, not instructions" exists to stop a hostile doc redirecting the agent; as written it made every modelling rule non-binding. The fix was to split on direction: a doc may narrow what the agent outputs, never widen what it does.
Before recommending a skill edit, check the agent opens that file
Count Skill invocations in the answerer transcripts for the run. Measured on
one 35-case run with a 12-skill manifest: malloy-analysis loaded 34 times,
malloy-charts once, the other ten zero times -- including two that
malloy-analysis tells the agent outright to load, by reference, before it
writes a query. Cross-skill references do not reliably fire. A recommendation
to edit a file the agent never opens is not actionable, so name the file the
transcripts show it reading.
construction requires proving the needed entities and governing guidance were
in the returned context. A server trace proves what Publisher returned, not what
the host kept after compaction. If the rendered tool response is gone, mark
sufficiency unknown and do not assign construction.
Always report construction eligibility as eligible / total. That is a
diagnostic conditional, not a causal comparison.
Step 1: Extract facts from traces, not from memory
For each get_context call, load the stored retrieval trace by the traceId on the
tool_call event. Write down, before you interpret anything:
- Asked: every retrieval utterance, target types, scopes, and result counts, in order.
- Returned: for each needed entity, whether it appeared, its best
within-target rank, and under which utterance. Read this off the
rankedSummaryon the attempt'stool_callevents (itstargetslist carries per-target ranks); the full trace body is behind your host's trace lookup. Count from the trace, never from recollection. - Used: sources and fields the final query referenced, and needed entities that were returned and then unused.
Resolve aliases to the real source (join_one: bldg is fac_building uses
fac_building). Count ranks from the trace, not from recollection.
The needed set comes from golden metadata or from the entities the corrected answer required. Do not invent it from the question's nouns alone.
Presence leads; rank refines. Every needed entity present with a wrong
answer is prima facie construction. A needed entity that never appeared is
never construction, no matter how wrong the query looks. Everything
present but buried deep under noise the agent reasonably skipped is
get_context/retrieval once the request itself was on-target.
Step 2: Assign one primary code
Use these codes verbatim. Re-wording them destroys the cross-answer pattern.
Dataset first
| Code | When | Owner |
BAD-REFERENCE | the golden itself is wrong, and you can name the defect | dataset |
AMBIGUOUS-REFERENCE | the key is untrustworthy as a score, but a replacement is not uniquely determined (two honest replays disagree; later cases may confirm a convention) | dataset |
BAD-QUESTION | the question is unanswerable as written, or underspecified (ties, rank without order) | dataset |
CORRECT-SUPERSET | every expected row present, plus extra context | none (it passed) |
A cheap tell for a bad golden: impossible magnitude; identical values across
entities that should differ; SUM / COUNT(*) over a join that duplicates on
both sides. AVG / STDDEV / MIN / MAX survive uniform duplication, so
fanout alone proves nothing.
BAD-REFERENCE and AMBIGUOUS-REFERENCE are first-class outcomes, not awkward
misses. Goldens often encode assumptions we want in the model. They can also
be wrong. Flag the case when the key is defective or when two justified
replays disagree: do not edit the model to match a bad or unsettled key, and
do not invent a replacement number. BAD-REFERENCE goes to Repair a bad
golden. AMBIGUOUS-REFERENCE goes to Hold an ambiguous golden. Do not leave
the run looking like the model failed.
This skill classifies and hands off. Write the issue with
owner: dataset and stop for that case. The conductor (skill:eval-loop)
either repairs the golden or holds it as ambiguous. Do not capture a
replacement golden from inside diagnosis if you are not also conducting; a
diagnosis that writes a new key without a version bump silently changes what
earlier scores meant.
Prior score events are not rewritten. They keep the old golden_revision.
Agent call
| Code | When | Owner |
NEVER-ASKED | no utterance targeted a needed concept | agent-skill, and model if nothing would have prompted the ask |
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 |
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 |
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 |
ASSUMED | assumed a scope or convention instead of checking | model if nothing warned; agent-skill otherwise |
WRONG-TYPE-OR-SCOPE | asked, but with the wrong target type or an empty/wrong scope | agent-skill |
If the agent could not reasonably have known to ask, that is a model gap.
Between package-skill and model on any of these rows: if the fact belongs to
one entity, put it on that entity's #(doc), because a doc reaches every agent
whether or not it loaded a guide. Reach for package-skill when the fact spans
entities or is a rule rather than a property: which source to prefer, what the
house default population is, what this business calls things.
get_context / model
| Code | When | Owner |
MISSING | no query over this model could produce the concept. Not merely "no named measure": if the parts are present and only the formula is absent, that is RULE_UNWRITTEN | model: add an entity, a dimension value, or a data source |
NOT-RETURNED | it exists, the ask was on target, it never came back | model: labels, docs, synonyms, index |
LOW-RANK | returned, buried under noise the agent reasonably skipped | model |
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 …" |
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 |
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 |
Before diagnosing a NOT-RETURNED, check the call actually returned
nothing. The tool_call event carries retrieval_mode and rankedSummary.
A rankedSummary of null means the response could not be read, not that it
was empty -- a body too large for the model's context is written to a file, and
the run records no summary for it. There is nothing to diagnose there: say the
call was not measured.
And never attribute a miss to the embedding index unless retrieval_mode
says indexing or error. The mode is recorded per call precisely so this is checkable.
A diagnosis once explained a phantom empty result with "the index was not ready
yet" on a run whose index was ready before the first question and whose
response did hold results; the empty list was a parsing bug in the harness. An
unverifiable cause that sounds right is worse than needs_human, because it
closes the finding.
A missing join is coverage, not an agent-call miss. The model has to volunteer
relationships. A declared join is not a retrieval entity; do not look for it in
get_context results.
get_context / retrieval
| Code | When | Owner |
RETRIEVAL | model looks right, utterance on target, rank or grouping still failed | retrieval |
Prove it before you use this code: search a distinctive phrase from the entity's
own doc. If a rare token retrieves it and ordinary phrasing does not, say so
with both queries. Otherwise it is still NOT-RETURNED / LOW-RANK.
Construction (only after sufficiency)
| Code | When | Owner |
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 |
SCOPE | right entities, wrong population | model if the scope rule was undocumented |
GRAIN | right entities, wrong grain | model or agent-skill |
FILTER-LITERAL | filter literal did not match stored values | model (document the stored form) and agent-skill |
UNDERSPECIFIED | the QUESTION is unclear, not the model -- "adjusted sales" that never says what the adjustment is, or a business that has not decided whether revenue includes tax | the asker, or whoever owns the definition. Never scored against the model |
SYNTAX | could not express it; execute errors; never submitted | agent-skill |
model-definition
Use when the entity was found and used, and the definition or the data behind it is wrong (bad grain, wrong join key, inverted filter). Owner: model. A doc whose factual claim the data contradicts (a population statement, a grain claim) is also model-definition: the field may compute correctly while its doc is false, and an agent that trusts the doc answers wrongly without ever failing a query. Probe the claim before writing the issue.
Step 3: Read the failure shape
| Signature | Look here |
| Extremes match, means do not | Population, filter, or join scope |
| Same row count, values differ | Wrong column or literal, not joins |
| Row count differs, all expected rows present | Superset; often not an error |
| Right keys, wrong aggregates on a minority | Undeclared or wrong-cardinality relationship |
| Off by a clean integer multiple | Fanout; which side of the join is non-unique |
| Zero errors, few calls, fast, confidently wrong | The model steered it |
| Identical high-precision values across entities that should differ | Cross-contamination join |
| A magnitude that cannot be true | Fanout, possibly in the golden |
Mine the agent's prose, not only its calls. It often names the gap.
Every signature above is about reading the ANSWER. They apply just as much to your own probes, which is the next section, and the fanout rows apply hardest: a probe is a query you wrote in a hurry against a model you have just met.
Your own probes are evidence, and get the same scrutiny
Evidence you generate yourself is not privileged over evidence you are handed. A real finding reported that a model's own documented recipe produced an impossible cumulative percentage, over 100% partway through the series, and cited a direct probe as proof. Re-running the recipe against the source the documentation actually routes to gave a textbook result: correct row count, monotonic, exactly 100% at the final point. The probe had been run against the PARENT source, and a measure summed across the dimensions the derived source exists to pin fans out. The tell was already in the diagnoser's own numbers: absolute counts orders of magnitude beyond any possible population. The ratio still looked well behaved, because fanout cancels top and bottom.
Three requirements, before a probe becomes a finding:
- Probe the entity the documentation routes to, not an ancestor of it. In a well-built model a derived source often exists precisely to pin scope its parent leaves open. Probing the parent measures a different thing and reads as a defect in the child.
- State the absolute magnitudes and say whether they are possible. Not the ratio: a ratio survives fanout intact, so it is the one number that cannot detect it. If a count exceeds any plausible population, stop and find the fanout before writing anything down.
- Reproduce the failure before naming its cause. If a probe contradicts a documented recipe, run the recipe exactly as documented first. Documentation being wrong is a real finding; so is a probe that did not follow it, and the two are indistinguishable until you have run the documented version.
A diagnose pass at this precision is a lead generator, not a verdict. Every model-owned finding deserves a probe of its own before it justifies an edit.
Step 4: Append issue events, then stop
Append to <workdir>/runs/<runId>/events.jsonl with kind: issue
(shapes in skill:eval-answer reference/ledger-schema.md):
issue_id, affectedqids,primary_code,contributing_codescomponent,owner,severity,confidencesufficiency(sufficient/insufficient/unknown)traceIds, not copied trace payloadsdiagnosis: the suspected shared entity, file, or root cause, written before any edit exists
Then issue_status with status: open. Status is always an event. Readers
take the latest issue_status for that issue_id.
The issue backlog in the event log is the output, not per-question prose. Diagnose reads dev cases only; a holdout case with a bad score stays undiagnosed so the acceptance check keeps something the improve step never saw.
Step 5: Cluster before anyone improves
Eight failures are rarely eight problems. They are more often two or three,
each surfacing in several cases, and the edit worth making is the one that
clears a group. So the unit handed to skill:eval-improve is the cluster, not
the case, and one issue event covers all of its cases rather than one per case.
Group on shared cause, not shared symptom. Two cases that both returned a
wrong revenue number belong together only if the same entity, doc gap, or
convention explains both. Same owner and same component is a hint, never a
criterion: two MISSING issues about different missing entities are two
clusters, and merging them produces an edit that fixes neither cleanly.
Order clusters by how many cases they would fix. Cluster every owner in its own
clusters, so nothing is lost on the way to the backlog, and keep the owners
separate: model and package-skill each license a different edit, and the
rest license none here.
Say what you considered merging and chose not to. A cluster is a claim that one change fixes N cases, and the near-misses are what a reviewer needs to falsify it.
Falsify a behavioural cluster against the passes. This skill reads failures
only, so any behaviour common to the whole run looks causal from inside it.
diagnose.py hands the clustering step a CONTROLS block: the same
measurements -- retrieval calls, targets carrying no search_text, queries,
skills opened, turns -- taken on the cases that PASSED. Before claiming a
behaviour explains a cluster, compare it there. If it occurs at a similar rate
in the passes it does not separate the groups: mark the cluster contributing
rather than primary and do not route it to an edit as the root cause.
Measured, on the run this comes from: the largest cluster said the agent
"substitutes broad enumeration for targeted retrieval", and bare targets were
25% of all targets in the failures against 26% in the passes. What actually
separated them was volume -- failures made about 50% more retrieval calls --
and question difficulty explains that at least as well as call style does. With
no controls at all, a behavioural cluster is unfalsified, which is a different
claim from confirmed.
Still no patch. Naming the shared root cause precisely enough that someone else
can design the edit is the whole job here; the edit itself is
skill:eval-improve, working from this. A cluster carrying an honest open
question is more useful than one carrying a remedy nobody probed.
owner: model and owner: package-skill proceed to eval-improve. Both
are fixable in the model repo, so both are checkpointed and rolled back by the
same git commit. They are still separate owners, because the edit they license
is different and the acceptance check treats them differently: a model edit
changes what the data means, a package-skill edit changes what the agent is told.
owner: agent-skill findings go back into the shared analysis or
phrase-detection skill, outside this loop. Retrieval findings go to the tool.
BAD-REFERENCE and AMBIGUOUS-REFERENCE go to the golden side door in
skill:eval-loop (repair or hold). Do not send them to improve. Other dataset
findings (a bad question, a case worth excluding) go back to the case in
cases.jsonl via the conductor. Routing a guidance bug into the model is how
models accumulate scar tissue: if the entity was there, correctly defined, and
the agent still misused it, the fix is guidance, not a new field.
Anti-patterns
- Do not diagnose from the answer alone. Probe why a number differed.
- Do not treat a passing answer as uninformative. High call counts on a pass still name gaps.
- Do not conclude a model gap from two agents agreeing. They coin-flip onto the same undocumented sibling for the same reason.
- Do not assign
constructionwhen sufficiency is unknown.
Related skills
skill:eval-answer: the score this consumes.skill:eval-improve: the smallest edit, forowner: modelandowner: package-skill.skill:eval-loop: golden hold/repair, the acceptance check, and checkpoint.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

