# 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<String, AgentSessionError>`
(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<String>` (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**.
