Files
Loki/docs/superpowers/specs/2026-10-16-rust-migration-lot31-slots-design.md
T
Claude a005626e66 Lot 31 : slots du moteur pour le tour tenu par la façade
SIDE_SLOT et SLOT_PERSIST ne gardent plus la conversation au Go. La
machinerie reste au Go (configuration, requêtes en vol, tampon du slot) ;
la façade la consulte avant chaque requête au moteur local.

Go :
- /internal/v1/turn/slot (facade_slots.go) rend engineSlotFor et, pour
  une étape de tour, recharge d'abord l'état gardé de la discussion
  (slotPersistRestore). La sauvegarde à la bascule voit les étapes de la
  façade : /internal/v1/oai/served marque le slot (engineMarkMain).
- facadeChatBlocker : SIDE_SLOT et SLOT_PERSIST retirés.

Rust :
- TurnHost::engine_slot : l'id_slot posé dans chaque requête (0 pour le
  fil de la discussion, 1 pour la vérification), le ticket « en vol »
  ouvert sur le second slot.
- Cache KV plein en plein calcul (sideSlotPoolError) : rejoué deux fois
  tel quel, sans compaction, puis l'erreur.
- Résumés de compaction comptés en vol comme postSummary : transcription
  sur le second slot, continuation sur le slot 0.
- La passe de vérification est un travail annexe (engineSideJob) : le Go
  efface le slot à la fin s'il a servi (écart du lot 7 levé).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
2026-10-16 02:13:20 +00:00

4.0 KiB

Lot 31 — Slots du moteur pour le tour tenu par la façade

Contexte

Trois clés opt-in du moteur local gardaient la conversation au Go (facadeChatBlocker), faute d'être honorées par la façade :

  • SIDE_SLOT (llm_sideslot.go) : un second slot pour les travaux annexes. Chaque requête de Loki porte son id_slot : 0 pour le fil de la discussion (étape d'un tour, résumé en continuation), 1 pour le reste (vérification, résumé sur transcription). Un cache KV à court de place en plein calcul est rejoué tel quel, sans compaction : c'est la faute du voisin, pas un prompt trop long.
  • SLOT_PERSIST (llm_slotpersist.go) : l'état du slot de la discussion est gardé sur disque à la bascule de preset, et rechargé avant la première requête de la discussion au retour.

Et l'isolation des travaux annexes (engineSideJob, CACHE_ISOLATE) : après la passe de vérification du mode Code, le Go efface le slot local s'il a servi. La façade la sautait (écart du lot 7), le Go ne voyant pas passer ses requêtes.

Depuis la fusion de main, le Go voit passer chaque requête de la façade au moteur local (tickets « en vol », /internal/v1/oai/begin, served, end). La machinerie peut donc rester au Go ; il suffit que la façade la consulte.

Le lot

Go

  • /internal/v1/turn/slot (facade_slots.go) :
    • entrée : {"conv":"…","main":true} ;
    • sortie : {"slot":0|1|-1}, la valeur d'engineSlotFor ;
    • avec main et conv, recharge d'abord l'état gardé de la discussion (slotPersistRestore), comme la boucle Go avant une requête d'un tour.
  • La sauvegarde à la bascule ne change pas. Le slot porte la discussion dès que le moteur accepte une étape de son tour : /internal/v1/oai/served appelle engineMarkMain.
  • SIDE_SLOT et SLOT_PERSIST ne gardent plus la conversation.

Rust

  • TurnHost::engine_slot, appelé avant chaque requête. La boucle pose l'id_slot rendu dans le corps.
  • Le rejeu « cache KV plein » (sideSlotPoolError) n'a lieu qu'avec un slot en service et un moteur local :
    • deux fois au plus, après l'attente des reprises réseau ;
    • puis l'erreur telle quelle ;
    • ni compaction ni réduction.
  • GoTurnHost :
    • moteur externe : aucun slot ;
    • étape d'un tour : main avec la discussion (donc le rechargement SLOT_PERSIST) ;
    • vérification : le second slot, et le ticket « en vol » ouvert sur lui (side).
  • EngineSummarizer : un résumé est désormais compté en vol, comme postSummary. Il part sur le second slot s'il est fait sur la transcription, sur le slot 0 s'il est fait en continuation.
  • La passe de vérification est encadrée par /internal/v1/engine/sidejob. À la fin, le Go efface le slot si le moteur a servi.

Tests

  • Go (facade_slots_test.go) :
    • sans la clé, aucun slot et /props jamais lu ;
    • fil de la discussion sur le 0, le reste sur le 1 ;
    • second slot hors service ;
    • rechargement une seule fois, pour la bonne discussion et une étape de tour seulement ;
    • une étape de la façade acceptée par le moteur suffit à la sauvegarde ;
    • corps illisible, GET refusé.
  • La boucle du tour :
    • id_slot dans chaque requête, slot demandé avant chaque envoi ;
    • cache KV plein : rejoué deux fois sans compaction, puis l'erreur ;
    • sans slot, ou moteur externe : un débordement comme un autre ;
    • motif sideSlotPoolError repris des cas du Go.
  • Le vrai hôte (tests/lot6.rs) :
    • contrat du mode Code : le tour sur le 0, la vérification sur le 1 ;
    • tickets ouverts sur le second slot, travail annexe ouvert puis rendu ;
    • résumés : transcription sur le 1, continuation sur le 0, tickets rendus.

Écarts assumés

  • Le rejeu « cache KV plein » se fie au slot demandé pour cette requête. Le Go relit sideSlotLive au moment de l'erreur ; les deux lectures partagent le même cache de dix secondes.
  • Le slot est demandé avant de composer la requête, et non juste avant de l'envoyer : rien ne se passe entre les deux.