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:
@@ -1147,8 +1147,35 @@ def _create_replay_state(
|
||||
total_actions: int,
|
||||
params: Optional[Dict[str, Any]] = None,
|
||||
machine_id: Optional[str] = None,
|
||||
actions: Optional[List[Dict[str, Any]]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Créer un état de replay enrichi avec les champs de suivi d'erreur."""
|
||||
"""Créer un état de replay enrichi avec les champs de suivi d'erreur.
|
||||
|
||||
Args:
|
||||
actions: Liste des actions du replay. Une copie slim (sans anchors
|
||||
base64) est stockée pour permettre à `/replay/result` de
|
||||
retrouver le `target_spec` de l'action courante — nécessaire
|
||||
pour l'apprentissage mémoire (Phase 1 plan Léa).
|
||||
"""
|
||||
# Copie slim des actions : on strip les anchor_image_base64 pour ne
|
||||
# pas gonfler la mémoire (anchors peuvent faire 50-200 KB chacun).
|
||||
actions_slim: List[Dict[str, Any]] = []
|
||||
if actions:
|
||||
for a in actions:
|
||||
a_copy = {
|
||||
"action_id": a.get("action_id"),
|
||||
"type": a.get("type"),
|
||||
"x_pct": a.get("x_pct"),
|
||||
"y_pct": a.get("y_pct"),
|
||||
}
|
||||
ts = a.get("target_spec")
|
||||
if isinstance(ts, dict):
|
||||
a_copy["target_spec"] = {
|
||||
k: v for k, v in ts.items()
|
||||
if k not in ("anchor_image_base64",)
|
||||
}
|
||||
actions_slim.append(a_copy)
|
||||
|
||||
return {
|
||||
"replay_id": replay_id,
|
||||
"workflow_id": workflow_id,
|
||||
@@ -1161,6 +1188,7 @@ def _create_replay_state(
|
||||
"current_action_index": 0,
|
||||
"params": params or {},
|
||||
"results": [], # Historique des résultats action par action
|
||||
"actions": actions_slim, # Copie slim pour lookup par index (Phase 1 mémoire)
|
||||
# Champs enrichis pour le suivi d'erreur (#7)
|
||||
"retried_actions": 0,
|
||||
"unverified_actions": 0,
|
||||
|
||||
Reference in New Issue
Block a user