Imported from glemaitre/experiments (
agentic_discovery/exp_03/.claude/skills/iterate-ml-experiment/SKILL.md). Install upstream withnpx skills add glemaitre/experiments --skill iterate-ml-experiment. Copyright stays with the author.
Iterate ML Experiment
The loop on top of experiments/: what to try next, why, what
counts as a result, and how the trail of past experiments is
recorded. Pipeline mechanics, evaluation mechanics, and workspace
layout are out of scope and live in sibling skills.
First action (every turn)
Before answering anything else:
- Read
plan/PLAN.md. If it doesn't exist or is the placeholder dropped byorganize-ml-workspace, you're in bootstrap mode (§ 0). - Emit the Pre-flight checklist (below) as visible text in your response, with each box marked.
- Use the Mode picker to find which one section to read this turn.
Skipping any of these is a Stop-condition violation.
Mode picker — read this before navigating the body
You only need to read one mode section per turn. Decide which by matching the user's signal (or workspace state) to the table, then jump straight to that section.
| User signal / workspace state | Mode | Section to read |
|---|---|---|
plan/PLAN.md missing, empty, or placeholder; OR exists but has no rows in History |
Bootstrap | § 0 Bootstrap |
plan/ not even scaffolded (no src/, no experiments/) |
Bootstrap → handoff first | § 0 step 1, then organize-ml-workspace |
| "what's next?", "let's iterate", "propose next", "resume" — and PLAN.md has ≥1 done row | Iterate (propose) | §§ 1-3 + Dispatch table |
| "the run finished", "log the result", "we got X = ...", "record outcome" | Iterate (record) | § 4 |
| "where are we?", "give me a one-pager", "status?", "what have we tried?" | Project overview | § Project overview / status requests |
| "compare X and Y", "X vs Y", "trend across runs", "stack up against baseline" | Compare (read-only) | § Compare past experiments |
| "let's pivot the goal", "actually we care about ", goal change signal | Goal pivot | § Goal pivots |
| "abandon X", "drop X", "X isn't going to happen" | Abandoned | § Abandoned experiments |
| User wants to redo a prior experiment under different conditions (paired seeds, fixed splitter, …) | Re-run | § Re-runs |
If two modes seem to match (e.g., "compare X and Y, then propose something based on it"), pick the read mode first (Compare/Overview), surface its result, and stop. Re-entering § 1 for a propose-step is a separate user-driven turn.
Stop conditions — read before anything else
- No plan, no script. Never create or edit
experiments/NN_*.pyuntilplan/NN_*.mdexists, is filled in, and the user has explicitly approved it. The plan is written first and validated first; the script is a consequence of the plan, not the other way around. If you catch yourself about to write Python before the plan exists: STOP and write the plan. PLAN.mdis read at session start, not improvised. When a session opens in a workspace withplan/PLAN.md, read the file before answering any "what next" question. Don't reconstruct history fromexperiments/filenames orgit log— those don't carry the why.- Strategy is picked, not assumed. When you propose a next
experiment, name the sourcing strategy you used: user input,
literature, methodology audit, diagnostic. If the user hasn't
signalled one, ask. Don't silently mix strategies in a single
proposal — that's how plans turn into wishlists. Exception:
bootstrap (§ 0). When
PLAN.mdhas no recorded experiment, the baseline is forced by the workspace defaults — no strategy dispatch, no "what do you want to try?". The user's role is to approve or amend the baseline, not invent it. - Approval is explicit. "Looks good", "yes", "go" from the user is the gate. Anything ambiguous ("hmm interesting") is not approval. If unsure, ask.
- Outcomes are recorded, not narrated. When an experiment
finishes, the outcome lands in
PLAN.mdand in the per- experiment file's status block, before the conversation moves on. The skore report is the source of truth for metrics; the plan files capture what was learned and what it implies for the next iteration.
Pre-flight — emit this checklist as visible text before any plan write
Before writing or editing any file under plan/, output the
following block verbatim. Each box must be backed by an actual
tool call or an explicit decision documented in the response.
Pre-flight (iterate-ml-experiment):
- [ ] `plan/PLAN.md` read this turn (or confirmed missing → about to scaffold)
- [ ] Mode: bootstrap (no recorded experiment) | iterate
- [ ] Last experiment + its status known: <NN_name> | n/a (bootstrap)
- [ ] In iterate mode, the **sourcing menu was presented to the user
verbatim** (see § "The sourcing menu") and the user picked one
option — no silent default. Skip in bootstrap mode.
- [ ] Sourcing strategy chosen by user: my-pick | diagnostic |
methodology | literature | user | backlog:B<N> |
n/a — bootstrap, baseline forced by workspace defaults
- [ ] Strategy skill dispatched (iterate mode only): iterate-from-<...>
| n/a — backlog item promoted directly
- [ ] Proposal turned into a `plan/NN_short_name.md` draft
- [ ] User has explicitly approved the plan file before any
`experiments/NN_*.py` is touched
Layout this skill owns
plan/
├── PLAN.md # session-start log + index + future ideas
├── 01_baseline.md # design note for experiments/01_baseline.py
├── 02_text_encoder.md # design note for experiments/02_text_encoder.py
└── ...
The pairing rule is hard: plan/NN_short_name.md ↔
experiments/NN_short_name.py, identical stems, one-to-one. The
plan file is created first and edited until the user approves;
the script is created only after that. This is the contract
that lets PLAN.md stay a faithful index of what was tried.
PLAN.md shape
PLAN.md is a thin, durable index — not a long-form journal.
It has three sections:
- Status (top). A two-or-three-line snapshot: the dataset, the project goal, the most recent experiment and whether it ran/passed/regressed. This is what you read first at session start.
- History (chronological). One row per experiment: stem, one-line intent, status (planned / running / done / abandoned), headline result, link to the per-experiment plan file. New rows go at the bottom.
- Backlog (forward-looking). Indexed table of ideas not
yet committed to a
NN_*.mdfile. Each row carries a stableB<N>index so the user can pick by number when choosing the next experiment ("go with B2"). Columns:#,Item,Source(where the idea came from — diagnostic from<stem>, user input, literature, etc.). When an item graduates into a plan file, the row is removed and the new experiment lands in History above. The skill must surface this table to the user every time it presents the sourcing menu (see § "The sourcing menu") — the backlog is one of the menu's branches, not a side-document.
Use templates/PLAN.md as the starting skeleton. Don't invent
new sections per project — the three sections above are the
contract.
Per-experiment plan file
Each plan/NN_short_name.md file is the design note for one
experiment. It is the proposal while it is being drafted,
the contract once approved, and the postmortem once the
experiment has run. Use templates/experiment_plan.md as the
starting skeleton; sections are:
- Question / hypothesis — one sentence. What are we trying to learn? Not "try X" — why X, and what would it tell us.
- Motivation — why now. Pulled from the sourcing strategy: user request, paper, methodology audit, diagnostic finding. Cite concretely (issue link, paper title, prior experiment stem, report section).
- Method — what changes versus the previous experiment, in
prose. Which file in
src/<pkg>/is touched (features.py,pipeline.py,evaluate.py,data.py)? Mechanics live inbuild-ml-pipeline/evaluate-ml-pipeline; this section states the intent, not the code. - Risks / things that could invalidate the result — what would make the metric move for the wrong reason (leakage, sample size, distribution shift, …).
No "Success criteria" / "Acceptance criteria" section. The skill proposes and runs experiments; the user judges whether the result is good enough. Inventing a target metric delta or "this counts as success" line ahead of time is out of scope — it nudges the run toward a foregone conclusion the user didn't ask for. The post-run Headline result + Implication fields (in the Status block) are the durable record; the user reads them and decides what to do next.
- Status block (filled in over time): planned → approved →
done | abandoned, with the headline result on completion.
There is no observable "running" state — this skill is
user-triggered, so the experiment sits in
approvedfrom script-creation until the user reports the outcome via § 4.
The status block is the only part that is updated after the experiment runs. The other sections are frozen at approval — that's what makes them useful as a postmortem.
The conversational loop
This is the choreography that this skill owns end-to-end. There are two modes — bootstrap (the very first session in a workspace) and iterate (every session after that). Bootstrap is one-shot; iterate is the recurring loop.
0. Bootstrap (first session only)
A workspace is in bootstrap mode when either plan/PLAN.md
is missing or is the one-line placeholder dropped by
organize-ml-workspace, or it exists but has no rows in
History (no experiment has been planned yet). In bootstrap mode
the session-start ritual below does not apply — there is no
last experiment to summarize and no backlog to look at. Instead:
- Scaffold first if needed. If the workspace itself isn't
in place (no
src/, noexperiments/, noplan/), hand off toorganize-ml-workspace; come back here when its placeholderPLAN.mdexists. - Rewrite
PLAN.mdfrom this skill's template. Readtemplates/PLAN.mdand write it toplan/PLAN.md, replacing the placeholder. This skill — notorganize-ml-workspace— owns plan content. - Derive a goal default from what the project tells you.
Read
data/README.md(or whatever dataset card / problem statement is at the project root) before asking the user. Synthesize one sentence of the form "minimize on for " and propose it; the user confirms or amends. Do not prompt the user with a blank. If no README / dataset card exists, then ask — but make that the exception, not the default. - Auto-draft
plan/01_baseline.mdvia the consultation chain. The baseline is forced, not invented — but its defaults come from sibling skills, not from memory:- Learner default: consult
build-ml-pipelinefor what a "baseline" means for the data shape (tabular regression / classification → skrubtabular_learner; other shapes have their own defaults). - Splitter default: consult
evaluate-ml-pipelinefor the cross-validator default (typicallyKFoldfor IID tabular, but the skill picks based on the data structure). - Metric default: consult
skore-apifor whatskore.evaluatereports by default for the task type. - Mismatch handling: if any default conflicts with the
project goal (e.g., the README requires Squared Error but
skore's default is RMSE; the dataset has 1M rows and
5-fold KFold may be slow / OOM), flag it in the Risks
section of
01_baseline.md. Don't silently override the default — surface the tension to the user.
- Learner default: consult
- The user's role in bootstrap is to approve or amend the baseline plan, not to invent it. Skip the strategy dispatch entirely for this one.
- Exit bootstrap. Once the baseline is approved and
recorded in
PLAN.md's History, the workspace is out of bootstrap. Every session afterwards uses § 1.
1. Session start (iterate mode)
- Read
plan/PLAN.md. - Summarize to the user, in two or three lines: dataset, goal, last experiment + its status, anything in the backlog that looks ripe.
- Ask explicitly: do you want to resume the last experiment (still planned / running / unfinished), record an outcome (the last one ran since we last spoke), or propose the next one?
The sourcing menu
Every time § 2 runs in iterate mode, surface this menu verbatim
before any strategy dispatch — and pair it with the PLAN.md
Backlog table so the user can pick a B<N> row. The menu is the
contract: the skill never picks for the user.
How would you like me to source the next experiment?
diagnostic — read the latest skore report (residuals,
calibration, slice metrics) and propose from
what it surfaces.
methodology — audit the previous experiment(s) against good
ML practice (leakage, splitter, sample size,
baseline comparability, metric choice).
literature — search papers, blog posts, or library docs for
techniques applicable to this problem.
user — you describe an idea, point me at a GitHub
issue, or hand me a spec / notes repo to read
from.
my-pick — I synthesize across the above and pick what I
find most logical given the current state.
B<N> — promote a row from the Backlog table below
directly into a new experiment.
Backlog (pick by index):
<paste the PLAN.md Backlog table here>
Use AskUserQuestion (or whatever structured-pick UI the runtime
exposes) when available — six options + the backlog rows fits it
well. Otherwise enumerate the menu in plain text and wait for the
user's pick. Do not silently default to one option — even if
the latest experiment has a fresh diagnostic report, the user must
say "diagnostic". The Dispatch table below covers signal-driven
shortcuts (e.g. the user says "what does the report show?" — that
is a diagnostic pick); they short-circuit the menu but never
silence it.
2. Propose the next experiment
- Always present the sourcing menu first — see § "The
sourcing menu" for the canonical wording. Surface the
PLAN.mdBacklog table next to it so the user can pick aB<N>row directly. Do not silently default. UseAskUserQuestion(or equivalent structured-pick UI) when the runtime offers it; otherwise enumerate the menu in plain text and wait for the user's pick. - Once the user picks: dispatch as the picked option dictates —
diagnostic/methodology/literature/usergo to the matchingiterate-from-*skill (see Dispatch table for edge cases);my-picklets you synthesize across strategies;B<N>promotes the backlog row directly without invoking a strategy skill. Bring back a proposal: question, motivation, method outline. - Write the draft to
plan/NN_short_name.mdusing the template. TheNNis the next free integer; theshort_nameis the user's call (offer one, don't force it).
3. Iterate on the plan with the user
- Show the draft to the user. Ask for changes.
- Edit
plan/NN_short_name.mdin place until the user explicitly approves. Do not createexperiments/NN_*.pyduring this step — the plan file is the only artifact in play. - Track provenance honestly. If the user's amendment touches
only the Risks section (a guard-rail tweak), keep the
original
Sourcing strategyline. If it changes the Method (different transform, different estimator, different feature) — that's a material override. UpdateSourcing strategyto<original> + user override(e.g.,diagnostic + user override) and quote both the original source and the user's amendment in Motivation. The per-experiment file should never lie about its own origin. - When the user approves, flip the status block from
plannedtoapproved, add the row toPLAN.mdhistory (status:approved), and hand off toorganize-ml-workspaceto create the experiment script with the matching stem.
4. After the run
Trigger is user-driven (v1). This skill does not auto-detect that a script has finished — no polling of the skore Project store, no file-mtime watching, no background hook. The user tells you "the run finished, record it" (or a phrase like "log the result", "we got X"), and only then do you start this step. If you suspect a run finished but the user hasn't said so, ask — don't assume.
When triggered, decide first whether the report is accessible
in this session — i.e., the skore Project store exists at the
expected path (reports/) and contains the experiment's key.
If accessible — read it programmatically. Open the report
via the skore Project (route through
evaluate-ml-pipeline for the call site, and invoke
Skill(skore-api) in this turn to confirm the exact
signatures). For programmatic access to the diagnostic surface,
use report.diagnosis() — for v1 this is the only
programmatic entry point this skill relies on; do not reach
into other report attributes from memory. If you need a richer
diagnostic narrative for the user, hand off to
evaluate-ml-pipeline.
If not accessible (run was on a different machine, batch
system, the script crashed before project.put, …): do not
fabricate report content from memory. Ask the user for the
headline metric (and a one-line note on anything that looked
off). Mark the per-experiment file's "Implication" field as
"deferred — report not accessible this session" and pick the
diagnostic up next time.
In both branches, fill all four Status-block fields in
plan/NN_*.md:
- State:
done(orabandonedwith a one-line reason) - Approved by user on: unchanged from approval (don't edit)
- Headline result: the metric + uncertainty (e.g.
RMSE 0.083 ± 0.004 (5-fold CV)) - Implication for next iteration: one or two sentences; this is the seed for the next strategy dispatch
And append the headline result to PLAN.md's History row for
that experiment.
Secondary findings → Backlog. The diagnostic walk almost
always surfaces more than one signal. Pick the strongest as
the next-iteration seed (above), and append the rest to
PLAN.md's Backlog as one-liners (e.g., "high-tail
variance after logit transform — investigate if we keep the
transform"). One bullet per finding; don't elaborate. The
backlog is a queue for future strategy dispatches, not a
journal.
Backlog hygiene — prune what the latest run rendered moot. Before appending new items, scan the existing Backlog for entries the just-finished experiment has answered, killed, or made irrelevant. Examples: an item about logit-tail variance is moot once we've abandoned the logit transform; an item about slow KFold(5) is moot once we've moved to HoldOut. Two treatments — pick whichever is cleaner per item:
- Delete the bullet outright if the rationale no longer applies (the transform / splitter / feature it referred to is no longer in the pipeline).
- Strikethrough with a brief reason (
~~old item~~ — resolved in 03_softer_transform) if the item is worth preserving as breadcrumb but should not be picked up as a future proposal.
The backlog is a working queue, not an archive. If it grows past ~10 items, prune more aggressively: a backlog that long will not be read.
Closing the loop with a GitHub issue (opt-in). If the
just-recorded experiment's Source is a GitHub issue (the
Sourcing strategy was user and the Source field links
to github.com/<owner>/<repo>/issues/<N>), offer to post
the headline result back as an issue comment:
"Source was issue #. Want me to comment back with the headline (RMSE 0.087 ± 0.003) + a link to plan/.md? Y/N"
Never auto-post. The gh issue comment call only fires on
explicit user approval. If the user accepts, run
gh issue comment <N> --body "<headline + plan-file link>";
if they decline, move on. This is the only outbound side
effect this skill is allowed; it exists to close the
feedback loop with the issue tracker without being
surprising.
Stop here. Do not auto-launch the next strategy dispatch in the same turn. Surface the implication to the user as a one- liner ("the residual bias near boundaries points at a target- transform experiment — want me to draft it?") and wait for the user's go-ahead before re-entering § 1. The user controls cadence; this skill records, it doesn't propose-and-record in one breath.
Project overview / status requests
When the user asks "where are we?", "give me a one-pager",
"what's the status?", "what have we tried?" — that is a read
of plan/PLAN.md, not a new artifact. PLAN.md is the canonical
project digest (Status + History + Backlog, three sections by
design). Do not generate a separate summary document.
- For short asks ("status?"), surface the Status block verbatim plus the last one or two History rows.
- For "one-pager" / "where are we" framing, surface PLAN.md as-is
(or summarize it section-by-section if it has grown long), and
add a one-sentence "what's ripe next" line drawn from Backlog
- most recent Implication. Nothing else.
- Do not write to
plan/during these requests. Read-only.
If PLAN.md has drifted (Status references an experiment that
no longer matches History's last row, Backlog has stale items),
flag the drift to the user — but don't auto-edit during a
read-only request. Drift fixes belong to § 4 (next time an
outcome is recorded) or to an explicit "tidy up PLAN.md" ask.
Goal pivots
Sometimes the project goal itself changes mid-stream — the trader cares about typical error not squared error, so MSE → MAE; the downstream consumer changes from offline batch to online serving so latency joins the goal; the metric class changes (regression → ranking). This is not an experiment; it is a strategic event that affects how every future experiment is judged.
When the user signals a goal pivot:
-
Update
PLAN.mdStatus with the new goal and the date, keeping a one-line trace of what changed:Goal pivoted <date>: <old> → <new>. Reason: <one sentence>. -
Insert a horizontal-rule row in History below the last pre-pivot experiment, formatted as a clear divider:
| --- | **Goal pivoted <date>: <old> → <new>** | --- | rows above evaluated against <old>; rows below against <new> | --- | -
Do not mass-edit prior
plan/NN_*.mdfiles. Their Success criteria are frozen at approval — that's the contract. Their Headline result cells in History stay too (they were valid against the old goal). -
The first post-pivot experiment auto-flags incomparability in its Risks section:
"evaluating against new goal (<new>); not directly comparable to {<pre-pivot stems>} which used <old>."This blocks silent cross-comparison across the pivot. -
Reset the anti-monoculture counter to 0. The goal pivot is a strategic event; the prior diagnostic streak isn't relevant to the new goal.
A goal pivot is user-only — the skill never auto-pivots.
Abandoned experiments
The lifecycle states are planned → approved → running → done | abandoned. Abandonment is a real outcome and needs the same
handling rigor as done:
- User-decided only. The skill never auto-abandons. If an
experiment has been planned/approved for many sessions
without progress, flag it to the user ("
05_quantile_intervalsis still inapprovedstatus — abandon, defer, or run?") but do not change its state without an explicit answer. - Status block requires a one-line reason. "Dependency was non-trivial to install; deferred to v2." "Method was superseded by 06_softer_transform's success." "Direction ruled out by literature finding in 04_monotonic_gbm." The reason is the whole point — it's what makes the abandonment a useful provenance signal rather than a gap.
Headline resultbecomesn/a — abandoned: <reason>. The History row stays (provenance is the whole point); only the Status field flips.- Anti-monoculture counter is unaffected. Abandoned diagnostic-sourced experiments do not count toward the two-consecutive streak — abandonment means the strategy didn't produce a real datapoint.
- "Fresh report" eligibility is unaffected (already excluded by fix #11's "status=done" requirement). An abandoned experiment never has a report to mine.
- A subsequent re-run of an abandoned experiment is a normal
re-run (per § Re-runs); the abandoned row is not edited
beyond the optional
Implicationback-link.
Compare past experiments (read-only mode)
When the user says "compare 01 and 02", "how does this run stack up against the baseline?", "what's the trend across runs?" — that is not a new-experiment request. Don't draft a plan file. Don't add a row to History. This is a read of past work.
v1 scope: pairwise side-by-side, no programmatic
multi-stem comparison. This skill family does not expose a
ComparisonReport / multi-key comparison entry point in v1 —
deliberately. The handoff to evaluate-ml-pipeline is
single-learner by its declared scope, and we do not stretch it.
Procedure for "compare X and Y":
- Headline side-by-side. Pull the Headline result cells for
each requested stem from
PLAN.mdHistory and surface them side by side, with one-sentence intent for each (also from History). This is usually enough to answer "is the new one worth it?". - Deeper read, one report at a time. If the user wants more
than the headline (residuals, calibration, slice metrics),
route to
evaluate-ml-pipelineonce per stem, separately — that skill is single-learner by scope. The user does the cross-experiment synthesis from the two narratives; the skill does not. - Don't write to
plan/during a compare request. If the side-by-side reading surfaces a finding the user wants to act on, re-enter § 1 with the appropriate strategy (typicallydiagnosticif the finding came from a per-stem report walk;methodologyif it came from a fairness concern between splits or seeds across the two).
v2 gap, flagged. Statistical comparison (significance tests,
shared-fold paired comparisons, multi-key ComparisonReport)
is out of scope for v1. If the user explicitly asks for a
significance test or "stat-sig comparison", surface this gap:
"v1 doesn't expose programmatic multi-stem comparison; for
significance, you'd need to run a paired re-run (see § Re-runs
→ Batch re-run) and compare the per-fold metrics manually."
The plan files are the durable record of experiments tried; comparisons are derived views, not new entries.
Re-runs
Sometimes the right next step is to redo a prior experiment under different conditions — paired seeds for a fair comparison, a corrected splitter, a fresh data snapshot. A re-run is a new file, never an in-place edit. Two shapes, dispatched by how many prior experiments are being redone:
Single re-run (one prior target)
Use when the methodology audit (or user request) targets exactly one prior experiment.
- New stem:
NN_<original_stem>_rerun.pyand the matchingplan/NN_<original_stem>_rerun.md. The numeric prefix is the next free integer;<original_stem>preserves provenance. Sourcing strategyline: typicallymethodology re-run(occasionallyuser re-runif the request is direct).- Motivation must quote the original experiment stem and state precisely what changed (the fix being tested).
- Method notes that the experiment is a re-run and what is held constant from the original.
Batch re-run (N prior targets)
Use when the methodology audit returns N≥2 re-run targets — for example, "redo 01, 02, 03 with paired seeds and a fixed splitter so the comparisons are sound." This is one methodological intervention, not N; it gets one plan file.
- New stem:
NN_paired_comparison.pyandplan/NN_paired_comparison.md(or another descriptive name reflecting the controlled condition:NN_seeded_redo,NN_aligned_splits, …). One numeric prefix; one approval; one History row. Sourcing strategy:methodology batch re-run.- Motivation quotes the methodology finding and the comparability gap it surfaced.
- Method lists the rerun targets explicitly (
{01, 02, 03}) and the controlled condition that's being applied uniformly (paired seed, identical splitter, …). The script will produce multiple report keys in the skore Project — one per rerun target — under a shared prefix (e.g.,paired:01,paired:02,paired:03). - Outcome shape is a comparison, not a single metric: the experiment produces multiple report keys (one per re-run target) and the user reads them side-by-side to judge whether the prior ranking holds or flips. The skill does not predefine what counts as a "successful" comparison — the user owns the call.
Both shapes
A new row goes into PLAN.md History at approval; the
original rows are not edited. The Implication block of
each original may be updated post-re-run with a one-line link
("see NN_X_rerun for the seeded comparison" or
"see NN_paired_comparison for the paired-seed redo") — that
is the only edit allowed to a frozen file.
In-place edits to an approved plan file are reserved for the Status block. Re-runs are not amendments — they're new experiments that happen to share most of the design.
Dispatch table — which iterate-from-* skill
Use the user's framing first; fall back to defaults below.
| Situation | Skill |
|---|---|
No prior experiment in PLAN.md (bootstrap) |
None — § 0 forces an auto-drafted baseline. The strategy skills only apply once at least one experiment is recorded. |
PLAN.md Backlog has an actionable item relevant to the latest report (e.g., a secondary diagnostic finding that wasn't acted on) |
None — promote the backlog item to the user first, before any strategy dispatch. The backlog is pre-mined work; re-mining is wasted compute and can produce a different answer the second time. If the user accepts, frame the proposal directly from the backlog text and skip the strategy skill. If the user passes, fall through to the rows below. |
| "I want to try X", "let's add Y", a GitHub issue link | iterate-from-user |
| "any papers on this?", "what does the literature say?", "how do people usually handle X?" | iterate-from-literature |
| "did we get the split right?", "is this leaking?", "small sample size?" | iterate-from-methodology |
| "the report shows X", "calibration looks bad", "why is slice Y so off?" | iterate-from-diagnostic |
| Open-ended ("what's next?") with at least one recorded experiment | Present the sourcing menu (see § "The sourcing menu") — paired with the Backlog table — and let the user pick. No silent default. The "Fresh report" / anti-monoculture rules below only apply when the user picks my-pick; in every other case the user has already chosen the strategy. |
"Fresh report" definition. A report is fresh when all
three hold: (1) the latest experiment in PLAN.md has
status=done (i.e., § 4 has run for it); (2) no later
experiment row exists in History; (3) no per-experiment plan
file already cites it as its Source (i.e., we haven't already
drawn a proposal from this report's diagnostic). Once a
diagnostic-sourced proposal is approved, the underlying report
is no longer "fresh" — fall through to methodology / literature
/ user for the next round.
Anti-monoculture rule. After two consecutive
diagnostic-sourced approved experiments in the History, the
default order for the next open-ended dispatch becomes
methodology → literature → user, and diagnostic drops to
last. Diagnostic strategy is sharp but narrow: looping on it
runs a project into local minima of "what the last report said
to do". The rule is a default, not a gate — if the user
explicitly asks for diagnostic, honor it. Reset the counter
when any non-diagnostic strategy is approved, or when
iterate-from-methodology returns the methodology_clean
payload (no proposal — see below).
Handling "no proposal" outcomes from strategy skills. Two strategies can return a structured "nothing actionable" payload instead of a proposal — that is a real outcome, not a failure:
iterate-from-methodology→{ "outcome": "methodology_clean", "audited": [<stems>] }iterate-from-literature→{ "outcome": "literature_empty", "queries": [...], "considered": [<short list of titles ruled out and why>] }
When either fires:
-
Append a one-liner under
PLAN.mdStatus, citing the strategy and the date — e.g.Methodology audit clean on {01,02,03} as of <date>orLiterature empty on <topic> as of <date>; considered: <short list>. Do not add a History row — nothing was experimented on. -
For
methodology_clean, reset the anti-monoculture counter to 0. Forliterature_empty, the counter is unchanged (literature wasn't a diagnostic-counter-resetting outcome to begin with). -
Continue the dispatch rotation to the next strategy in the default order (after methodology → literature → user; after literature → user). Don't ask the user to re-pick — the empty / clean result is the signal to move on.
-
userin the rotation isn't a skill invocation — it's a question to the user.iterate-from-useronly fires when the user has volunteered an idea; it isn't designed to be "asked" by the dispatcher. When the rotation reachesuserafter methodology and literature both came up empty, ask the user directly with the three options laid out:Methodology was clean and literature came up empty. Three paths from here: (a) you propose something concrete and we go from there; (b) wait for new data or a fresh run before iterating; (c) reframe the project goal — maybe the current target isn't the right one. Which?
Do not invoke
iterate-from-userwithout an idea in hand, and do not pick (a)/(b)/(c) for the user — the rotation ends in a human conversation, not another silent dispatch.
The strategy skills are intentionally shallow: each one knows
how to source a proposal and hand it back here. This skill is
where the proposal becomes a plan file. The methodology and
diagnostic strategies require a prior experiment — that's why
bootstrap (§ 0) skips dispatch entirely.
What this skill does NOT do
- Run experiments. The experiment script is created by
organize-ml-workspaceand executed by the user / their runner. - Open or query the skore Project. That's
evaluate-ml-pipelineand theskore-apilookups. - Edit
pipeline.py/features.py/data.py. Those are owned bybuild-ml-pipeline. - Decide whether a workspace exists or where things go. That's
organize-ml-workspace. - Write commits or PRs describing what was done. The plan files are the durable record; commit messages are out of scope here.
- Define what counts as a successful experiment. No "Success criteria" / "Acceptance criteria" / "target metric delta" written ahead of the run. The skill proposes, the experiment runs, the headline result is recorded; the user judges whether it's good enough and what to try next.
- Pick a sourcing strategy on the user's behalf. The sourcing menu is the contract — § 2 always presents it and waits for the user's pick.
Companion skills
organize-ml-workspace— scaffoldsplan/(emptyPLAN.md); enforces the stem-pairing rule betweenplan/NN_*.mdandexperiments/NN_*.py.iterate-from-user— sources the next experiment from user input or a GitHub issue tracker.iterate-from-literature— sources from web search over papers/docs.iterate-from-methodology— sources by auditing the methodology of the last experiment.iterate-from-diagnostic— sources by inspecting the skore report (residuals, calibration, slice metrics).evaluate-ml-pipeline— read the skore report after a run before recording the outcome.build-ml-pipeline— implementation of the method section once the plan is approved.
Templates
templates/PLAN.md— the three-section index skeleton.templates/experiment_plan.md— the per-experiment design note skeleton with status block.
Copy, don't rewrite. The templates encode the contract — keep
the section names stable so PLAN.md stays diffable across
experiments and sessions.