# Agents & Skills pour Flowdeck — Phase 2 : L'Agent choisit, l'Agent personnel se personnalise | Champ | Valeur | |---|---| | Phase | **2 sur 5** — intelligence d'usage de l'Agent personnel | | Document parent | `architecture-agents-skills-notion-flowdeck.md` v1.1 — en particulier §3.2 (l'Agent personnel), §10.2 et §10.5, §12.1–12.4, §13.2–13.3 | | Version | 1.0 — 9 octobre 2026 | | Auteur | Spark, pour Bruno | | Statut | Prêt à implémenter **après la Phase 1** | | Prérequis | Phase 1 livrée (skills = pages, descriptions indexées, Skill Runner, enablements) | | Migration créée | **40** | | Débloque | Phase 3 (les Custom Agents héritent du contexte de run, du routeur et du journal de run), Phase 4 (le journal d'usage s'appuie sur `agent_runs`) | > **Résultat visible à la fin de cette phase.** L'Agent personnel a une identité et une **page d'instructions** personnelle (« Mon Flowdeck AI »), commutable ; dans le chat, le sélecteur **All sources** choisit ce que l'Agent peut consulter, le sélecteur de modèle propose **Auto** ; l'Agent **applique tout seul un skill pertinent** quand la demande correspond à sa description, le nomme dans sa réponse et le liste dans le panneau des sources ; les conversations s'épinglent et se titrent automatiquement ; et **chaque exécution devient un objet `agent_runs` relisible** — modèle réellement utilisé, sources lues, skills appliqués, étapes, coût en tokens. --- ## Table des matières 1. [Objectif](#1-objectif) 2. [Périmètre](#2-périmètre) 3. [État d'entrée](#3-état-dentrée) 4. [Modèle de données — migration 40](#4-modèle-de-données--migration-40) 5. [Tickets de travail](#5-tickets-de-travail) 6. [API livrée par la phase](#6-api-livrée-par-la-phase) 7. [Comportements détaillés](#7-comportements-détaillés) 8. [Tests](#8-tests) 9. [Critères d'acceptation](#9-critères-dacceptation) 10. [Risques spécifiques et vigilance](#10-risques-spécifiques-et-vigilance) 11. [Ordre d'exécution](#11-ordre-dexécution) 12. [Définition de « terminé »](#12-définition-de--terminé-) --- ## 1. Objectif Deux manques séparent l'Agent personnel actuel de son modèle Notion, et cette phase les comble : 1. **Il ne choisit rien.** Ni le skill pertinent (l'utilisateur doit le nommer), ni le modèle adapté (le modèle est celui de la configuration), ni ses sources au-delà des mentions. La phase livre le **Skill Router** (déterministe d'abord, arbitrage LLM en zone grise), le **routage de modèle `Auto`** et le sélecteur **All sources** persisté par conversation. 2. **Il n'a pas de mémoire de comportement durable et éditable.** Les instructions vivent dans des champs, pas dans une page que l'utilisateur peut écrire, versionner et commuter comme ses autres documents. La phase livre la **page d'instructions personnelle** et son assemblage dans le prompt du run. Le troisième livrable est invisible mais structurel : **`agent_runs`**. Sans un objet « run » explicite, ni l'Activity des Custom Agents (Phase 3), ni la comptabilité (Phase 4), ni le débogage des appariements de skills ne sont possibles. ## 2. Périmètre ### 2.1 Inclus - Migration 40 : `agent_personal_settings`, `agents.instruction_page_id`, `agent_runs`, `agent_actions.run_id`, colonnes de conversation (`pinned`, `title_auto`, `sources_json`). - Personnalisation de l'Agent personnel : nom, avatar/accessoires, mode d'affichage du chat (barre latérale / flottant). - Page(s) d'instructions personnelle(s) : création (« Mon Flowdeck AI »), choix d'une page existante, modèles de départ, commutation de la page active ; résolution des instructions dans le prompt du run (ordre du document parent §12.3). - Modification des instructions « par l'Agent » sur demande de l'utilisateur — en proposition diff, jamais en écriture silencieuse. - Contexte de run typé (`RunContext`, §12.1 du parent) refactoré dans le moteur : surface, acteur, régime (`user` en cette phase), instructions, sources, modèle demandé, budget. - Sélecteur **All sources** dans le compositeur : workspace, connecteurs actifs, serveurs MCP ; état persisté par conversation ; bouton `@` existant conservé. - Sélecteur de modèle dans le panneau : `Auto` + modèles configurés ; affichage du modèle **réellement utilisé** dans la réponse ; signalement des modèles « web seulement » (sources workspace décochées pour le run). - **Skill Router** : appariement description ↔ demande (recherche hybride existante, seuils, arbitrage LLM optionnel), au plus 2 skills automatiques, jamais d'écartement d'un skill forcé, journal de décision. - Nomination du skill utilisé dans la réponse et **panneau des sources** du run (pages, connecteurs, MCP, skills). - Conversations : épinglage, titrage automatique (premier échange), historique cherchable dans l'onglet *Chat*. - Étapes nommées du run dans le flux SSE (`step`, `skill_used` — extension du protocole existant). - Actions suggérées contextuelles au-dessus du compositeur (amorces de prompt selon la page courante et les blocs sélectionnés). ### 2.2 Exclu - Les **crédits** et limites tarifaires (Phase 4) : en cette phase, `agent_runs` comptabilise les **tokens** ; le champ `credits` existe mais reste à 0 et n'est jamais affiché comme un coût. - Les modèles **autorisés par surface** et l'activation admin des premium (Phase 4) : le sélecteur montre les modèles de la configuration LLM existante. - Les runs **autonomes** (déclencheurs, régime `allowlist`) → Phase 3. `agent_runs.surface` accepte dès maintenant les valeurs futures, sans les produire. - Les fichiers de conversation (dépôt, production) → Phase 4. `sources_json` ne contient pas encore d'entrées fichiers. ## 3. État d'entrée | Élément (v7.69.8 + Phase 1) | Usage dans cette phase | |---|---| | Moteur ReAct, flux SSE (`reasoning`, `action`, `notice`, `final`, `error`) | Reçoit le `RunContext` ; émet `step` et `skill_used` en plus | | Context Builder (snapshot Markdown filtré ACL, mentions résolues) | Devient « v2 » : ajoute page/blocs courants, page d'instructions, sources choisies, skills résolus | | Mémoire par conversation (`agent_memory`) | Couche 3 de l'assemblage d'instructions (§7.1), inchangée | | Client LLM 23 fournisseurs + précédence de config en 4 niveaux | Le routeur `Auto` choisit **parmi** les modèles de cette configuration | | Recherche hybride (FTS5 + cosinus, RRF k=60) | Le Skill Router la réutilise avec `resource_type='skill'` | | Skills = pages, descriptions indexées, enablements | Entrées du routeur | | Skill Runner (Phase 1) | Exécute les skills forcés **et** résolus par le routeur (même chemin d'exécution) | ## 4. Modèle de données — migration 40 ```sql -- Migration 40 — Phase 2 CREATE TABLE agent_personal_settings ( user_id INTEGER PRIMARY KEY REFERENCES users(id), display_name TEXT, avatar_json TEXT DEFAULT '{}', instruction_page_ids TEXT DEFAULT '[]', active_instruction_page_id INTEGER REFERENCES pages(id), chat_mode TEXT DEFAULT 'sidebar', -- 'sidebar' | 'floating' default_model TEXT -- NULL = Auto ); ALTER TABLE agents ADD COLUMN instruction_page_id INTEGER REFERENCES pages(id); -- (les autres colonnes d'agents — kind, web_access, délégation — arrivent en Phase 3) CREATE TABLE agent_runs ( id INTEGER PRIMARY KEY, agent_id INTEGER REFERENCES agents(id), -- NULL en cette phase (Agent personnel) user_id INTEGER REFERENCES users(id), conversation_id INTEGER REFERENCES agent_conversations(id), parent_run_id INTEGER REFERENCES agent_runs(id), -- posé dès maintenant, utilisé en Phase 3 surface TEXT NOT NULL, -- 'chat' | 'editor' (+ valeurs futures admises) trigger_id INTEGER, -- NULL en cette phase (Phase 3 rattachera agent_triggers) trigger_payload_json TEXT DEFAULT '{}', status TEXT NOT NULL DEFAULT 'running', -- 'running'|'waiting_approval'|'done'|'failed'|'cancelled'|'budget_exceeded' provider TEXT, model TEXT, -- modèle RÉELLEMENT utilisé tokens_in INTEGER DEFAULT 0, tokens_out INTEGER DEFAULT 0, credits REAL DEFAULT 0, -- toujours 0 en cette phase (Phase 4) skills_json TEXT DEFAULT '[]', -- [{skill_id, name, invocation}] sources_json TEXT DEFAULT '[]', -- sources réellement lues (panneau des sources) router_decision_json TEXT DEFAULT '{}', -- candidats, scores, seuils, arbitre (§7.3) error TEXT, started_at TEXT NOT NULL, finished_at TEXT ); ALTER TABLE agent_actions ADD COLUMN run_id INTEGER REFERENCES agent_runs(id); ALTER TABLE agent_conversations ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0; ALTER TABLE agent_conversations ADD COLUMN title_auto INTEGER NOT NULL DEFAULT 0; ALTER TABLE agent_conversations ADD COLUMN sources_json TEXT DEFAULT '{}'; -- Rattachement de l'existant : créer rétroactivement un agent_runs 'done' par -- conversation ayant des agent_actions ? NON — décision : les runs commencent -- à la livraison de la phase ; l'historique ancien reste lisible comme avant. ``` Règles : 1. **Un run par exécution du moteur**, créé avant le premier appel LLM, terminé dans tous les cas (succès, erreur, annulation, budget) — un run qui reste `running` après un redémarrage du processus est soldé en `failed` (« interrompu ») au démarrage par le lifespan. 2. `skill_runs.run_id` (Phase 1) devient renseigné systématiquement ; les lignes de Phase 1 sans run restent valides. 3. `sources_json` du **run** (ce qui a été lu) est distinct de `sources_json` de la **conversation** (ce qui est autorisé à être lu) — la collision de nom est assumée et documentée partout où les deux apparaissent. ## 5. Tickets de travail ### Le run comme objet **P2-T01 — Migration 40 et modèle de run.** Fichiers : `app/migrations.py`, nouveau `app/services/agent_runs.py` (création, transitions d'état, solde au démarrage). Acceptation : un run de chat crée exactement une ligne ; un arrêt du processus en plein run laisse une ligne soldée `failed/interrompu` après redémarrage, jamais un zombie `running`. **P2-T02 — `RunContext` dans le moteur.** Fichiers : `app/services/agent_engine.py`, `app/services/context_builder.py` (v2). Travail : introduire le dataclass du document parent §12.1 ; tous les points d'entrée existants (panneau, éditeur via le Runner, API v2 synchrone) construisent un `RunContext` ; le régime est `user` pour tous en cette phase ; les écritures de journal (actions) portent `run_id`. Acceptation : les tests existants du moteur passent sans modification de leurs attentes fonctionnelles ; chaque `agent_actions` nouveau a un `run_id` non nul. **P2-T03 — Étapes nommées en SSE.** Travail : événements `step` {key, label, status} émis aux frontières réelles (contexte, routage, chaque famille d'outils, génération) et `skill_used` {skill_id, name, invocation} ; le panneau les rend comme une liste cochée ; repli : un client qui ignore ces événements affiche le flux actuel inchangé. Acceptation : pendant un run long, l'utilisateur voit progresser les étapes ; aucune étape « décorative » (une étape affichée = un travail réel du moteur). ### Personnalisation et instructions **P2-T04 — Réglages personnels et identité de l'Agent.** Fichiers : routes `GET/PUT /api/agent/personal-settings`, panneau (en-tête, menu de personnalisation). Travail : nom d'affichage, avatar (accessoires en `avatar_json`), mode d'affichage persisté, modèle par défaut personnel ; valeurs par défaut = comportement actuel pour les utilisateurs sans ligne de réglages. Acceptation : la personnalisation d'un utilisateur ne fuit jamais chez un autre (test multi-utilisateurs sur la même instance). **P2-T05 — Page d'instructions personnelle.** Fichiers : route `POST /api/agent/personal-settings/instructions-page`, sélecteur de page existant. Travail : à la première demande, créer la page privée « Mon Flowdeck AI » (gabarit : ton souhaité, ce que l'Agent doit retenir — rôle, habitudes —, pages/canaux à consulter d'abord) ; permettre de choisir une page existante ou un modèle, d'en maintenir plusieurs (`instruction_page_ids`) et de commuter la page active ; rappel dans l'interface : quiconque édite cette page modifie le comportement de l'Agent — pour partager un exemple, dupliquer la page. Acceptation : modifier la page active change la réponse au run suivant (test en mode offline avec consigne détectable) ; une page active devenue illisible (ACL retirée) est ignorée avec un `notice`, pas un échec. **P2-T06 — Assemblage des instructions du run.** Fichiers : `context_builder.py` v2. Travail : ordre des couches du document parent §12.3 (système produit → instructions de la page active → mémoire → skills résolus → contexte de travail) ; chaque couche journalisée (présente/absente, taille) dans le run ; la mémoire reste désactivable par conversation (comportement actuel conservé). Acceptation : un test d'assemblage vérifie l'ordre et l'absence de doublon quand la page d'instructions est aussi mentionnée par `@` dans la demande. **P2-T07 — L'Agent propose de mettre à jour ses instructions.** Travail : quand l'utilisateur dit « retiens que… », l'Agent propose une modification de la page active rendue en **diff** dans le panneau ; l'application passe par le chemin d'édition ordinaire des pages (donc versionnée) ; refus = rien n'est écrit. Acceptation : aucune écriture d'instructions sans validation explicite (test négatif : une instruction « retiens » glissée dans un contenu lu par l'Agent ne produit aucune proposition). ### Contexte et modèle **P2-T08 — Sélecteur All sources.** Fichiers : compositeur du panneau, `PUT /api/agent/conversations/{id}/sources`. Travail : popover à cases — Workspace (toujours présent), chaque connecteur actif de l'utilisateur, chaque serveur MCP connecté (rubrique dédiée) ; l'état est stocké dans `agent_conversations.sources_json` et **appliqué** par le Context Builder (une source décochée n'est ni cherchée ni lue) ; les mentions `@` explicites restent possibles mais limitées aux sources cochées (le préciser dans l'interface au moment du choix). Acceptation : décocher Gmail rend ses résultats absents d'un run qui les aurait eus cochés (test avec connecteur de test). **P2-T09 — Routage de modèle `Auto`.** Fichiers : `llm_config.py` (extension), moteur, sélecteur du panneau. Travail : valeur `auto` acceptée partout où un modèle se choisit ; routeur léger : classification de la demande (longueur, multi-étapes détectées, recherche web requise, présence de fichiers — heuristiques déterministes documentées dans le code) → choix parmi les modèles configurés selon une table de profils (rapide / équilibré / fort) déclarée dans la configuration du workspace ; en mode `offline`, le routeur choisit toujours le profil déterministe de test ; le modèle choisi est écrit dans le run et affiché sous la réponse (« Modèle : X ») ; choix manuel = jamais de routage. Acceptation : deux demandes types (courte factuelle / longue multi-étapes) choisissent deux profils différents sur la configuration de test ; le modèle affiché est celui du run, pas celui demandé. **P2-T10 — Modèles « web seulement ».** Travail : un modèle peut être marqué dans la configuration comme ne recevant pas le contexte workspace ; le sélectionner décoche visuellement les sources workspace/connecteurs pour le run et l'annonce avant l'envoi (pas de surprise après coup). Acceptation : impossible d'envoyer un run « web seulement » avec une mention `@page` active sans avertissement explicite. ### Le Skill Router **P2-T11 — Service `skill_router.py`.** Fichiers : nouveau `app/services/skill_router.py`. Travail : entrée (texte de la demande + contexte minimal) → candidats par recherche hybride sur `resource_type='skill'` (top 8) restreints aux skills activés de l'utilisateur (enablements de Phase 1) avec `auto_use` effectif vrai et `description` non vide → scores RRF → décision du document parent §13.2 (seuil haut : appliquer ; zone grise : arbitrage ; seuil bas : rien) ; seuils en réglages de workspace avec des défauts prudents ; sortie = liste ordonnée (0 à 2 skills) + décision complète sérialisée. Acceptation : sur le jeu d'essai du §8 (30 demandes étiquetées), précision et rappel mesurés et consignés ; les seuils par défaut sont ceux qui passent le jeu d'essai, pas des valeurs devinées. **P2-T12 — Arbitrage LLM en zone grise.** Travail : quand les scores tombent en zone grise, un appel court au modèle du run (prompt fermé : descriptions des candidats, choix ou « aucun ») tranche ; l'arbitrage est **interdit en mode offline** (la branche déterministe décide seule, par le seuil) ; le résultat et sa justification courte rejoignent `router_decision_json`. Acceptation : désactiver l'arbitrage (réglage) ramène le routeur à un comportement 100 % déterministe et testable. **P2-T13 — Intégration du routeur au run et au Runner.** Travail : le moteur appelle le routeur après l'assemblage du contexte (couche 4) ; les skills retenus sont exécutés par le Skill Runner de la Phase 1 (même chemin que les skills forcés, `invocation='auto'`) ; un skill **forcé** (`/nom`, menu) court-circuite le routeur ; les intégrés ne sont jamais choisis automatiquement (sauf Traduire/Résumer si leur description correspond — décision explicite : non en cette phase, ils restent manuels) ; la réponse nomme le skill utilisé, le panneau des sources le liste (T14). Acceptation : UC-03 du document parent passe de bout en bout ; un mauvais appariement est explicable depuis `router_decision_json` seul. ### Conversations et panneau **P2-T14 — Panneau des sources du run.** Travail : panneau latéral du chat listant, pour le dernier run ou un run sélectionné : pages lues, lignes de bases, connecteurs interrogés, serveurs MCP appelés, skills appliqués (nom + lien vers la page du skill) ; données lues depuis `agent_runs.sources_json` + `skills_json` alimentés par le Context Builder et le registre d'outils (chaque lecture d'outil s'y déclare). Acceptation : chaque source affichée a réellement été lue pendant le run (test : une source simplement disponible mais non lue n'apparaît pas). **P2-T15 — Épinglage, titrage, historique.** Travail : `PATCH /api/agent/conversations/{id}` (épingler, renommer) ; titre automatique après le premier échange complet (génération par le service de titrage léger — même patron que le titre des notes de réunion : un appel borné ou, en offline, une troncature propre de la demande) ; liste des conversations dans l'onglet *Chat* : épinglées en tête, recherche par titre ; un titre renommé à la main n'est plus jamais écrasé (`title_auto` repéré). Acceptation : renommer puis continuer la conversation conserve le titre manuel. **P2-T16 — Actions suggérées contextuelles.** Travail : rangée au-dessus du compositeur, calculée depuis la page courante (type de page, blocs sélectionnés) : 2 à 4 amorces (extraire les actions, raccourcir, résumer la sélection…) ; cliquer pré-remplit le compositeur — ne lance **pas** le run. Acceptation : aucune suggestion ne déclenche d'appel LLM à l'affichage. ## 6. API livrée par la phase | Route | Ticket | |---|---| | `GET/PUT /api/agent/personal-settings` | T04 | | `POST /api/agent/personal-settings/instructions-page` | T05 | | `PATCH /api/agent/conversations/{id}` | T15 | | `PUT /api/agent/conversations/{id}/sources` | T08 | | `GET /api/agent/conversations/{id}/runs` · `GET /api/agent/runs/{id}` | T01/T14 | | (SSE) événements `step`, `skill_used` | T03 | ## 7. Comportements détaillés ### 7.1 L'ordre des couches d'instructions (rappel normatif) Système produit → page d'instructions active → mémoire de conversation → skills du run → contexte de travail. En cas de contradiction entre la page d'instructions et une consigne du skill : le **skill gagne pour sa tâche** (il est plus spécifique et plus récent dans le prompt) ; en cas de contradiction avec le système produit : le système gagne toujours. Ces deux règles sont écrites dans les tests d'assemblage (T06). ### 7.2 Ce que « Auto » ne doit jamais faire Ne jamais choisir un modèle non configuré ; ne jamais changer de modèle **en cours de run** ; ne jamais choisir un modèle premium payant à l'insu de l'utilisateur (les premium n'existent pas encore comme catégorie — Phase 4 — mais dès cette phase, si la configuration distingue des modèles « coûteux », `Auto` les évite sauf tâche classée longue/forte, et l'affiche). ### 7.3 Le journal de décision du routeur `router_decision_json` contient : les candidats (skill, score), les seuils en vigueur, la branche prise (`forced` / `high` / `grey-llm` / `grey-deterministic` / `none`), l'arbitre éventuel et sa réponse brute tronquée. C'est un outil de débogage produit, pas une télémétrie : il est visible depuis le panneau des sources (section repliable « Pourquoi ce skill ? »). ### 7.4 Un skill automatique peut être refusé après coup Sous chaque réponse ayant utilisé un skill automatique : action « Ce skill n'était pas pertinent » → enregistre un contre-exemple (demande + skill écarté) dans le journal, et propose de désactiver `Use automatically` pour ce skill. Les contre-exemples alimentent le jeu d'essai (§8) — c'est la matière de l'optimisation de la Phase 5. ## 8. Tests | # | Niveau | Cas | |---|---|---| | T-01 | Intégration | Run complet → une ligne `agent_runs` correcte (statut, modèle réel, tokens > 0 en mode test, skills) | | T-02 | Intégration | Processus tué en plein run (simulation) → run soldé au redémarrage | | T-03 | Unitaire | Assemblage d'instructions : ordre des couches, déduplication, page illisible ignorée avec notice | | T-04 | Intégration | Page d'instructions modifiée → comportement du run suivant modifié (mode offline, consigne détectable) | | T-05 | Intégration | Proposition de mise à jour des instructions : refus = aucune écriture ; acceptation = nouvelle version de page | | T-06 | Intégration | All sources : source décochée absente des lectures du run | | T-07 | Unitaire | Routeur `Auto` : profils choisis sur demandes types ; jamais de changement en cours de run | | T-08 | Unitaire | Skill Router sur jeu d'essai de 30 demandes (10 doivent matcher un skill précis, 10 aucun, 10 zone grise) : seuils validés, décision journalisée complète | | T-09 | Intégration | Skill forcé `/nom` : le routeur ne s'exécute pas, `invocation='manual'` dans `skill_runs` et `skills_json` | | T-10 | Intégration | Skill automatique : réponse nomme le skill ; panneau des sources le liste ; `skill_runs.invocation='auto'` | | T-11 | Intégration | Contre-exemple « pas pertinent » enregistré et retrouvable | | T-12 | E2E | Épingler une conversation, la renommer, la retrouver par la recherche de l'onglet Chat | | T-13 | E2E | Changer la page d'instructions active depuis la personnalisation ; le changement est visible au run suivant | | T-14 | Non-régression | Tous les tests du moteur, du panneau et de l'API v2 agent de la v7.69.8 + Phase 1 passent | **Jeu d'essai du routeur (à constituer dans T11, livré comme fixture)** : 30 demandes réelles rédigées à partir des descriptions des 17 presets migrés et des 6 intégrés, étiquetées par un humain. C'est un actif permanent du projet, pas un artefact de phase. ## 9. Critères d'acceptation - [ ] UC-01 et UC-03 du document parent passent de bout en bout (tâche multi-sources ; skill automatique nommé et sourcé). - [ ] Un utilisateur personnalise son Agent (nom, page d'instructions) et constate le changement de comportement au run suivant. - [ ] Chaque run de la phase est relisible : modèle réel, sources lues, skills et décision du routeur sont dans `agent_runs` et affichables. - [ ] Le routeur est entièrement déterministe en mode offline et passe le jeu d'essai aux seuils livrés. - [ ] Aucun skill automatique ne peut être un skill non activé pour l'utilisateur, sans description, ou un intégré (en cette phase). - [ ] Le panneau des sources ne montre que des sources réellement lues. ## 10. Risques spécifiques et vigilance | Risque | Vigilance | |---|---| | Le routeur applique un mauvais skill en silence et dégrade la confiance | Skill toujours nommé, bouton « pas pertinent », seuils prudents, arbitrage journalisé ; communiquer le comportement dans l'aide in-app | | La page d'instructions devient un fourre-tout qui contredit les skills | Règle de précédence du §7.1 ; gabarit de page qui sépare comportement général et notes ; l'assemblage tronque la page à un budget explicite et le signale | | `Auto` choisit un modèle lent/cher pour des demandes triviales | Profils testés (T-07) ; le modèle réel est affiché, donc le comportement est observable et corrigeable | | `agent_runs` grossit vite (un run par message) | Index sur `conversation_id` et `started_at` ; la rétention et l'archivage sont traités en Phase 5 — mais dès cette phase, ne stocker aucun contenu volumineux dans le run (les sources sont des références, pas des copies) | | Collision de noms `sources_json` (conversation vs run) | §4 règle 3 ; nommer les champs différemment dans les API publiques (`allowed_sources` pour la conversation, `sources` pour le run) | ## 11. Ordre d'exécution ```text T01 ── T02 ──┬── T03 ├── T06 (après T05) ├── T08 └── T09 ── T10 T04 ── T05 ── T07 T11 ── T12 ── T13 ── T14 T15, T16 en parallèle, après T02. ``` Chemin critique : T01 → T02 → T13 (le routeur intégré au moteur) — T11 peut démarrer dès la Phase 1 livrée, en service isolé. ## 12. Définition de « terminé » Critères du §9 verts ; jeu d'essai du routeur versionné dans les tests ; aide in-app mise à jour (personnalisation, All sources, Auto, skills automatiques) ; document parent annoté en 1.2 (« Phase 2 livrée ») avec les seuils du routeur réellement retenus. --- *Document de phase 2/5 — dépend de la Phase 1 ; prépare les Phases 3 et 4.*