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>
221 lines
11 KiB
Markdown
221 lines
11 KiB
Markdown
# 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.py`
|
|
- `visual_workflow_builder/backend/api/workflows.py`
|
|
- `core/evaluation/*`
|
|
|
|
## Modules dormants à valeur immédiate
|
|
|
|
### TargetMemoryStore — le Crystallizer qu'on pensait devoir écrire
|
|
|
|
- SQLite `data/learning/target_memory.db` + JSONL audit `data/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_text`
|
|
- `hard_constraints` : `within_container_text`, `min_area`
|
|
- `weights` : `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**
|
|
```python
|
|
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)**
|
|
```python
|
|
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éutiliser `core/execution/screen_signature.py` si 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)
|
|
|
|
**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.db` se 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)**
|
|
1. Pour chaque clic `(x, y)` dans la trace
|
|
2. OCR la zone autour (±300px)
|
|
3. Identifier les 3-5 textes les plus proches avec direction (left/right/above/below) et distance
|
|
4. Populer `ResolutionStrategy.context_hints` :
|
|
```python
|
|
{
|
|
"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 :
|
|
1. Lookup mémoire (Phase 1) → si hit, clic direct
|
|
2. Sinon : OCR de l'écran actuel
|
|
3. Trouver les ancres de `context_hints` via OCR (normalisation accents + fuzzy Fiche #8)
|
|
4. Calculer la zone candidate par intersection des contraintes spatiales
|
|
5. Cliquer
|
|
6. 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 :
|
|
```python
|
|
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_hints` cohé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 lignes
|
|
- `agent_v0/server_v1/resolve_engine.py:254` — `_get_omniparser()` singleton thread-safe, lazy-load
|
|
- `agent_v0/server_v1/resolve_engine.py:293` — `_resolve_by_yolo()` défini et importé dans `api_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 :
|
|
1. **Ré-activer `_resolve_by_yolo`** dans la cascade V4 (injecter un appel dans `_resolve_target_sync` comme fallback après OCR/template/VLM). Il produit déjà une liste d'éléments détectés avec bbox et role approximatif.
|
|
2. **Pont `_resolve_by_yolo → List[UIElement]`** : adapter la sortie YOLO pour alimenter `target_resolver` V3. 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.py` et `tests/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_signature` stable 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 `ResolutionStrategy` avec `context_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_candidates` adapté
|
|
- [ ] 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`
|