Files
IdeA/.ideai/briefs/option1-terminal-mcp-design.md
Blomios eca2ba95c4 feat(agent): conversation par paire + entrée médiée + pivot terminal/MCP
Coeur inter-agents consolidé et surface front réalignée sur la décision
"terminal natif PTY, pas d'UI chat" (Option 1).

Domaine
- nouveaux modules conversation, mailbox, input, fileguard (ports + types)
- orchestrator/profile/events étendus (conversation par paire, FIFO)

Application / Infrastructure
- orchestrator/service + context_guard : sérialisation FIFO par agent,
  garde RW mémoire/contexte, dispatch ask/reply
- adapters in-memory conversation / mailbox / input / fileguard
- registry session + lifecycle agent durcis (1 agent = 1 session vivante)
- outils MCP idea_* alignés sur le nouveau dispatch

Frontend
- MediatedInput + useAgentBusy : entrée utilisateur médiée par IdeA,
  terminal = vue sortie inchangée
- suppression de la vue chat structurée (AgentChatView) — abandonnée
- adapter input + ports mis à jour

Divers
- .ideai/ : mémoire projet + briefs de cadrage versionnés ;
  requests/ runtime ignoré ; agents projet réels (DevBackend/DevFrontend/QA)

Tests : Rust (domain/application/infrastructure/app-tauri) + front (346) verts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 07:33:04 +02:00

6.5 KiB

Design — Option 1 « Terminal + MCP » (orchestration inter-agents)

Décision produit arbitrée (2026-06-11). Remplace la vue chat structurée par le terminal natif + délégation inter-agents par outils MCP. Source : agent Architecte. Statut : design validé, dev NON commencé (limite de session atteinte le 2026-06-11, reset 3:40am Europe/Paris). Reprendre par les lots backend B-0→B-5 et frontend F-1.

Objectif

  • Vue humaine = terminal brut natif (PTY interactif). Réflexion live + Échap = natifs CLI, zéro parsing par modèle. On abandonne AgentChatView/stream-json comme vue.
  • Délégation cross-model via MCP : idea_ask_agent(target, task) bloquant → la cible traite quand libre (FIFO) → rend son résultat via NOUVEL outil idea_reply(result) → IdeA débloque l'appelant. Fin-de-tour = signal MCP explicite.
  • Principes : 1 agent = 1 employé (1 process/session, input FIFO) ; hexagonal + SOLID stricts ; plus aucun parse_event requis pour vue ni orchestration.

Découvertes clés de l'architecte (état réel du code)

  1. La file FIFO existe déjà : OrchestratorService (crates/application/src/orchestrator/service.rs) a ask_locks: Mutex<HashMap<AgentId, Arc<AsyncMutex<()>>>> + ask_lock_for() + ASK_QUEUE_WAIT_CAP (600s) + ASK_AGENT_TIMEOUT (300s). On la formalise en port AgentMailbox (pour porter un oneshot de réponse).
  2. idea_ask_agentagent.messageOrchestratorCommand::AskAgent{target_agent, task} déjà câblé (mcp/tools.rs, domain/orchestrator.rs, service.rs). On réimplémente le corps de ask_agent().
  3. Aujourd'hui ask_agent exige une session structurée et renvoie AppError::Invalid si la cible est en PTY brut (service.rs ~400-410). Inverser cette branche : PTY vivant = canal normal.
  4. Routage structuré dans crates/application/src/agent/lifecycle.rs (LaunchAgent ~1100). Levier de bascule : ne plus injecter la fabrique structurée au composition root (crates/app-tauri/src/state.rs, with_structured).
  5. apply_mcp_config (lifecycle.rs ~1391) écrit déjà .mcp.json + --mcp-config AVANT le spawn, chemin PTY inclus → la CLI PTY a déjà le serveur MCP IdeA (à vérifier par test B-0). Vigilance : ensure_mcp_server doit piloter McpServer::serve sur le loopback.
  6. idea_reply n'existe nulle part : seul vrai ajout de surface.

