Bug observé sur replay_sess_56c10222 (2026-05-24 20:14) :
action 11 (clic 'Enregistrer' expected_after='Enregistrer sous')
marquée success=True alors que 2 actions plus tard la fenêtre observée
est 'NoMachine Desktop Viewer'. Le polling post-vérif a probablement
matché brièvement 'Enregistrer sous' puis l'écran a changé sans
qu'on ne revérifie.
Dom : "Le contrat est rompu : Léa passe d'une action à l'autre sans
vérifier que la précédente est bonne. Il faut un contrôle de résultat,
si on ne sait pas on demande."
Patch : juste après le match initial, attendre 0.5s et reverifier
la fenêtre active. Si elle a divergé (race condition, dialog auto-
fermée, focus change OS) → matched=False, le flow strict existant
prend le relais avec wrong_window + needs_human.
Ne touche que les cas où expected_after est défini ET pas de
runtime_dialog géré entre temps (le runtime_dialog est légitime de
changer la fenêtre).
Tag rollback : rollback/pre-P0.9-2026-05-24_2148
Léa avait déjà une infra pour les dialogs runtime (`_match_known_runtime_dialog`
+ `_handle_known_runtime_dialog`) mais avec un catalog local limité à
2 entrées. Le DialogResolver R2 côté serveur a 10 entrées centralisées.
P1.MVP : `_try_dialog_resolver_server()` consulte l'endpoint
`/api/v1/dialog/resolve` quand le catalog local n'a pas matché. La
réponse `DialogResolution` est convertie en dialog_spec compatible
avec `_handle_known_runtime_dialog` qui réutilise la cascade existante
(serveur VLM grounding + template matching local).
- Flag `RPA_DIALOG_RESOLVER_AGENT_ENABLED` (OFF par défaut) — rollback runtime
- Auth Bearer via `_auth_headers()` existant
- Timeout 3s, fail-safe sur exception/503/no-match → fallback humain intact
- Zéro régression sur les chemins existants (le catalog local reste 1ère ligne)
Tests unitaires en local (6/6 OK) :
- flag OFF → None
- serveur 503 → None
- matched=False → None
- policy=pause (UAC) → None
- match auto + click_button → dialog_spec valide
- exception réseau → None
Tag rollback : rollback/pre-P1-2026-05-24_2105
Friction UX remontée par Dom sur replay live (replay_sess_63a1313b) :
latence excessive 2-3 minutes après un échec d'action avant que Léa
ne reprenne la main. 120s = trop long pour un humain en supervision.
10s d'inactivité reste le critère prioritaire (déjà en place), donc :
- humain actif : la correction est captée et le replay reprend en ~1s
- humain absent : on libère après 30s au lieu de 120s
5 sites d'appel + signature de fonction (default param) alignés.
Tag rollback : rollback/pre-P0.8-2026-05-24_1912
Référence : message 2026-05-24_1910_claude-to-codex_p07-memory-sanity-fix-human-supervised-bug-frictions-ux.md
Bug observé sur replay_sess_63a1313b 2026-05-24 18:31-18:32 :
_capture_human_correction() côté Léa retourne des human_actions sans
clic humain réel (cause racine côté agent à investiguer = P0.6).
En cascade, memory_record_success était appelé avec coords (0.0, 0.0)
et stockait des entrées poison dans target_memory.db.
Le sanity check existant rejetait < 0 ou > 1 mais laissait passer (0,0)
qui est mathématiquement valide. Au prochain replay, memory_lookup
trouvait l'entrée poison et faisait cliquer Léa au coin haut-gauche.
Patch : rejet explicite de (0,0) + warning au lieu de debug pour les
coords hors [0,1] (besoin de tracabilité runtime).
Filet en aval — la vraie cause côté Léa reste à corriger (P0.6).
Tag rollback : rollback/pre-P0.7-2026-05-24_1850
Symptôme observé sur replay_sess_7a4c8e72 (24/05 17:57) :
- Action act_setup_sess_verify (type=verify_screen) échoue 4x (+3 retries)
- Logs: [VALIDATOR_V2] override success→False verdict=continue conf=0.30
failure_category=None reason='Aucun changement visible pour
verify_screen (normal pour ce type d'action)'
- Replay tombe en status=error à 7/15 (régression vs 12/15 sans V2)
Cause: api_stream.py:3674 testait `if verdict != COMPLETE` (trop large) →
toute action qui ne change pas drastiquement l'écran (verify_screen, wait,
key_combo Ctrl+S avant ouverture dialog, etc.) renvoie verdict=CONTINUE
conf=0.30 du PixelDiffChecker via le default_checker de l'orchestrator,
ce qui était traité comme un échec à overrider.
Fix: override SEULEMENT sur verdict=TERMINATE (échec certain avec
failure_category). CONTINUE = faible signal = on laisse le pipeline
historique trancher.
COMPLETE n'a pas besoin d'être traité ici car on est déjà dans
`if report.success:` (success initial vrai).
Effet:
- verify_screen/wait/key_combo non-interactif → orchestrator retourne
CONTINUE conf=0.30 → V2 ne touche pas report.success (comportement
legacy préservé)
- click qui rate (act_raw_6c1432b3 type cible) → OcrRoiChecker retourne
TERMINATE conf=0.85 failure_category=WRONG_APPLICATION → override OK
Tests R1 inchangés (TERMINATE branch testée explicitement).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptôme observé replay_sess_4c38dbb8 (24/05) :
- 0/15 actions avec expected_result rempli
- Conséquence : api_stream.py:3630 verify_with_critic() jamais appelé
(conditionné à action.expected_result non vide)
- Donc Critic sémantique (Ollama) désarmé en production, seul le
pixel-diff tournait
Causes racines identifiées :
1. _GEMMA4_PORT=11435 hardcodé (legacy Docker dédié supprimé) →
check /api/tags timeout silencieux → fonction sort early
2. _CRITIC_MODEL="gemma4:e4b" hardcodé → modèle non installé
3. "think": True dans le payload → "qwen2.5vl:7b-rpa" does not
support thinking → 400 sur tous les appels → if not resp.ok: continue
4. Prompt sans few-shot → qwen2.5vl converse au lieu de respecter
le format strict INTENTION/AVANT/APRES → parsing vide
Fix (stream_processor.py) :
- _GEMMA4_PORT default 11435 → 11434 (Ollama native)
- _CRITIC_MODEL = os.environ.get("RPA_CRITIC_MODEL", "qwen2.5vl:7b-rpa")
- Remplacement de 3 "gemma4:e4b" hardcodés → _CRITIC_MODEL
- _unload_gemma4() → no-op (legacy Docker n'existe plus)
- Prompt enrichissement : ajout exemple few-shot (Cliquer Enregistrer)
- "think": True → False (qwen2.5vl ne supporte pas)
Config .env.local :
- RPA_VLM_MODEL=qwen2.5vl:7b → qwen2.5vl:7b-rpa (variant num_ctx=8192,
créé via Modelfile pour permettre offload partiel GPU sur RTX 5070
12 GB ; sans ça, num_ctx=128k par défaut = 12.5 GB requis = OOM full
CPU fallback observé 17:11 le 24/05)
Validation :
- Avant fix : 0/8 actions enrichies (110 ms total = appels échoués
immédiatement avec 400)
- Après fix : 5/8 actions enrichies en 35s (~7s/action, cohérent avec
appels VLM réels qwen2.5vl)
Side effects systemd (à committer séparément côté infra) :
- OLLAMA_KEEP_ALIVE: 5m → 24h
- t2a-viewer.service stopped + disabled (libère ~2.9 GB VRAM)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptome (3 incidents 24h les 24/05) : apres relance distante de Lea via SSH,
les polls /replay/next repartent un moment puis s'arretent. Diagnostic :
- agent_v1/ui/smart_tray.py:875 utilise pystray.Icon.run() comme boucle principale
- main.py:132-133 lance _replay_poll_loop et _background_heartbeat_loop en
daemon threads
- Quand Lea est lancee via sshpass sans session interactive Windows, pystray
echoue (pas de systray accessible) et icon.run() sort
- agent.run() retourne, main() retourne, main thread termine
- Les daemon threads meurent avec le main thread (par design Python)
Fix : _headless_keepalive() maintient le main thread vivant via threading.Event
quand agent.run() sort en laissant agent.running=True (cas anormal). Handlers
SIGTERM/SIGINT/SIGBREAK pour shutdown propre.
Invisible en mode interactif normal (icon.run() ne sort jamais).
Pas de modification de smart_tray ni de la cascade visuelle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backup état complet après enregistrement vidéo démo de bout en bout.
À utiliser comme point de référence pour la consolidation post-démo.
Changements majeurs de la session 18-19 mai :
- AIVA-URGENCE : page autonome avec preset URL + auto-focus chain
- Workflow Demo_urgence_3_db : merge linux_db + steps AIVA + pause humaine NoMachine
- Bypass LLM (static_result / static_text) dans replay_engine
pour démos déterministes sans appel Ollama
- Fix api_stream:3013 — replay_paused au premier polling /next
- dag_execute : lift duration_ms vers top-level pour wait runtime
- NPM bypass auth /aiva-urgence/ via location ^~ (proxy_host/10.conf hors git)
- scripts/cancel-replays.sh — workaround Stop VWB qui ne purge pas la queue
Anchors visuels (468) forcés dans le commit pour garantir restorabilité.
DB workflows actuelle + ~12 .bak DB de la journée incluses.
Sujets identifiés pour consolidation post-démo (TODO) :
1. Bug VWB recapture anchor ne régénère pas le PNG
2. Léa client accumule état mémoire (restart périodique requis)
3. Stop VWB ne purge pas la queue serveur (lien manquant vers /replay/cancel)
4. Bug coord client mss tronqué 2560x60 → mapping Y cassé
5. delay_before/delay_after ignorés au runtime (fix partiel duration_ms)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bench standalone qui exécute build_dpi_enriched + appel LLM sur les 11
dossiers POC GHT Sud 95 (docs/clients/ght_sud_95/mockup_easily_assure/data.js),
sans passer par Demo_urgence_2 ni Léa/Windows. Permet de mesurer la
convergence durée/décision Python ↔ LLM sur un panel représentatif AVANT
d'écrire le garde-fou serveur du commit 2.
core/llm/t2a_mappings.py :
- Module partagé TERRAIN_VERS_T2A (4 entrées validées par Dom 12/05)
- Importé par le bench, sera importé aussi par le garde-fou serveur commit 2
- Cas non mappés volontairement documentés (Retour structure d'origine,
chaîne vide pour statut_attente)
scripts/bench_t2a_dryrun.py :
- Parsing data.js via node (vm.runInContext) → 11 dossiers en JSON
- Reconstruction d'un dpi_raw plat simulant l'OCR scroll auto :
bandeau Easily Assure répété 5x (1 par onglet) + sections motif /
examens / imagerie / notes médicales / Synthèse Urgences au format
LIBELLÉ VALEUR
- NE bypasse PAS build_dpi_enriched : le dpi_raw est texte plat re-parsé
par la fonction (test de robustesse réel du parser regex)
- Appel LLM déterministe : temperature=0, seed=42, model=gemma4:31b-cloud
- Vérification empirique du respect du seed (2 appels successifs sur 1er
dossier, comparaison decision/durée/justif) → warning si bruit cloud
- 4 traces structurées par dossier dans logs/t2a_dryrun/<IPP>_<ts>.log :
[t2a_dryrun_metadata] / [t2a_dryrun_prompt] / [t2a_dryrun_response]
ou [t2a_dryrun_error] en cas d'échec API
- Filet data_quality_warning (incohérence âge déclaré vs date naissance,
motif vs diagnostic principal, décision vide) — filet, pas analyse
exhaustive ; signale sans corriger (anonymisation v1 incertaine)
- Tableau récap stdout 9 colonnes + CSV scripts/bench_t2a_dryrun_<ts>.csv
- Stats agrégées : convergence durée X/N, convergence décision X/N
mappés, liste détaillée des divergences avec pointeurs vers logs
- Recommandation auto : réécrire PROMPT 3 ou non selon convergence durée
Activation : T2A_DRYRUN=1 python scripts/bench_t2a_dryrun.py
Options : --ipp <IPP> (1 dossier), --skip-seed-check
Smoke test pré-commit (sans LLM) : parsing + dpi_raw + build_dpi_enriched
sur les 11 dossiers → 11/11 metadata complets, 0 parsing_warning,
durées calculées de 2.0h à 12.02h, décompo décisions terrain conforme
(7 Consultation + 1 Hosp + 1 UHCD + 1 Transfert + 1 Retour structure).
Brief complet : docs/handoffs/2026-05-12_brief_S1_build_dpi_enriched.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Préprocesseur Python qui injecte un bloc FAITS_CALCULÉS en tête du DPI
avant l'appel LLM, pour neutraliser l'hallucination de durée (bug "23h"
sur cas MOREL, confusion avec "depuis 23h" de l'Observ. IDE Urg).
Extrait depuis le bandeau Easily Assure et la Synthèse Urgences :
- âge (dateutil.relativedelta)
- date admission / sortie + durée passage (format humain + décimal)
- CCMU / GEMSA libellé complet (parser multi-ligne)
- priorité IAO, mode de venue / médicalisation / mode d'entrée
- diagnostic principal
- decision_terrain + orientation_terrain (metadata only, jamais injectés
dans le prompt pour ne pas biaiser le LLM)
Retour tuple (dpi_enriched, metadata) pour permettre les garde-fous
serveur Python ↔ LLM au commit 2.
Robustesse :
- re.search 1re occurrence + WARNING si bandeau divergent multi-occurrences
- Synthèse Urgences priorité sur bandeau pour dates
- Valeur exigée sur même ligne que label (évite capture de section title)
- Cas négatif (horaires absents) → "NON CALCULABLE" + parsing_warnings
- Jamais de crash, retour tuple toujours valide
Tests : 4/4 verts (golden MOREL string + metadata, négatif sortie absente,
DPI vide). Pas de régression sur tests/integration/test_t2a_extract.py.
Brief complet : docs/handoffs/2026-05-12_brief_S1_build_dpi_enriched.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Avant : 4 occurrences de parsing en cascade dans resolve_engine.py
(L840-885, L903-915, L2569-2580, ~110 lignes au total).
Après : centralisation dans core/grounding/bbox_parser.py avec
paramètre formats= permettant de filtrer les formats reconnus
selon le contrat sémantique de chaque site d'appel.
Préservation des contrats sémantiques (strict no-op) :
- Occ 1+2 (cascade principale) : tous formats (par défaut)
- Occ 3 (retry multi-image) : formats={"xy_json", "raw_array"}
pour respecter le prompt qui impose {"x": NNN, "y": NNN} in pixels
- Occ 4 (_locate_popup_button) : formats={"bbox_2d"} pour respecter
le prompt qui demande "bounding box"
Notes :
- Mini-bug Occ 3 retry multi-image (division systématique sans
heuristique x>1, produisait coordonnées aberrantes ~0.0004 si
VLM retournait déjà du pourcentage) corrigé incidemment via
centralisation. Pas de régression possible (résultat précédent
aberrant par construction).
- Occ 4 : bbox_2d strict 4-coords élargi à bbox_2d 2 ou 4 coords.
Contrat sémantique "bounding box" respecté ; un point 2-coords
interprété comme centre de bbox.
Tests : 26 cas dans test_bbox_parser.py (tous formats × cascade
+ filtre formats= + validated). 121 PASS / 0 FAIL sur le périmètre
refactor (5 fichiers ciblés).
Net : -96 lignes dans resolve_engine.py, +120 lignes module
+ 250 lignes tests.
refs DETTE-006 (étape 2/5 du fix smart_resize)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Module pur core/grounding/smart_resize.py implémentant la formule
smart_resize officielle (transformers.qwen2_vl.image_processing_qwen2_vl,
utilisée par Qwen3VLProcessor pour les images via wrap Qwen2VLImageProcessor).
Helpers exposés : _round_by_factor, _floor_by_factor, _ceil_by_factor.
Constantes : FACTOR_DEFAULT=28, MIN_PIXELS_DEFAULT=3136,
MAX_PIXELS_DEFAULT=1_003_520, MAX_RATIO_DEFAULT=200.
Tests : tests/unit/test_smart_resize.py — 32 cas, 100% coverage sur le
module (mesure via coverage API directe, pytest-cov bloqué par bug cv2
préexistant tracé dans DETTE-011).
refs DETTE-006 (étape 1/5 du fix smart_resize)
refs DETTE-007 (création de la 3ème implémentation, à unifier post-démo)
refs DETTE-010 (vérif preprocessor_config.json checkpoint Qwen3-VL-8B
bloquante avant Étape 2)
refs DETTE-011 (bug cv2 contourné pour mesure coverage)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 commits du jour : pré-check OCR réactivé + instrumenté + bug
spatial documenté. Plan demain : fix smart_resize vLLM ciblé
selon MIGRATION_VLM_PLAN_2026-05-09.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug découvert pendant test live du 2026-05-08.
_text_match_fuzzy valide la présence du texte dans le crop (560×560 px)
sans vérifier sa position au point cliqué. Sur onglets serrés (3 px),
valide à tort les clics adjacents.
À fixer post-démo Kerella - Option B préférée
(bboxes EasyOCR + distance).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Avant : succès silencieux (seul rejet loggé)
Après : log INFO à chaque appel avec by_text, position, méthode,
observed, is_valid, latence
Permet de valider en runtime que le pré-check OCR tourne bien
sur les résolutions resolved=True (cf commit 731b5bcae).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diagnostic post-bench E2E (rapport docs/E2E_TEST_RUN_2026-05-08.md) :
1. BUG SILENCIEUX MAJEUR (api_stream.py:4549) — quand le pré-check OCR
rejette, mon code de rejet hier soir met x_pct=None / y_pct=None.
Le log structuré faisait result.get('x_pct', 0):.4f → None:.4f →
TypeError → réponse "analysis_error" qui MASQUE le vrai motif
"rejected_text_mismatch". Conséquence : pendant toute la session
du 7 mai soir, les rejets pré-check ont été silencieusement
transformés en erreurs analyse → cascade locale Léa V1 → clic au pif.
Fix : `(result.get('x_pct') or 0):.4f` traite None | None | 0
uniformément.
2. FLAG ENV pré-check OFF par défaut — le pré-check
_validate_text_at_position introduit hier soir a 2 défauts
identifiés par le bench E2E sur 8 click_anchor :
* radius_px=200 trop petit pour les tabs à 2 tokens (Examens
cliniques, Synthèse Urgences) — OCR voit un crop tronqué
"Maquette POC ler en cours Codage Statistiques" qui n'inclut
pas "Examens" → fuzzy match 1/2 = 50% < seuil 0.60 → REJET.
À radius 300/400 le mot est inclus → match passe.
* min_token_ratio=0.60 trop strict pour cibles 2 tokens.
Solution démo : flag env RPA_ENABLE_TEXT_PRECHECK (défaut "false").
Le pré-check est désactivé par défaut → retour au comportement
stable d'avant-hier (hybrid_text_direct ≥ 0.80 utilisé direct,
exemption drift préservée). Code et fonction _validate_text_at_position
conservés en place pour reprise post-démo après calibrage radius
adaptatif (≈ 0.17 × min(screen_w, screen_h)) et token_ratio descendu
à 0.50.
Pour ré-activer en dev/test : `RPA_ENABLE_TEXT_PRECHECK=true`
dans .env.local ou env du service rpa-streaming.
Inclus aussi :
- docs/E2E_TEST_RUN_2026-05-08.md (rapport agent test E2E ~1700 mots)
- tests/e2e/urgence_aiva_demo_expected.yaml (tolérances re-écrites)
- tests/e2e/fixtures/urgence_aiva_demo/live/*.png (8 fixtures
recapturées headless 1920x1080 pour itérer demain)
- _ocr_inventory.json + _run_resolve_results.json (raw runs)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Audit exhaustif des 101 fichiers .md de ~/.claude/projects/-home-dom-ai-rpa-vision-v3/memory/.
Aucun fichier mémoire modifié — diagnostic seul, à valider par Dom.
Constats critiques :
- MEMORY.md = 273 lignes (limite chargement 200) → ~73 lignes
silencieusement perdues à chaque démarrage de session
- ~50% des fichiers réels ne sont pas indexés dans MEMORY.md
- Référence cassée : MEMORY ligne 257 pointe vers
feedback_pull_not_push.md qui n'existe pas
- 3 feedback NEW créés le 7 mai (non ajoutés à l'index) sont
précisément les règles qui sécurisent la démo GHT jeudi 8 mai :
* feedback_orphans_are_projections.md
* feedback_verifier_avant_apres_clic.md
* architecture_lea_v1_find_text_client.md
Risque concret : un Claude futur (sans ces feedback en mémoire active)
va reproposer les bourdes que Dom a explicitement nommées hier soir :
"re-capturer les ancres" et "nettoyer les modules orphelins".
Top 7 feedback proposés en TOP CRITICAL :
1. prendre_le_temps (DEVISE)
2. orphans_are_projections (NEW)
3. verifier_avant_apres_clic (NEW)
4. lea_v1_find_text_client (NEW architecture)
5. ollama_vs_transformers
6. no_rustine
7. anonymisation_stricte
Proposition réorganisation 4 zones :
- 🔥 TOP CRITICAL ~12 fichiers
- 📌 ACTIVE ~25 fichiers
- 📚 REFERENCE ~12 fichiers
- 🗄️ ARCHIVE ~50 fichiers
Compactage cible : MEMORY.md → 150 lignes (marge 50 avant
retrigger limite chargement).
4 décisions ouvertes pour Dom (cf rapport §8) :
1. feedback_pull_not_push.md — créer ou supprimer la référence
2. Valider l'archivage des ~45 fichiers proposés
3. Trancher 4 fichiers INCERTAIN (dashboard_config, data_extraction,
objectif_6avril, actor_*)
4. Approuver 7 règles de gestion future (1 feedback = 1 violation
observée, MEMORY ≤ 180 lignes, rotation sessions > 21j, etc.)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Audit du cœur métier de la démo GHT Sud 95 (8 mai 2026), du point de vue
d'un médecin DIM senior qui se ferait challenger par le DSI Carvella.
Confronte : arbre officiel RPU UHCD IA.pptx (7 slides), code métier
agent_chat/urgences_orchestrator.py + core/llm/t2a_decision.py, prompts
LLM en place, 11 dossiers anonymisés data.js, bench Dom 18 modèles,
référentiels officiels (SFMU 2024, instructions DGOS, arrêtés 2021/2024
ATIH, recommandations IPAQSS).
Findings critiques (avant démo) :
1. Bug silencieux modèle — t2a_decision.py:28 met DEFAULT_MODEL=qwen2.5:7b
(64 % accuracy au bench Dom) alors que gemma3:27b-cloud (73 %) est
retenu par BENCH_T2A_DECISION_11DOSSIERS. Si T2A_MODEL pas posé via
env, on tourne sur le mauvais modèle. 9 points d'accuracy laissés
sur la table.
2. Règle de combinaison incorrecte dans le prompt — code dit "au moins
2 sur 3 ⇒ REQUALIFICATION" alors que l'arbre PPTX d'Eaubonne dit
"si oui aux 3 critères". Cause probable des faux positifs UHCD du
bench (25003284, 25056615). Quick win = passer à 3/3.
3. Trous métier dans le prompt : aucune mention CCMU, GEMSA, durée,
mode de sortie, type de forfait précis (SU2/PE2/Standard). C'est
exactement où se loge le ROI 100k€/mois. 5 quick wins prompt
rédigés prêts à coller dans §E.4 du rapport.
4. Trois dossiers à NE PAS montrer en démo (25056615, 25151530, 25003475,
25048485) — trop ambigus, hallucinations LLM, structure non tranchée.
5. Trois dossiers à mettre en avant (25003451 SU2 plaie 2h, 25010621
PE2 laryngite, 25003364 UHCD pneumo SLA) — décisions justes,
justifications béton.
Argumentaire pré-démo : 9 questions/réponses face à Carvella
(instructions DGOS, SFMU, cumul SU2+PE2, hallucination LLM, ROI 100k€).
Roadmap post-démo pour Amina : bench étendu 50-100 dossiers + 3
inférences/dossier, fine-tune t2a-gemma3-27b, distinction forfaits
fine, module ATIH-aware, couverture pédia/géria/psy, sortie contre
avis, transferts.
Note : aucun changement de code dans ce commit. Rapport seul. Les
quick wins identifiés (3/3, modèle par défaut, prompts enrichis)
sont à appliquer demain matin avec validation Dom + Amina.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Réduit le cycle debug d'un workflow de 1-2 min (replay manuel via
Windows + Léa V1 + maquette) à ~2-5s (mock client Linux contre
serveur de streaming localhost:5005). 30-60× plus rapide.
Architecture :
- tools/test_replay_e2e.py — harness CLI (~580 lignes), reproduit la
chaîne réelle : VWB /api/v3/execute-windows → streaming /replay/raw
→ boucle /replay/next côté harness avec resolve_target sur un
screenshot fixture → POST /replay/result. Pas de modification serveur.
- tests/e2e/test_urgence_aiva_demo.py — wrapper pytest (smoke).
- tests/e2e/urgence_aiva_demo_expected.yaml — référence générée par
--export-expected, pour comparaison régression auto.
- pytest.ini — ajout du marqueur e2e.
Usage :
python tools/test_replay_e2e.py --execution-mode autonomous --max-iter 120 --verbose
python tools/test_replay_e2e.py --single-step 8 --shot <heartbeat>.png
python tools/test_replay_e2e.py --expected tests/e2e/urgence_aiva_demo_expected.yaml
pytest tests/e2e -v -m e2e
Sortie : tableau Markdown step × méthode × score × pos × status × diag.
Limitations connues (extensions post-démo) :
- Une seule fixture screenshot pour tout le replay → click_anchor réalistes
échouent dès qu'on dépasse l'écran fixture. Carte step_id → fixture à venir.
- extract_text/table/t2a_decision exécutés côté serveur, observables mais
pas modifiables.
- Pas de simulation screenshot_after → ReplayVerifier (Critic VLM) ne tourne pas.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Démo GHT 8 mai 2026 — Dom utilise UNIQUEMENT Léa V1 sur Windows pendant
la démo (pas le frontend VWB Linux), donc les pause_message du serveur
doivent être visuellement évidents sur l'écran Windows. Modifications
client validées par Dom + redéployées via SCP (procédure 2026-04-28).
1. ui/paused_toast.py (NEW) — Toast Tkinter custom autonome :
Toplevel topmost overrideredirect, fond bleu Léa (#2563EB), 380px,
haut-droite, auto-close 15s, click-to-close. Re-pin -topmost à
100/500/2000 ms (Windows démet le flag quand le focus part). Rate
limit 3s sur message identique. Aucune dépendance externe (tkinter
stdlib uniquement). Thread-safe : root.after si Tk root existe,
sinon Tk dédié dans un daemon thread. Remplace plyer qui s'avère
silencieux sur Windows 11 (Focus Assist + manque app-id COM).
2. ui/chat_window.py — _add_paused_bubble force la visibilité :
La fenêtre Léa démarrait avec root.withdraw() — la bulle paused
était bien rendue mais invisible. Ajout deiconify+lift+focus_force
avant render, plus appel à show_paused_toast en complément.
3. ui/notifications.py — niveau BLOCAGE déclenche aussi le toast :
Quand notify_message reçoit un MessageUtilisateur.BLOCAGE (cible
non trouvée, mode apprentissage, fenêtre incorrecte), appelle
show_paused_toast en plus de plyer. Couvre la branche supervision
client (executor.py:1012) qui ne passe pas par Plan B serveur.
4. core/executor.py — Plan B replay_paused (lignes 1812-1850) :
Intercepte data["replay_paused"]=True dans la réponse /replay/next,
appelle chat_window._add_paused_bubble si _chat_window_ref défini,
sinon fallback notifier.notify. Idempotence via _last_pause_msg_shown
pour ne pas spammer (1 toast par (replay_id, message) unique).
Threshold FIND-TEXT _find_text_on_screen : 0.50 → 0.75 pour rejeter
les faux positifs (placeholders italiques, tabs voisins) et tomber
en mode apprentissage humain plutôt qu'un clic au pif.
5. main.py — Wiring ChatWindow → Executor pour Plan B.
6. tools/test_lea_toast.py + ui/_test_paused_toast.py (NEW) — Scripts
de test isolé pour validation visuelle rapide sans relancer un
replay complet (commande dans les docstrings).
Validé visuellement sur DESKTOP-58D5CAC. Toasts apparaissent en haut-
droite, fond bleu, auto-close 15s. Test isolé Dom : 3 toasts successifs
visibles sans accroc.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Trois changements complémentaires dans la cascade de résolution serveur,
finis ce soir 7 mai pour la démo GHT 8 mai. Restaure le comportement strict
d'avril 2026 (workflow qui passait 20 fois d'affilée sans incident).
1. resolve_engine.py — _validate_resolution_quality (lignes 2255-2289) :
Le commit b584bbabc du 1er mai 2026 ("fix(stream): démo UHCD") avait
transformé le rejet strict (resolved=False, method="rejected_drift_*")
en fallback aveugle (resolved=True, method="fallback_recorded_coords",
coords du record). Symptôme observé : Léa cliquait sur "Dossier en
cours" du menu au lieu de "Synthèse Urgences" du tab — le VLM Quick
Find Ollama hallucinait à (0.526, 0.918), drift dépassé, fallback
ratait. Restauré : resolved=False explicite, le client passe en
pause supervisée comme prévu (philosophie échec = apprentissage).
2. resolve_engine.py — exemption high-confidence élargie :
L'exemption drift>0.20 IGNORÉ ne couvrait que template_matching ≥ 0.95
(commit 35b27ae49 du 2 mai). Étendue à hybrid_text_direct ≥ 0.80 :
un OCR direct qui trouve le texte cible exact à score 0.80+ est aussi
sûr qu'un template à 0.95 — la position est sémantiquement vraie,
le drift reflète juste un changement de layout (résolution écran,
refonte UI, scroll), pas une erreur de résolution.
3. resolve_engine.py + api_stream.py — pré-check OCR sémantique :
Nouvelle fonction _validate_text_at_position (singleton EasyOCR fr+en,
crop 200px autour de la coord résolue, fuzzy match 60% des tokens
≥3 caractères de l'expected_text). Câblée dans api_stream.py juste
après _validate_resolution_quality. Si le by_text attendu n'est PAS
présent dans la zone autour de la coord résolue → resolved=False
method="rejected_text_mismatch" → pause supervisée.
Pattern Verification-Aware Planning (state of the art 2026 — voir
recommandations agent archéologue + agent SOTA review) : le serveur
ne renvoie une coord que s'il est sémantiquement sûr du résultat.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Deux corrections liées au scenario démo Urgence GHT (workflow lecture
multi-onglets + t2a_decision + pause_for_human + saisies dans Codage) :
1. Mode supervised propagé jusqu'au pipeline replay
---------------------------------------------------
Symptôme constaté ce 7 mai : Léa lit les onglets, t2a_decision tourne
(variable `dec` présente avec decision="FORFAIT_URGENCE"), mais la
pause_for_human est SKIPPÉE silencieusement et les saisies type_text
s'enchaînent dans le mauvais écran.
Cause : api_stream.py:2140 passait `params={}` codé en dur lors de la
création du replay_state. Conséquence : le code en aval qui lit
`replay_state.params.execution_mode` (api_stream.py:2964) avait toujours
le défaut "autonomous" → branche QW4 :
# Mode autonome sans safety_checks → skip (comportement legacy)
logger.info("pause_for_human ignorée (mode autonome)")
Modifications :
- RawReplayRequest gagne un champ `params: Optional[Dict[str, Any]]`
- start_raw_replay propage `request.params or {}` à _create_replay_state
- dag_execute.execute_windows force par défaut
`data['params']['execution_mode'] = 'supervised'` quand le frontend
ne précise rien (cas démo VWB → Windows). Override possible.
Conséquence : la pause_for_human du workflow Urgence déclenche bien la
PauseDialog VWB ("Décision : {{dec.decision_court}}"). Le médecin valide
ou annule avant que les saisies type_text ne s'exécutent dans Codage.
Note pour la démo réelle (post-aujourd'hui) : le scénario crédible
veut que Léa soit déclenchée depuis SON chat (port 5004), pas depuis
VWB. C'est un autre commit à venir — pour l'instant VWB suffit pour
le développement (cf. handoff session).
2. Seuil détection image tronquée élargi
----------------------------------------
Le seuil initial (height < 200 OR width < 400) ne capturait que les
cas extrêmes 2560x60 / 600x72. Mais le client envoie aussi 622x856
(Edge en fenêtre réduite ?) qui passait sous le radar. Élargi à
height < 800 OR width < 1200 — un écran moderne fait toujours ≥
1920x1080, donc le seuil est sain.
Sans ce fallback élargi, _resolve_target_sync recevait une image
trop petite pour matcher l'anchor → cascade VLM hallucinante.
Bug client constaté ce 2026-05-07 sur PC Windows 192.168.1.11 (agent V1) :
mss.monitors[1] retourne parfois une image tronquée type 2560x60, 2560x108,
600x72 — possiblement la barre des tâches Windows confondue avec un monitor,
ou un état mss corrompu. Reproduit même PC en mono physique. Cause exacte
non isolée côté client.
Sans cette image, _resolve_target_sync ne peut rien résoudre :
- Template matching échoue (anchor 104x31 vs image 600x72)
- OCR direct ne trouve pas la cible (texte hors de l'image tronquée)
- VLM Quick Find hallucine systématiquement la même position
- Fallback recorded_coords clique au mauvais endroit
Conséquence reproduite hier soir : "Léa clique partout au pif"
(cf. session_20260506_handoff_v2.md).
Filet de sécurité côté serveur : si l'image reçue est anormalement
tronquée (height < 200 ou width < 400), le serveur la remplace par le
dernier heartbeat full screen avant la cascade _resolve_target_sync.
Sources de fallback dans l'ordre :
1. _last_heartbeat (mémoire, peuplé par /stream/image en runtime)
2. Scan disque data/training/live_sessions/*/bg_*/shots/heartbeat_*.png
(utile après restart serveur ou si l'agent V1 ne polle pas)
Validé en isolation : image tronquée 600x60 → fallback heartbeat 2560x1600
→ template matching score 0.999 → coords (0.0312, 0.3500) = exactement
la position de l'IPP cible '25003284' en première ligne d'Easily Assure.
Bug client à traiter post-démo. Le fallback heartbeat reste utile en
roadmap autonome (résilience aux états mss transitoires).
Note : également retiré un import os local redondant dans le finally
(masquait la variable globale et provoquait UnboundLocalError dans
le scope du bloc fallback).
Revert effectif du commit c969f93a2.
Le Win+D auto au retry 1 produit un cercle vicieux quand combiné avec
le VLM-first qui hallucine systématiquement (positions répétitives
type 0.529/0.874 avec confidence 0.93 sans justification) :
click rate (cible mal localisée par VLM) → no_screen_change
→ Win+D auto → minimise Easily Assure
→ retry click → cible plus visible (Easily masquée par Win+D)
→ no_screen_change → Win+D encore → boucle infernale
Reproduit ce 2026-05-06 sur le workflow Urgence : 10 Win+D dispatchés
en moins de 2 minutes. Régression majeure ressentie par Dom :
"clic partout au pif, aucune action contrôlée".
L'idée du self-healing par gesture reste valide mais demande :
1. un déclenchement plus sélectif (genre overlay/popup détecté
visuellement, pas no_screen_change générique)
2. ou un Alt+Tab plutôt que Win+D (fait passer la fenêtre arrière
sans minimiser l'app cible)
3. ou une vraie analyse "y a-t-il une fenêtre qui obstrue ma cible"
avant de décider du gesture
À retravailler post-démo avec un vrai détecteur d'obstruction.
Quand le frontend ne passe pas de machine_id explicite, le backend VWB
auto-sélectionne une machine Windows en interrogeant /api/v1/traces/
stream/machines. Le code prenait la première de la liste sans tri, donc
l'ordre dépendait de l'ordre arbitraire renvoyé par le streaming server.
Conséquence reproduite ce 2026-05-06 : un replay du workflow Urgence a
été dispatché vers DESKTOP-ST3VBSD_windows alors que l'agent V1 actif
polait depuis DESKTOP-58D5CAC_windows. /replay/next ne dispatchait
aucune action puisque state.machine_id != polling_machine_id.
Symptôme côté Dom : "rien ne se passe sur Windows".
Correction : tri explicite par last_activity desc avant sélection.
La machine retenue est désormais celle qui a heartbeaté le plus
récemment (= celle qui POLLE actuellement le serveur).
Le workflow.machine_id (machine d'origine d'enregistrement) reste
distinct de la cible d'exécution : un workflow enregistré sur PC A
peut être rejoué sur PC B grâce au pipeline 100% visuel qui recalcule
anchors et coordonnées selon la résolution courante. C'était la
vraie intention architecturale, masquée par le bug de tri.
Audit project-quality-guardian (2026-05-06) Cas #2 : le mécanisme
qui invoquait gesture_catalog.win_minimize_all (Win+D) en cas
d'échec de grounding a été archivé le 24/04 dans
_archive/dead_code_20260424/core/visual/rpa_integration_manager.py
(_attempt_self_healing_resolution). Le catalogue
agent_chat/gesture_catalog.py:84 reste intact mais orphelin —
aucun caller actif.
Conséquence : quand une fenêtre/popup obstrue la cible, Léa
retente N fois la même action ratée puis pose une pause supervisée,
alors qu'un Win+D ("Afficher le bureau") règle souvent le problème
en 200 ms.
L'audit proposait observe_reason_act.py mais ce module est utilisé
uniquement par /execute/instruction (lui aussi sans client actif,
Cas #10). Le bon point d'insertion dans le pipeline replay actif
est _schedule_retry (replay_engine.py) — la fonction qui construit
la liste d'actions à réinjecter en tête de queue avant chaque retry.
Modification :
Au next_retry == 1 ET reason in ("verification_failed",
"no_screen_change"), insertion en tête de queue de :
1. Action key_combo {keys: ["super", "d"]} (format reconnu par
agent_v1/core/executor.py:1151), tagué
_recovery_gesture: "win_minimize_all" pour audit.
2. Wait 500 ms pour laisser l'OS terminer l'animation Win+D.
3. Le retry de l'action originale.
Au retry 2 et au-delà, comportement inchangé (wait 2s + retry).
Tests : 27/27 baseline sprint QW verts.
Audit project-quality-guardian (2026-05-06) : la fonction
_resolve_by_ocr_text (resolve_engine.py:1447) existait déjà mais
n'était appelée QUE depuis _resolve_with_precompiled_order (V4),
endpoint sans client côté frontend (Cas #5 du même audit). La
cascade legacy _resolve_target_sync sautait directement d'étape 0
(grounding-window) → étape 0' (template icônes) → étape 1 (VLM
Quick Find) sans tenter l'OCR direct.
Conséquence reproduite ce 2026-05-06 sur le workflow Urgence :
chaque action visuelle avec by_text payait 2-23 s de VLM Quick
Find (ui-tars-1.5-7b-q8_0 sur Ollama) au lieu de <500 ms d'OCR
direct, total replay > 10 min vs quelques secondes attendues.
Constat utilisateur : "habituellement on est plutôt à quelques
secondes". Régression silencieuse.
Modification :
Étape 0.5 ajoutée entre l'étape 0' (template icônes) et l'étape 1
(VLM Quick Find). Si by_text_strict est non vide, appel à
_resolve_by_ocr_text — fonction docTR existante, cache singleton
_V4_OCR_PREDICTOR, score 1.0 si match exact, 0.9 si mot exact,
0.8 si contenu. Seuil de retour : 0.80 (cohérent avec
_RESOLUTION_MIN_SCORES["hybrid_text_direct"]).
Le method retourné est rebadgé "hybrid_text_direct" pour cohérence
avec :
- _RESOLUTION_MIN_SCORES (seuil 0.80, ligne 2092)
- agent_v0/agent_v1/core/executor.py:1534 (client Windows)
- logs Learning historiques ([hybrid_text_direct])
Tests : 39/39 sprint QW + grounding/resolver verts.
Suite directe des commits 35b27ae49 (lock async sur /replay/next) et
87dbe8c5f (get_replay_status non-bloquant) qui n'avaient traité que
2 endpoints sur les 19 utilisant _replay_lock dans api_stream.py.
Reproduit aujourd'hui en pré-démo : un replay urgences a réussi
extract_text + t2a_decision (50s, OK), puis a hang sur l'action
suivante. start_raw_replay (POST /replay) du nouveau replay a tenté
`with _replay_lock:` synchrone à la ligne 2085 → MainThread asyncio
gelé → tous les endpoints derrière. Stack via py-spy confirmée.
Le pattern systémique : 17 sites `with _replay_lock:` synchrones
dans des handlers `async def` (start_replay, start_raw_replay,
replay_from_session, enqueue_single_action, launch_replay_from_plan,
get_next_action [×3], report_action_result [×5], register_error_callback,
list_replays, resume_replay, cancel_replay). Chacun gèle l'event
loop FastAPI dès qu'un autre thread tient le lock.
Modifications :
1. Helper _async_replay_lock(timeout=4.5) (api_stream.py:516).
Acquire via run_in_executor (event loop libre pendant l'attente),
timeout 4.5s puis HTTPException 503 plutôt que gel infini.
Sémantique acquire+release identique au `with` synchrone.
2. Remplacement automatisé des 17 sites async :
`with _replay_lock:` → `async with _async_replay_lock():`
2 sites sync intentionnellement préservés (cleanup loop ligne 689,
chat_status_provider ligne 5048 — pas dans des handlers async).
3. Import contextlib ajouté en haut du fichier.
Tests : 27/27 baseline sprint QW verts, /health 200 (3ms),
/replays 200 (2ms — endpoint qui utilise le nouveau helper).