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:
@ -633,7 +633,7 @@ IdeA/
|
||||
| L13 | **OrchestratorApi** | File-watcher `.ideai/requests/`, port `OrchestratorApi`, adapter `FsOrchestratorAdapter`, protocole `agent.run`/`agent.stop`/`agent.attach`/`agent.detach`/`agent.message`, sessions agent visibles ou arrière-plan. | `domain/agent`, `application/agent`, `infrastructure/orchestrator`, `app-tauri`, `frontend/features/agents` |
|
||||
| L14 | **Mémoire** | Base de connaissance projet model-agnostic. Modèle 2 étages : `.md` source de vérité (port `MemoryStore`), rappel adaptatif (port `MemoryRecall`), embeddings déclaratifs (port `Embedder`), bascule auto sur seuil. Découpé en sous-lots **A/B/C** (voir §14.5). | `domain/memory`, `application/memory`, `infrastructure/store`, `frontend/features/memory` |
|
||||
| L15 | **Agent = entité à session persistante** | Hot-swap de l'AI profile d'un agent existant (chantier A) + reprise des sessions au redémarrage d'IdeA / réouverture projet (chantier B). Fondation commune « l'agent porte un cycle de vie de session ». Découpé en sous-lots **A0/A1/A2 + B0/B1/B2** (voir §15). | `domain/agent`, `application/agent`, `application/layout`, `infrastructure/store`, `app-tauri`, `frontend/features/agents`, `frontend/features/layout` |
|
||||
| L16 | **Orchestration v3 — invocation native (CLI `idea` universelle + MCP optionnel)** | **Voie principale (cross-model)** : binaire `idea` (`ask` synchrone, `launch`, `reply`, `list-agents`, `next`) + skill built-in « Orchestration IdeA » auto-assigné, branchés sur le **même** `OrchestratorService` ; messagerie inter-agents synchrone via port `AgentReplyChannel` + outbox `.ideai/outbox/` ; tâche à un agent vivant via **inbox** `.ideai/inbox/`. **Optionnel/postérieur** : surface MCP (`idea_*`) + capacité `mcp` déclarative sur le profil, par-dessus le même backend. Découpé en bloc **universel (C0, C1, C-univ-1/2)** puis **MCP (C-mcp-0/1, C4)** — voir §16. | `crates/idea-cli`, `domain/{orchestrator,skill,events,ports,profile}`, `application/orchestrator`, `infrastructure/orchestrator` (+`mcp` optionnel), `app-tauri`, `frontend/features/agents` |
|
||||
| L16 | **Orchestration v3 — invocation native** | **Voie principale = §17 (LIVRÉE)** : messagerie inter-agents synchrone intrinsèque à `AgentSession::send_blocking` (`AskAgent`, `AgentReplied`), **sans** binaire `idea`/outbox/inbox/`AgentReplyChannel` (abandonnés). **Reste à livrer = surface MCP optionnelle** (§14.3.1) : capacité `mcp` déclarative sur le profil + adapter entrant d'infra (outils `idea_*`) par-dessus le **même** `OrchestratorService::dispatch`, repli homogène fichier+prose sinon. Lots **M0→M3** (+M4 optionnel) — voir §14.3.1 et `.ideai/briefs/orchestration-v3-cadrage.md`. | `domain/profile`, `application/agent`, `infrastructure/orchestrator/mcp`, `app-tauri` |
|
||||
|
||||
---
|
||||
|
||||
@ -740,7 +740,35 @@ Exemple `skill.create` :
|
||||
|
||||
**Impact UI/UX** : le layout n'est pas la source de vérité du travail agent. La grille affiche des vues attachées aux sessions. L'UI doit exposer un registre des sessions visibles et arrière-plan, avec actions ouvrir dans une cellule, détacher, déplacer, arrêter, et afficher la relation `requestedBy`/`targetAgent` quand elle existe.
|
||||
|
||||
> **Évolution v3 (§16)** : ce protocole fichier reste le **contrat partagé**. L'**orchestration v3** en fait la **voie principale et universelle** via un **binaire `idea`** (client mince sur le PATH de l'agent) + un **skill built-in** auto-assigné, qui écrivent ces fichiers à la place de l'agent — et comble le manque de §14.3 : la **messagerie inter-agents synchrone** (`agent.ask` : transmet une tâche et **renvoie la réponse de contenu** inline via l'outbox, alors qu'ici `task` est ignoré pour un agent déjà vivant et la réponse n'est qu'un ACK). Une tâche à un agent **déjà vivant** passe par une **inbox** qu'il relit (pas un write dans son TUI). Une **surface MCP** (outils `idea_*`) est ajoutée **optionnellement** par-dessus le même `OrchestratorService`. Voir §16.
|
||||
> **Évolution v3 — état réel (révisé 2026-06-10, cf. `.ideai/briefs/orchestration-v3-cadrage.md`)** : ce protocole fichier reste le **contrat partagé / repli universel**. Le manque historique de §14.3 — la **messagerie inter-agents synchrone** (`task` ignoré pour un agent vivant, réponse = simple ACK) — est désormais **comblé par §17** (pivot livré) : `OrchestratorCommand::AskAgent` + `OrchestratorService::ask_agent` transmettent la tâche et **renvoient la réponse de contenu inline** via `AgentSession::send_blocking` (le `Final` du flux *est* la fin de tour déterministe), event `AgentReplied` pour l'observabilité, **sans outbox ni corrélation fichier** (abandonnés). La cible réutilise sa session structurée vivante (invariant « 1 session/agent » §17.4) ; un timeout laisse la cible vivante (erreur typée).
|
||||
>
|
||||
> **Ce qui reste pour v3 = la seule surface MCP optionnelle** (§14.3.1) : un **adapter entrant d'infrastructure** (outils typés `idea_*`) qui appelle le **même** `OrchestratorService::dispatch`, en repli homogène sur ce protocole fichier + prose quand le profil ne déclare pas MCP. **Abandonnés** (ne pas implémenter) : binaire `idea` (`ask/reply/next`), skill built-in auto-rapporté, inbox `.ideai/inbox/`, outbox `.ideai/outbox/`, port `AgentReplyChannel`/`OutboxReplyChannel`, `CorrelationId` (le rendez-vous synchrone est intrinsèque à `send_blocking`, §17.1). §16 est conservé pour mémoire historique uniquement.
|
||||
|
||||
#### 14.3.1 Surface MCP — invocation native d'agents (adapter entrant OPTIONNEL, par-dessus le même `OrchestratorService`)
|
||||
|
||||
> Cadrage complet : `.ideai/briefs/orchestration-v3-cadrage.md`. Cette sous-section fige les 4 décisions et le découpage en lots. Elle **ne réécrit ni le domaine ni l'application** : elle ajoute une **capacité déclarative de profil** + un **adapter entrant d'infra**.
|
||||
|
||||
**Objectif** : rendre l'invocation d'un agent par un autre **aussi native qu'un subagent** (outil typé visible dans la liste d'outils, résultat **inline**), de façon **model-agnostic**, **toujours médiée par IdeA**. On **garde l'interdiction des subagents natifs** (prose) et on offre **la vraie alternative native** (outils `idea_*`).
|
||||
|
||||
**Décision 1 — Capacité MCP = champ optionnel `mcp: Option<McpCapability>` sur `AgentProfile`** (Open/Closed, comme `session`/`structured_adapter` ; `skip_serializing_if = None` ⇒ zéro régression sérialisation). `None` ⇒ **repli fichier `.ideai/requests` + prose** (comportement actuel). `Some(_)` ⇒ IdeA matérialise la conf MCP de cette CLI au lancement et l'agent voit les outils. `McpCapability { config: McpConfigStrategy::{ConfigFile{target}|Flag{flag}|Env{var}}, transport: {Stdio|Socket} }`. Modèle **en couches** : `surface(agent) = if profile.mcp.is_some() { Mcp } else { FileProtocol }` — les deux produisent le **même** `OrchestratorCommand` ; aucun agent n'est jamais bloqué.
|
||||
|
||||
**Décision 2 — Retour synchrone d'`ask` : RIEN de neuf.** L'outil `idea_ask_agent` appelle le **même** `dispatch(AskAgent{target,task})` qui **renvoie déjà** `OrchestratorOutcome.reply` via `send_blocking` (§17.4) ; l'adapter MCP renvoie ce contenu **inline**. La corrélation requête↔réponse est portée **nativement par JSON-RPC** (côté MCP) et par le sibling `*.response.json` (côté fichier). **Pas** de `CorrelationId`, pas d'outbox. Timeout borné (300 s) ⇒ cible **vivante**, erreur typée. Cible PTY brut ⇒ erreur explicite (jamais d'ACK trompeur).
|
||||
|
||||
**Décision 3 — Interdiction conservée + injection de la conf MCP par CLI au `LaunchAgent`.** La conf MCP est matérialisée dans le **run dir isolé** (§14.1), **après `apply_injection`, avant le spawn/`factory.start`**, selon `McpConfigStrategy` (`ConfigFile`→write non-clobbering ; `Flag`→`SpawnSpec.args` ; `Env`→`SpawnSpec.env`) — **symétrique** au convention file et au seed de permissions. La prose `compose_convention_file` est **adaptée selon la surface** (outils `idea_*` si MCP, sinon `.ideai/requests`). Le **serveur MCP** est démarré **par projet ouvert**, dans le **même hook** `ensure_orchestrator_watch`, à côté du `FsOrchestratorWatcher`.
|
||||
|
||||
**Décision 4 — Frontières.** Le serveur MCP est un **driving adapter d'infra** (`infrastructure/src/orchestrator/mcp/`), **pair** du `FsOrchestratorWatcher`, qui traduit un appel d'outil (`idea_ask_agent`/`idea_launch_agent`/`idea_list_agents` + parité `idea_update_context`/`idea_create_skill`/`idea_stop_agent`) en `OrchestratorCommand` et appelle le **même** `OrchestratorService::dispatch`. **Aucun nouveau port** domaine/application. Trois portes d'entrée substituables (fichier, MCP, UI) ⇒ une seule logique applicative. JSON-RPC, stdio/socket, le crate MCP : **confinés à l'adapter** ; le domaine/application ignorent MCP.
|
||||
|
||||
**Découpage en LOTS (méthode §3)** — MCP uniquement ; le chemin fichier reste vert à chaque lot ; spike S-MCP (crate + transport + format de conf par CLI) **confiné au lot M2** :
|
||||
|
||||
| Lot | Côté | Périmètre | Tests attendus |
|
||||
|---|---|---|---|
|
||||
| **M0** | back | `McpCapability`/`McpConfigStrategy`/`McpTransport` (domaine validés) + champ `AgentProfile.mcp` (builder `with_mcp`, `new` inchangé) ; catalogue Claude/Codex annotés. | round-trip `mcp=None` **identique à avant** ; `Some(_)` round-trip ; constructeurs valident ; catalogue annoté. |
|
||||
| **M1** | back | `LaunchAgent` injecte la conf MCP (run dir, après `apply_injection`) ; prose adaptée selon `mcp.is_some()`. | `mcp=None` ⇒ aucun write/flag/env MCP (chemin inchangé) ; `ConfigFile` non-clobbering ; `Flag`/`Env` enrichissent `spec` ; prose correcte selon surface. |
|
||||
| **M2** | back | `infrastructure/src/orchestrator/mcp/` : serveur MCP, outils `idea_*`→`OrchestratorCommand`→`dispatch`→résultat inline. Spike S-MCP isolé. | chaque outil mappe la bonne commande ; `idea_ask_agent` renvoie `reply` inline ; timeout typé, cible vivante ; JSON-RPC malformé → erreur, jamais panic ; hors-réseau. |
|
||||
| **M3** | back | Démarrer le serveur MCP par projet dans `ensure_orchestrator_watch` (registre `mcp_servers` jumeau de `orchestrator_watchers`) ; arrêt à la fermeture. | un serveur/projet, idempotent ; arrêt à la fermeture ; coexiste avec le watcher fichier. |
|
||||
| **M4** *(optionnel)* | front | Surfacer la source (`mcp`/`file`) d'une délégation dans l'UI Agents. | badge source ; pas de régression sans event. |
|
||||
|
||||
**Ordre** : **M0 → M1 → M2 → M3** (→ M4 optionnel). Chantiers adjacents **déjà livrés** (non prérequis) : hot-swap profil (A, §15.1) et reprise auto (B, §15.2) — au relance, `LaunchAgent` (ré)injecte/retire la conf MCP automatiquement puisque la surface suit le profil courant.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user