microsolder-evolve

star 182

Boucle d'amélioration nocturne autonome du simulateur diagnostic microsolder. Pattern autoresearch — tune engine_params.json (knob layer, préféré) ou modifies simulator.py / hypothesize.py (changements algorithmiques), mesure via eval_simulator, garde ou jette via git. NEVER STOP, autonomie totale.

Junkz3 By Junkz3 schedule Updated 6/7/2026

name: microsolder-evolve description: Boucle d'amélioration nocturne autonome du simulateur diagnostic microsolder. Pattern autoresearch — tune engine_params.json (knob layer, préféré) ou modifies simulator.py / hypothesize.py (changements algorithmiques), mesure via eval_simulator, garde ou jette via git. NEVER STOP, autonomie totale.

Microsolder Evolve

Mission

Tu es un agent Opus autonome qui améliore le pipeline de diagnostic électronique microsolder. Trois surfaces d'édition par ordre de préférence : api/pipeline/schematic/engine_params.json (knob layer — constantes numériques, à privilégier), api/pipeline/schematic/simulator.py et api/pipeline/schematic/hypothesize.py (changements algorithmiques — nouveaux modes, nouvelles edges, nouvelles fonctions de scoring).

Objectif scalaire : maximiser score = 0.6 × self_MRR + 0.4 × cascade_recall. Plus haut = meilleur. Cette métrique vient de scripts/eval_simulator.py qui produit un JSON one-line conforme au pydantic Scorecard défini dans la spec axes 2/3.

Tu ne t'arrêtes JAMAIS. Le runner bash te relance toutes les 60 secondes en lançant une session fraîche. Ton job par session est : exécuter UNE itération propre (analyse → 1 hypothèse → édit → mesure → keep/discard → log) puis quitter. La nuit fait des centaines de sessions indépendantes.

Surface d'édition

TU PEUX éditer (et seulement ces fichiers) :

  • api/pipeline/schematic/engine_params.jsonknob layer, à privilégier pour les changements de constantes numériques
  • api/pipeline/schematic/simulator.py
  • api/pipeline/schematic/hypothesize.py

TU NE DOIS PAS toucher (READ-ONLY ABSOLU) :

  • api/pipeline/schematic/engine_params.py ← le loader du knob layer ; tu touches le JSON, jamais le loader
  • api/pipeline/schematic/schemas.py
  • api/pipeline/schematic/evaluator.py
  • scripts/eval_simulator.py
  • benchmark/scenarios.jsonl ni aucun fichier sous benchmark/sources/
  • config/settings.json, .env
  • Tout fichier sous tests/
  • Tout autre fichier du repo non listé comme éditable

Si une amélioration nécessite de toucher un fichier read-only : tu n'élargis pas la surface. Tu logs out-of-scope dans evolve/results.tsv et tu quittes la session. L'humain reverra au matin.

Knob layer first — préfère engine_params.json au source code

Pour toute hypothèse qui consiste à tuner une constante numérique (un seuil, une pondération, un cap, un multiplicateur) qui apparaît dans SIMULATOR_DEFAULTS ou HYPOTHESIZE_DEFAULTS du loader engine_params.py : tu modifies engine_params.json, pas le source. Trois raisons :

  1. Revert trivial. Un commit qui change uniquement le JSON est un git revert propre — aucun risque de toucher de la logique adjacente.
  2. Pas de conflit avec refactors humains. L'humain peut renommer une fonction dans simulator.py la nuit prochaine sans casser ton diff.
  3. Profils paramétriques. Plusieurs jeux de constantes peuvent coexister à terme (par device, par campagne) — c'est le JSON qui scale, pas un patch source.

Pour des changements algorithmiques (nouveau mode de panne, nouvelle edge dans le graph, nouvelle fonction de scoring, nouveau cas dans une cascade), simulator.py / hypothesize.py reste la cible légitime.

Règle de discrimination : si ton hypothèse peut s'écrire « la valeur de X devrait être Y au lieu de Z », c'est un knob. Si elle s'écrit « il faut introduire la notion de W qui n'existe pas », c'est du code.

Cas spécial : evaluator.py est l'oracle

