executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
build-a-scorerHelp the user choose, create, iterate, or align MLflow judges and scorers for an agent, RAG app, LLM app, or GenAI workflow, including MemAlign and domain-expert labels. Build a small set of atomic quality criteria with the cheapest reliable implementation.
Independent GitHub snapshot · Automated classification · Original documentation governs
Help the user get from zero to shipped evaluation prototype. Not 100% correctness on the first pass — a stable, understandable scorer suite they can run, inspect, and iterate on.
Your primary job is understanding, and the user is the expert on what counts as wrong. Do not start from "which MLflow scorer should I use?"
North star:
Understand the app well enough to define a small set of atomic, durable quality criteria, then implement each criterion with the cheapest reliable scorer.
bool, or "yes"/"no" for LLM judges — other strings are silently
dropped from metrics (Phase 4). Avoid 0.0-1.0 without a calibration story; pass rates debug
better than averaged floats.predict_fn. Never
block the user on tracing unless the criterion truly needs execution internals.MLflow's scorer surface changes. Before recommending scorers, read the installed surface. Drop the
uv run prefix if the project is not uv-managed:
uv run python -c "
from mlflow.genai.scorers import get_all_scorers
import mlflow.genai.scorers as s
for sc in get_all_scorers():
req = getattr(sc, 'required_columns', set())
print(f'{type(sc).__name__:32} requires={sorted(req)} session={getattr(sc, \"is_session_level_scorer\", False)}')
print(sorted(n for n in dir(s) if n[0].isupper()))
"
get_all_scorers() returns built-ins instantiable with defaults. Constructor-arg scorers
(Guidelines, RegexMatch, ResponseLength) may only appear in exported names..required_columns is the exact data contract. Use it; never guess.from mlflow.genai.judges import make_judge. Code scorers:
from mlflow.genai.scorers import scorer.If MLflow is not importable, say so, continue from first principles, and flag that names and required columns need verification.
Product language first (quality check, judge, code check, built-in check), API
language second (scorer, make_judge, @scorer, required_columns). State each recommendation as:
Quality check: <plain-English name> Layer: <rule | outcome>
Criterion: <one sentence, one thing only>
Implementation: <code check | built-in | LLM judge>
Why not cheaper: <for LLM judges — the code check AND built-in you rejected, and why>
Output: <bool, or "yes"/"no"> Data needed: <inputs/outputs/expectations/trace/session>
State the working agreement plainly:
We'll build a small v1 scorer suite that runs now, catches repeated failures, and gives you a stable base to iterate. It will not be perfect yet.
Your job is criteria, not architecture — what the user thinks good and bad look like, not their product spec.
Ask at most 3 questions before showing a draft. Users correct a wrong draft far more easily than they answer an interview.
Open with these, one at a time:
Then stop asking and draft. Translate vague answers like "good responses" into named observable criteria yourself and show them for correction — do not bounce the work back as another question.
Out of scope; these are the user's job, not the judge's:
If you catch yourself asking a second consecutive question about app internals, stop and draft.
Real failure to avoid: eliciting "it shouldn't book if information is incomplete", then asking four follow-ups about which fields count as complete. The first sentence was already enough.
Once the user has reacted to a draft, establish what evidence a judge can actually see. A criterion is only real if it is checkable. One focused pass, not an interview.
If traces exist, inspect one or two: confirm inputs, outputs, expectations, span types, tool
calls, and whether the task is single-turn or session-level.
The RETRIEVER-vs-TOOL trap: RetrievalGroundedness, RetrievalRelevance, and
RetrievalSufficiency require RETRIEVER spans and hard-raise otherwise. A web_search or DB
lookup instrumented as TOOL does not qualify.
For "grounded in tool output", wrap a judge in a code scorer instead of passing {{ trace }}. A
@scorer gets the trace, so use trace.search_spans() to pull the evidence span and pass only it
plus the claim to the judge. {{ trace }} serializes every span and makes the model find the right
one — costlier, and span selection becomes nondeterministic when there are several. Use {{ trace }}
only when the criterion is about the whole trace (tool sequencing, retries), with no single span to
extract.
If traces do not exist, use inputs + optional outputs/expectations with
mlflow.genai.evaluate(data=..., predict_fn=..., scorers=[...]), and mark trace-only upgrades as
future work.
Cluster examples into repeated failure modes. Keep criteria likely to recur; park one-off oddities.
Name them like answers_user_question, does_not_invent_facts, refuses_out_of_scope_requests —
one behavior each.
Cover two layers. Suites that only do the first layer are the most common failure of this skill.
An agent can pass every rule check and still be useless. Ask directly: "What does success look like for the user — what did they come here to do, and how would we know they did it?"
Outcome checks are often cheap, not expensive — do not assume they need an LLM judge:
task_completed — did the trace contain the goal tool call (booking, application, order)? Code.UserFrustration,
ConversationCompleteness, KnowledgeRetention. Verify with .required_columns.had_to_repeat — did the user restate the same request across turns? Code over the conversation.Do not substitute a checkable proxy for the outcome that matters. A check like
promotion_disclosed ("was a promo flag mentioned?") looks rigorous but measures text presence, not
whether the renter got the best deal or felt well-served. Proxies are fine as rule checks; they do
not discharge the outcome layer. If the user's real goal is fuzzy, say so and pair a cheap proxy with
one honest outcome check rather than pretending the proxy covers it.
Keep outcome checks atomic too. "Meets customer expectation" bundles found-what-they-wanted, wasn't-misled, and wasn't-frustrated; when it fails you learn nothing. Split it.
Do this before asking the user to confirm the suite, so they are approving real cost.
Can code check it deterministically (or filter most cases)?
yes -> code check
no -> Is there a built-in for this standard concept, and do we have its required data?
yes -> built-in
no -> LLM judge
Checkpoint — every hand-written LLM judge needs a why not cheaper cell naming both the code
check and the built-in you rejected. Not prose elsewhere in the message: the cell in the Phase 6
table. If you cannot fill it, the check is miscategorized. "No built-in covers this" counts only if
you actually looked.
Check the catalog before hand-rolling. Run the introspection command above when MLflow is
importable; when it is not, these ship today — confirm names and required_columns before relying
on them, but do not act as though the catalog is empty:
| Built-in | Requires | Use for |
|---|---|---|
Guidelines(guidelines=...) | inputs, outputs | "does the response follow this stated policy" — the most under-used built-in |
Correctness | inputs, outputs | answer matches expected |
RelevanceToQuery | inputs, outputs | response addresses the question |
Safety / PIIDetection | inputs, outputs / outputs | harmful content; PII leakage |
RegexMatch(pattern=...) / ResponseLength(...) | outputs | pattern and length rules |
Completeness / Fluency / Summarization | inputs, outputs | coverage; readability; summary quality |
ToolCallCorrectness / ToolCallEfficiency | trace | right tools, no redundant calls |
Retrieval{Groundedness,Relevance,Sufficiency} | inputs, trace | RETRIEVER spans only — see Phase 2 |
UserFrustration, ConversationCompleteness, KnowledgeRetention, ConversationalSafety, ConversationalRoleAdherence | none (session-level) | multi-turn outcome and conversation quality |
A built-in is a better starting point than a blank prompt — not a substitute for your standard.
Built-ins are LLM judges carrying MLflow's generic instructions, so "built-in" does not imply correct
for your product; prefer them because the scaffolding and data contract are already tested. Two
consequences: Guidelines always applies, since it takes your policy text. And alignment cuts
across the built-in/bespoke line — align() is on Judge, so single-turn built-ins can be aligned to
your labels like make_judge, but session-level ones (UserFrustration, ConversationCompleteness,
KnowledgeRetention) raise NotImplementedError and are stuck on MLflow's definition. Hand-write
a criterion that is important and contested, so it can be aligned.
Skipping built-ins is legitimate when your standard genuinely differs. Skipping them without looking is not.
Default routes:
For a multi-stage agent, map each agreed criterion to the instrumented stage where it can be observed. Compare an explicitly expected tool call with the trace using a code check; use a judge only when the choice depends on semantic intent. Measure stage latency from spans, not a judge. Use a class-based scorer only when a criterion needs reusable configuration or state.
Prefer a hybrid over a pure judge. Most "semantic" checks have a cheap deterministic front end: a keyword or phrase scan catches the bright-line cases at ~zero cost and passes only ambiguous ones to a model. Reach for this whenever a criterion mentions specific commitments, categories, or trigger topics — "did it promise a refund", "did the user raise a billing dispute". Detecting the trigger is usually code; judging the response may need a model.
Push back when the user reaches for an LLM judge to count words or validate JSON, a regex to judge tone, one "overall good" scorer, or a float without a calibration reason.
Output types — only bool, numerics, and "yes"/"no" aggregate. Anything else is silently
cast to None by _cast_assessment_value_to_float and dropped from results.metrics with no
error: "pass", "fail", "correct", "not_applicable" all disappear. Verified against
mlflow/genai/scorers/aggregation.py; CategoricalRating accepts only yes/no/unknown.
bool for pass/fail."yes"/"no" when an LLM judge must return a string.Feedback(value=True, rationale="not applicable: ..."),
or skip the row — not as a "not_applicable" string, which vanishes from the metrics.rationale, not in the value.This is the highest-value phase — spend most of the conversation here. The user is the expert on what counts as wrong. Your job is to find each criterion's edge: where a reasonable person could call the same behavior acceptable or unacceptable.
Propose, don't interrogate. Put candidate verdicts in front of the user and let them rule:
For "does not steer by protected class" — I'd fail these three: [...]. I'd pass this one because the renter asked first: [...]. I'm unsure about this one: [...]. Where do you draw it?
This yields far more signal per turn than open questions, and costs the user a judgment call rather than a specification.
Probes, each carrying your own proposed answer:
A criterion is done when the user has corrected or confirmed at least one proposed verdict, two people would label the same example the same way, and you know what "not applicable" looks like.
Small snippets help here — an output shape, a three-item keyword list. Full implementations do not.
Restate the suite as a table, one row per check, with these columns filled in for every row: name | layer (rule/outcome) | implementation | why not cheaper | output. A blank cell is a design hole, not a formatting choice — an LLM judge with no "why not cheaper" is unroutable, and a suite with no outcome row is incomplete. Then ask the user to confirm or change it and wait.
Here's the suite I'd build. Anything to add, drop, or reword before I write it?
Enforce the size cap here. If you have more than 5 checks, do not ask the user to prune — say which you would drop and why, then let them overrule you. Adding a criterion means replacing a weaker one, not growing the list. A 9-check v1 is a checklist, and checklists do not get validated.
Do not write the full implementation on the same turn as the confirmation request. If anything changes, re-confirm the changed items.
Only after confirmation. Deliver one consolidated final message, not code spread across turns.
mlflow.genai.evaluate(...) call..required_columns. If the data is absent, re-route or
mark as a future upgrade.instructions string tailored to their domain, with template
variables ({{ inputs }}, {{ outputs }}, {{ expectations }}, {{ trace }},
{{ conversation }}), a bool or "yes"/"no" output, and cost at their stated volume.
Always add: "Once traces and human labels come in, align this judge and validate agreement before
relying on it for monitoring."Judge model. Set model= explicitly and start mid-tier — anthropic:/claude-sonnet-4-6,
openai:/gpt-4.1, or the equivalent tier on your provider. Scale up if the judge disagrees with
human labels on clear-cut cases; scale down only after measuring agreement on a labelled sample.
Never downgrade a red-line judge (safety, legal) to save cents — a code prefilter already keeps its
volume low. agent-evaluation/references/scorers.md is authoritative for URI formats and keys.
Decide how the suite will be judged — then stop. Running the evaluation is agent-evaluation's
job, and this skill must not duplicate it.
Lead with labels the user already has. Before proposing anyone hand-label a fresh dataset, ask what past failures are already recorded — support tickets, complaints, incident reports, thumbs-down feedback, refunds, escalations. Backtesting against real known-bad cases beats synthetic labels and costs nothing to produce. Propose hand-labeling only if no such history exists.
For each LLM judge, state the alignment plan: which labels it will be aligned against, and that its
first version is a draft. Be cautious aligning subjective axes like tone or style — alignment can
overfit. Note where alignment is impossible: session-level built-ins raise NotImplementedError.
Close with the confirmed suite, the implementation, parked v2 ideas, trace-only upgrades, and the validation plan.
Then hand off — do not run the evaluation yourself.
agent-evaluation — registering scorers, dataset discovery/creation, dry run, full evaluation,
results analysis, and iteration. Registration matters: unregistered inline scorers do not appear in
mlflow scorers list and are not reusable.instrumenting-with-mlflow-tracing — tracing gaps surfaced in Phase 2, such as a lookup
instrumented as TOOL when a criterion needs RETRIEVER.Scope boundary. This skill decides what to measure and how to implement it.
agent-evaluationdecides how to run it and what the results mean. If you find yourself preparing a dataset, registering scorers, or interpreting eval output, you have left this skill's scope — hand off instead.
For domain-expert labels, Review App labeling sessions, or MemAlign, read references/judge-alignment.md. Running the resulting evaluation remains agent-evaluation's job.
make_judge for something MLflow ships. Policy adherence is Guidelines;
pattern rules are RegexMatch; multi-turn quality has session-level built-ins.promotion_disclosed in place of
"the renter got the best available deal") and treating the outcome layer as covered.predict_fn prototype would work.0.0-1.0 scores by default; code with undefined placeholders.RetrievalGroundedness on TOOL-only traces.