# Spec — QW Suite Mai 2026 | Champ | Valeur | |---|---| | Date | 2026-05-05 | | Auteur | Dom + Claude (brainstorming structuré) | | Branche | `feature/qw-suite-mai` (depuis `feature/feedback-bus`) | | Backup | `backup/pre-qw-suite-mai-2026-05-05` à pousser sur Gitea avant 1er commit | | Statut | Design approuvé — spec à valider par Dom avant `writing-plans` | | Cibles démo | GHT Sud 95 (1ère sem mai 2026, date à confirmer) | | Contraintes inviolables | 100% vision · 100% local (Ollama) · backward compatible | ## 1. Contexte & motivation Suite à l'exploration comparative de 5 frameworks computer-use (Simular Agent-S, browser-use, OpenAI CUA sample, Coasty open-cu, Showlab OOTB), trois quick wins ont été identifiés comme améliorations à fort ratio valeur/risque pour RPA Vision V3, alignés avec la philosophie du projet (vision pure, souveraineté, supervision médicale) : - **QW1 — Multi-écrans propre** (inspiré OOTB) : capture et grounding sur l'écran cible plutôt que sur le composite tous écrans. Gain de perf grounding + correction des coordonnées. - **QW2 — LoopDetector composite** (inspiré browser-use) : détecter quand Léa exécute des actions techniquement valides mais que l'écran ne progresse pas, et escalader vers l'humain plutôt que de tourner en rond muettement. - **QW4 — Safety checks hybrides** (inspiré OpenAI CUA + browser-use Pydantic registry) : enrichir l'action `pause_for_human` avec une liste de vérifications à acquitter, mêlant déclaratif (workflow) et contextuel (LLM local). Effet cumulé attendu : Léa devient observable, robuste et auditable sans rien céder sur le 100% local. ## 2. Décisions de design (récap) | Sujet | Décision | |---|---| | Activation | Default-ON pour tous les workflows (Dom recréera ce qui en a besoin) | | QW1 — Stratégie ciblage écran | `monitor_index` enregistré à la capture → fallback focus actif → fallback composite (backward) | | QW1 — Niveau de stack | Client Agent V1 (capture) + serveur (routeur) + `core/execution/input_handler.py` (capture locale) | | QW2 — Signal de boucle | Composite OR : screen_static (CLIP) + action_repeat + retry_threshold | | QW2 — Sortie | `replay_state["status"] = "paused_need_help"` avec `pause_reason` structuré | | QW4 — Source des checks | Hybride : déclaratif workflow + LLM contextuel sur `safety_level: "medical_critical"` | | QW4 — Robustesse LLM | `medgemma:4b` + timeout 5s + `format=json` Ollama + JSON Schema strict + fallback safe (zéro check additionnel) + kill-switch env var | | QW4 — UX VWB | Bulle existante préservée + `` au-dessus de Continuer (bouton désactivé tant que required non cochés) | | Ordre de livraison | QW1 → QW2 → QW4 (du moins invasif au plus visible) | | Plan timing | Option A : QW1+QW2 avant démo ; QW4 enchaîné dès validation des deux premiers | | Kill-switches | Env vars sur QW2 et QW4, surchargeables par `systemctl edit` | | Backward compatibility | 100% — aucun champ obligatoire ajouté au DSL ; workflows existants se comportent comme avant | ## 3. Architecture globale ``` ┌─────────────────────────┐ ┌─────────────────────────────────┐ │ Agent V1 (Windows) │ │ Serveur Streaming (5005) │ │ │ │ │ │ ┌──────────────────┐ │ │ ┌───────────────────────────┐ │ │ │ ScreenCapture │ │ │ │ LoopDetector [QW2] │ │ │ │ + monitor_index │───┼────────▶│ │ • screen_static (CLIP) │ │ │ │ [QW1] │ │ HTTP │ │ • action_repeat │ │ │ └──────────────────┘ │ │ │ • retry_threshold │ │ │ │ │ │ → paused_need_help │ │ │ ┌──────────────────┐ │ │ └───────────────────────────┘ │ │ │ FeedbackBus lea:*│◀──┼─────────┤ │ │ │ chat_window │ │ │ ┌───────────────────────────┐ │ │ └──────────────────┘ │ │ │ SafetyChecksProvider │ │ └─────────────────────────┘ │ │ [QW4] │ │ │ │ • declarative (workflow) │ │ │ │ • LLM contextual │ │ │ │ ‒ medgemma:4b 5s/JSON │ │ │ │ ‒ fallback safe │ │ │ │ • kill-switch env var │ │ │ └───────────────────────────┘ │ │ │ │ ┌───────────────────────────┐ │ │ │ MonitorRouter [QW1] │ │ │ │ • cible monitor_index │ │ │ │ • fallback focus actif │ │ │ └───────────────────────────┘ │ └─────────────────────────────────┘ │ ▼ ┌─────────────────────────────────┐ │ VWB Frontend (3002) │ │ │ │ PauseDialog (étendu) [QW4-UX] │ │ • bulle existante préservée │ │ • + ChecklistPanel │ │ (cases à cocher acquittables)│ │ • + pause_reason si loop │ │ Continuer désactivé tant que │ │ required-checks non cochés │ └─────────────────────────────────┘ ``` ### Principes invariants 1. Aucun nouveau service, aucune nouvelle DB. Tout dans la stack existante (Agent V1 + serveur 5005 + VWB 3002). 2. 3 modules serveur isolés (`monitor_router.py`, `loop_detector.py`, `safety_checks_provider.py`) — couplage faible, testables individuellement, désactivables par env var. 3. Backward compatible : workflows sans nouveaux champs se comportent comme avant. 4. Kill-switches env vars sur QW2 et QW4, override possible via `systemctl edit` pendant la démo. 5. 100% vision : QW1 pure capture + grounding ; QW2 réutilise le `_clip_embedder` déjà chargé ; QW4 LLM = Ollama local strict. 6. Bus `lea:*` étendu de 4 events d'observabilité : `lea:loop_detected`, `lea:safety_checks_generated`, `lea:safety_checks_llm_failed`, `lea:monitor_routed`. ### Surface de modification (ordre A) | QW | Fichiers nouveaux | Fichiers modifiés | |---|---|---| | QW1 | `agent_v0/server_v1/monitor_router.py` | `agent_v0/agent_v1/capture/screen_capture.py`, `core/execution/input_handler.py`, `agent_v0/server_v1/api_stream.py` (~10 lignes) | | QW2 | `agent_v0/server_v1/loop_detector.py` | `agent_v0/server_v1/replay_engine.py` (~30 lignes), `agent_v0/server_v1/api_stream.py` (~20 lignes) | | QW4 | `agent_v0/server_v1/safety_checks_provider.py`, `visual_workflow_builder/frontend_v4/src/components/PauseDialog.tsx` | `agent_v0/server_v1/replay_engine.py`, `agent_v0/server_v1/api_stream.py` (`/replay/resume`), `visual_workflow_builder/frontend_v4/src/types.ts`, `visual_workflow_builder/frontend_v4/src/components/PropertiesPanel.tsx` | ## 4. QW1 — Multi-écrans ### 4.1 Composants **Client Agent V1** — `agent_v0/agent_v1/capture/screen_capture.py` (existant à modifier) - Enrichit chaque heartbeat / event avec : - `monitor_index: int` - `monitors_geometry: [{idx, x, y, w, h, primary}]` - Détection via `screeninfo` (port direct depuis Showlab OOTB) - Capture de l'écran *actif uniquement* (poids réseau identique à aujourd'hui) - Si `screeninfo` indisponible côté Windows : envoie `monitors_geometry: []`, comportement composite préservé **Serveur** — nouveau `agent_v0/server_v1/monitor_router.py` (~80 lignes) - API : `resolve_target_monitor(action: dict, session_state: dict) → MonitorTarget` - `MonitorTarget = {idx, offset_x, offset_y, w, h, source: "action" | "focus" | "composite_fallback"}` - Stratégie : 1. Lit `action.get("monitor_index")` si présent → cible cet écran 2. Sinon `session_state.get("last_focused_monitor")` → cible focus actif 3. Sinon `monitors[0]` composite (comportement actuel — backward) **Input local Linux** — `core/execution/input_handler.py` modifs ciblées - Signature changée : `_capture_screen(monitor_idx=None) → (image, w, h, offset_x, offset_y)` - Quand `monitor_idx` fourni : capture uniquement ce monitor - Toutes les fonctions `_grounding_*` (`_grounding_ocr`, `_grounding_ui_tars`, `_grounding_vlm`) propagent l'offset pour traduire les coords retournées en coords absolues écran ### 4.2 Data flow replay ``` Action [monitor_index=1] reçue par serveur → MonitorRouter.resolve() → target_monitor = {idx:1, offset:(1920,0), w:1920, h:1080, source:"action"} → grounding capture monitor 1 uniquement (image 1920×1080, pas 3840×1080) → UI-TARS / OCR / VLM cherche cible → coords locales (640, 540) → coords absolues = (640+1920, 540+0) = (2560, 540) → pyautogui.click(2560, 540) → bus.emit("lea:monitor_routed", {idx:1, source:"action"}) ``` ### 4.3 Error handling | Cas | Comportement | |---|---| | `monitor_index` absent (vieille session) | Fallback focus actif, log info `lea:monitor_routed source=focus` | | Monitor enregistré n'existe plus (2nd écran débranché) | Fallback focus actif, event `lea:monitor_unavailable` warning | | `mss.monitors[i]` hors limites | Fallback `monitors[0]` composite, event `lea:monitor_invalid_index` error | | `screeninfo` non installé côté Agent V1 | `monitors_geometry: []`, fallback composite (comportement actuel) — pas de blocage | ### 4.4 Tests QW1 - `tests/unit/test_monitor_router.py` : 4 cas (cible OK, fallback focus, fallback composite, monitor débranché) - `tests/integration/test_grounding_offset.py` : capture 1 monitor + clic résolu avec offset (mock pyautogui) - Smoke : 1 workflow Easily rejoué, vérification visuelle que le clic atterrit au bon endroit ### 4.5 Compat workflows existants Aucune action n'a `monitor_index` aujourd'hui → 100% des workflows existants partent en fallback focus actif → comportement quasi-identique au composite actuel mais sur un seul écran (gain de perf grounding même sans recréation de workflow). ## 5. QW2 — LoopDetector composite ### 5.1 Composants **Nouveau** `agent_v0/server_v1/loop_detector.py` (~150 lignes) - Classe `LoopDetector` avec 3 sous-détecteurs - API : `evaluate(replay_state, screenshot_history, action_history) → LoopVerdict` - `LoopVerdict = {detected: bool, reason: str, signal: str, evidence: dict}` **Hook** dans `agent_v0/server_v1/api_stream.py` - Après chaque `report_action_result`, appel `loop_detector.evaluate(...)` si `RPA_LOOP_DETECTOR_ENABLED=1` (défaut) - Si `verdict.detected` : - `replay_state["status"] = "paused_need_help"` - `replay_state["pause_reason"] = verdict.reason` - `replay_state["pause_message"] = f"Léa semble bloquée — {verdict.signal}"` - bus.emit `lea:loop_detected` avec `{signal, evidence, replay_id}` **Étendu** dans `replay_engine.py` : - `_create_replay_state()` ajoute : - `"_screenshot_history": []` (anneau de 5 derniers embeddings CLIP) - `"_action_history": []` (anneau des 5 dernières actions) - `_pre_check_screen_state()` continue indépendamment (signal différent : check pré-action vs détection post-action de stagnation) ### 5.2 Signaux composites | Signal | Détecteur | Seuil par défaut | Source | |---|---|---|---| | `screen_static` | A | 4 captures consécutives avec CLIP similarity > 0.99 | `_clip_embedder` déjà chargé serveur | | `action_repeat` | B | 3 actions consécutives identiques (type + coords) | `_action_history` | | `retry_threshold` | C | 3 retries sur même `action_id` | `replay_state["retried_actions"]` (déjà existant) | Un seul signal positif suffit à déclencher l'escalade. ### 5.3 Data flow ``` Action exécutée → result reçu via /replay/result ↓ LoopDetector.evaluate(state, screenshots, actions) si RPA_LOOP_DETECTOR_ENABLED=1 ├─ A.check_screen_static() → embed(latest), compare aux N-1 derniers ├─ B.check_action_repeat() → compare action_history[-3:] └─ C.check_retry_threshold() → state["retried_actions"] >= 3 ↓ Si verdict.detected: state["status"] = "paused_need_help" state["pause_reason"] = verdict.reason state["pause_message"] = f"Léa semble bloquée — {verdict.signal} ({evidence})" bus.emit("lea:loop_detected", {signal, evidence, replay_id}) ``` ### 5.4 Error handling | Cas | Comportement | |---|---| | CLIP embedder unavailable | Signal A désactivé (warning log 1×), B+C continuent. Pas de blocage. | | `_screenshot_history` < N | Signal A skip silencieusement (pas assez d'historique) | | `embed_image()` lève une exception | Catch + log warning, replay continue (verdict = `detected=False`) | | `RPA_LOOP_DETECTOR_ENABLED=0` | Module entier bypassé, comportement antérieur | | Faux positif détecté en pleine démo | `RPA_LOOP_DETECTOR_ENABLED=0` via `systemctl edit rpa-streaming` + restart → reprise immédiate | ### 5.5 Configuration env vars - `RPA_LOOP_DETECTOR_ENABLED=1` (défaut) - `RPA_LOOP_SCREEN_STATIC_THRESHOLD=0.99` - `RPA_LOOP_SCREEN_STATIC_N=4` - `RPA_LOOP_ACTION_REPEAT_N=3` - `RPA_LOOP_RETRY_THRESHOLD=3` ### 5.6 Tests QW2 - `tests/unit/test_loop_detector.py` : 8 cas (chaque signal isolé, chaque combinaison, kill-switch, embedder absent) - `tests/integration/test_loop_detector_replay.py` : 3 cas — replay simulé qui boucle → vérifier transition `running → paused_need_help` avec bonne raison - Pas de smoke démo (impossible à reproduire fiable, on s'appuie sur les tests intégration) ### 5.7 Compat VWB Aucune côté frontend pour QW2 : la pause `paused_need_help` existe déjà. Le `pause_reason` enrichi sera affiché par le composant `PauseDialog` étendu en QW4. Avant la livraison de QW4, la raison s'affichera en texte dans le `pause_message` (donc utile dès le commit QW2). ## 6. QW4 — Safety checks hybrides ### 6.1 Contrat de l'action étendue (rétro-compatible) ```json { "type": "pause_for_human", "parameters": { "message": "Validation T2A avant codage", "safety_level": "medical_critical", "safety_checks": [ {"id": "check_ipp", "label": "Vérifier IPP patient", "required": true}, {"id": "check_cim10", "label": "Confirmer code CIM-10", "required": true} ] } } ``` `safety_level` et `safety_checks` sont **optionnels**. Action sans ces champs → comportement actuel (bulle simple, aucun appel LLM). ### 6.2 Composants serveur **Nouveau** `agent_v0/server_v1/safety_checks_provider.py` (~180 lignes) - API : `build_pause_payload(action, replay_state, last_screenshot) → PausePayload` - Concatène : checks déclaratifs (workflow) + checks contextuels (LLM si `safety_level == "medical_critical"`) - Chaque check porte sa source : `source: "declarative" | "llm_contextual"` et son `evidence` (vide pour déclaratif, justification courte pour LLM) - Format check final : ```json { "id": "check_xxx", "label": "...", "required": true, "source": "declarative" | "llm_contextual", "evidence": null | "..." } ``` **LLM contextual call** — sous-fonction `_call_llm_for_contextual_checks()` - Modèle : `medgemma:4b` (env `RPA_SAFETY_CHECKS_LLM_MODEL`) - Timeout dur : 5s (env `RPA_SAFETY_CHECKS_LLM_TIMEOUT_S`) - `format=json` natif Ollama + JSON Schema strict : ```json {"additional_checks": [{"label": "string", "evidence": "string"}]} ``` - Max 3 checks ajoutés (env `RPA_SAFETY_CHECKS_LLM_MAX_CHECKS`) - Prompt : screenshot heartbeat actuel + workflow message + liste des checks déclaratifs (évite doublons) - Tout échec (timeout, exception, JSON invalide post-schema) → `additional_checks = []`, event `lea:safety_checks_llm_failed`, replay continue **Hook** dans `replay_engine.py` — branche `action_type == "pause_for_human"` - Avant de basculer en `paused_need_help`, appel `safety_checks_provider.build_pause_payload(...)` - Stocke `replay_state["safety_checks"] = payload.checks` - Stocke `replay_state["pause_payload"] = payload` (pour debug/audit) **Modif** `api_stream.py` — endpoint `/replay/resume` - Reçoit `{acknowledged_check_ids: [...]}` dans le body POST - Vérifie : tous les checks `required=true` doivent être dans `acknowledged_check_ids` - Sinon : `400 {error: "required_checks_missing", missing: [...]}` - Stocke `replay_state["checks_acknowledged"] = acknowledged_check_ids` (audit trail) - Reprise normale du replay ### 6.3 Composants frontend VWB **Nouveau** `visual_workflow_builder/frontend_v4/src/components/PauseDialog.tsx` (~200 lignes) - Props : `pauseMessage`, `pauseReason`, `safetyChecks`, `onResume(ackIds)`, `onCancel` - Si `safetyChecks.length === 0` : rend la bulle existante (legacy, comportement actuel) - Sinon : bulle + `` avec checkboxes - Bouton Continuer disabled tant que `checks.filter(c => c.required && !checked).length > 0` - POST `/replay/resume` avec body `{acknowledged_check_ids: [...]}` - Visuel source : - Badge `[Léa]` pour `source: "llm_contextual"` (avec tooltip `evidence`) - Badge `[obligatoire]` pour `required: true` **Étendu** `types.ts` - `PauseAction['parameters']` : ajout `safety_level?`, `safety_checks?` - `Execution` : ajout `pause_reason?`, `safety_checks?` **Étendu** `PropertiesPanel.tsx:1356` — éditeur de l'action `pause_for_human` - Section "Niveau de sécurité" : dropdown `standard | medical_critical` - Section "Checks à valider" : liste éditable (id + label + required) ### 6.4 Data flow complet ``` Action pause_for_human (medical_critical, 2 checks déclaratifs) atteinte ↓ SafetyChecksProvider.build_pause_payload() ├─ checks = [...declarative] (2 entrées) ├─ if safety_level == "medical_critical" and RPA_SAFETY_CHECKS_LLM_ENABLED=1: │ llm_checks = _call_llm_for_contextual_checks() (max 3, timeout 5s) │ checks += llm_checks └─ return PausePayload(checks, pause_reason, message) ↓ replay_state["status"] = "paused_need_help" replay_state["safety_checks"] = checks bus.emit("lea:safety_checks_generated", {count, sources}) ↓ Frontend VWB poll /replay/state → reçoit pause_payload ↓ rend ChecklistPanel ↓ Médecin coche les 4 checks → clique Continuer ↓ POST /replay/resume {acknowledged_check_ids: [4 ids]} ↓ Serveur valide (tous required acquittés) → reprise du replay replay_state["checks_acknowledged"] = [...] (audit trail conservé) ``` ### 6.5 Error handling | Cas | Comportement | |---|---| | `safety_level` absent | Pas d'appel LLM ; checks déclaratifs uniquement (peut être `[]`) → bulle simple si vide, checklist sinon | | Ollama timeout 5s | Event `lea:safety_checks_llm_failed`, `additional_checks=[]`, fallback safe (déclaratifs seuls) | | Ollama JSON malformé (post `format=json` — théoriquement impossible) | Idem timeout, fallback safe | | LLM produit un check absurde | Accepté tel quel, le superviseur ignore (pas de filtrage en V1) | | Frontend reçoit `safety_checks=[]` | Bulle simple, comportement legacy | | `RPA_SAFETY_CHECKS_LLM_ENABLED=0` | Couche LLM bypassée, déclaratifs gardés | | `/replay/resume` sans `acknowledged_check_ids` sur required | `400 required_checks_missing` | | Frontend POST `/replay/resume` rejeté | Toast d'erreur côté UI, état pause conservé, possibilité de cocher manquants et réessayer | ### 6.6 Configuration env vars - `RPA_SAFETY_CHECKS_LLM_ENABLED=1` (défaut) - `RPA_SAFETY_CHECKS_LLM_MODEL=medgemma:4b` - `RPA_SAFETY_CHECKS_LLM_TIMEOUT_S=5` - `RPA_SAFETY_CHECKS_LLM_MAX_CHECKS=3` ### 6.7 Tests QW4 - `tests/unit/test_safety_checks_provider.py` : 7 cas (déclaratif seul, hybride réussi, LLM timeout, LLM JSON invalide, kill-switch, max_checks respecté, déclaratif vide) - `tests/integration/test_replay_resume_acknowledgments.py` : 3 cas (resume OK, missing required → 400, audit trail enregistré dans `checks_acknowledged`) - Frontend : `tests/components/PauseDialog.test.tsx` si suite Vitest existe (à confirmer pendant l'implémentation), sinon test manuel avec checklist écrite - Smoke : 1 workflow Easily avec `pause_for_human medical_critical` enrichi → vérification full chain ### 6.8 Compat workflows existants 100% backward — `pause_for_human` actuels n'ont ni `safety_level` ni `safety_checks` → comportement strictement identique. Aucune recréation forcée. Dom enrichira uniquement les workflows qu'il veut promouvoir au niveau `medical_critical`. ## 7. Tests, sécurité de la branche, livraison ### 7.1 Filet de sécurité avant TOUT commit sur `feature/qw-suite-mai` 1. Branche backup poussée Gitea : `backup/pre-qw-suite-mai-2026-05-05` 2. Capture baseline E2E : ``` pytest tests/test_pipeline_e2e.py \ tests/test_phase0_integration.py \ tests/integration/test_stream_processor.py \ -q 2>&1 | tee .qw-baseline.log ``` 3. Smoke démo : 1 dérouler complet d'un workflow Easily Assure, archivage screenshot/vidéo de référence 4. État VWB validé : démarrage Vite local, ouverture d'un workflow, lancement d'un replay simple, screenshot "tout va bien" ### 7.2 Discipline TDD légère par QW - Test unitaire écrit AVANT le code de production (1 test rouge → 1 implémentation → vert) - Pas de TDD complet sur le frontend (Vitest + React = trop d'outillage à valider en parallèle), test manuel cadré avec checklist écrite - Re-run de la suite baseline après chaque commit QW, comparaison au log archivé - Toute régression bloque le passage au QW suivant tant qu'elle n'est pas comprise et résolue ### 7.3 Compat VWB — checklist explicite avant commit QW4 - [ ] Workflow ancien (sans `safety_checks`) → bulle simple s'affiche normalement - [ ] Workflow nouveau avec `safety_checks` déclaratifs uniquement → checklist visible, **pas** d'appel Ollama (vérification logs) - [ ] Workflow `medical_critical` → checklist + checks LLM apparaissent (vérification logs Ollama call dans les 5s) - [ ] Continuer désactivé tant que required non cochés - [ ] POST `/replay/resume` avec mauvais payload → toast d'erreur côté UI, pas de crash - [ ] PropertiesPanel : édition de `safety_checks` ne casse pas l'édition d'autres params de `pause_for_human` - [ ] DB `workflows.db` : ouverture après commit, aucune migration cassante (schéma JSON est libre) ### 7.4 Plan de commits ``` 1. test(qw1): tests monitor_router + grounding_offset (rouges) 2. feat(qw1): multi-écrans piloté par monitor_index (verts) 3. test(qw2): tests loop_detector composite (rouges) 4. feat(qw2): LoopDetector composite avec kill-switch env 5. test(qw4): tests safety_checks_provider + replay_resume (rouges) 6. feat(qw4): safety_checks hybride déclaratif + LLM contextuel 7. feat(vwb): PauseDialog + ChecklistPanel + extension PropertiesPanel 8. docs(qw): docs/QW_SUITE_MAI.md + mise à jour MEMORY.md ``` Chaque commit signé Co-Authored-By Claude. Branche poussée régulièrement sur Gitea pour backup distant. ### 7.5 Stratégie en cas de régression critique pendant la démo Kill-switches env vars surchargeables sans redéploiement code : ``` systemctl edit rpa-streaming # Ajouter sous [Service] : Environment=RPA_LOOP_DETECTOR_ENABLED=0 Environment=RPA_SAFETY_CHECKS_LLM_ENABLED=0 systemctl restart rpa-streaming ``` Si problème grave au-delà des kill-switches : rollback à `backup/pre-qw-suite-mai-2026-05-05`. ``` git checkout backup/pre-qw-suite-mai-2026-05-05 ./svc.sh restart ``` ### 7.6 Plan de livraison (Option A validée) **Avant démo GHT (cette semaine) — Sprint priorité 1** - QW1 : tests + code + smoke (~1j) - QW2 : tests + code + tests intégration (~2j) - Capture baseline + replay smoke entre chaque - Si QW1+QW2 validés et probants → on enchaîne sur QW4 dès que possible (Dom accepte le weekend si "effet waouh" auprès de spécialistes RPA) **Après démo / dès validation QW1+QW2 — Sprint priorité 2** - QW4 serveur (provider + LLM + endpoint resume) (~3j) - QW4 frontend (PauseDialog + PropertiesPanel) (~2j) - Doc + mise à jour MEMORY.md **Total estimé** : ~8.5j-h ingénieur senior, étalable selon le retour démo. ## 8. Ce qui n'est PAS dans ce spec (out of scope) - F1 (DSL d'actions Pydantic-first) : refactor de fond, sera son propre spec après la démo. - F2 (Mixture-of-Grounding routeur adaptatif) : nécessite F1, son propre spec. - F3 (Best-of-N + Reflection) : nécessite F1, son propre spec. - QW3 (`output_model_schema` Pydantic pour `extract_text`) : opportuniste, sera intégré quand on touchera `extract_text` pour autre chose. - Toute introduction de Pydantic-AI / instructor / Playwright / accessibility-tree : interdit (contraintes inviolables). - Refonte du composant pause en `` à 3 modes (option C de Q6) : reportée après démo si retour utilisateurs justifie l'investissement. ## 9. Open questions Aucune. Toutes les décisions de design ont été tranchées via les 7 questions clarifiantes du brainstorming du 5 mai 2026.