Lots BACKEND (Rust — agent dev backend) ; NE PAS faire B-6 (nettoyage) avant coordination

  • B-0 Prérequis transport MCP : garantir CLI PTY reçoit --mcp-config <path> (endpoint/project/requester) + serve piloté loopback. Test : CLI factice PTY appelle idea_list_agents, reçoit réponse.
  • B-1 Port AgentMailbox + InMemoryMailbox. Domaine pur (crates/domain/src/mailbox.rs ou ports.rs) : trait + Ticket{id,requester,task}, TicketId, MailboxError. Infra (crates/infrastructure/src/mailbox/) : HashMap<AgentId, VecDeque<(Ticket, oneshot::Sender<String>)>> + mutex ; enqueue rend PendingReply (sur oneshot::Receiver). Tests : FIFO ; resolve réveille le bon pending ; 2 ask même cible sérialisés ; cibles ≠ non bloquants ; timeout retire ticket de tête.
  • B-2 Bascule routage : tous en PTY. state.rs : retirer with_structured de LaunchAgent/OrchestratorService/ChangeAgentProfile. Tests : profil Claude → PTY ; DTO renvoie CellKind::Pty. Ne pas supprimer launch_structured (mort-code, nettoyage ultérieur).
  • B-3 Réimplémenter ask_agent : résoudre id → mailbox.enqueue → ticket en tête → garantir cible vivante PTY (sinon LaunchAgent PTY bg) → PtyPort::write préfixe [IdeA · tâche de {A} · ticket {id}] {task}\nawait PendingReply borné ASK_AGENT_TIMEOUT. PTY vivant = normal. Timeout : garder agent vivant, retirer ticket de tête. Publier AgentReplied. Injecter Arc<dyn AgentMailbox> + Arc<dyn PtyPort>. Tests : injection bon handle ; agent mort relancé ; timeout libère file ; AgentReplied.
  • B-4 Outil/action idea_reply : ToolDef idea_reply (schéma {result:string} seul, pas de ticket_id exposé), action wire agent.reply, OrchestratorCommand::Reply{from:AgentId, result}, validate, map_tool_call (passe requester du handshake comme from), bras dispatch → mailbox.resolve(from, result). Corrélation implicite : idea_reply résout le ticket en tête de la file de l'émetteur (identité via handshake, pas via id géré par le modèle). tool_returns_reply : idea_reply = ACK sans inline. Tests : mapping ; validate exige result ; resolve corrèle tête ; reply sans ask = erreur typée (pas de panic).
  • B-5 Protocole délégation dans le contexte : injecter dans convention file (apply_injection) + description outil : « reçois [IdeA · tâche …] → traite → appelle IMPÉRATIVEMENT idea_reply(result=…) ; ne réponds jamais qu'en texte. » Test : convention file contient l'instruction.

Lots FRONTEND (TS/React — agent dev frontend) ; NE PAS faire F-2 (suppression) avant coordination

  • F-1 Router toute cellule agent vers TerminalView (jamais AgentChatView) ; ré-attache PTY + scrollback OK. Backend renverra cellKind:"pty". Lire frontend/src/features/layout/LayoutGrid.tsx, features/chat/AgentChatView.tsx, TerminalView, adapters/agent.ts, ports/index.ts, domain/index.ts. Laisser AgentChatView inerte (non monté), pas supprimé. Tests Vitest : agent rend TerminalView, jamais AgentChatView ; re-mount repeint pty.

Ordre / dépendances

B-0 ─┬─ B-2 ─┬─ B-3 ─ B-4 ─ B-5
B-1 ─┘       └─ F-1
Nettoyage (B-6, F-2) en dernier, coordonné.

Chemin critique : B-0 → B-2 → B-3 → B-4 → B-5. B-1 ∥ B-0. F-1 dès B-2.

Cohérence

Domaine sans I/O (port + entités pures) ; oneshot/PTY/MCP = infra ; application via ports. Open/Closed (idea_reply = ajout, dispatch intact) ; Liskov (Claude/Codex identiques derrière PTY+MCP) ; 1 process/agent préservé.

Fichiers à toucher

  • Domaine : mailbox.rs (nouveau) / ports.rs ; orchestrator.rs (variante Reply + action agent.reply).
  • Application : orchestrator/service.rs (ask_agent + reply + injection ports) ; agent/structured.rs (supprimé au nettoyage) ; agent/lifecycle.rs (routage).
  • Infra : mailbox/ (nouveau) ; orchestrator/mcp/tools.rs (idea_reply) ; orchestrator/mcp/server.rs (passer requester).
  • app-tauri : state.rs (retrait with_structured + injection mailbox + ensure_mcp_server) ; commands.rs/dto.rs (nettoyage ultérieur).
  • Frontend : features/layout/LayoutGrid.tsx (routage TerminalView) ; features/chat/* (nettoyage ultérieur).