From 36be0cb396482473059846f1fc8e87f16cf413b6 Mon Sep 17 00:00:00 2001 From: Blomios Date: Mon, 22 Jun 2026 17:53:02 +0200 Subject: [PATCH] =?UTF-8?q?docs(live-state):=20cl=C3=B4ture=20programme=20?= =?UTF-8?q?persistance/live-state=20(LS8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- ARCHITECTURE.md | 40 ++++++- docs/LS8-live-state-persistence-closure.md | 123 +++++++++++++++++++++ 2 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 docs/LS8-live-state-persistence-closure.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 992a141..0036379 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -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 : 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. 4. La **persona/rôle** de l'agent (son `.md` dans `.ideai/agents/`). 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** : - 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é). - **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`). -- **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é) - 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 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 `). 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//handoff.md` | Reprise par fil = dérivé distillé **borné** (≤4096) | Agent (distillé) | Non | Oui (`# Reprise de la conversation`, borné LS5) | +| Transcript | `.ideai/conversations//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.* diff --git a/docs/LS8-live-state-persistence-closure.md b/docs/LS8-live-state-persistence-closure.md new file mode 100644 index 0000000..ab7846b --- /dev/null +++ b/docs/LS8-live-state-persistence-closure.md @@ -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//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//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//`, §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) + +# 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.