feat(agent): orchestration v3 — surface MCP model-agnostic (M0→M4) — §14.3
Expose l'orchestration IdeA comme serveur MCP par-dessus le même OrchestratorService::dispatch, avec repli fichier .ideai/requests pour les CLI sans MCP. v3 réduite à la surface MCP : la messagerie inter-agents et la corrélation requête↔réponse étaient déjà résolues par §17 (send_blocking). - M0 capacité MCP sur le profil (McpCapability/McpConfigStrategy/McpTransport) - M1 injection conf MCP au LaunchAgent + prose adaptée selon la surface - M2 serveur/adapter MCP (JSON-RPC 2.0 maison ; outils idea_*) + ListAgents - M3 câblage par projet (registre mcp_servers jumeau du watcher) - M4 observabilité UI : OrchestratorRequestProcessed.source = file|mcp + badge Trois portes d'entrée (fichier, MCP, UI) → un seul dispatch ; aucun nouveau port applicatif ; MCP confiné à l'adapter infra. Tous lots verts (cycle §3). Cadrage : .ideai/briefs/orchestration-v3-cadrage.md ; ARCHITECTURE.md §14.3. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
280
.ideai/briefs/orchestration-v3-cadrage.md
Normal file
280
.ideai/briefs/orchestration-v3-cadrage.md
Normal file
@ -0,0 +1,280 @@
|
||||
# Cadrage Architecture — Orchestration v3 : invocation native d'agents (surface MCP)
|
||||
|
||||
> Produit par **Architect** en réponse au brief `orchestration-v3-invocation-native.md`.
|
||||
> Cadrage **avant tout code** (méthode §3). Livrable : ce document + mise à jour `ARCHITECTURE.md` §14.3.
|
||||
> Aucun code de production ici.
|
||||
|
||||
---
|
||||
|
||||
## 0. État réel du terrain — ce qui est DÉJÀ résolu (lu dans le code, pas présumé)
|
||||
|
||||
Le brief décrit trois faiblesses (conscience = prose, pas de discussion inter-agents,
|
||||
fire-and-forget). **Deux des trois sont déjà comblées par le pivot §17** (livré, lots D0→D7).
|
||||
Il faut le constater honnêtement pour ne **pas re-cadrer** ce qui existe :
|
||||
|
||||
| Faiblesse du brief | Statut réel | Référence code |
|
||||
|---|---|---|
|
||||
| Pas de discussion inter-agents (`agent.message` « future », `task` ignoré pour agent vivant) | ✅ **RÉSOLU** | `OrchestratorCommand::AskAgent` (`domain/src/orchestrator.rs`), `OrchestratorService::ask_agent` (`application/src/orchestrator/service.rs`) |
|
||||
| Fire-and-forget, pas de corrélation requête↔réponse | ✅ **RÉSOLU sans outbox** : le rendez-vous synchrone est **intrinsèque** à `AgentSession::send()` (flux → `Final` déterministe). Le `Final` *est* la fin de tour. | `application/src/agent/structured.rs` (`send_blocking`), `domain/src/ports.rs` (`ReplyEvent::Final`) |
|
||||
| Pas de réveil du demandeur / event de réponse | ✅ **RÉSOLU** | `DomainEvent::AgentReplied`, `OrchestratorResponse.reply` (`infrastructure/src/orchestrator/mod.rs`) |
|
||||
| Conscience = soft prompt (l'agent doit deviner le schéma JSON) | ⚠️ **PARTIEL** : la prose `# Orchestration IdeA` est injectée (`compose_convention_file`), mais **aucun outil typé natif** n'est exposé. | `application/src/agent/lifecycle.rs` |
|
||||
| Interdiction des subagents natifs **+** alternative native | ⚠️ **PARTIEL** : interdiction présente (prose) ; l'alternative native (outils `idea_*`) **manque encore**. | idem |
|
||||
| Capacité MCP sur le profil | ❌ **ABSENT** | — |
|
||||
| Serveur MCP / config MCP par CLI | ❌ **ABSENT** | — |
|
||||
|
||||
**Conclusion de cadrage** : l'orchestration v3 **n'est plus** « combler la messagerie inter-agents »
|
||||
(c'est fait). Elle se réduit à **un seul chantier net** : **exposer l'orchestration IdeA comme
|
||||
serveur MCP model-agnostic**, en tant qu'**adapter entrant supplémentaire** par-dessus le **même**
|
||||
`OrchestratorService::dispatch`, avec **repli homogène** sur le protocole fichier `.ideai/requests`
|
||||
(§14.3) + prose (`compose_convention_file`) pour les CLI sans MCP. C'est ce que cadre la suite.
|
||||
|
||||
> **Principe directeur (zéro régression, §9/§17.3)** : MCP est un **confort de conscience native**
|
||||
> (outils typés, plus de schéma à deviner). Il **n'invente aucune sémantique** : tout outil MCP se
|
||||
> ramène à un `OrchestratorCommand` déjà existant. La voie principale du *retour de valeur* reste
|
||||
> §17 (`send_blocking`) ; MCP ne fait que **déclencher** `dispatch`, jamais re-router la réponse.
|
||||
|
||||
---
|
||||
|
||||
## 1. Décisions tranchées (les 4 points durs du brief)
|
||||
|
||||
### Décision 1 — Capacité MCP par runtime = champ optionnel `mcp` sur `AgentProfile` (Open/Closed)
|
||||
|
||||
**Tranché** : on ajoute un champ **optionnel** `mcp: Option<McpCapability>` sur `AgentProfile`,
|
||||
exactement comme `session: Option<SessionStrategy>` et `structured_adapter: Option<StructuredAdapter>`
|
||||
le sont déjà. `None` (défaut) ⇒ **repli fichier + prose** (comportement actuel, zéro régression).
|
||||
`Some(_)` ⇒ IdeA matérialise la config MCP de cette CLI au lancement et l'agent voit les outils `idea_*`.
|
||||
|
||||
```rust
|
||||
// domain/src/profile.rs — capacité MCP déclarative (pur, validé par constructeur, comme SessionStrategy)
|
||||
|
||||
/// Stratégie de matérialisation de la config MCP propre à UNE CLI : chaque CLI
|
||||
/// déclare son serveur MCP différemment (fichier `.mcp.json` pour Claude Code,
|
||||
/// flag de lancement, ou variable d'env). Déclaratif = donnée, pas code (§9).
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", tag = "strategy")]
|
||||
pub enum McpConfigStrategy {
|
||||
/// Écrire un fichier de conf MCP au chemin (relatif au run dir isolé §14.1)
|
||||
/// attendu par la CLI, au format JSON propre à cette CLI (ex. `.mcp.json`).
|
||||
ConfigFile { target: String }, // relative_safe(target) — pas de `..`, pas d'absolu
|
||||
/// Passer le serveur via un flag de lancement (ex. `--mcp-config {path}`).
|
||||
Flag { flag: String }, // non_empty(flag)
|
||||
/// Passer via une variable d'environnement.
|
||||
Env { var: String }, // valid_env_var(var)
|
||||
}
|
||||
|
||||
/// Capacité MCP d'un profil : COMMENT déclarer le serveur MCP IdeA à cette CLI,
|
||||
/// et QUEL transport. `None` sur le profil ⇒ repli fichier `.ideai/requests` + prose.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct McpCapability {
|
||||
/// Comment matérialiser la config MCP au lancement (relatif au run dir).
|
||||
pub config: McpConfigStrategy,
|
||||
/// Transport du serveur MCP IdeA (détail invisible au domaine ; voir D3).
|
||||
/// `stdio` = défaut robuste cross-OS ; `socket` = optimisation (point ouvert).
|
||||
#[serde(default)]
|
||||
pub transport: McpTransport,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum McpTransport { #[default] Stdio, Socket }
|
||||
```
|
||||
|
||||
Sur `AgentProfile`, additif et **non cassant** (sérialisation inchangée pour les profils sans MCP) :
|
||||
```rust
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub mcp: Option<McpCapability>,
|
||||
```
|
||||
Builder additif (comme `with_structured_adapter`) : `AgentProfile::new(...).with_mcp(cap)` ; la
|
||||
signature de `AgentProfile::new` reste **inchangée** ⇒ tous les appels du catalogue/tests restent verts.
|
||||
|
||||
**Justification** : cohérence §9 (« ajouter une IA = donnée, pas code »), symétrie avec les deux autres
|
||||
capacités optionnelles déjà sur le profil, `skip_serializing_if = None` ⇒ **zéro régression** de
|
||||
sérialisation. Le **prédicat de surface** est `profile.mcp.is_some()` — un seul point de vérité.
|
||||
|
||||
> **Modèle en couches (exigé par le brief §4.1)** : la surface effective d'un agent est
|
||||
> `surface(agent) = if profile.mcp.is_some() { Mcp } else { FileProtocol }`. Les deux couches
|
||||
> produisent le **même** `OrchestratorCommand`. Aucun agent n'est jamais bloqué : sans MCP, la prose
|
||||
> `# Orchestration IdeA` + `.ideai/requests` reste pleinement fonctionnelle.
|
||||
|
||||
---
|
||||
|
||||
### Décision 2 — Retour synchrone d'`ask` = AUCUN nouveau modèle de corrélation : on réutilise `send_blocking`
|
||||
|
||||
**Tranché (et c'est la décision la plus importante)** : l'outil MCP `idea_ask_agent` **ne crée
|
||||
aucune corrélation requête↔réponse, aucun outbox, aucun event de réveil neufs**. Il appelle le
|
||||
**même** `OrchestratorService::dispatch(AskAgent { target, task })` que le watcher fichier, qui
|
||||
**retourne déjà** `OrchestratorOutcome { reply: Some(content) }` via `send_blocking`. L'adapter MCP
|
||||
**renvoie ce `content` inline** comme valeur de retour de l'outil. Fin.
|
||||
|
||||
Le brief (rédigé avant le pivot §17) supposait qu'`ask` était un point dur à résoudre via outbox +
|
||||
corrélation fichier. **Le pivot §17 l'a déjà tranché autrement** : le `Final` du `ReplyStream` *est*
|
||||
la fin de tour déterministe ; pas besoin de deviner, pas d'outbox, pas de `request_id`. On **n'y
|
||||
revient pas**. Le tableau ci-dessous fige la sémantique, déjà implémentée :
|
||||
|
||||
| Aspect | Décision (déjà en place) | Code |
|
||||
|---|---|---|
|
||||
| **Corrélation** | Aucune : `dispatch` est un appel synchrone `async` ; la réponse est la valeur de retour. Le transport MCP (JSON-RPC) porte nativement la corrélation requête/réponse. | `service.rs::ask_agent` |
|
||||
| **Outbox** | **Supprimé de la voie principale** (§17.4). Pas réintroduit. | — |
|
||||
| **Event `AgentReplied`** | **Observabilité UI uniquement** (« Architect a répondu à Main »), best-effort, ne porte pas la valeur. | `reply_outcome` |
|
||||
| **Timeout** | Borné (`ASK_AGENT_TIMEOUT = 300 s`). À l'expiration : `AgentSessionError::Timeout` → la cible **reste vivante** (non tuée), l'outil MCP renvoie une **erreur typée** ; l'appelant décide. | `send_blocking` |
|
||||
| **Cible a déjà une session vivante** (one-live-session-per-agent) | `ask` **réutilise** la session structurée vivante (`session_for_agent`) — rendez-vous direct, pas de respawn. Si la cible est vivante en **PTY brut** (profil sans `structured_adapter`) ⇒ erreur typée explicite (**jamais** un ACK trompeur). | `service.rs::ask_agent` étapes 1→3 |
|
||||
| **Cible morte** | `LaunchAgent` en mode structuré (background) puis `send_blocking`. Garde d'unicité sur **les deux** registres. | idem |
|
||||
|
||||
**Justification** : DRY radical (une seule logique de rendez-vous, partagée par UI chat, watcher
|
||||
fichier et MCP) ; frontière nette (le domaine ne connaît qu'un `prompt` et un `Final`, jamais un
|
||||
transcript ni un id de corrélation) ; universalité (marche pour toute CLI structurée Claude/Codex).
|
||||
**Le seul travail v3 ici est de brancher l'outil MCP sur `dispatch` — pas de re-cadrer le rendez-vous.**
|
||||
|
||||
> **Conséquence produit** : `idea_ask_agent` cible **toujours un agent structuré** (Claude/Codex),
|
||||
> cohérent avec le menu restreint §17.3/§17.6. Un agent **demandeur** peut être n'importe quelle CLI
|
||||
> MCP (Claude, Codex, Gemini…) ; un agent **cible** d'un `ask` doit être structuré. C'est déjà
|
||||
> l'invariant en vigueur — MCP ne le change pas.
|
||||
|
||||
---
|
||||
|
||||
### Décision 3 — MCP vs subagents natifs : on garde l'interdiction ET on offre l'alternative native, config injectée par CLI au lancement
|
||||
|
||||
**Tranché** : l'interdiction des subagents natifs (prose `# Orchestration IdeA`) **reste** — elle
|
||||
protège l'identité/mémoire/observabilité IdeA. Mais on offre désormais la **vraie alternative
|
||||
native** : les outils `idea_*` apparaissent dans la liste d'outils de la CLI. La prose est **adaptée
|
||||
selon la surface** :
|
||||
- agent **MCP** (`profile.mcp.is_some()`) : la prose pointe vers les outils `idea_ask_agent` /
|
||||
`idea_launch_agent` / `idea_list_agents` (au lieu d'« écris un JSON dans `.ideai/requests` »).
|
||||
- agent **fichier** (`mcp == None`) : prose `.ideai/requests` actuelle, **inchangée**.
|
||||
|
||||
**Injection de la config MCP par CLI = au `LaunchAgent`, dans le run dir isolé (§14.1), via la
|
||||
`McpConfigStrategy`** — exactement le même point et la même mécanique que le convention file :
|
||||
```
|
||||
LaunchAgent::execute (après apply_injection, avant spawn/factory.start) :
|
||||
if let Some(mcp) = &profile.mcp:
|
||||
// IdeA matérialise SA config MCP au format de CETTE CLI dans <run_dir>/...
|
||||
apply_mcp_config(mcp, &run_dir, &spec) // ConfigFile→write ; Flag→spec.args ; Env→spec.env
|
||||
```
|
||||
- `ConfigFile { target }` : écrit `<run_dir>/<target>` (ex. `.mcp.json`) avec la déclaration du
|
||||
serveur MCP IdeA (commande/transport). Non-clobbering, best-effort, **comme le seed de permissions**.
|
||||
- `Flag { flag }` : ajoute le flag + chemin au `SpawnSpec.args`.
|
||||
- `Env { var }` : ajoute la variable au `SpawnSpec.env`.
|
||||
|
||||
Le **serveur MCP lui-même** est démarré **par projet ouvert**, à côté du `FsOrchestratorWatcher`,
|
||||
dans le **même hook** `ensure_orchestrator_watch` (`app-tauri/src/state.rs`). Une CLI qui se lance
|
||||
avec la config injectée se connecte à ce serveur (stdio : IdeA spawn un pont par session ; socket :
|
||||
adresse partagée — détail d'adapter, point ouvert S-MCP).
|
||||
|
||||
**Justification** : symétrie totale avec le convention file et le seed de permissions (même run dir,
|
||||
même best-effort non-clobbering, même moment) ⇒ aucune nouvelle plomberie de cycle de vie. La config
|
||||
MCP est **donnée déclarative par profil**, donc « ajouter une CLI MCP = donnée, pas code ».
|
||||
|
||||
---
|
||||
|
||||
### Décision 4 — Frontières hexagonales : le serveur MCP est un adapter entrant d'infrastructure ; AUCUN nouveau port applicatif
|
||||
|
||||
**Tranché** : le serveur MCP est un **driving adapter d'infrastructure**
|
||||
(`infrastructure/src/orchestrator/mcp/`), **pair** du `FsOrchestratorWatcher`. Il appelle le **même**
|
||||
`OrchestratorService::dispatch` (application) et **ne duplique rien**. Trois portes d'entrée
|
||||
substituables se ramènent au même `OrchestratorCommand` :
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
Agent MCP ───▶│ Serveur MCP (infra/orchestrator/mcp) │──┐
|
||||
└─────────────────────────────────────────────┘ │
|
||||
┌─────────────────────────────────────────────┐ │ OrchestratorCommand
|
||||
Fichier ───▶│ FsOrchestratorWatcher (infra/orchestrator) │──┼──▶ OrchestratorService::dispatch
|
||||
(.ideai/ └─────────────────────────────────────────────┘ │ (application — INCHANGÉ)
|
||||
requests) ┌─────────────────────────────────────────────┐ │ │
|
||||
UI ───▶│ Commandes Tauri (app-tauri) │──┘ ▼
|
||||
└─────────────────────────────────────────────┘ use cases agent/terminal
|
||||
```
|
||||
|
||||
- **Où vit le serveur MCP** : `infrastructure/src/orchestrator/mcp/`. Il **traduit** un appel d'outil
|
||||
MCP (`idea_ask_agent`, `idea_launch_agent`, `idea_list_agents`, et par parité `idea_update_context`,
|
||||
`idea_create_skill`, `idea_stop_agent`) en `OrchestratorCommand`, appelle `dispatch`, et renvoie
|
||||
`OrchestratorOutcome` (`reply`/`detail`) inline comme résultat d'outil. JSON-RPC, stdio/socket,
|
||||
le crate MCP : **tout reste dans cet adapter**. Le domaine/application ignorent MCP.
|
||||
- **Quel port côté domaine/application** : **aucun nouveau**. `OrchestratorService::dispatch`
|
||||
(application) est déjà l'unique seam. `idea_list_agents` réutilise `ListAgents`. La validation
|
||||
(`OrchestratorRequest::validate`) reste le point unique « parse, don't validate » — l'adapter MCP
|
||||
construit un `OrchestratorCommand` (directement, ou via `OrchestratorRequest` pour réutiliser la
|
||||
validation, au choix d'implémentation).
|
||||
- **Réutilisation de `OrchestratorService` plutôt que duplication** : le serveur MCP reçoit
|
||||
`Arc<OrchestratorService>` au composition root (`state.rs`), exactement comme le watcher. Une seule
|
||||
logique applicative ; les adapters ne portent que leur techno d'entrée.
|
||||
|
||||
**Justification** : DRY + règle de dépendance hexagonale. Cible, identité, mémoire, observabilité UI
|
||||
passent **toujours** par le seul chemin applicatif. Les spikes MCP (transport, crate) sont **confinés**
|
||||
à l'adapter infra et ne touchent ni le domaine ni l'application.
|
||||
|
||||
---
|
||||
|
||||
## 2. Modèle de messages corrélés — état figé (rien de neuf)
|
||||
|
||||
La « corrélation requête↔réponse » du brief est portée **nativement par le transport** :
|
||||
- **MCP** : JSON-RPC corrèle requête/réponse par `id` de message ⇒ rien à modéliser côté IdeA.
|
||||
- **Fichier** : `<file>.json` → `<file>.json.response.json` (sibling), déjà en place.
|
||||
- **Valeur de retour** : `OrchestratorOutcome { detail, reply }` (application) → `OrchestratorResponse
|
||||
{ ok, action, detail, error, reply }` (infra fichier) **ou** résultat d'outil MCP. **Structs déjà
|
||||
définies**, réutilisées telles quelles.
|
||||
|
||||
Aucun `CorrelationId`, aucun `AgentReply`, aucun port `AgentReplyChannel`, aucun outbox : **abandonnés
|
||||
par le pivot §17** et **non réintroduits** par v3. C'est la simplification clé.
|
||||
|
||||
---
|
||||
|
||||
## 3. Découpage en LOTS testables (méthode §3) — MCP uniquement
|
||||
|
||||
> Chaque lot = binôme dev+test, vert avant le suivant. Backend/frontend séparés.
|
||||
> Les terminaux non-IA et le chemin fichier `.ideai/requests` restent verts à chaque lot.
|
||||
> **Spike S-MCP** (crate MCP Rust + transport stdio/socket + format de conf par CLI) est **confiné au
|
||||
> lot M2** et n'invalide pas l'ossature (le contrat d'entrée reste `OrchestratorCommand`).
|
||||
|
||||
| Lot | Côté | Périmètre | Crates/dossiers | Contrats | Tests attendus |
|
||||
|---|---|---|---|---|---|
|
||||
| **M0 (capacité profil)** | back | `McpCapability` + `McpConfigStrategy` + `McpTransport` (domaine, validés) ; champ `AgentProfile.mcp: Option<McpCapability>` (+ builder `with_mcp`, `new` inchangé) ; catalogue Claude/Codex annotés (ex. `ConfigFile { target: ".mcp.json" }`). | `domain/src/profile.rs`, `application/src/agent/catalogue.rs` | enum + struct + champ optionnel sérialisé | unit purs : `mcp = None` round-trip **identique à avant** (zéro régression sérialisation) ; `Some(_)` round-trip ; constructeurs valident (`relative_safe` target, `non_empty` flag, `valid_env_var`) ; catalogue annoté. |
|
||||
| **M1 (injection conf MCP au lancement)** | back | `LaunchAgent` matérialise la conf MCP selon `McpConfigStrategy` dans le run dir isolé (après `apply_injection`, avant spawn/`factory.start`) : `ConfigFile`→write non-clobbering, `Flag`→`args`, `Env`→`env`. Prose `compose_convention_file` adaptée selon `mcp.is_some()`. | `application/src/agent/lifecycle.rs` | `LaunchAgent` (chemin MCP additif) | unit (fakes) : profil `mcp=None` ⇒ **aucun** write/flag/env MCP (chemin actuel inchangé) ; `ConfigFile` ⇒ fichier écrit au bon chemin, non-clobbering ; `Flag`/`Env` ⇒ `spec` enrichi ; prose contient les outils `idea_*` si MCP, sinon `.ideai/requests`. |
|
||||
| **M2 (serveur/adapter MCP)** | back | `infrastructure/src/orchestrator/mcp/` : serveur MCP exposant `idea_ask_agent`/`idea_launch_agent`/`idea_list_agents` (+ parité `idea_update_context`/`idea_create_skill`/`idea_stop_agent`) → `OrchestratorCommand` → `dispatch` → résultat inline. **Spike S-MCP** (crate, transport) isolé ici. | `infrastructure/src/orchestrator/mcp/` | mapping outil→commande ; `Arc<OrchestratorService>` injecté | unit (fakes + `OrchestratorService` à use cases fakes) : chaque outil mappe la bonne commande ; `idea_ask_agent` renvoie `reply` inline ; timeout → erreur typée, cible non tuée ; `idea_list_agents` liste ; JSON-RPC malformé → erreur, jamais panic. Hors-réseau (transport en mémoire/pipe scriptable). |
|
||||
| **M3 (câblage par projet)** | back | Démarrer le serveur MCP par projet ouvert dans `ensure_orchestrator_watch` (à côté du watcher) ; registre `mcp_servers` jumeau de `orchestrator_watchers` ; arrêt à la fermeture du projet. | `app-tauri/src/state.rs`, `commands.rs` | hook `ensure_orchestrator_watch` étendu | app-tauri : un serveur MCP par projet, idempotent ; fermeture du projet ⇒ arrêt ; coexiste avec le watcher fichier (les deux portes vivantes). |
|
||||
| **M4 (observabilité UI — optionnel)** | front | Surfacer dans l'UI Agents qu'une délégation est passée par MCP vs fichier (badge/source sur l'event `OrchestratorRequestProcessed` / `AgentReplied`). Non bloquant. | `frontend/src/features/agents` | DTO d'event enrichi (`source: "mcp"|"file"`) | Vitest : badge source affiché ; absence d'event ⇒ pas de régression. |
|
||||
|
||||
**Ordre conseillé** : **M0 → M1 → M2 → M3** (→ M4 optionnel). M0 débloque tout (donnée pure) ;
|
||||
M1 injecte la conf (testable sans serveur) ; M2 livre l'adapter derrière un transport scriptable
|
||||
(spike confiné) ; M3 le câble par projet. M4 est du confort d'observabilité.
|
||||
|
||||
---
|
||||
|
||||
## 4. Conformité hexagonale & SOLID (rappel)
|
||||
|
||||
- **Règle de dépendance** : `McpCapability`/`McpConfigStrategy` sont **domaine** (purs, validés).
|
||||
Le serveur MCP, JSON-RPC, stdio/socket, le crate MCP sont **exclusivement** infra. Le domaine et
|
||||
l'application **ignorent** MCP (l'application ne voit que `OrchestratorCommand`/`dispatch`).
|
||||
- **S** : le serveur MCP = une seule techno d'entrée (MCP→commande). `OrchestratorService` garde sa
|
||||
responsabilité (commande→use cases). `LaunchAgent` gagne une étape d'injection homogène, pas une
|
||||
responsabilité nouvelle.
|
||||
- **O** : ajouter une CLI MCP = un bloc `mcp` sur le profil (**donnée**). Aucun cœur touché.
|
||||
- **L** : les trois portes d'entrée (fichier, MCP, UI) sont substituables — même `dispatch`, même
|
||||
résultat. Repli fichier ≡ MCP du point de vue de la réponse.
|
||||
- **I** : le serveur MCP ne reçoit que `Arc<OrchestratorService>` (pas les use cases en détail).
|
||||
- **D** : tout injecté au composition root (`state.rs`) ; aucun `new` d'adapter MCP ailleurs.
|
||||
|
||||
---
|
||||
|
||||
## 5. Chantiers adjacents (situés, NON cadrés ici)
|
||||
|
||||
- **Hot-swap de l'AI profile** (chantier A, §15.1) : **LIVRÉ** (`ChangeAgentProfile`). Interaction
|
||||
avec v3 : un swap vers/depuis un profil MCP change la surface (`mcp.is_some()`) ⇒ au relance,
|
||||
`LaunchAgent` (ré)injecte ou retire la conf MCP automatiquement. **Rien à cadrer** : la surface suit
|
||||
le profil courant, point de vérité unique.
|
||||
- **Reprise auto des sessions au redémarrage** (chantier B, §15.2) : **LIVRÉ** (`ListResumableAgents`,
|
||||
`conversation_id` persisté sur la cellule). Interaction avec v3 : à la reprise, `LaunchAgent`
|
||||
ré-matérialise la conf MCP comme à tout lancement (M1). **Rien à cadrer**.
|
||||
|
||||
Ces deux chantiers **ne sont pas un prérequis** de v3/MCP et n'en bloquent aucun lot.
|
||||
|
||||
---
|
||||
|
||||
## 6. Synthèse des décisions
|
||||
|
||||
1. **Capacité MCP = `Option<McpCapability>` sur le profil** (Open/Closed, `None` ⇒ repli fichier+prose, zéro régression sérialisation).
|
||||
2. **Retour synchrone d'`ask` : RIEN de neuf** — réutilise `send_blocking`/`AskAgent`/`AgentReplied`/`OrchestratorOutcome.reply` déjà livrés (§17). Pas d'outbox, pas de corrélation fichier, pas de `CorrelationId`. Le transport MCP corrèle nativement.
|
||||
3. **Interdiction subagents natifs conservée + alternative native** : prose adaptée selon surface ; conf MCP injectée **par CLI** au `LaunchAgent` dans le run dir isolé (`McpConfigStrategy` : ConfigFile/Flag/Env), symétrique au convention file et au seed permissions.
|
||||
4. **Frontières** : serveur MCP = **adapter entrant infra** (`infrastructure/src/orchestrator/mcp/`), pair du watcher fichier, appelant le **même** `OrchestratorService::dispatch`. **Aucun nouveau port** applicatif/domaine.
|
||||
5. **Lots** : M0 (capacité profil) → M1 (injection conf) → M2 (adapter/serveur MCP, spike confiné) → M3 (câblage par projet) → M4 (observabilité, optionnel).
|
||||
Reference in New Issue
Block a user