feat: Phase 1 apprentissage — greffe TargetMemoryStore sur V4

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>
This commit is contained in:
Dom
2026-04-10 21:08:14 +02:00
parent e66629ce1a
commit b92cb9db03
6 changed files with 674 additions and 2 deletions

View File

@@ -767,6 +767,15 @@ class ActionExecutorV1:
f"({real_x}, {real_y}) sur ({width}x{height}), bouton={button}"
)
self._click((real_x, real_y), button)
# Phase 1 apprentissage : exposer les coordonnées RÉSOLUES
# utilisées pour le clic. Le serveur (/replay/result) les lit
# directement comme source de vérité pour la mémoire.
# On donne des percentages car la mémoire est indépendante
# de la résolution écran du client.
result["actual_position"] = {
"x_pct": float(x_pct),
"y_pct": float(y_pct),
}
logger.info(
f"Replay click [{mode}] : ({x_pct:.3f}, {y_pct:.3f}) -> "
f"({real_x}, {real_y}) sur ({width}x{height})"
@@ -1509,6 +1518,8 @@ Example: x_pct=0.50, y_pct=0.30"""
"resolution_method": result.get("resolution_method"),
"resolution_score": result.get("resolution_score"),
"resolution_elapsed_ms": result.get("resolution_elapsed_ms"),
# Coordonnées RÉSOLUES effectivement cliquées (Phase 1 apprentissage)
"actual_position": result.get("actual_position"),
# Champs enrichis pour target_not_found (pause supervisée)
"target_description": result.get("target_description"),
"target_spec": result.get("target_spec"),