Files
flowdeck/docs/agents-skills-phase-2-agent-personnel-et-routeur-skills.md
T
bruno fc8548194a
FlowDeck CI / lint (push) Failing after 1m32s
FlowDeck CI / test (push) Failing after 27m50s
FlowDeck CI / docker (push) Skipped
feat(templates): refonte complète des templates façon Notion + vues/agents-skills
Templates (v7.71.x) :

- registre unifié \	emplates\ (migrations 48-49) + TemplateService.instantiate unique (UI, API v2, agent, scheduler)

- sélecteur (pilule page vide, menu •••, commande /template), gestionnaire /templates, menu New ▾, From template, base inline dans un document

- 141 presets système (59 pages, 42 bases, 15 blocs, 25 lignes), titre auto depuis le template, variables title réservée

- récurrences RRULE + scheduler dédupliqué, agent apply_template/list_templates, API /api/templates + /api/v2/fd-templates

- correctifs : bouton Templates, centrage fenêtre, filtres CSP, flux de création, variable title

- tests : tests/test_fd_templates.py (19) et e2e/templates_picker.spec.js (8)

Inclut le travail déjà présent dans le working tree (vues Notion : view_query/view_aggregate/form_projection/geocoding, property_types, database_table, docs agents-skills) et ignore .playwright-mcp/.
2026-10-10 18:52:19 -04:00

26 KiB
Raw Blame History

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
  2. Périmètre
  3. État d'entrée
  4. Modèle de données — migration 40
  5. Tickets de travail
  6. API livrée par la phase
  7. Comportements détaillés
  8. Tests
  9. Critères d'acceptation
  10. Risques spécifiques et vigilance
  11. Ordre d'exécution
  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

-- 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

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.