Greffe minimale du mécanisme d'apprentissage persistant (Fiche #18, target_memory_store.py) sur le pipeline streaming V4 sans toucher à V3. Architecture (docs/PLAN_APPRENTISSAGE_LEA.md) : - Lookup mémoire AVANT la cascade résolution coûteuse OCR/template/VLM dans _resolve_target_sync → hit = <10ms, miss = overhead zéro - Record APRÈS validation post-condition (title_match strict) dans /replay/result → 2 succès → cristallisation par répétition - Single source of truth : l'agent remplit report.actual_position avec les coords effectivement cliquées, le serveur les lit directement. Pas de cache intermédiaire (option C du plan). Signature écran V4 : sha256(normalize(window_title))[:16]. Robuste aux données variables, faux positifs rattrapés par le post-cond qui décrémente la fiabilité via record_failure(). Fichiers : - agent_v0/server_v1/replay_memory.py : nouveau wrapper 316 lignes exposant compute_screen_sig/memory_lookup/record_success/failure, lazy-init du store, normalisation texte stable, garde sanity coords - agent_v0/server_v1/resolve_engine.py : lookup mémoire en tête de _resolve_target_sync (30 lignes) - agent_v0/server_v1/replay_engine.py : _create_replay_state stocke une copie slim des actions (sans anchor base64) pour retrouver le target_spec par current_action_index - agent_v0/server_v1/api_stream.py : 4 callers passent actions=..., record success/failure dans /replay/result lit actual_position du rapport (click-only), correction du commentaire Pydantic - agent_v0/agent_v1/core/executor.py : remplit result["actual_position"] après self._click(), transmis dans le report de poll_and_execute Tests : 56 E2E + Phase0 passent, zéro régression. Cycle Phase 1 validé en simulation : miss → record → miss → record → HIT au 3ème passage. Le deploy copy executor.py a une divergence pré-existante de 1302 lignes non committées — traité séparément lors du cleanup prochain. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
11 KiB
Plan Apprentissage Léa — Phase 1 / 2 / 3
Date : 10 avril 2026 Auteur : Dom + Claude (session cartographie target_resolver) Statut : Plan validé par Dom, implémentation non commencée
Contexte
Après deux semaines à debugger le replay sur Windows et avoir écrit du code (V4 : surface_classifier, UIA, execution_plan, executor strict) qui dupliquait sans le savoir des concepts déjà présents dans le V3 legacy, une cartographie exhaustive a été lancée.
Fichiers lus en profondeur :
core/execution/target_resolver.py(3495 lignes)core/learning/target_memory_store.py(545 lignes — Fiche #18)core/models/workflow_graph.py(TargetSpec — 570-640)core/detection/spatial_analyzer.py(595 lignes)
Découverte critique
Les pipelines V3 et V4 sont complètement découplés au runtime de replay.
REPLAY V4 (actif aujourd'hui) LEGACY V3 (dormant au replay)
============================= =============================
stream_processor workflow_pipeline
↓ ↓
execution_plan_runner execution_loop
↓ ↓
agent_v1/core/executor.py action_executor
↓ ↓
OCR + template + VLM direct target_resolver
↓
target_memory_store (Fiche #18)
↓
SpatialAnalyzer
Vérifié par grep "from core.execution" agent_v0/ → zéro import.
Callers V3 encore vivants (mais pas sur le chemin de replay critique) :
agent_chat/app.pyvisual_workflow_builder/backend/api/workflows.pycore/evaluation/*
Modules dormants à valeur immédiate
TargetMemoryStore — le Crystallizer qu'on pensait devoir écrire
- SQLite
data/learning/target_memory.db+ JSONL auditdata/learning/events/YYYY-MM-DD/*.jsonl - API propre et testée :
record_success(screen_sig, target_spec, fingerprint, strategy, confidence)record_failure(screen_sig, target_spec, error)lookup(screen_sig, target_spec, min_success_count=2, max_fail_ratio=0.3)→ fingerprint ou None
- Clé unique :
(screen_signature, target_spec_hash) - Fingerprint :
(element_id, bbox, role, etype, label, confidence) - Critère de fiabilité : au moins 2 succès et < 30% d'échecs → c'est ça la "cristallisation par répétition"
TargetSpec — vocabulaire déjà riche
Dans core/models/workflow_graph.py:572 :
context_hints:near_text,below_text,right_of_text,same_row_as_text,within_region,exclude_near_texthard_constraints:within_container_text,min_areaweights:proximity,alignment,container,roi_iou
ResolutionStrategy V4 — vocabulaire pauvre (à enrichir)
Dans core/workflow/execution_plan.py:27 :
target_text,anchor_b64,zone,vlm_description,uia_*,dom_*- Pas de context_hints, pas de hard_constraints → trou dans l'expressivité
Décision validée
Léa = stagiaire qui apprend de la répétition. La mémoire précède la généralisation. Mais le raisonnement spatial reste indispensable comme filet de sécurité quand la mémoire ne suffit pas (décalages de layout, premier replay sur nouvel écran, généralisation entre écrans similaires).
Plan séquencé
Phase 1 — Mémoire sur V4 (≈1 jour, ~150 lignes)
Objectif : greffer TargetMemoryStore directement sur le resolve V4, sans passer par target_resolver ni UIElement.
Lookup avant OCR/template/VLM
fp = memory.lookup(screen_sig, target_spec)
if fp:
# On a vu ce clic réussir ≥2 fois sur cet écran
return fp.bbox # clic direct, <10ms
Record après validation post-condition (déjà en place — title_match strict)
if post_condition_passed:
memory.record_success(screen_sig, target_spec, fingerprint, "v4_ocr", confidence)
else:
memory.record_failure(screen_sig, target_spec, reason)
À construire
screen_signature(screenshot)→ hash stable. Piste :window_title+ tokens OCR dominants, ou réutilisercore/execution/screen_signature.pysi compatible.- Fingerprint léger :
(x, y, w, h, method). Pas besoin de role/type/label en V4. - Point de branchement exact à confirmer avant implémentation :
- Côté serveur dans
resolve_engine(si resolve serveur) - Côté agent dans
agent_v1/core/executor.py(si resolve local)
- Côté serveur dans
Bénéfice observable
- 3ème passage d'un workflow sur même écran : 10-15s VLM remplacés par <10ms lookup
- Léa apprend vraiment — pas parce qu'on a écrit un Crystallizer, parce qu'on a consommé celui qui dort depuis mars
Tests de validation
- Rejouer un workflow 3 fois, mesurer le temps du 3ème passage
- Vérifier que
data/learning/target_memory.dbse remplit - Vérifier que les événements JSONL s'écrivent
Phase 2 light — Raisonnement spatial OCR-only (≈3-5 jours, ~300-400 lignes)
Principe clé : pur pixel/OCR. Pas d'UIElement, pas de role/type, pas de parser UI. On évite le piège "ressusciter V3 complet".
À l'enregistrement (IRBuilder, côté serveur)
- Pour chaque clic
(x, y)dans la trace - OCR la zone autour (±300px)
- Identifier les 3-5 textes les plus proches avec direction (left/right/above/below) et distance
- Populer
ResolutionStrategy.context_hints:{ "right_of_text": "Nom du patient", # 60px à gauche du clic "below_text": "Identité", # 120px au-dessus "near_text": "Enregistrer", # le texte du clic lui-même }
Au replay (resolve_engine), en cascade :
- Lookup mémoire (Phase 1) → si hit, clic direct
- Sinon : OCR de l'écran actuel
- Trouver les ancres de
context_hintsvia OCR (normalisation accents + fuzzy Fiche #8) - Calculer la zone candidate par intersection des contraintes spatiales
- Cliquer
- Si post-cond échoue : retombée VLM (exception handler)
Logique à porter depuis target_resolver.py
_apply_context_hints_to_candidates(lignes 2601-2803) — adaptée à "candidats = zones OCR" au lieu de "candidats = UIElement"_find_element_by_text+ normalisation (_norm_text,_fuzzy_ratio) lignes 211-235- Healing profile (ligne 395) pour relaxation progressive
Décision tranchée
- OCR côté serveur Linux (docTR déjà présent via SomEngine)
- Zéro changement sur le client Windows
- Le serveur reçoit le screenshot au moment du build IR, extrait les context_hints, les intègre dans
ResolutionStrategy
Enrichissement de ResolutionStrategy (execution_plan.py)
Ajouter au dataclass :
context_hints: Dict[str, Any] = field(default_factory=dict)
Et dans execution_plan_runner._strategy_to_target_spec : propager context_hints dans target_spec.
Tests de validation
- Enregistrer un workflow, vérifier que le plan contient des
context_hintscohérents - Modifier la résolution de la VM (1920→1280), rejouer, vérifier que les clics atteignent la bonne cible
- Ajouter un champ au-dessus de la cible, rejouer, vérifier robustesse
Phase 3 — Spatial V3 complet (pas maintenant)
Correction 10 avril 2026 : une version précédente de ce document affirmait qu'OmniParser avait été retiré. C'était faux. OmniParser est toujours présent :
core/detection/omniparser_adapter.py— 429 lignesagent_v0/server_v1/resolve_engine.py:254—_get_omniparser()singleton thread-safe, lazy-loadagent_v0/server_v1/resolve_engine.py:293—_resolve_by_yolo()défini et importé dansapi_stream.py
Ce qui est vrai : _resolve_by_yolo n'est jamais appelé dans la cascade V4 (_resolve_target_sync ne l'invoque pas). C'est du code dormant, pas supprimé.
Conséquence pour Phase 3 : on a potentiellement déjà un parser UI utilisable. Deux pistes :
- Ré-activer
_resolve_by_yolodans la cascade V4 (injecter un appel dans_resolve_target_synccomme fallback après OCR/template/VLM). Il produit déjà une liste d'éléments détectés avec bbox et role approximatif. - Pont
_resolve_by_yolo → List[UIElement]: adapter la sortie YOLO pour alimentertarget_resolverV3. Un pont d'une centaine de lignes devrait suffire.
Avant de lancer Phase 3, vérifier :
- Les modèles YOLO sont-ils toujours sur disque ? (
omniparser.detect()lazy-loads) - Quelle qualité de détection sur des écrans Citrix/DPI réels ?
- Les tests
tests/integration/test_auto_healing_integration.pyettests/unit/test_fiche11_*passent-ils encore ?
Tant qu'on n'a pas fait cette vérification, Phase 3 reste pending.
Ce qu'on ne fait PAS
| Tentation | Pourquoi on résiste |
|---|---|
Refactorer target_resolver.py pour le rendre V4-compatible |
3495 lignes couplées à UIElement disparu — plus économique de le laisser dormir et recoder l'essentiel minimal dans V4 |
Brancher action_executor sur le streaming replay |
2000 lignes de pipeline pour un bénéfice qu'on a en 150 lignes avec TargetMemoryStore seul |
Ressusciter SpatialAnalyzer maintenant |
Zéro valeur sans UIElement riches en amont |
| Faire Phase 2 avant Phase 1 | Léa raisonnerait à chaque clic, lent et coûteux — pas un "stagiaire qui apprend", juste un agent qui réfléchit en boucle |
Suivi d'avancement
Phase 1 — Mémoire sur V4
- Identifier le point de branchement exact (serveur vs agent)
- Définir
screen_signaturestable pour V4 - Définir le format fingerprint léger
- Brancher
memory.lookup()avant cascade OCR/template/VLM - Brancher
memory.record_success()après post-cond validée - Brancher
memory.record_failure()sur échec - Test : workflow rejoué 3 fois, 3ème en <100ms sur le resolve
- Vérifier remplissage de
data/learning/target_memory.db
Phase 2 light — Spatial OCR-only
- Enrichir
ResolutionStrategyaveccontext_hints - IRBuilder : extraire context_hints via OCR au build
execution_plan_runner: propager context_hints dans target_spec- resolve_engine : implémenter fallback spatial OCR
- Porter
_apply_context_hints_to_candidatesadapté - Porter normalisation texte (
_norm_text,_fuzzy_ratio) - Test : résolution VM modifiée, clic atteint toujours la cible
- Test : champ ajouté dans le formulaire, robustesse préservée
Phase 3 — Spatial V3 complet
- BLOQUÉ jusqu'à ce qu'un parser UI produise des
UIElement
Liens
- Code de référence :
core/execution/target_resolver.py,core/learning/target_memory_store.py - Architecture V4 :
core/workflow/execution_plan.py,core/workflow/execution_compiler.py,agent_v0/server_v1/execution_plan_runner.py - Replay runtime :
agent_v0/agent_v1/core/executor.py