# Brief Dev — Lot D6 : messagerie inter-agents via `send_blocking` (§17.9) > Demandé par **Main** à **DevBackend** (dev) + **QA** (test). Cycle §3 : code → tests → vert. > Périmètre **backend** (domaine + application). Pas de frontend. ## 0. Le trou que D6 comble (important) Aujourd'hui, « Main demande à Architect » ne **retourne jamais le contenu** de la réponse : - `OrchestratorCommand` n'a **pas** de variante « demander/attendre une réponse ». Le wire `agent.run` replie `task` dans `context`, et `context` n'est utilisé que pour un agent **neuf** (`.md` initial) ⇒ pour un agent **déjà existant**, le `task` est **silencieusement ignoré**. - La réponse (`*.response.json`) ne porte qu'un **ACK de cycle de vie** (`detail: "launched agent X"`), jamais la sortie produite par la cible. D6 = brancher la **messagerie synchrone** sur le port `AgentSession` : la cible est pilotée en mode structuré, on **attend son tour** et on **renvoie son contenu**. La primitive existe déjà : `crate::agent::structured::send_blocking(session, prompt, timeout) -> Result` (livrée en D1 ; retourne le contenu du `Final`, `Timeout` typé **sans tuer la session**). ## 1. Périmètre D6 (réf. tableau §17.9 ligne D6) ### A. Domaine (`crates/domain/src/`) 1. **`OrchestratorCommand::AskAgent { target: String, task: String }`** (`orchestrator.rs`, enum ~l.111). Nouvelle variante « j'attends une réponse ». 2. **Validation** : nouvelle action wire **`agent.message`** → `AskAgent` dans `OrchestratorRequest::validate` (~l.175). `targetAgent` (ou `name`) **et** `task` requis non-vides (sinon `OrchestratorError::MissingField`). Garde le mapping `agent.run` actuel inchangé (lancement fire-and-forget). Ajoute un cas de test de round-trip JSON `agent.message`. 3. **`DomainEvent::AgentReplied { ... }`** (`events.rs`, à côté de `AgentLaunched`) pour l'observabilité : au minimum le nom/id de l'agent cible et, si pertinent, la taille/preview de la réponse (reste pur, pas de payload lourd imposé — calque le style des variantes voisines). ### B. Application (`crates/application/src/orchestrator/service.rs`) 4. **`OrchestratorOutcome` gagne le contenu** : ajoute `reply: Option` (l'ACK actuel `detail` reste). Les commandes existantes mettent `reply: None` ; `AskAgent` met `reply: Some(contenu)`. (Champ additif ⇒ aucune régression des call sites/tests existants.) 5. **`dispatch` route `AskAgent`** : - résous l'agent cible par nom (`find_agent_id_by_name`) → sinon `AppError::NotFound` typé. - cherche sa **session structurée vivante** dans le registre **`StructuredSessions`** (PAS `TerminalSessions`). Si vivante ⇒ `send_blocking(session, &task, timeout)`. - si **pas vivante** ⇒ lance l'agent en mode structuré (via `LaunchAgent`, background) puis `send_blocking`. Respecte l'invariant **1 session/agent**. - si la cible est **PTY-only** (profil sans `structured_adapter` ⇒ pas adressable en `ask`) ⇒ **erreur typée explicite** (`AppError::Invalid`/`NotFound` avec message clair « agent X n'est pas pilotable en mode structuré »). C'est acceptable (le menu ne crée plus que des agents structurés, cf. D7 à venir). - **timeout** ⇒ remonte une erreur typée, **sans tuer la session** (déjà la sémantique de `send_blocking`). - en cas de succès ⇒ **publie `DomainEvent::AgentReplied`** sur l'`EventBus`, et retourne `OrchestratorOutcome { detail, reply: Some(content) }`. 6. **Injection** : le service a besoin du registre `StructuredSessions` + de l'`EventBus` (et de quoi piloter `send_blocking`). **Ajoute-les par builder additif** (`with_structured(...)` / `with_events(...)` façon D3 sur `LaunchAgent`) pour que `OrchestratorService::new` reste compatible et que les tests/call sites legacy restent verts. Câble au composition root (`crates/app-tauri/src/state.rs`). ### C. Nettoyage voie principale 7. **Aucun accès outbox/inbox** dans le chemin `AskAgent` (le rendez-vous est intrinsèque à `send_blocking`). Les seules occurrences `outbox/inbox` actuelles sont des commentaires de doc dans `agent/structured.rs` — ne ré-introduis rien. Vérifie qu'aucun `AgentReplyChannel`/outbox n'est utilisé. ## 2. Invariants à respecter - **1 session vivante par agent** across registres (PTY + structuré). - **Timeout ne tue jamais la session** (retry possible). - **Cible PTY non adressable** par `ask` ⇒ erreur typée, jamais un ACK trompeur ni un panic. - Frontières hexagonales : le domaine reste pur (pas d'I/O dans `orchestrator.rs`/`events.rs`) ; l'orchestration vit dans l'application ; aucun `new` d'adapter infra dans le service. - **Zéro régression** : `agent.run`/`spawn_agent`/`stop_agent`/`update_agent_context`/`skill.create` inchangés ; le watcher d'orchestration et ses tests restent verts. ## 3. Tests attendus (QA — colonne « Tests attendus » D6 du §17.9) Unitaires avec **fakes** (fake `AgentSession`, fake registres, fake EventBus — aucun vrai CLI) : - cible **vivante** (session structurée enregistrée) ⇒ `send_blocking` appelé, `reply: Some(...)` porte le contenu du `Final`. - cible **morte** ⇒ `LaunchAgent` invoqué (structuré) **puis** `send` ; `reply` renvoyé. - **timeout** ⇒ erreur typée remontée, **session non tuée** (le fake atteste qu'aucun `shutdown` n'a été appelé), pas de `reply`. - **`AgentReplied`** publié sur l'EventBus en cas de succès. - cible **PTY-only** (profil sans adapter / présente seulement dans `TerminalSessions`) ⇒ erreur typée explicite, **pas** d'ACK « launched ». - **validation** : `agent.message` sans `task` ⇒ `MissingField` ; round-trip JSON `agent.message`. - **non-régression** : `agent.run` ne change pas de comportement ; aucun accès outbox. - garde anti-always-green sur au moins l'invariant timeout-ne-tue-pas OU ask-retourne-le-contenu. ## 4. Méthode DevBackend code → vérifie `cargo build -p domain -p application -p app-tauri`. **N'exécute pas `cargo test --workspace`** si un build concurrent tient le lock (sinon, lance-le). QA écrit + exécute les tests, produit un rapport clair si rouge. **Ne pas committer, ne pas push** — Main relit et commit. ## 5. Références - Domaine : `crates/domain/src/orchestrator.rs` (enum `OrchestratorCommand` ~l.111, `validate` ~l.175), `crates/domain/src/events.rs` (`DomainEvent`). - Application : `crates/application/src/orchestrator/service.rs` (struct/deps ~l.37, `dispatch` ~l.88, `OrchestratorOutcome` ~l.50, `spawn_agent` comme modèle de résolution d'agent). - Primitive : `crates/application/src/agent/structured.rs` (`send_blocking`). - Registre structuré : `StructuredSessions` (livré D1, jumeau de `TerminalSessions`) — repère-le et lis son API avant de t'en servir. - Composition root : `crates/app-tauri/src/state.rs`. - Spec : `ARCHITECTURE.md` §17.4 (réconciliation §16) et tableau §17.9 ligne **D6**.