docs(live-state): clôture programme persistance/live-state (LS8)

Documentation de clôture du programme live-state/persistance (LS1→LS7, livré @ fd7adbb) :
- docs/LS8-live-state-persistence-closure.md : référence durable (4 stores, flux
  d'injection, chemin chaud/froid, règle de frontière, points ouverts).
- ARCHITECTURE.md : §19 marqué LIVRÉ, §14.1 items 7-8 (live-state + handoff),
  §18.5 catalogue MCP de 14 outils, nouvelle §21 (cartographie de clôture).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 17:53:02 +02:00
parent fd7adbb8a0
commit 36be0cb396
2 changed files with 158 additions and 5 deletions

View File

@ -647,11 +647,13 @@ IdeA/
**Convention file généré par IdeA** : IdeA écrit dans ce dossier le fichier conventionnel attendu par le profil (`CLAUDE.md`, `AGENTS.md`, etc.). Ce fichier contient : **Convention file généré par IdeA** : IdeA écrit dans ce dossier le fichier conventionnel attendu par le profil (`CLAUDE.md`, `AGENTS.md`, etc.). Ce fichier contient :
1. Le **chemin absolu du project root** (pour que l'agent sache où opérer). 1. Le **chemin absolu du project root** (pour que l'agent sache où opérer).
2. Le contrat d'**orchestration IdeA** (délégation via `.ideai/requests`, pas via les subagents natifs du fournisseur). 2. Le contrat d'**orchestration IdeA** + brief capacités (délégation via outils `idea_*` en surface MCP, sinon via `.ideai/requests` — jamais les subagents natifs du fournisseur).
3. Le **contexte projet partagé** (`.ideai/CONTEXT.md`), si présent. 3. Le **contexte projet partagé** (`.ideai/CONTEXT.md`), si présent.
4. La **persona/rôle** de l'agent (son `.md` dans `.ideai/agents/`). 4. La **persona/rôle** de l'agent (son `.md` dans `.ideai/agents/`).
5. Les **skills actifs** assignés à cet agent (voir §14.2). 5. Les **skills actifs** assignés à cet agent (voir §14.2).
6. Le **rappel mémoire** du projet (index/hooks), si présent (voir §14.5.4). 6. Le **rappel mémoire** du projet (index/hooks — pointeurs), si présent (voir §14.5.4).
7. L'**état du projet** (section `# État du projet`) — projection *live-state* maigre « qui fait quoi maintenant », bornée (cap `LIVE_STATE_INJECT_MAX`, agent lancé exclu, ordre manifeste, vide ⇒ omise) (LS4 ; voir §21 et `docs/LS8`).
8. La **reprise de la conversation** (section `# Reprise de la conversation`) — *handoff* distillé du fil, **borné** (`HANDOFF_SUMMARY_MAX_CHARS=4096`) à l'écriture **et** à l'injection (LS5 ; voir §21). Omise sans handoff.
**Avantages** : **Avantages** :
- Zéro collision entre agents, même N instances du même profil. - Zéro collision entre agents, même N instances du même profil.
@ -1926,16 +1928,16 @@ Nouvelles commandes (jumelles des commandes PTY existantes ; réutilisent `resol
- **Vivant de bout en bout** : `apply_mcp_config` matérialise `.mcp.json` dans le **run dir isolé** de l'agent **AVANT** le split structuré/PTY (`crates/application/src/agent/lifecycle.rs` ~1094) ⇒ Claude/Codex le lisent **nativement**. La déclaration porte l'**exe réel** injecté par `McpRuntime` (`$APPIMAGE` sinon `current_exe`, `crates/app-tauri/src/mcp_endpoint.rs:139` `idea_exe_path`), l'**endpoint loopback** du projet, le `--project` et le `--requester` (agent réel, fin du `"mcp"` figé). - **Vivant de bout en bout** : `apply_mcp_config` matérialise `.mcp.json` dans le **run dir isolé** de l'agent **AVANT** le split structuré/PTY (`crates/application/src/agent/lifecycle.rs` ~1094) ⇒ Claude/Codex le lisent **nativement**. La déclaration porte l'**exe réel** injecté par `McpRuntime` (`$APPIMAGE` sinon `current_exe`, `crates/app-tauri/src/mcp_endpoint.rs:139` `idea_exe_path`), l'**endpoint loopback** du projet, le `--project` et le `--requester` (agent réel, fin du `"mcp"` figé).
- **Endpoint loopback** = **UDS** (Linux/macOS) / **named pipe** (Windows), **zéro port réseau** ; source de vérité unique `mcp_endpoint(project_id)` (`mcp_endpoint.rs`), bindé à l'open / fermé au close (`crates/app-tauri/src/state.rs` `ensure_mcp_server`/`bind_endpoint`). **Fix D1** : cadavre `.sock` (run SIGKILL) **unlinké avant bind** (`state.rs` ~1019-1033) — sinon `EADDRINUSE`. - **Endpoint loopback** = **UDS** (Linux/macOS) / **named pipe** (Windows), **zéro port réseau** ; source de vérité unique `mcp_endpoint(project_id)` (`mcp_endpoint.rs`), bindé à l'open / fermé au close (`crates/app-tauri/src/state.rs` `ensure_mcp_server`/`bind_endpoint`). **Fix D1** : cadavre `.sock` (run SIGKILL) **unlinké avant bind** (`state.rs` ~1019-1033) — sinon `EADDRINUSE`.
- **Serveur** `McpServer` (`crates/infrastructure/src/orchestrator/mcp/server.rs`) = **jumeau du `FsOrchestratorWatcher`** : autre porte sur le **même** `OrchestratorService::dispatch`, `serve(conn)` **par pair**. Transport `StdioTransport` (JSON Lines stdin/stdout du pont) + `MemoryTransport` (tests, sans socket ni process). Pont = sous-commande `mcp-server` du binaire app-tauri (`mcp_bridge.rs`). - **Serveur** `McpServer` (`crates/infrastructure/src/orchestrator/mcp/server.rs`) = **jumeau du `FsOrchestratorWatcher`** : autre porte sur le **même** `OrchestratorService::dispatch`, `serve(conn)` **par pair**. Transport `StdioTransport` (JSON Lines stdin/stdout du pont) + `MemoryTransport` (tests, sans socket ni process). Pont = sous-commande `mcp-server` du binaire app-tauri (`mcp_bridge.rs`).
- **Outils** `idea_ask_agent` / `idea_reply` / `idea_list_agents` (`mcp/tools.rs`) mappés 1:1 vers `OrchestratorCommand` ; `dispatch` appelé **à l'identique** par les trois portes (fichier, MCP, UI). - **Outils** (`mcp/tools.rs`) — **catalogue de 14**, tous mappés 1:1 vers `OrchestratorCommand` et servis par le **même** `dispatch` (les trois portes : fichier, MCP, UI) : `idea_list_agents`, `idea_ask_agent`, `idea_reply`, `idea_launch_agent`, `idea_stop_agent`, `idea_update_context`, `idea_create_skill` (7 base) · `idea_context_read`, `idea_context_propose`, `idea_memory_read`, `idea_memory_write` (4 FileGuard C7) · `idea_skill_read` (skill-awareness) · `idea_workstate_read`, `idea_workstate_set` (LS4 ; `set` n'écrit que la ligne de l'agent courant, via l'identité handshake).
### 18.6 Invariant « 1 agent = 1 session vivante » (livré) ### 18.6 Invariant « 1 agent = 1 session vivante » (livré)
- Registres `TerminalSessions` + `StructuredSessions` (`crates/application/src/terminal/registry.rs`) agrégés en `LiveSessions` (PTY+structuré). `session_for_agent` (singulier, **non ambigu**) **+** `sessions_for_agent` (pluriel). Garde reattach `Rebind`/`Refuse`/`Idempotent` dans `LaunchAgent` (`lifecycle.rs`). Ancienne ambiguïté `session-registry-agent-ambiguity` = **fermée par construction**. - Registres `TerminalSessions` + `StructuredSessions` (`crates/application/src/terminal/registry.rs`) agrégés en `LiveSessions` (PTY+structuré). `session_for_agent` (singulier, **non ambigu**) **+** `sessions_for_agent` (pluriel). Garde reattach `Rebind`/`Refuse`/`Idempotent` dans `LaunchAgent` (`lifecycle.rs`). Ancienne ambiguïté `session-registry-agent-ambiguity` = **fermée par construction**.
--- ---
## 19. Cadrage — couche de persistance conversationnelle + handoff cross-profile incrémental (chantier à découper, PAS d'implémentation) ## 19. Persistance conversationnelle + handoff cross-profile incrémental (cadrage — LIVRÉ P1→P8 + LS1→LS7)
> **Cadrage architecture** (le prochain chantier prioritaire après robustesse : **persistance/reprise → handoff cross-profile**). Produit les **ports**, les **adapters**, les **frontières** et un **découpage en lots testables**. **Aucun code applicatif ici** : la doc est livrable, les lots seront confiés aux binômes Dev/Test (cycle §3). > **⚠️ STATUT : LIVRÉ (clôture programme live-state/persistance, @ `fd7adbb`).** Ce qui suit était le **cadrage** ; il est désormais **implémenté** (P1→P8 du handoff, puis LS1→LS7 : live-state, borne `summary_md`, rotation/pagination, viewer). La **cartographie de clôture** (4 stores, flux d'injection, chemin chaud vs froid, ce qui reste ouvert) fait foi en **§21** et dans [`docs/LS8-live-state-persistence-closure.md`](docs/LS8-live-state-persistence-closure.md). Précisions sur l'état réel : (1) **P10 (`LlmHandoffSummarizer`)** reste **non activé** — le défaut runtime est l'heuristique borné (LS5, ADR `docs/adr/LS5-handoff-summary-bound-and-llm-seam.md`) ; (2) la **borne `summary_md`** (LS5 : `HANDOFF_SUMMARY_MAX_CHARS=4096`, `bound_handoff_summary`, `TURN_LINE_MAX_CHARS=240`) est appliquée à l'écriture **et** à l'injection ; (3) la **rotation/rétention** du `log.jsonl` (LS6 : archive segmentée, port `ConversationArchive`, lecture paginée, invariant INV-LS6) et le **live-state** (LS1→LS4) ont été ajoutés **au-delà** de ce cadrage initial. Le texte ci-dessous est conservé comme **genèse** ; en cas de divergence, **§21 + `docs/LS8` font foi**.
### 19.0 Problème & objectif produit ### 19.0 Problème & objectif produit
Aujourd'hui la continuité d'une conversation repose sur le **`resumable_id` CLI** (`Conversation.resumable_id`, profil `SessionStrategy{assign_flag,resume_flag}`) : au redémarrage on **rejoue la session du provider** (`--resume <id>`). Deux trous : Aujourd'hui la continuité d'une conversation repose sur le **`resumable_id` CLI** (`Conversation.resumable_id`, profil `SessionStrategy{assign_flag,resume_flag}`) : au redémarrage on **rejoue la session du provider** (`--resume <id>`). Deux trous :
@ -2345,4 +2347,32 @@ pub enum ScheduledTask {
--- ---
## 21. Clôture du programme live-state / persistance (LS1→LS7) — cartographie des 4 stores
> **Référence durable de clôture** (programme livré @ `fd7adbb`). Détail complet, acquis lot par lot et points ouverts : [`docs/LS8-live-state-persistence-closure.md`](docs/LS8-live-state-persistence-closure.md). §21 fait foi sur la séparation des stores et le flux d'injection.
### 21.1 Quatre stores disjoints (frontière gravée)
| Store | Fichier(s) | Nature | Surface | Versionné | Injecté agent |
|---|---|---|---|---|---|
| Mémoire projet | `.ideai/memory/*.md` + `MEMORY.md` | Savoir stable, curé, low-noise | Agent + humain | Oui | Oui (index/hooks — pointeurs) |
| Handoff | `.ideai/conversations/<id>/handoff.md` | Reprise par fil = dérivé distillé **borné** (≤4096) | Agent (distillé) | Non | Oui (`# Reprise de la conversation`, borné LS5) |
| Transcript | `.ideai/conversations/<id>/log.jsonl` (+ `log.N.jsonl`) | Journal append-only **riche**, source de vérité | **Humain seul** | cf. point ouvert | **JAMAIS** |
| Live-state | `.ideai/live-state.json` | Coordination transitoire **maigre**, keyed LWW, prune TTL+max | Agent (maigre) + humain | Non (gitignoré) | Oui (`# État du projet`, borné LS4) |
**Règle** : surface AGENT = borné/distillé/pointeur ; surface HUMAINE = riche. Le **transcript append-only ne franchit JAMAIS** vers un contexte agent ; seul le **handoff distillé** passe la frontière (borné des deux côtés). Le live-state est keyed last-writer-wins (jamais d'append).
### 21.2 Injection au lancement (ordre `compose_convention_file`)
`# Project root` → `# Orchestration IdeA` (+ capacités) → `# Skills disponibles` → `# Contexte projet` → persona → `# Mémoire projet` (pointeurs) → **`# État du projet`** (live-state lean, LS4) → **`# Reprise de la conversation`** (handoff borné, LS5). Sections vides omises.
### 21.3 Chemin chaud vs froid
- **Chaud** (cheap, jamais ralenti) : `append` (O(1)+fsync), `fold` incrémental + `bound_handoff_summary`, auto-update live-state best-effort sur `ask`/`reply`. **Aucun LLM.**
- **Froid** : rotation `RotateConversationLog` (à la reprise, best-effort, idempotente, **INV-LS6** : jamais d'élagage d'un tour d'id ≥ `up_to`), lecture paginée `read_conversation_page` (viewer humain), `GetLiveStateLean` (prune-on-read).
### 21.4 Reste ouvert
(1) activation réelle du seam LLM (non activé, défaut heuristique, contrat ADR LS5) ; (2) balayage périodique de rotation (idempotent, non câblé) ; (3) discordance D19-4 vs `.gitignore` sur `.ideai/conversations/` (à trancher Git/Main) ; (4) intégration MCP e2e UX ; (5) évolutions multi-fenêtres du registre de sessions ; (6) auto-update mémoire/contexte *en cours* de session. Détail : `docs/LS8` §7.
*Document maintenu par l'Agent Architecture — base du jalon « cadrage architecture » avant tout code applicatif.* *Document maintenu par l'Agent Architecture — base du jalon « cadrage architecture » avant tout code applicatif.*

View File

@ -0,0 +1,123 @@
# LS8 — Clôture du programme *live-state / persistance conversationnelle*
> **Statut : LIVRÉ.** Lots LS1→LS7 mergés dans `develop` @ `fd7adbb`. Ce document est la **référence durable** du programme : ce qui est acquis, la cartographie des **4 stores**, le flux d'injection au lancement, la séparation chemin chaud / chemin froid, la règle de frontière gravée, et ce qui **reste ouvert**.
>
> Propriété : Agent Architecture. Fait foi avec `ARCHITECTURE.md` §18 (modules livrés), §19 (cadrage persistance, désormais livré) et §21 (cartographie de clôture). ADR lié : [`docs/adr/LS5-handoff-summary-bound-and-llm-seam.md`](adr/LS5-handoff-summary-bound-and-llm-seam.md).
---
## 1. Objet du programme
Donner à un agent IA, **sans dégrader ses performances**, le contexte minimal pour :
1. **se coordonner** avec les autres agents du projet (qui fait quoi *maintenant*) — *live-state* ;
2. **reprendre** un fil de travail au redémarrage ou après un swap de profil, indépendamment du `resumable_id` propre au provider — *handoff cross-profile incrémental* ;
3. **conserver** un transcript humain fidèle, sans croissance disque non bornée — *log canonique + rotation* ;
4. **relire** ce transcript dans l'UI, par paire d'agents — *viewer LS7*.
Principe directeur (priorité produit n°1) : **efficacité agent d'abord**. Tout ce qui touche le contexte injecté est **borné/distillé** ; le résumé n'appelle **jamais** un LLM sur le chemin chaud ; la rétention ne ralentit **jamais** l'append.
---
## 2. Acquis lot par lot (LS1→LS7)
| Lot | Livré | Emplacement |
|---|---|---|
| **LS1** | Modèle live-state pur : `LiveState`/`LiveEntry`/`WorkStatus`, port `LiveStateStore`. Keyed **last-writer-wins** (jamais d'append), champs free-text bornés (soft-trunc `FIELD_PREVIEW_MAX_CHARS=160`, hard-reject `FIELD_MAX_BYTES=2 KiB`), `prune(now, ttl, max_n)`. | `domain/src/live_state.rs`, port dans `domain/src/ports.rs` |
| **LS2** | Use cases `UpdateLiveState` (estampille via `Clock`, applique les bornes domaine) et `GetLiveStateLean` (**prune-on-read** TTL+max, renvoie le DTO maigre `LeanLiveState`). Adapter `FsLiveStateStore` (`live-state.json`, écriture atomique tmp+rename, fichier absent ⇒ état vide). | `application/src/workstate/live.rs`, `infrastructure/src/store/live_state.rs` |
| **LS3** | Auto-update **dérivé** du cycle de délégation (zéro token agent, zéro appel modèle) : `ask` accepté ⇒ cible `Working` ; `reply` rendu ⇒ cible `Done` (+ `last_delegation`). **Best-effort strict** : un échec n'échoue jamais la délégation. Provider par root `LiveStateProvider`. | `application/src/orchestrator/service.rs`, wiring `app-tauri/src/state.rs` |
| **LS4** | (a) Injection bornée d'une section `# État du projet` dans le convention file au lancement — **entre** `# Mémoire projet` et `# Reprise de la conversation`, cap `LIVE_STATE_INJECT_MAX`, exclut l'agent lancé, ordre manifeste, vide ⇒ section omise. (b) Outils MCP `idea_workstate_read` (lecture lean enrichie du nom) et `idea_workstate_set` (écrit la ligne de **l'agent courant** via identité handshake, jamais celle d'un autre). **Catalogue MCP 12 → 14.** Clôt le cold-start de coordination. | `application/src/agent/lifecycle.rs`, `domain/src/orchestrator.rs`, `infrastructure/src/orchestrator/mcp/tools.rs`, `app-tauri/src/state.rs` |
| **LS5** | Borne du `summary_md` du handoff (le gain de perf central) : fn pure domaine `bound_handoff_summary` + `HANDOFF_SUMMARY_MAX_CHARS=4096`, borne par tour `TURN_LINE_MAX_CHARS=240` dans le résumeur. Appliquée **à l'écriture** (`RecordTurn`, après `fold`) **et défensivement à l'injection** (`resolve_handoff`, protège les `handoff.md` legacy). Stratégie de dépassement = **troncature distillée** (objectif + tours les plus récents), jamais re-fold ni rejet, jamais bloquant. **Seam LLM durci mais NON activé** : défaut runtime = `HeuristicHandoffSummarizer`. | `domain/src/conversation_log.rs`, `infrastructure/src/conversation_log/summarizer.rs`, `application/src/conversation/record.rs`, `application/src/agent/lifecycle.rs`. ADR : `docs/adr/LS5-…` |
| **LS6** | Rétention/rotation du `log.jsonl` : **archive segmentée** (`log.jsonl` actif + `log.N.jsonl` anciens), **hors chemin chaud** (use case déclenché à la reprise, best-effort, idempotent ; l'`append` ne déclenche jamais la rotation). Politique pure `rotation_plan`, seuils `ROTATE_AFTER_TURNS=500` / `ROTATE_AFTER_BYTES=1 MiB` / `MAX_ARCHIVE_SEGMENTS=20`. Port `ConversationArchive` (`stats`/`rotate`/`page`) + use cases `RotateConversationLog` / `ReadConversationPage` + commande Tauri `read_conversation_page`. | `domain/src/conversation_log.rs`, `infrastructure/src/conversation_log/mod.rs`, `application/src/conversation/{rotate,paginate}.rs`, `app-tauri` |
| **LS7** | Viewer humain **fil-par-paire** (frontend pur, lecture seule) : gateway `ConversationGateway.readPage` + adapter Tauri + mock, drill-down depuis le **Work panel**, swap du main-area en **état local** (pas de nouveau `kind` backend). | `frontend/src/{ports,adapters,domain}`, `frontend/src/features/conversations/`, intégration `ProjectsView.tsx` |
---
## 3. Cartographie des 4 stores (frontière gravée)
Quatre stockages **disjoints**, chacun avec une nature, une surface et un cycle de vie propres. **Aucun** ne se substitue à un autre ; un seul (le handoff distillé) franchit vers un contexte agent.
| Store | Fichier(s) | Nature | Surface | Versionné ? | Injecté à un agent ? |
|---|---|---|---|---|---|
| **Mémoire projet** | `.ideai/memory/*.md` + `MEMORY.md` | Savoir projet **stable, low-noise**, curé | Agent **et** humain | **Oui** (savoir projet) | **Oui** — index/hooks (pointeurs), §14.5.4 |
| **Handoff** | `.ideai/conversations/<id>/handoff.md` | Reprise par fil = **dérivé distillé borné** (≤ 4096 chars) | Agent (distillé) | Non (runtime) | **Oui** — section `# Reprise de la conversation`, **bornée** (LS5) |
| **Transcript canonique** | `.ideai/conversations/<id>/log.jsonl` (+ `log.N.jsonl`) | Journal **append-only riche**, source de vérité, volumineux/bruité | **Humain uniquement** | Voir §7 (point ouvert) | **JAMAIS** — interdit d'injection |
| **Live-state** | `.ideai/live-state.json` | Coordination **transitoire maigre** (« qui fait quoi maintenant »), keyed LWW, prune TTL+max | Agent (maigre) + humain (Work panel) | **Non** — gitignoré, reconstruit au runtime | **Oui** — section `# État du projet`, **bornée** (LS4) |
`providers.json` (resumable_id par provider, §19.7) complète le dossier conversation mais n'est pas une « mémoire » : c'est l'optimisation de reprise non portable, rangée à côté du handoff portable.
**Règle de frontière (réaffirmée)** :
- **Surface AGENT** = *borné / distillé / pointeur*. Live-state lean, handoff borné, index mémoire (pointeurs) — jamais de corps brut, jamais de transcript.
- **Surface HUMAINE** = *riche*. Transcript complet, viewer LS7 — jamais soumis aux bornes agent.
- **Le transcript append-only ne franchit JAMAIS vers un contexte agent.** Seul un **dérivé distillé** (le handoff) passe la frontière, et il est borné des deux côtés (écriture + injection).
---
## 4. Flux d'injection au lancement (`compose_convention_file`)
Le convention file généré dans le run dir isolé de l'agent (`.ideai/run/<id>/`, §14.1) est assemblé **dans cet ordre** (sections vides omises) :
```
# Project root
# Orchestration IdeA (prose adaptée surface MCP vs fichier ; brief capacités)
# Skills disponibles (MCP : affordances ; sinon dump en fin de fichier)
# Contexte projet (.ideai/CONTEXT.md si présent)
<persona .md de l'agent>
# Mémoire projet (index/hooks — pointeurs, §14.5.4)
# État du projet (LIVE-STATE, LS4 — lean, cap LIVE_STATE_INJECT_MAX, self exclu)
# Reprise de la conversation (HANDOFF, LS5 — summary_md borné ≤ HANDOFF_SUMMARY_MAX_CHARS)
```
Toutes les sections agent-facing sont **bornées et distillées**. Les deux dernières (live-state, handoff) sont les plus situationnelles : *où en sont les autres* puis *où en est ce fil*.
---
## 5. Chemin chaud vs chemin froid
La priorité perf se matérialise par une séparation stricte :
**Chemin chaud** (latence ressentie par la délégation — doit rester cheap) :
- `ConversationLog::append` d'un tour (`RecordTurn`) : append + `fsync`, **O(1)**, jamais de rotation.
- `HandoffSummarizer::fold` incrémental (seulement le tour neuf, jamais de relecture totale) + `bound_handoff_summary` (pure, déterministe) avant `save`.
- Auto-update live-state (LS3) : un seul `upsert` keyed, **best-effort**, déclenché uniquement sur les transitions `ask`/`reply` (jamais par tour/outil ⇒ pas de write-storm).
- **Aucun appel LLM** sur ce chemin (seam non activé, défaut heuristique).
**Chemin froid** (action humaine ou maintenance — peut être plus coûteux) :
- Rotation `RotateConversationLog` : déclenchée **à la reprise/ouverture**, best-effort, idempotente.
- Lecture paginée `read_conversation_page` (viewer LS7) : action humaine, archive-aware.
- `GetLiveStateLean` (prune-on-read) à l'injection / via `idea_workstate_read`.
**Invariant de cohérence INV-LS6** : la rotation n'élague/ne déplace **jamais** un tour d'id ≥ `up_to` du handoff courant ; le tour `up_to` et tous les postérieurs restent dans le segment **actif**. D'où `ConversationLog::read(since=up_to)` (fold incrémental) et la reprise restent corrects après toute rotation. Sans handoff (ou `up_to` nil) ⇒ aucune rotation.
---
## 6. Outils MCP (catalogue = 14)
`idea_list_agents`, `idea_ask_agent`, `idea_reply`, `idea_launch_agent`, `idea_stop_agent`, `idea_update_context`, `idea_create_skill` (7 base) · `idea_context_read`, `idea_context_propose`, `idea_memory_read`, `idea_memory_write` (4 FileGuard C7) · `idea_skill_read` (skill-awareness) · `idea_workstate_read`, `idea_workstate_set` (LS4). Tous mappés 1:1 vers un `OrchestratorCommand` et servis par le **même** `OrchestratorService::dispatch` (fichier · MCP · UI). `idea_workstate_set` n'écrit **que** la ligne de l'agent courant, via l'identité **handshake** (jamais un argument modèle).
---
## 7. Ce qui reste ouvert (hors périmètre du programme)
Aucun de ces points ne bloque la clôture ; ils sont listés pour la suite.
**Issus du programme (différés à dessein)** :
1. **Activation réelle du seam LLM** (`LlmHandoffSummarizer`) — *si un jour souhaité*. Contrat figé (ADR LS5) : déclenchement **froid/hors chemin** (reprise/rotation/débounce, jamais par tour), timeout + **fallback heuristique**, sélection par flag, **défaut heuristique**. Le défaut runtime reste l'heuristique borné ; rien à activer tant que le besoin n'est pas exprimé.
2. **Balayage périodique de rotation** — aujourd'hui la rotation est déclenchée à la reprise/ouverture (idempotente). Un *sweep* de fond optionnel n'est pas câblé ; `rotation_plan` étant idempotent, il se branchera sans changement de contrat le jour voulu.
3. **Discordance doc/réalité sur le gitignore des conversations** — D19-4 fige `.ideai/conversations/` comme **gitignoré** (état d'exécution), mais `.gitignore` ne l'exclut pas et des `log.jsonl`/`handoff.md` sont actuellement **suivis** dans ce dépôt de dogfood. À trancher par Git/Main : soit aligner `.gitignore` sur D19-4, soit acter que le dépôt IdeA versionne ses propres logs comme artefacts (et amender D19-4). **Décision produit, hors lot doc.**
**Chantiers hors-programme** (toujours listés dans la mémoire `remaining-work-idea-agent-control-ide`) :
4. **Intégration MCP réelle end-to-end UX** : le transport natif M5 est vivant (§18.5), mais le parcours utilisateur complet (déclaration profil → pont → outils visibles → observabilité UI des délégations) reste à durcir/valider e2e.
5. **Registre de sessions / singleton agent** : l'invariant « 1 agent = 1 session vivante » est livré et gardé (§18.6) ; les évolutions multi-fenêtres / déplacement d'onglet (§L10) restent ouvertes.
6. **Auto-update mémoire/contexte pendant la vie de l'agent** : la mémoire est injectée **au lancement** (§14.5.4) ; un rafraîchissement *en cours de session* (re-recall, re-injection de contexte sans relance) n'est pas couvert et reste un chantier produit distinct.
---
## 8. Conformité hexagonale & SOLID (rappel)
- **Domaine pur** : `live_state.rs` et `conversation_log.rs` (value objects, ports, `rotation_plan`, `bound_handoff_summary`, `WorkStatus::parse`) — zéro I/O, zéro `tokio`.
- **Application** : use cases orchestrant les ports (`UpdateLiveState`, `GetLiveStateLean`, `RecordTurn`, `RotateConversationLog`, `ReadConversationPage`).
- **Infrastructure** : adapters FS (`FsLiveStateStore`, `FsConversationLog`/`ConversationArchive`, `FsHandoffStore`) + résumeur heuristique. `HandoffSummarizer` = **OCP** (heuristique ↔ LLM substituables sans toucher l'application).
- **Présentation** : outils MCP (driving adapter), commande Tauri `read_conversation_page`, viewer React via gateway/adapter/mock.
Le programme **n'a ajouté aucun couplage** vers la présentation/infrastructure depuis le domaine : la règle de dépendance (Présentation → Application → Domaine ← Infrastructure) tient.