docs: add POC specs, handoffs, and research notes

This commit is contained in:
Dom
2026-06-02 16:28:34 +02:00
parent 18ed6cb751
commit f2e9aac6b7
86 changed files with 27615 additions and 25 deletions

View File

@@ -0,0 +1,100 @@
# Audit — Point d'intégration agent pour le contrat `finalize` étendu
**Date** : 2026-05-20
**Mission** : Claude 4 (lecture seule)
**Périmètre** : `agent_v0/agent_v1/network/streamer.py`, `agent_v0/agent_v1/main.py`, `agent_v0/agent_v1/ui/smart_tray.py`, `agent_v0/agent_v1/ui/chat_window.py`, `agent_v0/agent_v1/ui/shared_state.py`, `agent_v0/lea_ui/server_client.py`
**Contexte** : le serveur va enrichir la réponse `POST /api/v1/traces/stream/finalize` avec un nouveau contrat `{ replay_ready, replay_request, launch_replay }`. Objectif : identifier le **plus petit point d'intégration** côté agent pour consommer ce contrat proprement, **sans remettre le VWB au centre**.
## Constat
Aujourd'hui, `streamer.py:622-623` reçoit la réponse `/finalize` (`result = resp.json()`) et la jette dans un `logger.info(f"Session finalisée: {result}")`. **Aucun consommateur ne voit ce payload**. Tous les fils en aval (`main.AgentV1.stop_session`, `smart_tray._on_stop_session`, `chat_window._on_quick_stop`) terminent sans rien savoir du résultat serveur. La surface unique de capture du payload existe déjà — il suffit de l'exploiter.
Côté déclenchement replay, **une seule surface client** émet l'appel HTTP : `smart_tray._launch_replay(workflow_id, workflow_name)` (ligne 459-505) qui fait `POST /api/v1/traces/stream/replay/start` avec `{"workflow_id": ...}`. Tout le reste (chat_window, lea_ui/server_client) consomme un replay déjà initié (resume/abort, polling actions). Le nouveau contrat `replay_request` pourrait juste enrichir le payload passé à cet appel existant.
L'orchestration globale (`AgentV1` dans `main.py`) wire déjà session start/stop via `AgentState` (`shared_state.py`). C'est le seul endroit qui voit à la fois le moment précis de la fin d'enregistrement (`stop_session`), l'identité de la session, et a accès à toutes les UI (tray + chat) via `self._state`.
## Tableau — surfaces d'arrêt / relance et recommandations
### Arrêt d'un enregistrement (où voir la fin)
| Fichier | Point d'entrée | Rôle | Risque branchement | Recommandation |
|---|---|---|---|---|
| `streamer.py:602-627` | `_finalize_session()` | Site **unique** qui reçoit `resp.json()` de `/finalize` | Aucun — actuellement payload jeté | 🟢 **POINT D'INTÉGRATION RECOMMANDÉ** — ajouter callback `on_finalize_result: Callable[[dict], None]` invoqué après `resp.ok`. Découplé, non-bloquant, contenu. |
| `streamer.py:131-157` | `TraceStreamer.stop()` | Drain queue + appel `_finalize_session` | Modifier le retour casse l'API publique (appelée par `main.stop_session`) | 🟡 Acceptable mais moins propre — retourner le payload via `stop()` couple les appelants à un retour qui n'existait pas. Préférer le callback. |
| `main.py:395-430` | `AgentV1.stop_session()` | Orchestre captor.stop + sleep + streamer.stop | Lieu naturel pour router le payload reçu — mais doit l'obtenir d'amont | 🟢 Destinataire idéal du callback — a accès à `self._state` pour notifier UI, et `self.user_id` pour reconstituer `agent_<user_id>` session de polling. |
| `shared_state.AgentState.stop_recording()` | Méthode | Source de vérité de l'arrêt UI | Étendre la signature `_on_stop` casse rétrocompat | 🟡 À éviter en première intention — l'état partagé doit rester muet sur le résultat serveur. |
| `smart_tray._on_stop_session()` (ligne 396) | Bouton tray "C'est terminé" | Notif "Merci ! J'ai bien mémorisé..." | Faire du HTTP ici dupliquerait `_launch_replay` | 🔴 À éviter — la couche UI ne doit pas appeler `/finalize` ni interpréter sa réponse. |
| `smart_tray._on_emergency_stop()` (ligne 507) | Bouton ARRÊT D'URGENCE | Stop immédiat sans finalize | Hors scope (arrêt brutal, pas de proposition replay) | ⚫ Aucun branchement — un emergency stop n'attend pas le replay. |
| `chat_window._on_quick_stop()` (ligne 1458) | Bouton chat "Arrêter" | Appelle `shared_state.stop_recording()` | Idem ligne au-dessus | 🔴 À éviter — duplication. |
### Relance d'un replay (où injecter `replay_request` / `launch_replay`)
| Fichier | Point d'entrée | Rôle | Risque branchement | Recommandation |
|---|---|---|---|---|
| `smart_tray._launch_replay(workflow_id, workflow_name)` (ligne 459-505) | Appel HTTP `POST /replay/start` | **Seul site** qui déclenche un replay neuf côté client | Élargir la signature pour accepter un `replay_request: dict` (ou écraser `workflow_id` par tout le dict si fourni) | 🟢 Réutiliser tel quel — passer le `replay_request` du serveur comme JSON body. Surface existante, mature, notification Article 50 déjà cablée ligne 469-472. |
| `smart_tray._make_replay_callback()` (ligne 326) | Wrapping pour menu | Fabrique callbacks pour menu "Mes tâches" | Non touché par ce changement | ⚫ Aucun branchement |
| `main._replay_poll_loop()` (ligne 276-345) | Boucle permanente | **Récepteur** d'actions, pas déclencheur de replay | Auto-déclencher ici = invisible, non conforme Article 14 | 🔴 À éviter formellement — le poll loop reste polymorphique sur la session, ne doit pas démarrer de session. |
| `chat_window._on_paused_resume/_abort` (ligne 1016-1080) | Boutons bulle Pause | Resume/abort d'un replay **déjà en cours** | Hors scope (action sur replay existant) | ⚫ Aucun branchement |
| `lea_ui/server_client.LeaServerClient.start_polling()` (ligne 261) | API alternative | Polling parallèle non utilisé par main (qui a son propre loop) | Confusion : code dormant en partie | 🟡 Ne pas ajouter de logique ici — confirmer d'abord si ce client est encore actif au runtime. |
### Notification / proposition utilisateur post-enregistrement
| Surface | Mécanisme dispo | Utilisation recommandée |
|---|---|---|
| `smart_tray._notifier.notify(title, msg)` (NotificationManager) | Notification système non-bloquante | 🟢 Notif simple "Tâche prête, voulez-vous la tester ?" si `replay_ready=true` mais `launch_replay=false`. |
| `smart_tray._ask_consent(title, msg)` (ligne 74) | Dialog tkinter Yes/No | 🟢 Si le serveur signale `replay_ready=true`, dialog post-enregistrement (réutilise le même pattern que le consentement avant enregistrement). |
| `chat_window` (bulle paused) | Tkinter custom bubble | 🟡 Réutiliser le pattern paused_bubble (ligne 902-1014) pour proposer un "Tester maintenant" / "Plus tard" serait propre mais plus de code. À garder pour une v2 produit. |
| `shared_state` listeners | Notification générique | 🟡 Émettre un nouvel événement type `recording_finalized` peut servir si plusieurs UI doivent réagir — mais ajoute du contrat. |
## Trois options d'intégration
### Option A — Solution minimale (recommandée)
1. `TraceStreamer` reçoit un setter optionnel `set_on_finalize_result(callback: Callable[[dict], None])`. Stocké en attribut.
2. Dans `_finalize_session()` après `resp.ok`, appel non-bloquant `self.on_finalize_result(result)` si défini.
3. `AgentV1.__init__` wire le callback : `self.streamer.set_on_finalize_result(self._on_finalize_result)`.
4. `AgentV1._on_finalize_result(payload)` :
- si `payload.get("launch_replay") is True` ET `replay_request` présent → invoque `self.ui._launch_replay(replay_request)` (avec adaptation signature pour accepter un dict)
- sinon si `payload.get("replay_ready") is True``self.ui._notifier.notify("Léa", "J'ai compris la tâche. Voulez-vous la tester ?")` + ouvre `_ask_consent` (en thread)
- sinon : silencieux
**Volume estimé** : ~15-20 lignes de code dans 2-3 fichiers. Aucun changement de contrat dans `shared_state`, `chat_window`, `lea_ui`. Réutilise `_launch_replay` et `_ask_consent` existants.
**Conforme** : Article 14 (contrôle humain) si dialog `_ask_consent` ; Article 50 (transparence) si `_launch_replay` est utilisé (notification déjà émise ligne 469-472).
### Option B — Solution produit propre (à viser ensuite)
Tout de Option A, plus :
- Ajouter une bulle interactive dans `chat_window` (pattern paused_bubble), au lieu/en plus du dialog tkinter, pour proposer le test avec contexte (nom de la tâche, durée, nombre d'actions).
- Persister la proposition non répondue dans `AgentState` pour que l'utilisateur la retrouve même s'il a fermé la notif.
- Logger côté audit_trail l'événement "test post-enregistrement proposé / accepté / refusé" pour conformité Article 12.
**Volume estimé** : +50-80 lignes, principalement UI chat_window. Plus de couplage shared_state.
### Option C — À éviter
- **Auto-déclencher le replay sans confirmation** depuis le callback finalize, même si `launch_replay=true`. Sans dialog, non conforme Article 14 (l'utilisateur peut ne pas être devant l'écran à ce moment). Si le serveur impose vraiment `launch_replay=true`, l'agent doit quand même afficher un compte à rebours visible (3-5s) avec annulation possible — mais pas exécuter silencieusement.
- **Faire le HTTP `/finalize` depuis `chat_window` ou `smart_tray`** pour lire la réponse. Duplication, l'API serveur est déjà appelée par streamer.
- **Modifier `_replay_poll_loop` pour intercepter une transition "finalize→start replay"**. Couplage faux entre poll permanent et événement ponctuel ; race condition si l'utilisateur enchaîne plusieurs enregistrements.
- **Étendre la signature de `AgentState.stop_recording()`** pour propager le payload. La couche état partagé doit rester muette sur le contenu serveur.
- **Modifier `_finalize_session()` pour retourner la valeur via `streamer.stop()`**. Casse l'API publique de `stop()` (utilisée par `main.stop_session`), oblige à modifier deux endroits, et empêche un fan-out à plusieurs listeners éventuels.
## Conclusion
**Un seul point d'intégration recommandé** : ajouter un callback `on_finalize_result` à `TraceStreamer`, invoqué dans `_finalize_session` après réception de la réponse, wired par `AgentV1` qui dispatche selon `launch_replay` / `replay_ready` vers les surfaces UI déjà existantes (`smart_tray._launch_replay` ou `smart_tray._ask_consent` + `_notifier.notify`).
Ce point :
- **Capture la réponse là où elle existe déjà** (`streamer.py:622`) sans dupliquer d'appel HTTP.
- **Découpe la responsabilité** : streamer = transport, main = orchestration, smart_tray = UI/consentement.
- **Préserve le VWB hors-jeu** : aucun appel ne transite par VWB, le replay est lancé directement par l'agent via l'endpoint `/replay/start` déjà éprouvé.
- **Respecte Article 14** si l'auto-lancement passe par `_ask_consent` ou une notif validable.
- **Coût d'implémentation** : ~15-20 lignes de code, 2-3 fichiers.
Hors périmètre de cette mission : la définition exacte du contenu de `replay_request` (qui touche au workflow_id, à la machine cible, aux paramètres execution_mode). À spécifier côté serveur avant de figer la signature client.
## Méthode d'audit
- Lecture intégrale : `shared_state.py` (190 lignes), `smart_tray.py` (781 lignes), `lea_ui/server_client.py` (375 lignes).
- Lecture déjà réalisée à l'audit Léa-first (2026-05-19) : `streamer.py` (734 lignes), `main.py` (561 lignes).
- Grep ciblé `chat_window.py` (1619 lignes) : `stop|replay|workflow|finalize|launch|on_stop|on_replay` (60 résultats analysés).
- Aucune modification de code, aucune exploration VWB, aucune refonte UI proposée — conformément aux interdits.