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>
6.5 KiB
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 outilidea_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_eventrequis pour vue ni orchestration.
Découvertes clés de l'architecte (état réel du code)
- La file FIFO existe déjà :
OrchestratorService(crates/application/src/orchestrator/service.rs) aask_locks: Mutex<HashMap<AgentId, Arc<AsyncMutex<()>>>>+ask_lock_for()+ASK_QUEUE_WAIT_CAP(600s) +ASK_AGENT_TIMEOUT(300s). On la formalise en portAgentMailbox(pour porter unoneshotde réponse). idea_ask_agent→agent.message→OrchestratorCommand::AskAgent{target_agent, task}déjà câblé (mcp/tools.rs, domain/orchestrator.rs, service.rs). On réimplémente le corps deask_agent().- Aujourd'hui
ask_agentexige une session structurée et renvoieAppError::Invalidsi la cible est en PTY brut (service.rs ~400-410). Inverser cette branche : PTY vivant = canal normal. - 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). apply_mcp_config(lifecycle.rs ~1391) écrit déjà.mcp.json+--mcp-configAVANT le spawn, chemin PTY inclus → la CLI PTY a déjà le serveur MCP IdeA (à vérifier par test B-0). Vigilance :ensure_mcp_serverdoit piloterMcpServer::servesur le loopback.idea_replyn'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) +servepiloté loopback. Test : CLI factice PTY appelleidea_list_agents, reçoit réponse. - B-1 Port
AgentMailbox+InMemoryMailbox. Domaine pur (crates/domain/src/mailbox.rsou ports.rs) : trait +Ticket{id,requester,task},TicketId,MailboxError. Infra (crates/infrastructure/src/mailbox/) :HashMap<AgentId, VecDeque<(Ticket, oneshot::Sender<String>)>>+ mutex ;enqueuerendPendingReply(suroneshot::Receiver). Tests : FIFO ;resolveré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: retirerwith_structureddeLaunchAgent/OrchestratorService/ChangeAgentProfile. Tests : profil Claude → PTY ; DTO renvoieCellKind::Pty. Ne pas supprimerlaunch_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::writepréfixe[IdeA · tâche de {A} · ticket {id}] {task}\n→await PendingReplybornéASK_AGENT_TIMEOUT. PTY vivant = normal. Timeout : garder agent vivant, retirer ticket de tête. PublierAgentReplied. InjecterArc<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 wireagent.reply,OrchestratorCommand::Reply{from:AgentId, result},validate,map_tool_call(passerequesterdu handshake commefrom), bras dispatch →mailbox.resolve(from, result). Corrélation implicite :idea_replyré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ÉRATIVEMENTidea_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(jamaisAgentChatView) ; ré-attache PTY + scrollback OK. Backend renverracellKind:"pty". Lirefrontend/src/features/layout/LayoutGrid.tsx,features/chat/AgentChatView.tsx,TerminalView,adapters/agent.ts,ports/index.ts,domain/index.ts. LaisserAgentChatViewinerte (non monté), pas supprimé. Tests Vitest : agent rendTerminalView, jamaisAgentChatView; 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(varianteReply+ actionagent.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).