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>
This commit is contained in:
2026-06-12 07:33:04 +02:00
parent 5f45c22941
commit eca2ba95c4
61 changed files with 9491 additions and 2512 deletions

View File

@ -14,6 +14,27 @@
"mdPath": "agents/architect.md",
"profileId": "664cc20c-47b8-53ad-9351-dce3c09c0de4",
"synchronized": false
},
{
"agentId": "73c853d1-c0fd-463b-ad17-1d24fefa371f",
"name": "DevBackend",
"mdPath": "agents/devbackend.md",
"profileId": "664cc20c-47b8-53ad-9351-dce3c09c0de4",
"synchronized": false
},
{
"agentId": "af7f86da-76bc-48e1-9900-71f45a624800",
"name": "DevFrontend",
"mdPath": "agents/devfrontend.md",
"profileId": "664cc20c-47b8-53ad-9351-dce3c09c0de4",
"synchronized": false
},
{
"agentId": "aefdbd61-e3d4-4bc1-9f42-c259446a97b5",
"name": "QA",
"mdPath": "agents/qa.md",
"profileId": "664cc20c-47b8-53ad-9351-dce3c09c0de4",
"synchronized": false
}
]
}

View File

@ -0,0 +1,515 @@
# Conversation par paire — cadrage d'architecture (multi-agent solide par construction)
> **Agent Architecture.** Ce document tranche le modèle qui rend le multi-agent
> **solide par construction** : l'utilisateur n'a plus à « faire les choses dans le
> bon ordre ». Aucun code de production ici — décisions, contrats (ports/entités),
> découpage en lots testables, frontière backend/frontend.
>
> **Décisions produit arbitrées (NON négociables, rappel) :** (A) conversation par
> paire = un fil entre deux parties, session propre, matérialisation paresseuse ;
> (B) entrée médiée par IdeA (le terminal xterm reste la vue de sortie brute
> INCHANGÉE, seule l'entrée change de chemin), Envoyer=enqueue / Interrompre=préempte ;
> (C) FileGuard borné aux `.md` de contexte + la mémoire, via outils MCP, verrou
> lecteurs/écrivain ; (D) zéro git, hexagonal+SOLID stricts, corrélation par ticket,
> MCP Claude-only, fix `bind_endpoint`, abandon du band-aid `\n`→`\r`.
>
> **État du terrain (lu, pas présumé).** L'essentiel des briques existe déjà :
> `domain/src/mailbox.rs` (`AgentMailbox`, `Ticket`, `TicketId`, `PendingReply`,
> `MailboxError`) ; `infrastructure/src/mailbox/mod.rs` (`InMemoryMailbox`, FIFO par
> agent + `oneshot`) ; `application/src/orchestrator/service.rs` (`ask_agent`,
> `reply`, `ensure_live_pty`, verrou de tour `ask_locks`) ; surface MCP complète
> (`mcp/tools.rs`, `mcp/server.rs`) ; transport bindé (`app-tauri/src/mcp_endpoint.rs`,
> `state.rs::bind_endpoint`/`ensure_mcp_server`/`serve_peer`). **Ce cadrage
> formalise et complète ; il ne réécrit pas.**
---
## 0. Synthèse exécutive (décisions tranchées)
1. **La conversation devient une entité de premier plan** (`Conversation` + `ConversationId`),
absente aujourd'hui. Le couplage actuel « 1 session vivante / agent »
(`session-registry-agent-ambiguity`) est **remplacé** par « 1 session vivante /
**conversation** ». Un agent peut donc avoir **N sessions** simultanées (une par
fil), mais **une seule tâche traitée à la fois** (l'entrée reste sérialisée, §B).
C'est ce qui supprime la fuite de contexte : la délégation A→B n'emprunte plus la
conversation User↔B.
2. **L'entrée passe par un `InputMediator`** (nouveau port application) : toutes les
entrées (humaine **et** inter-agents) convergent vers **une file FIFO unique par
agent**, `enqueue`/`preempt` distincts. Le terminal xterm n'écrit **plus jamais
en direct dans le PTY** ; il devient une **vue de sortie pure**. La file existante
(`AgentMailbox` + `ask_locks`) est **absorbée** par le `InputMediator` : la
messagerie inter-agents n'est qu'une **source d'entrée parmi deux**.
3. **`FileGuard` (nouveau port domaine)** : un verrou lecteurs/écrivain **borné** aux
fichiers qu'IdeA possède (`.md` de contexte d'agent + mémoire). Les agents perdent
l'accès fs brut à ces chemins et passent par de **nouveaux outils MCP**
`idea_context_read/propose` et `idea_memory_read/write`. Le contexte **global
projet** est **mono-écrivain (l'orchestrateur)** ; les autres *proposent*.
4. **Détection occupé/libre = double signal avec fallback sûr** : (a) **retour-de-prompt**
détecté par motif déclaré dans le profil CLI, (b) **signal explicite** de l'agent
(un `idea_reply`, ou fin de tour MCP). **En cas de doute → forwarder** (on
enqueue ; jamais piéger un message). L'occupé/libre remonte au front via un
`DomainEvent` (Channel Tauri), pas via parsing front.
5. **Fixes durables embarqués** : `bind_endpoint` unlink déjà le socket cadavre
(`reclaim_name(true)`, état OK — on **verrouille ce comportement par un test de
non-régression**) ; le band-aid `\n``\r` et l'« injection PTV » de
`service.rs:459` **disparaissent** (l'entrée passe désormais par le `InputMediator`,
pas par une écriture PTY préfixée d'un orchestrateur).
6. **Garde-fous d'orchestration** : timeout par tour (déjà), **plafond d'attente en
file** (déjà, `ASK_QUEUE_WAIT_CAP`), **détection de cycle** sur un graphe wait-for
(nouveau, dans le domaine — pur, testable) pour refuser une délégation
ré-entrante (A→B→A) avant deadlock.
---
## 1. Modèle de domaine
### 1.1 Nouvelles entités / VO
#### `ConversationId` (VO)
- `newtype(uuid::Uuid)`, calqué sur `TicketId`/`AgentId`. Immuable, non vide.
- **Implémenté** : `crates/domain/src/conversation.rs` (nouveau module, à exporter
dans `lib.rs` à côté de `mailbox`).
#### `ConversationParty` (VO, enum)
```text
ConversationParty =
| User // l'humain (une seule instance logique côté IdeA)
| Agent(AgentId) // un agent du projet
```
- Invariant : une `Conversation` relie **deux parties distinctes** (jamais
`Agent(x)↔Agent(x)`, jamais `User↔User`).
#### `Conversation` (entité)
```text
Conversation {
id: ConversationId,
left: ConversationParty,
right: ConversationParty,
session: ConversationSession, // état d'I/O (voir 1.2)
resumable_id: Option<String>, // session-id reprenable de la CLI (suspend = stocke)
}
```
- **Invariants** : `left != right` ; au plus **une** des deux parties est `User` ;
identité d'une conversation = la **paire non ordonnée** `{left, right}` pour un
agent donné (deux paires identiques ⇒ même conversation — clé de la matérialisation
paresseuse). Pur, I/O-free.
- **Matérialisation paresseuse** : une `Conversation` `Agent↔Agent` n'existe en
registre que s'il y a **au moins une tâche** ; suspendue, elle ne garde que
`resumable_id` (pas de session vivante). C'est une **règle du `ConversationRegistry`**
(application), pas un champ persistant lourd.
#### `ConversationSession` (VO, enum — l'état d'I/O du fil)
```text
ConversationSession =
| Dormant // jamais lancée, ou suspendue (resumable_id seul)
| Live { handle_ref: SessionRef } // un flux d'I/O vivant (PTY ou structuré)
```
- `SessionRef` = abstraction d'un handle de session (référence vers une `TerminalSession`
existante, cf. `domain/src/terminal.rs`). Le domaine ne tient **pas** le PTY (infra).
#### `Task` / `Ticket` (extension de l'existant)
- `Ticket` (`domain/src/mailbox.rs`) est **étendu** pour porter **l'origine** et la
**conversation cible** :
```text
Ticket {
id: TicketId, // existant
source: InputSource, // NOUVEAU : Human | Agent(AgentId)
conversation: ConversationId, // NOUVEAU : le fil dans lequel la tâche entre
requester: String, // existant (label d'affichage du préfixe)
task: String, // existant
}
```
- `InputSource` (VO, enum) : `Human | Agent(AgentId)`. Remplace l'actuel
`requester: String` libre comme **source de vérité** (le `String` reste un label
d'affichage dérivé). Permet de **propager l'identité du demandeur** (D) et
d'alimenter le graphe wait-for (détection de cycle).
- **Compat** : `Ticket::new` garde sa signature ; on ajoute `Ticket::from_human(...)`
et `Ticket::from_agent(source, conversation, ...)` (Open/Closed, pas de breaking).
#### File FIFO + état occupé/libre (VO)
- `AgentInbox` (concept porté par le port `InputMediator`, pas une entité persistée) :
**une file FIFO par `AgentId`**, **une tâche en cours à la fois**.
- `AgentBusyState` (VO, enum) : `Idle | Busy { ticket: TicketId, since_ms: u64 }`.
Dérivé, publié au front. Invariant : un agent passe à `Busy` **à l'enqueue qui
démarre un tour** ; revient `Idle` sur **retour-de-prompt** OU **signal explicite**
(cf. §6) ; **en cas de doute, reste `Busy`** mais la file **continue d'accepter**
(forward, jamais bloquer l'émetteur).
#### `WaitForGraph` (VO pur — détection de cycle)
- `domain/src/conversation.rs` : structure pure `wait_edges: Vec<(AgentId, AgentId)>`
(« A attend B »). Fonction pure `would_cycle(graph, from, to) -> bool`.
- Invariant : une `AskAgent` de `A` vers `B` est **refusée** (`MailboxError`/`AppError`
typé) si elle crée un cycle dans le graphe d'attente (A→B alors que B→…→A).
100 % testable sans I/O.
### 1.2 Invariants transverses
- **1 session vivante / conversation** (remplace « 1 / agent »). `session_for(conversation)`
est déterministe ; `sessions_for_agent(agent)` peut renvoyer N (une par fil actif).
- **1 tâche traitée à la fois / agent** : l'`InputMediator` sérialise l'entrée. Deux
fils d'un même agent partagent **la même file d'entrée** (le process CLI sous-jacent
est unique — « 1 agent = 1 employé »). *Conséquence assumée : un agent occupé par
son fil User retarde une délégation entrante — c'est voulu (un employé, une tâche).*
- **Séparation stricte des contextes** : écrire dans la conversation `A↔B` ne touche
jamais `User↔B`. Garanti par le fait que la session reprise (`resumable_id`) est
**par conversation**, pas par agent.
---
## 2. Ports (traits domaine)
> Signatures **conceptuelles**. « Consommé par » = application ; « Implémenté par » = infra/app-tauri.
### `ConversationRegistry` (NOUVEAU — domaine, `conversation.rs`)
- **Rôle** : résoudre/ouvrir paresseusement une conversation pour une paire, tenir son
`session`/`resumable_id`, suspendre/reprendre.
```rust
trait ConversationRegistry: Send + Sync {
/// Get-or-create paresseux : retourne le fil de la paire {a,b}, en l'ouvrant
/// (Dormant) s'il n'existait pas. Pur registre — n'ouvre AUCUNE session.
fn resolve(&self, a: ConversationParty, b: ConversationParty) -> Conversation;
/// Marque une conversation Live avec la session donnée.
fn bind_session(&self, id: ConversationId, session: SessionRef);
/// Suspend : passe Dormant, conserve le resumable_id rendu par la CLI.
fn suspend(&self, id: ConversationId, resumable_id: Option<String>);
fn get(&self, id: ConversationId) -> Option<Conversation>;
}
```
- **Consommé par** : `OrchestratorService` (au lieu de `session_for_agent` brut),
`LaunchAgent`, la reprise au redémarrage.
- **Implémenté par** : `InMemoryConversationRegistry` (infra) — `HashMap` + mutex sync,
jamais tenu en travers d'un `.await` (cf. `ask_locks` existant).
### `InputMediator` (NOUVEAU — domaine ou application ; **décision : domaine**, `input.rs`)
- **Rôle** : le point de convergence de **toutes** les entrées d'un agent (FIFO unique),
avec `enqueue` (Envoyer) et `preempt` (Interrompre) **distincts**, plus l'état busy.
```rust
trait InputMediator: Send + Sync {
/// Envoyer = enqueue : ajoute la tâche en queue FIFO de l'agent, retourne le
/// PendingReply à attendre (réutilise le type mailbox existant).
fn enqueue(&self, agent: AgentId, ticket: Ticket) -> PendingReply;
/// Interrompre = préempte : signale au tour en cours de s'arrêter (Échap/stop).
/// N'est PAS un enqueue ; ne corrèle aucun ticket.
fn preempt(&self, agent: AgentId);
/// Marque l'agent libre (retour-de-prompt ou signal explicite) ⇒ avance la file.
fn mark_idle(&self, agent: AgentId);
fn busy_state(&self, agent: AgentId) -> AgentBusyState;
}
```
- **Décision frontière** : `InputMediator` **absorbe** `AgentMailbox`. Le mailbox
existant devient le **moteur de corrélation par ticket** *interne* à
l'implémentation du `InputMediator` (l'`InMemoryMailbox` est réutilisé tel quel, sa
FIFO + `oneshot` sont exactement ce qu'il faut). On **n'a donc pas** deux files
concurrentes : `ask_locks` (verrou de tour) + `InMemoryMailbox` (slots de réponse)
sont unifiés derrière ce port. *(Voir §5 pour le chemin de migration.)*
- **Consommé par** : `OrchestratorService::ask_agent` (source = `Agent`), et le
**nouveau** use case `SubmitHumanInput` (source = `Human`).
- **Implémenté par** : `MediatedInbox` (infra) composant `InMemoryMailbox` + le
registre de verrous de tour + l'état busy.
### `FileGuard` (NOUVEAU — domaine, `fileguard.rs`)
- **Rôle** : verrou **lecteurs/écrivain par fichier** sur le périmètre **borné**
(contexte `.md` + mémoire). N lecteurs OU 1 écrivain ; mono-écrivain pour le
contexte global (l'orchestrateur).
```rust
enum GuardedResource { // VO — le périmètre borné, fermé
AgentContext(AgentId),
ProjectContext, // mono-écrivain : orchestrateur uniquement
Memory(MemorySlug),
}
trait FileGuard: Send + Sync {
async fn acquire_read(&self, who: ConversationParty, res: GuardedResource)
-> Result<ReadLease, GuardError>;
async fn acquire_write(&self, who: ConversationParty, res: GuardedResource)
-> Result<WriteLease, GuardError>;
}
```
- `ReadLease`/`WriteLease` = gardes RAII (libèrent à la fin de portée). `GuardError`
typé : `Busy` (attendre), `Forbidden` (un agent ≠ orchestrateur veut écrire
`ProjectContext` ⇒ refus, doit *proposer*).
- **Invariant clé** : toute lecture/écriture des ressources gardées **transite par ce
port** ; l'accès fs brut à ces chemins est retiré aux agents (cf. §3 outils MCP).
- **Consommé par** : `UpdateAgentContext`, `MemoryStore`-consumers, les nouveaux
use cases `ReadContext`/`ProposeContext`/`ReadMemory`/`WriteMemory`.
- **Implémenté par** : `RwFileGuard` (infra) — `HashMap<GuardedResource, RwLock-like>`
(tokio `RwLock` ou sémaphore), + la règle mono-écrivain pour `ProjectContext`.
### `AgentMailbox` (existant — **conservé**, statut révisé)
- Reste le **contrat de rendez-vous par ticket** (corrélation **par `TicketId`**, voir
§3.3 — on **abandonne** la corrélation purement positionnelle « tête de file » dès
qu'un agent peut avoir plusieurs fils). Devient un **détail d'implémentation** du
`InputMediator` ; n'est plus injecté seul dans `OrchestratorService`.
### Ports inchangés réutilisés
- `PtyPort` (écriture du tour dans le PTY = désormais le **seul** chemin d'écriture,
piloté par le `InputMediator`, plus par `ask_agent` directement).
- `ProfileStore` (porte le **motif de retour-de-prompt** par profil, §6).
- `EventBus` (publie `AgentBusyChanged`, `AgentReplied`).
---
## 3. Adapters (infra) + outils MCP
### 3.1 Adapters
| Port | Adapter | Notes |
|---|---|---|
| `ConversationRegistry` | `InMemoryConversationRegistry` | `HashMap<ConversationId, Conversation>` + index paire→id ; mutex sync. |
| `InputMediator` | `MediatedInbox` | compose `InMemoryMailbox` (existant) + verrous de tour + état busy ; publie `AgentBusyChanged`. |
| `FileGuard` | `RwFileGuard` | `RwLock` par `GuardedResource` ; règle mono-écrivain `ProjectContext`. |
| `AgentMailbox` | `InMemoryMailbox` | **inchangé** (réutilisé sous `MediatedInbox`). |
### 3.2 Nouveaux outils MCP (`infrastructure/src/orchestrator/mcp/tools.rs`)
Ajouts **purement additifs** au `catalogue()` (Open/Closed — le dispatch reste intact) :
- **`idea_context_read { target? }`** → action wire `context.read`
`OrchestratorCommand::ReadContext { target }`. `target` absent = le contexte **global
projet** ; sinon le `.md` d'un agent. Passe par `FileGuard::acquire_read`.
- **`idea_context_propose { target?, content }`** → `context.propose`
`OrchestratorCommand::ProposeContext`. Pour un agent : écriture directe sous verrou
écrivain. Pour le **global** : ce n'est **pas** une écriture, c'est une **proposition**
(déposée pour validation par l'orchestrateur/UI ; `FileGuard` refuse l'écriture
directe avec `Forbidden`).
- **`idea_memory_read { slug? }`** → `memory.read``ReadMemory` (sous `FileGuard`).
- **`idea_memory_write { slug, content }`** → `memory.write``WriteMemory` (verrou
écrivain ; mémoire = partagée projet, cf. `shared-project-memory`).
Chaque outil suit le **patron existant** : `map_tool_call` construit un
`OrchestratorRequest`, `validate()` reste l'**unique autorité** de validation, le
`requester` du handshake porte l'identité (`ConversationParty::Agent`).
### 3.3 Corrélation `idea_reply` **par ticket** (D)
- **Changement** : aujourd'hui `idea_reply` corrèle **positionnellement** (tête de la
file de l'émetteur — `mailbox.resolve(from, result)`). Dès qu'un agent peut avoir
**plusieurs fils**, la tête de « sa » file est ambiguë.
- **Décision** : le préfixe injecté dans le PTY (`[IdeA · tâche de A · ticket <id>]`)
porte **déjà** le `ticket_id`. On expose un champ **optionnel** `ticket` au schéma de
`idea_reply` (`{ result, ticket? }`) ; quand présent, `resolve` corrèle **par
`TicketId`** (déterministe, multi-fil) ; absent, on **retombe** sur la tête de file
(compat agents simples, mono-fil). Le préfixe doit donc **demander à l'agent de
renvoyer le `ticket`** (mise à jour de la description outil + protocole §B-5
existant). `AgentMailbox::resolve` gagne une variante `resolve_ticket(agent,
ticket_id, result)`.
---
## 4. Frontière front : vue de sortie (xterm inchangé) / entrée médiée
### 4.1 État actuel à modifier
`frontend/src/features/terminals/TerminalView.tsx` câble aujourd'hui **directement**
les frappes au PTY :
```ts
const onKey = term.onData((data) => {
if (handle) void handle.write(encoder.encode(data)); // ← chemin à couper
});
```
C'est **exactement** le couplage que le Modèle B retire.
### 4.2 Décision frontend
1. **xterm reste la vue de sortie brute, INCHANGÉE** : `onData (PTY) → term.write`
conservé tel quel. **Interdiction** de ressusciter `AgentChatView` (déjà supprimé
dans le diff courant — ne pas le réintroduire).
2. **`term.onData` (frappes) n'écrit plus dans le PTY** pour une cellule **agent**.
Deux modes :
- **Cellule terminal simple (non-agent)** : comportement actuel conservé (écriture
directe — pas de médiation, c'est un shell brut).
- **Cellule agent** : les frappes vont dans un **champ de saisie géré par IdeA**
(composant `MediatedInput`, rendu **sous** le terminal), pas dans le PTY. xterm
passe en lecture seule pour l'entrée (sortie toujours live).
3. **Nouveau port UI `InputGateway`** (`frontend/src/ports/index.ts`) :
```ts
interface InputGateway {
submit(projectId: string, agentId: string, text: string): Promise<void>; // Envoyer = enqueue
interrupt(projectId: string, agentId: string): Promise<void>; // Interrompre = preempt
}
```
Adapter Tauri : `invoke("submit_agent_input", …)` / `invoke("interrupt_agent", …)`
(nouvelles commands app-tauri → `SubmitHumanInput` / `preempt`). Mock pour tests.
4. **Occupé/libre remonte par event** : un `DomainEvent::AgentBusyChanged { agent_id,
busy }` relayé en event Tauri (pas un Channel haute-fréquence — événement discret).
Le `MediatedInput` désactive « Envoyer » pendant `Busy` mais **autorise toujours
l'enqueue** (le bouton enfile derrière ; jamais bloqué — fallback « forward »), et
active « Interrompre ». Le front **ne parse jamais** la sortie pour deviner l'état.
### 4.3 Composants/state touchés
- `features/terminals/TerminalView.tsx` : brancher le mode agent (entrée détournée).
- `features/terminals/MediatedInput.tsx` (**nouveau**) : champ + boutons Envoyer/Interrompre.
- `features/layout/LayoutGrid.tsx` : déjà route vers `TerminalView` ; ajoute le
`MediatedInput` sous le terminal quand `agent != null`.
- `ports/index.ts` + `adapters/agent.ts` (ou nouvel `adapters/input.ts`) + mock.
- state : un store léger `agentBusy: Record<agentId, boolean>` alimenté par l'event.
---
## 5. Impact sur le code existant
### 5.1 Supprimé / retiré
- **L'écriture PTY préfixée par `ask_agent`** (`service.rs` ~459 :
`pty.write(&handle, "[IdeA · tâche …]\n")`) **n'est plus le chemin d'entrée**. La
tâche déléguée entre désormais par `InputMediator::enqueue` (qui, dans son impl,
écrira la ligne dans le PTY — mais **sérialisée derrière l'entrée humaine** du même
agent, ce qui n'était pas le cas avant). → la logique d'écriture **déménage** de
`ask_agent` vers l'impl `MediatedInbox`.
- **Band-aid `\n`→`\r`** : abandonné (le « mode injection PTV » disparaît). Plus de
réécriture de fin de ligne ad hoc.
- **`AgentChatView`** (front) : déjà supprimé dans le diff courant — **rester** supprimé.
### 5.2 Modifié
- **`OrchestratorService`** : ne reçoit plus `with_mailbox(mailbox, pty)` séparément
mais `with_input_mediator(Arc<dyn InputMediator>)` + `with_conversations(Arc<dyn
ConversationRegistry>)`. `ask_agent` devient : résoudre la **conversation A↔B**
(paresseux), vérifier le **graphe wait-for** (refus si cycle), `enqueue` la tâche
(source = `Agent`), `await PendingReply` borné. `reply` corrèle **par ticket** (§3.3).
`ensure_live_pty` reste, mais branché sur `session_for(conversation)` au lieu de
`session_for_agent`.
- **`session_for_agent`** (registre `terminal/registry.rs`) : devient
`session_for(conversation_id)` ; `sessions_for_agent` (pluriel) ajouté. Lève
l'ambiguïté `session-registry-agent-ambiguity` **par construction** (la clé est la
conversation, pas l'agent).
- **`bind_endpoint`** (`state.rs`) : **déjà** `reclaim_name(true)` ⇒ unlink du cadavre.
**Action = verrouiller par un test** (ouvrir/fermer/SIGKILL simulé/rebind sans
`EADDRINUSE`). Pas de changement de code attendu, sauf si le test révèle un trou.
- **`idea_reply`** (tools.rs / orchestrator.rs / server.rs) : champ `ticket?` ajouté,
`Reply { from, ticket: Option<TicketId>, result }`, `map_tool_call` le propage.
- **`Ticket`** (`mailbox.rs`) : champs `source: InputSource`, `conversation:
ConversationId` ajoutés (constructeurs additifs).
### 5.3 Ajouté
- Domaine : `conversation.rs` (`ConversationId`, `Conversation`, `ConversationParty`,
`ConversationSession`, `WaitForGraph`), `input.rs` (`InputMediator`, `InputSource`,
`AgentBusyState`), `fileguard.rs` (`FileGuard`, `GuardedResource`, leases).
- Application : use cases `SubmitHumanInput`, `ReadContext`/`ProposeContext`,
`ReadMemory`/`WriteMemory` ; détection de cycle câblée dans `ask_agent`.
- Infra : `InMemoryConversationRegistry`, `MediatedInbox`, `RwFileGuard` ; outils MCP
`idea_context_*` / `idea_memory_*`.
- app-tauri : commands `submit_agent_input`, `interrupt_agent` ; relais event
`AgentBusyChanged` ; câblage des nouveaux ports au composition root (`state.rs`).
- Front : `MediatedInput`, `InputGateway` + adapter + mock + store busy.
---
## 6. Détection occupé/libre
**Mécanisme retenu = double signal, OR, avec fallback sûr.**
| Signal | Source | Fiabilité |
|---|---|---|
| **Retour-de-prompt** | motif (regex/literal) déclaré dans le **profil CLI** (`AgentProfile`, nouveau champ `prompt_ready_pattern: Option<String>`), détecté sur le flux PTY par l'impl `MediatedInbox` | bon pour un shell/CLI au prompt stable ; faillible (motif dans la sortie) |
| **Signal explicite** | l'agent appelle `idea_reply` (fin d'une délégation) **ou** un signal de fin-de-tour MCP | déterministe quand l'agent coopère |
- Transition `Busy → Idle` = **premier** des deux signaux qui arrive.
- **Fallback « en cas de doute → forwarder »** : si **aucun** signal n'est sûr (motif
absent du profil, agent muet), l'agent **reste marqué `Busy`** mais la file
**continue d'accepter** les `enqueue` ; un message entrant **n'est jamais rejeté**,
il patiente dans la FIFO. On ne « piège » donc jamais un message ; au pire il attend.
- **Garde-fou anti-blocage** : le timeout par tour (`ASK_AGENT_TIMEOUT`, existant)
retire le ticket de tête et **relâche** le tour même si aucun signal n'est venu ⇒
la file avance. L'agent reste vivant.
- Le motif vit **dans le profil** (donnée, pas code) ⇒ ajouter une CLI = éditer un
profil (Open/Closed, cohérent §9 CLAUDE.md).
---
## 7. Découpage en lots livrables (ordonnés par dépendance)
> Chaque lot = binôme dev/test. **B = DevBackend (Rust)**, **F = DevFrontend (TS/React)**.
> Chemin critique : C1 → C2 → C3 → C4. FileGuard (C6) et front (F1/F2) parallélisables.
### Bloc Conversation (cœur — backend)
| Lot | Côté | Périmètre | Tests |
|---|---|---|---|
| **C1** | B (domaine) | `conversation.rs` : `ConversationId`, `ConversationParty`, `Conversation`, `ConversationSession`, `WaitForGraph::would_cycle`. `input.rs` : `InputSource`, `AgentBusyState`. Extension `Ticket` (source+conversation, ctors additifs). | invariants paire (left≠right, ≤1 User) ; identité = paire non ordonnée ; `would_cycle` (A→B→A refusé, A→B→C ok) ; ticket porte source+conversation. Pur, sans I/O. |
| **C2** | B (domaine+infra) | Ports `ConversationRegistry` + `InputMediator` (domaine) ; adapters `InMemoryConversationRegistry` + `MediatedInbox` (compose `InMemoryMailbox` existant). | resolve paresseux (même paire ⇒ même id) ; enqueue→PendingReply ; preempt distinct d'enqueue ; busy_state transitions ; 2 enqueue même agent sérialisés ; agents ≠ parallèles. |
| **C3** | B (application) | `OrchestratorService` : `with_input_mediator`+`with_conversations` ; `ask_agent` réécrit (résout conversation A↔B, garde wait-for, enqueue source=Agent, await) ; `reply` par ticket. `session_for(conversation)`. Retrait écriture PTY directe + band-aid `\r`. | ask A→B route dans la bonne conversation (pas User↔B) ; cycle A→B→A ⇒ erreur typée avant deadlock ; reply corrèle par ticket (multi-fil) ; reply sans ticket = fallback tête ; timeout libère file, cible vivante. |
| **C4** | B (application+app-tauri) | Use case `SubmitHumanInput` (source=Human) + commands `submit_agent_input`/`interrupt_agent` ; event `AgentBusyChanged` relayé. Câblage composition root (`state.rs`). | submit humain enfile dans la **même** FIFO que les délégations ; interrupt = preempt (pas enqueue) ; busy event émis aux bons moments ; câblage : un ask et un submit concurrents sur A sérialisent. |
### Bloc détection occupé/libre (backend)
| Lot | Côté | Périmètre | Tests |
|---|---|---|---|
| **C5** | B (domaine+infra) | Champ profil `prompt_ready_pattern` ; détection retour-de-prompt dans `MediatedInbox` ; OR avec signal explicite ; fallback « reste Busy mais accepte ». | motif détecté ⇒ Idle ; idea_reply ⇒ Idle ; ni l'un ni l'autre ⇒ Busy mais enqueue accepté ; timeout ⇒ file avance. |
### Bloc FileGuard (backend — parallélisable après C1)
| Lot | Côté | Périmètre | Tests |
|---|---|---|---|
| **C6** | B (domaine+infra) | `fileguard.rs` (port + `GuardedResource` + leases) ; `RwFileGuard` ; règle mono-écrivain `ProjectContext`. | N lecteurs concurrents OK ; 1 écrivain exclusif ; agent≠orchestrateur écrit ProjectContext ⇒ `Forbidden` ; lease RAII libère. |
| **C7** | B (application+infra MCP) | Use cases `ReadContext`/`ProposeContext`/`ReadMemory`/`WriteMemory` sous FileGuard ; outils MCP `idea_context_*`/`idea_memory_*` ; retrait accès fs brut de ces chemins. | map_tool_call → command ; validate exige `content` ; propose global ≠ write direct ; lecture concurrente non bloquante ; écriture sérialisée. |
### Bloc frontend
| Lot | Côté | Périmètre | Tests (Vitest/RTL, gateways mock) |
|---|---|---|---|
| **F1** | F | `InputGateway` (port+adapter+mock) ; `MediatedInput` (Envoyer=submit / Interrompre=interrupt) ; store busy alimenté par event. | submit appelle gateway.submit ; interrupt appelle interrupt ; busy event désactive Envoyer (mais enqueue possible), active Interrompre. |
| **F2** | F | `TerminalView` mode agent : frappes → `MediatedInput` (plus le PTY) ; xterm reste sortie live INCHANGÉE pour le non-agent. `LayoutGrid` monte `MediatedInput` sous le terminal si `agent != null`. | cellule agent ⇒ onData ne write pas le PTY ; cellule simple ⇒ comportement actuel ; sortie PTY toujours peinte ; jamais d'AgentChatView. |
### Bloc durcissement
| Lot | Côté | Périmètre | Tests |
|---|---|---|---|
| **D1** | B (app-tauri) | Test de non-régression `bind_endpoint` : bind → drop (SIGKILL simulé : laisser le fichier socket) → rebind **sans** `EADDRINUSE`. Verrouille `reclaim_name(true)`. | rebind après cadavre OK ; idempotent ; pas de fuite de fichier après close. |
**Ordre recommandé** : **C1 → C2 → C3 → C4** (cœur), **C5** après C2, **C6 → C7**
en parallèle (après C1), **F1 → F2** dès que les commands C4 existent (mock avant),
**D1** isolé n'importe quand.
---
## 8. Stratégie de tests par couche
| Couche | Type | Comment |
|---|---|---|
| **domaine** (`conversation`, `input`, `fileguard`, `mailbox` étendu) | unitaires **purs**, sans I/O ni async là où possible | invariants de paire, `would_cycle`, transitions `AgentBusyState`, ctors `Ticket`. Déterministe. C'est là que vit la garantie « solide par construction ». |
| **application** (`OrchestratorService`, `SubmitHumanInput`, use cases FileGuard) | unitaires avec **ports mockés** (fakes manuels, façon `service.rs` actuel) | ask route la bonne conversation ; cycle refusé ; reply par ticket ; submit+ask sérialisés ; FileGuard mono-écrivain. **Aucun vrai PTY/fs/MCP.** |
| **infra** (`MediatedInbox`, `RwFileGuard`, `InMemoryConversationRegistry`, outils MCP) | intégration **ciblée** | FIFO réelle + `oneshot` ; RwLock concurrence ; `map_tool_call` round-trip ; `bind_endpoint` (D1). Réutilise les tests `InMemoryMailbox` existants. |
| **app-tauri** | commands ↔ use cases | `submit_agent_input`/`interrupt_agent` mappent bien ; event `AgentBusyChanged` émis ; câblage composition root cohérent (endpoint partagé). |
| **frontend** (`MediatedInput`, `TerminalView`) | Vitest + RTL, **gateways mock** | entrée détournée hors PTY ; busy désactive Envoyer sans bloquer enqueue ; xterm sortie inchangée ; **sans backend**. |
---
## 9. Risques / points ouverts
1. **Fiabilité de la détection retour-de-prompt** (C5) — le plus dur. Un motif dans la
sortie d'un agent peut **faussement** signaler Idle (libère trop tôt) ou ne jamais
matcher (reste Busy). *Mitigation* : OR avec le signal explicite `idea_reply` +
fallback « reste Busy mais accepte » + timeout par tour. *Reste ouvert* : faut-il un
« heartbeat » MCP de fin-de-tour côté CLI ? (hors périmètre immédiat, Claude-only).
2. **Suspension/reprise de session par conversation** (`resumable_id`) — un agent à N
fils doit reprendre **le bon** session-id par fil au redémarrage. Dépend du
`session{assignFlag,resumeFlag}` du profil (cf. `conversation-resume-architecture`).
*Ouvert* : capacité réelle des CLI à tenir N conversations resumables simultanées
pour un même process « 1 agent = 1 employé » — possible conflit entre « N fils » et
« 1 process ». **Décision de cadrage** : **1 process/agent**, les fils **partagent
la file d'entrée** (sérialisés) ; le `resumable_id` par conversation sert surtout à
la **reprise au redémarrage**, pas à du vrai parallélisme intra-process.
3. **Deadlock & détection de cycle** (`WaitForGraph`) — couvre A→B→A directs et
transitifs, mais le graphe doit être **alimenté en temps réel** (arête posée à
l'enqueue, retirée au reply/timeout). *Risque* : arête fantôme si un reply se perd
⇒ faux positif de cycle. *Mitigation* : retrait d'arête garanti par le RAII du tour
(comme `_turn` aujourd'hui) + timeout.
4. **Corrélation par ticket vs agents « simples »** — un agent qui ne renvoie pas le
`ticket` dans `idea_reply` retombe sur la corrélation positionnelle (tête de file),
ambiguë en multi-fil. *Mitigation* : protocole §B-5 (description outil) **insiste**
sur le renvoi du ticket ; mono-fil reste correct sans. *Ouvert* : forcer le ticket
requis casserait des agents simples — on garde optionnel.
5. **Périmètre FileGuard contournable** — tant que l'agent garde un shell brut (PTY),
il peut écrire les `.md`/mémoire **par le filesystem** malgré le verrou MCP. Le
verrou n'est étanche que si l'accès fs à ces chemins est **réellement** retiré
(sandbox, cf. `agent-permissions-architecture` / Landlock). *Ouvert* : sans sandbox
OS, le `FileGuard` est **coopératif** (protège des collisions IdeA↔IdeA, pas d'un
agent qui contourne). À acter : FileGuard = correction des collisions **dans le
chemin IdeA** d'abord ; étanchéité réelle = lot sandbox ultérieur.
6. **Migration `AgentMailbox` → `InputMediator`** — risque de double-file transitoire.
*Mitigation* : `MediatedInbox` **enveloppe** `InMemoryMailbox` (pas de réécriture),
`OrchestratorService` bascule d'un `with_mailbox` vers `with_input_mediator` en un
lot (C2→C3), tests existants `InMemoryMailbox` conservés verts.
---
*Document maintenu par l'Agent Architecture — cadrage « conversation par paire »,
base des lots C1→C7 / F1→F2 / D1 avant tout code.*

View File

@ -0,0 +1,48 @@
# 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_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** 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}\n``await 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).

View File

@ -0,0 +1,35 @@
# Protocole — Validation réelle de la conversation inter-agents via IdeA
> À exécuter depuis la **nouvelle AppImage** (build 2026-06-10 18:23, contenant R0+A0+M5,
> commits `37e7274` / `6ca519b` / `cf89b3b`). L'ancienne image (testée avant) ne contenait
> pas ce code et a renvoyé l'erreur attendue « agent Ask pas pilotable en mode structuré ».
## But
Prouver en conditions réelles qu'un agent (Main/Claude) peut **demander** une tâche à un autre
agent (Ask/Codex) **via IdeA** et **recevoir sa réponse inline** — pas seulement par tests à fakes.
## Pré-requis pour que le `ask` aboutisse
- La cible (**Ask**) doit être pilotée en **mode structuré** (profil Codex avec adapter structuré).
Le menu de sélection d'agent ne propose normalement que des profils structurés (Claude/Codex).
- Ask **ne doit pas** déjà tourner comme **terminal brut** (PTY) dans une cellule : sinon
`ask_agent` refuse (invariant « 1 session/agent », cible PTY brut = pas de canal de réponse).
- Le plus simple : **laisser Ask éteint** et laisser `ask_agent` le **lancer lui-même** en
structuré (sémantique : cible morte ⇒ launch structuré background ⇒ send ⇒ Final).
## Procédure (protocole fichier, identique au test précédent)
1. Déposer `.ideai/requests/main/<nom>.json` :
```json
{ "type": "agent.message", "requestedBy": "Main", "targetAgent": "Ask",
"task": "Petite recherche, pas de code : résume en 3 points ce que fait le module
crates/infrastructure/src/orchestrator/mcp/ et liste les outils idea_*." }
```
2. Attendre l'apparition de `.ideai/requests/main/<nom>.json.response.json`.
## Succès attendu
`{ "ok": true, "action": "agent.message", "reply": "<réponse de Codex>" }` — le champ **`reply`**
porte le contenu produit par Ask. (Échec précédent = `ok:false` + erreur PTY brut.)
## Voie native MCP (bonus)
Le bind transport S-MCP (M5) est livré : un agent lancé avec un profil MCP voit les outils
`idea_*` (dont `idea_ask_agent`) et le résultat revient inline. À valider quand un profil MCP
est branché sur Claude/Codex. Voir `.ideai/briefs/orchestration-v5-transport-bind-cadrage.md`.

View File

@ -1,25 +1,26 @@
{
"version": 1,
"activeId": "eed26045-b208-47ff-98ee-ca0d6e5933b3",
"activeId": "2fc8a7df-0bf6-4116-acd6-895ae04aa3e5",
"layouts": [
{
"id": "eed26045-b208-47ff-98ee-ca0d6e5933b3",
"id": "2fc8a7df-0bf6-4116-acd6-895ae04aa3e5",
"name": "Default",
"kind": "terminal",
"tree": {
"root": {
"type": "split",
"node": {
"id": "8cbe4c7c-357f-49ad-ac20-c96802a84684",
"id": "1eb96c70-e954-42d0-907b-f8528d0442bf",
"direction": "row",
"children": [
{
"node": {
"type": "leaf",
"node": {
"id": "e8933dbd-1c53-4342-96ca-020b9a9b7970",
"session": "51c96008-f875-4515-9b5b-50c4217f64d6",
"agent": "a6ced819-b893-4213-b003-9e9dc79b9641"
"id": "db40e3de-4980-4bed-b6fa-1c136f49d30e",
"session": "d7d4c099-eda3-4b33-82e6-e6f8d57b8b97",
"agent": "a6ced819-b893-4213-b003-9e9dc79b9641",
"agentWasRunning": true
}
},
"weight": 1.0
@ -28,8 +29,10 @@
"node": {
"type": "leaf",
"node": {
"id": "50da405b-18f3-4273-9fe0-57bb242cb2f7",
"session": "5a796d0e-433e-4ded-9955-ed02b389d9c1"
"id": "92826533-1a7c-4d9e-b6b4-f1e904ddc81a",
"session": "5d20f53a-ac76-4677-9a6f-064d3319cc73",
"agent": "edce8090-4c57-47c5-a319-c08fd172438b",
"agentWasRunning": true
}
},
"weight": 1.0

5
.ideai/memory/MEMORY.md Normal file
View File

@ -0,0 +1,5 @@
# Memory Index
- [agent-context-memory-and-profile-handoff](agent-context-memory-and-profile-handoff.md) — Decisions sur l'injection de contexte, la memoire durable, l'etat live et le handoff de profil entre agents IA.
- [idea-product-directives-main-handoff](idea-product-directives-main-handoff.md) — Directives produit consolidees pour guider Main sur la robustesse, la persistance, le handoff cross-profile et la sobriete UX.
- [remaining-work-idea-agent-control-ide](remaining-work-idea-agent-control-ide.md) — Etat des lieux des acquis et des chantiers restants pour aligner IdeA avec la cible d'IDE de controle d'agents IA.

View File

@ -0,0 +1,106 @@
---
name: agent-context-memory-and-profile-handoff
description: Decisions sur l'injection de contexte, la memoire durable, l'etat live et le handoff de profil entre agents IA.
metadata:
type: project
---
# Agent Context, Memory, and Profile Handoff
## Resume
This note captures the current product direction for IdeA around agent context injection, project memory, live state, and profile handoff between AI providers.
See also:
- `idea-product-directives-main-handoff` for product priorities and UX constraints.
- `remaining-work-idea-agent-control-ide` for the current implementation status and remaining work.
## Context Injection
- Agent context must be injected by IdeA at launch time; the model should not be expected to discover `AGENTS.md` or `CLAUDE.md` by itself.
- The existing `contextInjection` architecture is the right mechanism, especially `conventionFile` for providers that support a conventional file in the run directory.
- The current implementation is strongest for `conventionFile`; `flag`, `stdin`, and `env` do not yet receive the same fully-composed IdeA context.
- The `flag` strategy appears fragile with the isolated run directory model because the relative path passed to the CLI may not resolve from the run directory.
## Shared Project Context
- `.ideai/CONTEXT.md` is intended as shared project context.
- It is not created automatically; it only exists if something writes it.
- It should carry active project constraints and contribution rules.
- Example content for `CONTEXT.md`: architectural constraints, workflow rules, and operating conventions that every agent must apply immediately.
## Durable Memory
- `.ideai/memory/` is intended as durable, project-scoped memory shared by all agents of the same project.
- It is not created automatically; it only exists once at least one memory note is saved.
- The durable memory is a knowledge base, not a live activity log.
- It should contain stabilised knowledge such as:
- architecture decisions
- feature summaries to implement later
- user preferences that persist across sessions
- important project facts and references
- Durable memory should stay curated and low-noise.
## Live State Versus Durable Memory
- Durable memory should not be used as a shared real-time state feed for all agents.
- IdeA should distinguish between:
- global context (`.ideai/CONTEXT.md`)
- durable memory (`.ideai/memory/`)
- live operational state (separate store)
- handoff summaries between sessions or profiles
- Real-time work tracking, who-is-doing-what, and transient status should live in a dedicated state mechanism, not in durable memory.
## Memory Consumption by Agents
- The current launcher reads shared project context from `.ideai/CONTEXT.md` if present.
- It also recalls project memory via `MemoryRecall` and injects a `# Memoire projet` section into the convention file.
- This injection currently happens only for `conventionFile` profiles.
- The recalled memory is shared at the project level, but each agent may receive a different subset depending on its persona and recall query.
- Memory recall is computed at launch time and written into the generated context file.
- There is currently no automatic live refresh when `.ideai/memory/` changes during an active session.
## Recommendation for Live Memory Refresh
- Automatic memory refresh could be useful, but it should be explicit and controlled.
- If IdeA wants agents to benefit from memory changes while they are active, it should regenerate their effective context when needed instead of treating durable memory as a constantly streaming log.
- For PTY agents, no automatic reread exists today.
- For structured Claude/Codex sessions, each turn relaunches the CLI, but the generated convention file is not automatically rewritten when durable memory changes.
## Profile Handoff and Session Continuity
- A direct native session transfer from Claude to Codex is not the right mental model.
- The correct model is continuity of work state, not native provider-session continuity.
- IdeA should persist:
- a canonical conversation log
- a cumulative handoff summary
- the agent state
- the provider conversation id when useful
- On provider swap, IdeA should launch the new profile with:
- regenerated project context
- regenerated durable memory recall
- the current agent persona
- a handoff summary plus recent transcript
- The handoff summary should not be created only at the moment of swap.
- IdeA should maintain summaries incrementally or at checkpoints so that a swap is still possible when the current provider is near a token or session limit.
## Agent Ability To Write Durable Memory
- Agents should be allowed to promote important knowledge into durable memory.
- This should not rely on the agent guessing the capability.
- IdeA should expose the capability explicitly through tools or commands and should inject a clear rule explaining when an agent may save durable knowledge.
- This write ability should be constrained to stable, high-value information, not transient state.
## Practical Classification Rule
- Put immediate project rules and operating constraints in `.ideai/CONTEXT.md`.
- Put stable, reusable project knowledge in `.ideai/memory/`.
- Put current activity and coordination state in a separate live-state mechanism.
- Put cross-session or cross-profile recovery material in a handoff/session layer.
## Current Product Direction
- Keep `CONTEXT.md` for project rules.
- Keep `.ideai/memory/` for curated durable knowledge.
- Introduce a separate live-state mechanism if agents must stay aligned on in-progress work.
- Introduce persistent conversation logs and incremental handoff summaries to support profile swaps such as Claude to Codex.

View File

@ -0,0 +1,206 @@
---
name: idea-product-directives-main-handoff
description: Directives produit consolidees pour guider Main sur la robustesse, la persistance, le handoff cross-profile et la sobriete UX.
metadata:
type: project
---
# IdeA Product Directives For Main
## Resume
Cette note consolide les arbitrages produit explicites donnes par l'utilisateur pour aider `Main` a poursuivre le projet sans ambiguite.
See also:
- `agent-context-memory-and-profile-handoff` for the structural model of context, durable memory, live state, and handoff.
- `remaining-work-idea-agent-control-ide` for the current implementation status and remaining work.
Elle ne remplace pas les notes techniques existantes. Elle sert de reference prioritaire sur:
- la robustesse attendue,
- la persistance et la reprise,
- le handoff entre profils IA,
- la memoire projet partagee,
- le live state projet,
- la sobriete UX.
## Priorite Absolue
La priorite produit numero un est la robustesse.
Ordre de priorite impose:
1. robustesse et solidite avant tout
2. persistance et reprise
3. handoff cross-profile
4. live state projet
5. reste des features et du polish
Regle de pilotage:
- un systeme incomplet mais solide vaut mieux qu'un systeme riche mais fragile
- `Main` doit privilegier les architectures et comportements qui reduisent les crashes, les incoherences d'etat et les flows difficiles a reprendre
## Reprise Et Persistance
Quand IdeA redemarre, l'objectif n'est pas seulement de rouvrir une UI ou de restaurer des handles techniques.
La cible produit est:
- qu'un agent sache compactement sur quoi il travaillait
- qu'IdeA fournisse ce materiel de reprise automatiquement
- que la reprise soit exploitable meme si la conversation visible precedente n'est pas restauree a l'identique
Le bon modele est:
- un log canonique IdeA comme source durable
- un resume/handoff genere par IdeA comme couche compacte de reprise
Le resume/handoff n'est pas un confort secondaire. Il fait partie du comportement normal du produit.
## Handoff Cross-Profile
La cible ideale est double:
- reprendre correctement le travail
- donner si possible une impression de continuite presque sans rupture
Mais en cas de compromis, la priorite doit etre:
- fidelite operationnelle du travail repris
- avant la parfaite illusion de continuite terminale ou conversationnelle
Autrement dit:
- si un agent passe de Claude a Codex, IdeA doit d'abord garantir que Codex puisse reprendre le plus fidelement possible le travail utile
- l'absence de restauration parfaite de l'ancien terminal est acceptable si le handoff reste bon
## Perimetre Profils
Le perimetre de reference immediat est:
- Claude
- Codex
Toute fonctionnalite importante doit etre faisable pour ces deux profils.
Directive associée:
- reduire au maximum les dependances a des commandes, flags ou comportements specifiques a un profil
- construire un noyau le plus generique possible tout en restant concretement compatible Claude/Codex
- les autres profils pourront etre ajoutes plus tard si possible, mais ne doivent pas detourner le coeur du chantier actuel
## Memoire Projet Partagee
La memoire projet partagee doit rester petite, stable et utile.
Elle ne doit pas devenir un gros bloc qui siphonne les tokens de l'utilisateur a chaque requete.
Ce qu'un agent peut ecrire automatiquement dans la memoire partagee si c'est stable et utile:
- decisions durables d'architecture ou d'organisation
- preferences utilisateur durables
- regles de workflow durables
- references importantes a conserver
- resumes de handoff utiles a la reprise inter-session ou inter-profil
Ce qu'un agent ne doit pas y ecrire automatiquement:
- conversations brutes
- journaux detailles de travail
- etats temporaires
- files d'attente
- coordination temps reel
- essais/erreurs locaux
- hypotheses non stabilisees
- contenu redondant ou reconstructible ailleurs
Principe de fond:
- memoire durable = savoir stable
- log canonique = historique
- handoff = reprise compacte
- live state = coordination vivante
Ces couches doivent rester separees.
## Live State Projet
Le live state projet partage doit exister comme mecanisme interne d'IdeA.
Contraintes produit:
- il doit rester invisible pour l'utilisateur
- il doit survivre au redemarrage d'IdeA
Il ne doit pas se transformer en UI verbeuse ni en mecanisme demandant une intervention explicite de l'utilisateur.
## UX Et Philosophie Produit
IdeA doit etre tres facile d'utilisation.
Objectif UX:
- plug and play
- pas de sensation de parametrage impose
- pas de surcharge de tuto au premier lancement
- pas d'impression que le produit force des comportements internes a l'utilisateur
Ligne directrice souhaitee:
- esprit "maniere Linux"
- comportement simple et utile par defaut
- pas de contrainte tant qu'il n'y a pas un vrai besoin
- suggestion discrete seulement si IdeA detecte qu'une aide ou une optimisation devient utile
Le precedent du compactage de contexte est considere comme la bonne direction:
- pas de compactage impose d'emblee
- une popup proposee seulement si IdeA sent un besoin
## Transparence Des Mecanismes Internes
Les mecanismes suivants doivent rester quasi invisibles pour l'utilisateur:
- delegations inter-agents
- FIFO
- handoffs
Ils peuvent devenir visibles en debug ou quand le produit a une bonne raison UX de les exposer, mais ils ne doivent pas etre ressentis comme une charge cognitive normale d'utilisation.
## Frontiere Avec Le Chantier Inter-Agents De Main
Les choix fins touchant la communication entre agents ne doivent pas etre recadres ici si `Main` est deja en train de les traiter.
Cette note ne doit donc pas etre lue comme une specification d'implementation inter-agents detaillee.
Elle fixe seulement les invariants produit suivants:
- robustesse avant richesse fonctionnelle
- reprise automatique par IdeA
- log canonique + handoff genere par IdeA
- support de reference pour Claude et Codex
- memoire durable compacte et curatee
- live state interne et persistant
- UX discrete, simple et peu intrusive
## Directive Finale Pour Main
Si un arbitrage technique oppose:
- elegance theorique
- ou livraison rapide
contre:
- robustesse
- reprise fiable
- sobriete UX
alors `Main` doit privilegier:
- robustesse
- reprise fiable
- sobriete UX
avant le reste.

View File

@ -0,0 +1,273 @@
---
name: remaining-work-idea-agent-control-ide
description: Etat des lieux des acquis et des chantiers restants pour aligner IdeA avec la cible d'IDE de controle d'agents IA.
metadata:
type: project
---
# Remaining Work For IdeA Agent Control IDE
## Resume
Cette note sert de point de reprise pour l'agent `Main`. Elle distingue ce qui est deja implemente, ce qui reste a stabiliser, et ce qui reste a construire pour que IdeA corresponde pleinement a la vision "patron + employes IA" avec memoire projet partagee, contexte partage, messagerie inter-agents transparente, FIFO par agent, et persistance de reprise.
See also:
- `idea-product-directives-main-handoff` for product priorities and UX constraints.
- `agent-context-memory-and-profile-handoff` for the structural model separating context, durable memory, live state, and handoff.
Etat observe le 2026-06-11 sur le depot local:
- L'orchestration inter-agents synchrone est deja reelle cote application.
- La FIFO par agent, la conversation par paire et `idea_reply` sont deja couvertes par des tests verts.
- Le transport MCP natif par projet et le loopback sont testes verts localement.
- La reprise de conversation cote session/layout est largement presente dans le code.
- En revanche, plusieurs briques produit restent inachevees ou non consolidees de bout en bout.
## Deja Livre Ou Tres Avance
### 1. Artefacts projet dans `.ideai/`
- Les agents projet sont persistes dans `.ideai/agents.json` et `.ideai/agents/*.md`.
- Le contexte projet partage est modelise via `.ideai/CONTEXT.md`.
- La memoire projet partagee est modelisee via `.ideai/memory/*.md` + `.ideai/memory/MEMORY.md`.
- Les requetes d'orchestration fichier vivent sous `.ideai/requests/`.
Conclusion: la direction "tout ce qui releve d'IdeA pour un projet doit vivre dans `.ideai/`" est deja la bonne direction architecturale. Il reste surtout a eliminer les ecarts pratiques et a consolider l'usage reel.
### 2. Memoire projet partagee
- `FsMemoryStore` et `MemoryRecall` existent.
- Les agents peuvent recevoir un rappel de memoire projet a l'activation.
- Le frontend et les use cases CRUD memoire existent deja.
Conclusion: la memoire partagee du projet n'est plus un concept a inventer. Le travail restant est plutot sur la qualite du rappel, la curation, et l'usage continu pendant la vie des sessions.
### 3. Contexte partage et contexte agent
- Le contexte partage projet est separe du contexte agent.
- Les contextes agent sont persistes sous `.ideai/agents/*.md`.
- Le launcher injecte deja le contexte compose au demarrage.
Conclusion: la separation `contexte projet` / `contexte agent` est en place.
### 4. Messagerie inter-agents transparente
- `AgentMailbox` + `InMemoryMailbox` existent.
- `ConversationRegistry` + `InMemoryConversationRegistry` existent.
- `OrchestratorService::ask_agent` et `reply` existent.
- Les outils MCP `idea_ask_agent`, `idea_reply`, `idea_launch_agent`, `idea_list_agents`, `idea_stop_agent`, `idea_update_context`, `idea_create_skill` existent.
- Les tests applicatifs passent sur FIFO, reponse synchrone, prevention de cycle, timeout, parallélisme entre cibles differentes.
Verification locale du 2026-06-11:
- `cargo test -p application --test orchestrator_service` : OK
- `cargo test -p infrastructure --test mcp_server` : OK
- `cargo test -p app-tauri --test orchestrator_wiring` : OK
Conclusion: le coeur de la communication inter-agents n'est plus un chantier de conception. Il est deja implementé et teste.
### 5. FIFO transparente quand un agent est occupe
- La file d'entree par agent existe deja.
- La serialisation des tours vers une meme cible existe.
- Les `ask` concurrents vers des agents differents peuvent tourner en parallele.
Conclusion: l'exigence "si l'utilisateur ou un autre agent parle a un agent deja occupe, la requete part en file FIFO de maniere transparente" est deja largement satisfaite au niveau coeur applicatif.
### 6. Reprise de conversation et persistance de session
- Les cellules/layouts persistent `conversation_id` et `agent_was_running`.
- Les use cases de reprise (`ListResumableAgents`, popup de reprise, relance avec `conversation_id`) existent.
- Les sessions structurees Claude/Codex savent porter un `conversation_id`.
Conclusion: la persistance de reprise a deja une base concrete et substantielle.
## Reste A Faire En Priorite
### 1. Consolider la persistance "conversation continue" au niveau produit, pas seulement "resume technique"
Le code sait deja reprendre une conversation via `conversation_id`, mais la cible produit demande plus qu'une simple reprise technique:
- conserver une vraie continuite de conversation lisible pour l'utilisateur au redemarrage,
- permettre au nouvel agent/profil de repartir avec l'etat utile,
- rendre la reprise completement transparente dans l'UX.
Reste donc a verrouiller:
- la persistance canonique des conversations exploitable au niveau produit,
- la strategie de resume/handoff quand on change de profil IA,
- la coherence UX entre reprise de cellule, reprise de conversation, et reprise de travail.
### 2. Implementer une couche persistante de handoff / resume cross-profile
La memoire partagee existante dit explicitement qu'il faut persister:
- un canonical conversation log,
- un cumulative handoff summary,
- l'etat agent,
- les identifiants de conversation utiles par provider.
Ce point n'apparait pas comme livre de bout en bout dans le depot actuel.
Le besoin produit reste ouvert:
- si un agent passe de Claude a Codex, IdeA doit reconstituer l'etat de travail sans dependre d'une session native transferable,
- le handoff doit etre incremental, pas fabrique seulement au moment de la panne ou du swap.
### 3. Introduire un vrai live-state partage au niveau projet
La memoire durable ne doit pas servir de journal temps reel. La note memoire existante le dit deja.
Il manque encore une couche explicite de "live operational state" pour:
- qui travaille sur quoi,
- tickets/intentions en cours,
- etat d'avancement d'un agent,
- derniere delegation utile,
- elements transitoires de coordination inter-agents.
Sans cette couche, une partie de la coordination reste soit volatile, soit repoussee dans des endroits qui ne sont pas faits pour ca.
### 4. Verifier et finir l'integration MCP natif "IdeA-only" de bout en bout dans le flux reel de l'application
Les tests locaux du transport MCP passent, ce qui place cette zone en fin de chantier plutot qu'au debut.
Mais il reste a confirmer en situation reelle utilisateur:
- qu'un agent lance par IdeA voit effectivement ses outils MCP sans action manuelle,
- que les profils supportes utilisent bien cette voie par defaut,
- que le fallback fichier+prose reste coherent quand MCP n'est pas disponible,
- que l'observabilite UI des delegations et des replies est suffisamment claire.
Point important: l'architecture historique qui mentionne encore un verrou M5 ouvert est probablement en retard par rapport au worktree local. Avant de planifier le prochain lot, `Main` doit revalider la documentation d'architecture a la lumiere du code/tests actuels.
### 5. Stabiliser le registre de sessions et clarifier le modele singleton d'agent
Le produit veut "1 agent = 1 employe". Cela impose une verite unique sur:
- la session vivante de l'agent,
- sa conversation courante,
- sa cellule visible ou son execution en arriere-plan,
- son etat occupé/libre/interrompu.
Le code a deja beaucoup avance sur ce point, mais le worktree local montre encore un chantier actif autour de:
- `application/src/terminal/registry.rs`
- `application/src/orchestrator/service.rs`
- `application/src/agent/lifecycle.rs`
- `app-tauri/src/state.rs`
Conclusion: ne pas considerer le sujet comme totalement clos tant que le worktree n'est pas nettoye et que la suite de tests ciblee n'est pas executee sur l'ensemble du flux concerne.
### 6. Rendre la mise a jour de memoire/contexte vraiment automatique pendant la vie d'un agent
La cible utilisateur dit qu'il ne doit jamais demander:
- de charger une memoire,
- de charger un contexte,
- de mettre a jour la memoire,
- de mettre a jour le contexte.
Le lancement injecte deja beaucoup de choses automatiquement, mais il reste a verrouiller le comportement "pendant la vie" d'un agent:
- quand regenerer le contexte effectif,
- quand promouvoir une information stable vers la memoire durable,
- comment distinguer signal utile et bruit,
- comment eviter de compter sur des consignes manuelles a l'utilisateur.
### 7. Unifier la conversation utilisateur <-> agent et agent <-> agent dans l'UX
Le backend sait deja distinguer `User<->Agent` et `Agent<->Agent`.
Le travail restant est surtout produit/frontend:
- affichage clair des delegations et des retours,
- visualisation non confuse des conversations par paire,
- reprise lisible des threads,
- transparence totale pour l'utilisateur final.
Le diff local frontend suggere justement un remaniement en cours de la surface terminal/chat.
### 8. Consolider la restriction et l'affordance des profils supportes
Le modele actuel oriente fortement vers Claude/Codex structures, ce qui est coherent avec la fiabilite attendue.
Reste a clarifier produit:
- quels profils sont officiellement "employes IdeA" de premiere classe,
- quel fallback proposer pour les profils non structures,
- quelle UI montrer quand un profil ne supporte pas la delegation native fiable.
### 9. Persistance conversationnelle globale de l'application
La demande utilisateur mentionne explicitement qu'en relancant IdeA il faut retrouver la conversation.
La reprise par `conversation_id` et layouts existe, mais il reste a confirmer ou completer:
- la persistance lisible de l'historique conversationnel pour l'utilisateur,
- la restauration des vues au redemarrage,
- la coherence entre session technique, resume visuel et histoire de travail.
Autrement dit: "reprendre une session moteur" n'est pas encore automatiquement equivalent a "retrouver sa conversation produit" dans tous les cas.
## Chantiers Secondaires Mais Importants
### 1. Mettre la documentation d'architecture a jour
Le code local et les tests verts semblent avoir depasse certains passages de `ARCHITECTURE.md` et de briefs anciens.
Il faut une passe de synchronisation documentaire pour eviter que `Main` suive un etat obsolete, en particulier sur:
- statut reel du transport MCP,
- statut reel de la FIFO inter-agents,
- statut reel des conversations par paire,
- ce qui reste vraiment ouvert entre handoff, live-state et UX.
### 2. Curater le dossier `.ideai/`
Le principe "tout ce qui est IdeA-projet va dans `.ideai/`" est bon, mais il faudra surveiller:
- la proliferation de fichiers run/request/debug,
- ce qui est durable vs derivable,
- ce qui doit etre committe vs ignore.
### 3. Formaliser les regles de promotion memoire
Le systeme doit savoir quand enregistrer une connaissance stable sans polluer la memoire partagee.
Il manque probablement encore:
- une politique claire de promotion,
- des heuristiques/outils explicites pour les agents,
- des garde-fous contre la memoire bruit.
## Worktree Local A Prendre En Compte
Le depot local est actuellement dirty avec un chantier large non committe autour de:
- orchestration MCP / loopback / serveur Tauri,
- mailbox / conversations / session registry,
- adaptation frontend terminal/chat/layout,
- fichiers `.ideai/` du projet lui-meme.
Consequence pour `Main`:
- ne pas planifier a partir de `ARCHITECTURE.md` seulement,
- d'abord relire le diff local,
- ensuite reexecuter la suite de tests ciblee des zones touchees,
- puis seulement decider si le prochain lot est "finition", "integration UI", ou "harden/persistence".
## Ordre Recommande Pour La Suite
1. Revalider et documenter l'etat reel du chantier MCP/orchestration a partir du code courant, puis remettre `ARCHITECTURE.md` a jour.
2. Fermer proprement le sujet "1 agent = 1 session vivante coherente" en nettoyant le registre/session lifecycle encore en mouvement.
3. Concevoir puis implementer une vraie couche de live-state partage projet.
4. Concevoir puis implementer la couche persistante de handoff/canonical conversation log cross-session et cross-profile.
5. Finir l'integration UX/frontend pour que toute cette orchestration reste invisible et naturelle pour l'utilisateur final.
## Synthese Courte
Le plus gros changement de perception pour `Main` est le suivant:
- IdeA n'est plus au stade "il faut inventer la delegation inter-agents".
- IdeA est plutot au stade "le coeur de delegation existe deja; il faut maintenant le consolider, le documenter, le rendre pleinement persistant, et le rendre transparent dans l'UX".