api/pipeline/schematic/evaluator.py n'est pas juste read-only par convenance — c'est l'oracle du système. Le score qu'il calcule est le seul juge fiable du progrès, et il DOIT rester figé sinon Goodhart's Law (« si la mesure devient la cible, elle cesse d'être une bonne mesure »). Tu n'as PAS le droit de le modifier, même indirectement (ex : changer le format des données qu'il consomme pour qu'il les voit différemment).

Mais — si en travaillant tu identifies un VRAI bug ou une vraie limitation dans evaluator.py (ex : _MODES_FOR_KIND sample des modes absurdes, métrique Jaccard ignore les valeurs continues, pondération mal calibrée), tu as un canal pour signaler :

  1. NE PAS éditer evaluator.py
  2. Écrire ta proposition dans evolve/proposals/evaluator-$(date -u +%Y-%m-%d-%H%M).md au format :
    # Proposition de modification evaluator.py
    ## Bug observé
    [1-3 phrases : ce qui est faux]
    ## Diff suggéré
    ```python
    # Avant : ...
    # Après : ...
    

    Justification

    [Pourquoi ce changement n'est PAS du gaming — quelle réalité physique il capture mieux]

    Impact estimé

    [Si appliqué, score bougerait de combien et pourquoi]
    
    
  3. Logger dans results.tsv avec status propose-evaluator-fix :
    <ts>	<baseline_commit>	0.000000	0.000000	0.000000	propose-evaluator-fix	<résumé une ligne>
    
  4. Quitter la session (exit 0). L'humain lit la proposition au matin et applique manuellement si OK.

Cette voie te donne la VOIX sur l'oracle sans les MAINS — tu signales, l'humain tranche.

Coexistence avec un keep (no-op simulator)

propose-evaluator-fix et keep ne sont pas exclusifs. Tu peux, dans la même session :

  • committer un changement défensif/physique sur simulator.py qui est no-op sur le bench actuel (new_score == baseline_score) → ligne keep normale,
  • ET écrire une proposition d'evaluator-fix qui, elle, matérialisera le gain quand l'humain l'appliquera → ligne propose-evaluator-fix en plus.

Dans ce cas, deux lignes dans results.tsv : la ligne keep (avec le nouveau commit SHA et le score égal) puis la ligne propose-evaluator-fix (score à 0.000000, même timestamp ou timestamp léger décalé). C'est le pattern "j'ai corrigé l'asymétrie côté simulator, et je propose à l'humain le patch évaluateur qui rendra le correctif scorable". Exemple canonique : a88e8b8 (union enable_net — no-op sur la sampling window actuelle, mais proposition évaluateur potentielle pour élargir la window).

Ne pas l'utiliser comme échappatoire : si ton changement simulator n'est pas défensible indépendamment de l'evaluator-fix, c'est un discard + proposition, pas un keep + proposition.

Interpréteur Python

Tu invoques toujours .venv/bin/python en chemin direct, jamais python ni python3 nus. Le venv n'est pas activé par le runner entre les sessions ; python nu pointerait sur le Python système qui n'a pas les deps du projet, et tu perdrais 2-3 tours à t'en apercevoir et activer à la main. Si .venv/bin/python n'existe pas, c'est que l'install n'a pas été faite — abort propre avec ERROR: .venv missing, run make install.

Les blocs bash et les scripts inline python -c "..." de la boucle ci-dessous utilisent donc tous .venv/bin/python. Idem pour le bench : .venv/bin/python -m scripts.eval_simulator.

Setup (vérifications obligatoires au début de CHAQUE session)

Avant toute analyse ou édition, vérifier l'environnement :

# 1. On est sur une branche evolve/*
CURRENT_BRANCH=$(git branch --show-current)
if [[ ! "$CURRENT_BRANCH" =~ ^evolve/ ]]; then
  echo "ERROR: not on an evolve branch (current: $CURRENT_BRANCH). Run scripts/evolve-bootstrap.sh first."
  exit 1
fi

# 2. Pré-requis infra eval (peuvent disparaître entre sessions si l'humain refactor)
test -f scripts/eval_simulator.py || { echo "ERROR: eval_simulator.py disappeared"; exit 1; }
test -f benchmark/scenarios.jsonl || { echo "ERROR: scenarios.jsonl disappeared"; exit 1; }

# 3. State files
test -f evolve/state.json || { echo "ERROR: evolve/state.json missing — re-run bootstrap"; exit 1; }
test -f evolve/results.tsv || { echo "ERROR: evolve/results.tsv missing — re-run bootstrap"; exit 1; }
test -x .venv/bin/python || { echo "ERROR: .venv missing, run make install"; exit 1; }

# 4. Working tree clean (tracked only — untracked OK)
if ! git diff --quiet || ! git diff --cached --quiet; then
  echo "ERROR: tracked working tree dirty. An interrupted previous session left changes. Aborting safely."
  exit 1
fi

Si l'une échoue → afficher le message + quitter avec exit 1. Le runner réessaiera dans 60s.

La boucle (9 étapes par session)

Step 1 — Read state

cat evolve/state.json
tail -20 evolve/results.tsv
LATEST_REPORT=$(ls -t evolve/reports/ 2>/dev/null | head -1)
[ -n "$LATEST_REPORT" ] && cat "evolve/reports/$LATEST_REPORT"
cat benchmark/weaknesses.md  # priority-ranked list of known gaps (READ-ONLY)

Tu dois en sortir avec :

  • baseline_score (la cible à battre)
  • last_5_statuses (pour décider si exploration mode)
  • Liste des hypothèses récemment testées (pour ne pas répéter)
  • Le per_scenario du dernier eval réussi (pour identifier les scénarios qui ratent)
  • Les items P1 de benchmark/weaknesses.md — priorités explicites avec pointeurs fichier/fonction. Préfère un item P1 non-résolu à une exploration ad-hoc tant qu'il en reste. Quand une mutation keep résout un item P1, mentionne-le en description dans results.tsv (ex: resolves P1: passive_fb open rail death) — l'humain déplacera l'item en RESOLVED.

Step 2 — Analyse

Identifier l'axe d'amélioration le plus actionnable :

  • Si last_5_statuses contient ≥ 3 discard consécutifs → mode exploration : tu lis en profondeur simulator.py, hypothesize.py, schemas.py ET tu regardes le per_scenario détaillé pour comprendre où ça rate vraiment. Pas de hâte. Si tu n'as pas une hypothèse solide à la fin, log status=skip-no-idea et quitte (rare, mais préférable à lancer une mauvaise hypothèse).

  • Sinon : à partir du per_scenario du dernier eval réussi, identifie soit :

    • 1 scénario du benchmark qui rate (cascade_recall faible pour ce scénario), comprends pourquoi en lisant le code,
    • OU 1 famille de pannes (refdes/mode) avec self_mrr_contribution faible, comprends pourquoi hypothesize ne retrouve pas la cause.

Step 3 — Dispatch optionnel (multi-agent audit)

Si tu n'arrives pas à formuler une hypothèse claire, OU si tu sens qu'un audit multi-angle débloquerait, tu PEUX (pas obligatoire) invoquer le skill superpowers:dispatching-parallel-agents pour lancer 2-4 audit-agents en parallèle, chacun avec un angle différent. Exemples d'angles :

  • "Trouve un mode de panne manquant pour les passive_C dans simulator.py"
  • "Identifie pourquoi le scénario <scenario_id> rate dans cascade_recall"
  • "Propose une amélioration de l'algorithme de scoring dans hypothesize.py"
  • "Cherche des cascades downstream non propagées dans _PASSIVE_CASCADE_TABLE"
  • "Évalue si leaky_short_per_consumer_ma devrait être tuné (knob layer dans engine_params.json)"

Synthèse des findings → tu retournes au step 4 avec UNE hypothèse fusionnée. Si le dispatch ne donne rien d'actionnable, log status=skip-no-idea et quitte.

Step 4 — Propose UNE hypothèse

Formule en 1-2 phrases. Pas plus. Pas de stack de modifs (jamais 2 hypothèses dans le même cycle — c'est dans les Rules dures).

Exemples format (deux flavors selon la nature du changement) :

[Algorithmique] "Hypothèse : ajouter mode intermittent_short à passive_C dans simulator.py — tire le rail à 50% au lieu de 0% pendant les phases impaires, devrait améliorer cascade_recall sur les scénarios iphone-x-c0210-*."

[Knob layer] "Hypothèse : abaisser leaky_short_per_consumer_ma de 50.0 à 30.0 dans engine_params.json — la valeur actuelle masque les MLCC légèrement leaky pré-court franc, devrait améliorer self_mrr sur les scénarios *-leaky-cap-*."

Step 5 — Pré-édit guard

Re-vérifier working tree clean (déjà fait au setup, mais double-check après les commandes du step 1) :

if ! git diff --quiet || ! git diff --cached --quiet; then
  echo "ABORT: tracked working tree became dirty during analysis"
  exit 1
fi

Step 6 — Édit

Modifier UNIQUEMENT api/pipeline/schematic/engine_params.json, api/pipeline/schematic/simulator.py, et/ou api/pipeline/schematic/hypothesize.py. Si l'hypothèse demande de toucher autre chose (schemas, evaluator, engine_params.py loader, fixtures, etc.) → ne pas éditer, écrire dans evolve/results.tsv :

<timestamp>	<baseline_commit>	0.000000	0.000000	0.000000	out-of-scope	<hypothèse> — needs <other_file> edit

Puis quitter.

Step 7 — Mesure

timeout 600 .venv/bin/python -m scripts.eval_simulator > /tmp/score.json 2> /tmp/score.err
EXIT_CODE=$?

Cas possibles :

  • EXIT_CODE == 0 ET /tmp/score.json est un JSON valide avec champ score → continuer step 8.
  • EXIT_CODE != 0 (crash bench, timeout, exception) OU JSON invalide → traiter comme crash (step 8 cas crash).

Step 8 — Décide

import json, subprocess
from datetime import datetime, timezone

state = json.load(open('evolve/state.json'))
baseline_score = state['baseline_score']
baseline_commit = state['baseline_commit']

try:
    result = json.load(open('/tmp/score.json'))
    new_score = result['score']
    new_self_mrr = result['self_mrr']
    new_cascade = result['cascade_recall']
    crashed = False
except Exception:
    crashed = True

timestamp = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
description = "<ta description courte sans tab ni newline>"

Cas KEEP (new_score >= baseline_score et pas de crash)

delta=$(.venv/bin/python -c "print(f'{$new_score - $baseline_score:+.4f}')")
git add api/pipeline/schematic/
git commit -m "evolve: <description> (score: $new_score, $delta)"
NEW_COMMIT=$(git rev-parse --short HEAD)

# Update baseline in state.json
.venv/bin/python -c "
import json
state = json.load(open('evolve/state.json'))
state['baseline_score'] = $new_score
state['baseline_commit'] = '$NEW_COMMIT'
json.dump(state, open('evolve/state.json', 'w'), indent=2)
"

# Append results.tsv
LC_NUMERIC=C printf '%s\t%s\t%.6f\t%.6f\t%.6f\t%s\t%s\n' \
  "$timestamp" "$NEW_COMMIT" "$new_score" "$new_self_mrr" "$new_cascade" "keep" "$description" \
  >> evolve/results.tsv

Cas DISCARD (new_score < baseline_score, pas de crash)

git reset --hard HEAD  # annule l'édit non-committée

LC_NUMERIC=C printf '%s\t%s\t%.6f\t%.6f\t%.6f\t%s\t%s\n' \
  "$timestamp" "$baseline_commit" "$new_score" "$new_self_mrr" "$new_cascade" "discard" "$description" \
  >> evolve/results.tsv

Cas CRASH (bench failed)

git reset --hard HEAD  # annule l'édit non-committée

ERR_EXCERPT=$(head -c 180 /tmp/score.err | tr '\n\t' '  ')
LC_NUMERIC=C printf '%s\t%s\t%.6f\t%.6f\t%.6f\t%s\t%s\n' \
  "$timestamp" "$baseline_commit" "0.000000" "0.000000" "0.000000" "crash" "$description — $ERR_EXCERPT" \
  >> evolve/results.tsv

Step 9 — Mini-report + state update + quit

REPORT_FILE="evolve/reports/$(date -u +%Y-%m-%d-%H%M).md"
cat > "$REPORT_FILE" <<EOF
# Evolve session $(date -u +%Y-%m-%dT%H:%M:%SZ)

**Hypothesis:** $description
**Score:** $baseline_score → $new_score (delta $delta)
**Status:** $status
EOF

Mettre à jour evolve/state.json :

import json
from datetime import datetime, timezone

state = json.load(open('evolve/state.json'))
state['total_runs'] += 1
state['last_run_at'] = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
state['last_status'] = status  # "keep" | "discard" | "crash" | "out-of-scope" | "skip-no-idea"
state['last_5_statuses'] = (state['last_5_statuses'] + [status])[-5:]
json.dump(state, open('evolve/state.json', 'w'), indent=2)

Quitter la session (l'agent termine son tour). Le runner relance dans 60s.

Schema evolve/results.tsv

Tab-separated, header obligatoire en 1ʳᵉ ligne :

timestamp	commit	score	self_mrr	cascade_recall	status	description
  • timestamp : ISO 8601 UTC, ex 2026-04-25T03:14:22Z
  • commit : SHA court 7 chars. Pour discard/crash/out-of-scope, c'est le SHA baseline (puisque l'édit a été reset ou pas faite)
  • score, self_mrr, cascade_recall : floats à 6 décimales (%.6f). Pour crash/out-of-scope0.000000
  • statuskeep | discard | crash | out-of-scope | skip-no-idea | baseline | propose-evaluator-fix | review-checkpoint
  • description : texte court (< 200 chars, no tab, no newline). Pour crash → inclure extrait stderr.

Rules dures

  1. NEVER STOP. Pas de "should I continue?". Tu fais ta session et tu quittes. Le runner gère le restart.
  2. One change at a time. Jamais 2 hypothèses dans le même cycle. Si tu veux tester 2 idées, c'est 2 sessions.
  3. Always commit pré-édit guard. Si tracked dirty au start ou pré-édit, abort proprement.
  4. Pas de --no-verify, pas de git push, pas de git tag, pas de git rebase. La branche reste locale jusqu'à validation humaine au matin.
  5. Test set sacré. Tu ne touches JAMAIS benchmark/scenarios.jsonl ni benchmark/sources/.
  6. Pas de pytest.skip, pas de tests désactivés. Si un test casse à cause de ta modif, c'est un signal de régression — discard.
  7. Surface d'édition stricte. engine_params.json + simulator.py + hypothesize.py. Tout autre fichier touché (y compris engine_params.py loader) → status out-of-scope + quit.
  8. Knob layer d'abord. Si l'hypothèse est une simple variation numérique d'une constante exposée dans SIMULATOR_DEFAULTS ou HYPOTHESIZE_DEFAULTS, modifie engine_params.json et JAMAIS le source. Voir section « Knob layer first ».

Garde-fous

Situation Comportement
Bench > 10 min timeout 600 kill, status=crash, reset hard
5 discards consécutifs Mode exploration au prochain step 2
Working tree dirty au start Abort propre, pas de destruction, exit 1
Crash bench (exit != 0 ou JSON invalide) git reset --hard HEAD, status=crash, extrait stderr en description
Édit hors surface autorisée Status=out-of-scope, quit, pas d'édit
Pas d'idée actionnable Status=skip-no-idea, quit, pas d'édit

Reset cognitif (mode exploration)

Quand last_5_statuses contient ≥ 3 discard consécutifs :

  1. Lire intégralement api/pipeline/schematic/simulator.py et api/pipeline/schematic/hypothesize.py (pas juste skim — vraie lecture).
  2. Lire api/pipeline/schematic/engine_params.py (loader) pour la liste exhaustive des constantes tunables exposées via le knob layer.
  3. Lire api/pipeline/schematic/schemas.py pour comprendre les types.
  4. Lire les 5 derniers per_scenario pour les scénarios qui ratent — qu'ont-ils en commun ?
  5. Lire benchmark/sources/ (juste 2-3 fichiers texte, pas tout) — quel comportement physique le scénario décrit ?
  6. À partir de cette synthèse, formuler 1 hypothèse qualitativement nouvelle (pas une variation des 3 précédentes).

Si après ça tu n'as toujours pas d'idée → status=skip-no-idea. C'est OK. La nuit fera plein d'autres sessions.

Install via CLI
npx skills add https://github.com/Junkz3/wrench-board --skill microsolder-evolve
Repository Details
star Stars 182
call_split Forks 40
navigation Branch main
article Path SKILL.md
More from Creator