Files
IdeA/docs/LS8-live-state-persistence-closure.md
Blomios 36be0cb396 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>
2026-06-22 17:53:02 +02:00

13 KiB

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.


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.