Files
rpa_vision_v3/docs/AUDIT_LEA_FIRST_RUNTIME_2026-05-19.md

16 KiB
Raw Blame History

Audit runtime Léa-first — capture → replay direct → memory

Date : 2026-05-19 Auteur : Claude (mission audit n°1, lecture seule) Périmètre : agent_v0/agent_v1/, agent_v0/server_v1/, core/learning/ Branche : backup/post-demo-2026-05-19 (HEAD 5ea4960e6) Objectif : identifier 5-10 blocages concrets qui empêchent la voie nominale capture → replay direct → memory d'être fiable. Pas de VWB, pas de démo, pas de bench modèles, pas de refonte large.


Verdict global

La voie nominale existe partiellement en code mais comporte 3 ruptures fonctionnelles (P0) et 4 dégradations silencieuses (P1) qui la rendent non fiable en pratique. Le contournement actuel = passer par VWB pour fabriquer un workflow réutilisable, ou par le worker VLM offline. Pas de boucle "Léa capture → Léa rejoue" directe.

État composant par composant :

Composant État réel
Capture événements client (captor.py, vision/capturer.py) mature, production-grade, bug coord critique
Streaming vers serveur (streamer.py) mature, robuste (retry, buffer SQLite, backpressure)
Accumulation côté serveur (live_session_manager.py) OK, to_raw_session câblé au worker VLM
Construction workflow depuis session Léa (workflow_replay.py) ORPHELIN (0 caller runtime)
Replay direct sans VWB N'EXISTE PAS (replay actuel consomme workflow VWB)
Memory lookup (resolve_engine + replay_memory) branché, gated silencieusement sur window_title
Memory record_success / failure branché, même gating
Memory record_human_correction (apprentissage supervisé) CASSÉ (double bug)
core/learning/* (continuous_learner, feedback_processor, learning_manager) NON BRANCHÉ au runtime serveur Léa-first
Observabilité mémoire AVEUGLE (logs only, aucun endpoint)

P0 — Ruptures (la voie nominale ne marche pas)

Blocage #1 — record_human_correction cassé, double bug

Fichier : agent_v0/server_v1/replay_learner.py:210-219 Fonction : ReplayLearner.record_human_correction()

Bug A — import inexistant :

from .replay_memory import get_target_memory_store
store = get_target_memory_store()

La fonction get_target_memory_store n'existe pas dans replay_memory.py. La vraie s'appelle get_memory_store (replay_memory.py:47). Le try/except à la ligne 224 avale silencieusement l'ImportError. Aucune trace dans les logs au niveau INFO.

Bug B — signature obsolète :

store.record_success(
    screen_signature="human_correction",
    target_spec=target_spec,
    resolved_position={"x_pct": x_pct, "y_pct": y_pct},
    method="human_supervised",
    score=1.0,
)

La vraie signature (core/learning/target_memory_store.py:212-219) attend :

def record_success(
    self,
    screen_signature: str,
    target_spec,
    fingerprint: TargetFingerprint,
    strategy_used: str,
    confidence: float,
)

Les paramètres resolved_position, method, score n'existent pas. TypeError garanti si bug A est fixé sans fixer B.

Impact produit : l'apprentissage par correction humaine — la boucle "Léa apprend en regardant l'humain corriger" — est totalement inopérant. La correction est juste loguée en JSONL local (record() ligne 206), jamais hoistée dans la mémoire persistante consultée au prochain run.

Gravité : P0 Catégorie : bug réel (double)


Blocage #2 — build_workflow_replay orphelin (pas de pont capture → replay direct)

Fichier : agent_v0/server_v1/workflow_replay.py:29-186 Fonction : build_workflow_replay()

Constat :

$ grep -rn "build_workflow_replay" --include="*.py" | grep -v "workflow_replay.py:"
# (vide)

0 caller runtime. Le code décrit pourtant exactement le pont attendu (workflow enrichi → actions de replay avec vérification FAISS par node), mais il n'est appelé nulle part.

Ce qui marche aujourd'hui à la place :

  • api_stream.py:1479-1525 (POST /finalize) → enqueue session au worker VLM (process séparé)
  • Le worker construit un workflow via GraphBuilder (cf. stream_processor.py:2306-2335)
  • Mais rien ne renvoie ces actions à un replay direct. Le replay (replay_engine.py) consomme un workflow VWB (table steps DB), pas une séquence construite à partir d'une session Léa.

Impact produit : pas de chemin "Léa enregistre → on rejoue la session telle quelle". Toute session Léa doit transiter par VWB ou un commit DB manuel pour devenir rejouable.

Gravité : P0 Catégorie : branche non branchée (code mort)


Blocage #3 — Memory gated sur target_spec.window_title silencieusement inopérante

Fichiers :

  • agent_v0/server_v1/resolve_engine.py:1541-1554 (lookup)
  • agent_v0/server_v1/api_stream.py:3634-3639, 3666-3672 (record)

Bug structurel : la signature d'écran V4 = sha256(normalize(window_title))[:16] (cf. replay_memory.py:94-103). Si target_spec.window_title est vide ou absent :

def compute_screen_sig(window_title: str) -> str:
    norm = _norm_text(window_title)
    if not norm:
        return ""  # → memory_lookup/record skip silencieux
    return hashlib.sha256(norm.encode("utf-8")).hexdigest()[:16]

Conséquence runtime : sur les workflows édités à la main dans VWB ou construits sans renseigner window_title (cas dominant aujourd'hui), screen_sig=""ni lookup ni record déclenchés. Pas de log d'erreur, pas de signal. La mémoire reste vide pendant des semaines sans alerter.

Validation : sur le workflow Demo_urgence_3_db, beaucoup de steps ont target_spec sans window_title (anchors ciblés par by_text). Vérifiable rapidement par :

sqlite3 visual_workflow_builder/backend/instance/workflows.db \
  "SELECT json_extract(parameters_json, '$.target_spec.window_title')
   FROM steps WHERE workflow_id='wf_483910cdd851_1778750587';"

Impact produit : la mémoire persistante peut paraître branchée (singleton init OK, JSONL/SQLite créés) et ne stocker aucune entrée sur les workflows réels.

Gravité : P0 Catégorie : dette (gating sur condition fragile)


Blocage #4 — mss.monitors[1] aveugle aux dims aberrantes (corruption en amont)

Fichier : agent_v0/agent_v1/vision/capturer.py Sites : :107 (capture_full_context), :150 (capture_dual), :247 (capture_active_window)

Code commun :

with mss.mss() as sct:
    monitor = sct.monitors[1]
    sct_img = sct.grab(monitor)
    img = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX")

Bug observé en démo (19 mai) : mss.monitors[1] retourne intermittemment {width: 2560, height: 60} au lieu de {width: 2560, height: 1600} → coords y_pct × 60 = 16 px au lieu de y_pct × 1600 = 424 px. Aucune défense dans le code.

Impact produit : toute capture servant de référence à la mémoire peut être corrompue. Un fingerprint enregistré avec y_pct = 0.0099 au lieu de 0.265 empoisonne le store : au prochain hit, Léa clique à 16 px du haut au lieu du bon endroit. Et le fail_count augmente sans que la cause soit visible.

Fix attendu (lecture seule, donc juste indiqué) : refuser la capture si monitor.height < 200 (ou autre seuil sain), fallback sur autre monitor ou nouvelle tentative.

Gravité : P0 Catégorie : bug réel


P1 — Dégradations silencieuses (la voie marche mais fausse)

Blocage #5 — Captures Léa downscalées 800×500 envoyées au serveur

Fichier : agent_v0/agent_v1/core/executor.py:2895 Défaut : _capture_screenshot_b64(max_width=800, quality=60)

7 sites d'appel sans override : :633, :801, :824, :894, :935, :989, :1055, :1303. Seuls :1334 et :2147 (resolve_target) passent max_width=0 (full-res).

Impact produit :

  • Matching template au serveur reçoit du 800×500 → compensé par multi-scale étendu côté serveur (resolve_engine.py:130, fix du 18 mai)
  • Mais les coords stockées dans la mémoire dépendent du chemin de projection (full-res vs downscale). Bruit imprécis sur le store.

Gravité : P1 (workaround serveur tient, base mémoire bruitée) Catégorie : dette


Blocage #6 — _replay_active flag mal géré pendant les pauses

Fichier : agent_v0/agent_v1/main.py:319-345

Code problématique :

if had_action:
    if not self._replay_active:
        self._replay_active = True
        ...
else:
    if self._replay_active:
        print("[REPLAY] Replay termine — retour en mode capture")
        self._replay_active = False

Bug : si le serveur renvoie action=null + replay_paused=true (attente humaine), had_action=False → Léa interprète "fin du replay" → cleanup UI + bulle paused n'apparaît plus. Comportement déjà observé en démo (cf. handoff 19 mai bug P1 "Léa client interprète action=null + replay_paused=true comme fin du replay").

Impact produit : tracking de replay corrompu côté client pendant les pauses. Désaligne aussi le ChatWindow (bulle paused invisible après plusieurs replays).

Gravité : P1 Catégorie : bug réel


Blocage #7 — core/learning/* Phase 7 non branché au runtime serveur Léa

Fichiers :

  • core/learning/continuous_learner.py (644 lignes)
  • core/learning/feedback_processor.py (176 lignes)
  • core/learning/learning_manager.py (180 lignes)
  • core/learning/versioned_store.py (592 lignes)

Consumers réels (grep from core.learning) :

Caller Statut
core/execution/execution_loop.py:49, 71 runtime alternatif, pas le serveur Léa
core/pipeline/workflow_pipeline.py:29 pipeline batch GUI legacy
gui/orchestrator.py:52 GUI PyQt5 legacy
visual_workflow_builder/backend/services/learning_integration.py:36 service VWB
examples/test_phase7_*.py exemples
tests/unit/test_versioned_store.py tests
tests/test_correction_pack_integration.py tests

Le runtime serveur Léa-first (api_stream.py + replay_engine.py + resolve_engine.py + stream_processor.py) n'instancie rien de tout ça. Seul TargetMemoryStore est consommé via replay_memory.py.

Impact produit :

  • Drift detection (ContinuousLearner) = mort en flux Léa-first
  • Versioned prototypes (VersionedStore) = morts
  • Retraitement feedback bus (FeedbackProcessor) = mort
  • Stats globales (LearningManager) = mortes

Gravité : P1 Catégorie : branche non branchée


Blocage #8 — Pas de signal "session enregistrée → workflow rejouable" exposé côté API

Fichier : agent_v0/server_v1/api_stream.py:1479-1525 (POST /api/v1/traces/stream/finalize)

Constat : finalize marque la session, enqueue au worker VLM (_enqueue_to_worker(session_id)), rend la main. Aucun endpoint :

  • Pour savoir quand le workflow construit est prêt
  • Pour le déclencher en replay direct sur une cible (machine, agent)
  • Pour récupérer la liste des "workflows construits par Léa" disponibles

La séquence "Léa fais ça → maintenant Léa, rejoue ça" n'a pas de surface API exposée — elle passe implicitement par VWB (qui lit les workflows en DB et orchestre).

Impact produit : la voie nominale "capture → replay direct" n'a pas de point d'entrée client. C'est cohérent avec le blocage #2 (build_workflow_replay orphelin) : le pont produit n'existe pas non plus côté API.

Gravité : P1 Catégorie : branche non branchée (au niveau orchestration)


P2 — Bruit et observabilité

Blocage #9 — Deux boucles heartbeat parallèles, persistance non scoped

Fichier : agent_v0/agent_v1/main.py:131, 349-393

Constat : 2 boucles tournent :

  • _heartbeat_loop (ligne 434) — actif seulement si self.session_id (= recording actif)
  • _background_heartbeat_loop (ligne 349) — actif en permanence, pousse sous bg_<machine_id> toutes les 5s même sans session

Le serveur persiste ces sessions bg_* dans data/streaming_sessions/. Pas de purge automatique scoped (la purge générale tourne sur les sessions finalisées > 24h, mais bg_* ne se finalise jamais).

Impact produit : pollution disque indépendante de l'usage. Croissance non maîtrisée. Bruit dans toute analyse a posteriori des sessions Léa réelles.

Gravité : P2 Catégorie : dette


Blocage #10 — Aucune métrique runtime sur la mémoire

Fichiers :

  • agent_v0/server_v1/replay_memory.py (memory_lookup, memory_record_*)
  • core/learning/target_memory_store.py (get_stats)

Constat :

  • Les hits/misses sont seulement logger.info (replay_memory.py:191-196).
  • TargetMemoryStore.get_stats() (target_memory_store.py:440-479) renvoie total_entries, total_successes, total_failures, overall_confidence, jsonl_files_count, jsonl_total_size_mbmais n'est branché à aucune route API.
  • Pas de compteur Prometheus, pas d'endpoint /api/v1/memory/stats, pas de surface dashboard.

Impact produit : impossible de répondre en runtime à "la mémoire travaille-t-elle aujourd'hui ?" ou "combien d'entrées sur ce workflow ?" sans grepper les logs ou ouvrir la DB SQLite à la main. Debugging et validation Léa-first à l'aveugle.

Gravité : P2 Catégorie : dette (observabilité)


Tableau récapitulatif

# Sévérité Catégorie Fichier:fonction 1-line
1 P0 bug replay_learner.py:210 record_human_correction Import inexistant + signature obsolète, apprentissage humain mort
2 P0 branche non branchée workflow_replay.py:29 build_workflow_replay Orphelin, pas de pont capture→replay direct
3 P0 dette resolve_engine.py:1541 + api_stream.py:3634 Memory gated sur window_title souvent absent, silencieusement morte
4 P0 bug vision/capturer.py:107,150,247 mss.monitors[1] aveugle, base mémoire empoisonnée
5 P1 dette executor.py:2895 (7 sites) Captures 800×500 par défaut, store bruité
6 P1 bug main.py:319-345 _replay_poll_loop _replay_active mal géré pendant pause, état UI désynchro
7 P1 branche non branchée core/learning/* Phase 7 non branchée au runtime serveur
8 P1 branche non branchée api_stream.py:1479 /finalize Pas d'API "rejoue ce que tu viens d'enregistrer"
9 P2 dette main.py:131,349 Heartbeat background pollue la persistance
10 P2 dette replay_memory.py + target_memory_store.py:440 Aucune métrique runtime mémoire exposée

Recommandation de séquencement (si on devait choisir 4 fixes)

Pour rendre la voie nominale capture → replay direct → memory opérationnelle avec un effort minimal :

  1. #4 d'abord — fixer mss.monitors[1] aveugle. Sinon tout ce qu'on stocke après est faux.
  2. #3 ensuite — exiger ou dériver window_title dans le target_spec à l'enregistrement Léa (la capture client a déjà cette info via window_capture.title, à propager). Sans ça, la mémoire reste vide.
  3. #1 — corriger record_human_correction (import + signature). Ouvre la boucle d'apprentissage supervisé.
  4. #2 + #8 ensemble — soit rebrancher build_workflow_replay au worker VLM et exposer un endpoint client, soit assumer que VWB reste l'orchestrateur intermédiaire. Décision produit à arbitrer.

Pas dans le périmètre de cette mission : proposer le design des fixes (la mission demandait l'audit, pas la refonte).


Méthode d'audit

  • Lectures intégrales : core/learning/__init__.py, core/learning/target_memory_store.py, replay_memory.py, replay_learner.py, live_session_manager.py, workflow_replay.py, core/captor.py, vision/capturer.py, network/streamer.py, main.py
  • Lectures ciblées : api_stream.py:1479-1525, 3600-3690, stream_processor.py:1700-1745, 2285-2345, resolve_engine.py:1525-1565
  • Grep consumers : build_workflow_replay, memory_lookup, memory_record_*, ReplayLearner, record_human_correction, to_raw_session, TargetMemoryStore(, ShadowLearningHook, from core.learning
  • Croisement avec : handoffs 12-19 mai, DETTE_TECHNIQUE.md, AUDIT_CONTROLES_DEBRANCHES_2026-05-08.md