Commit Graph
694 Commits
Author SHA1 Message Date
MichaelandClaude Opus 5.5 d8e2c3dbf6 Lot 6 : analyse du flux SSE du moteur portée en Rust (chat::stream)
La partie « protocole » de runChatLoop devient une machine à états pure et
incrémentale (StreamParser) : lignes data:, décodage strict de streamChunk
façon encoding/json (un champ mal typé saute le chunk), télémétrie tolérante
(perfWire/applyPerf), timings et usage, accumulation des tool_calls par
index, découpage </think> côté client, frappe en direct (argPreview,
bodyTail), détection des appels d'outils écrits en texte (motifs embarqués
codés à la main, fenêtre retryScanStart), pré-vol du mode Code, stats de
secours sans timings. Plus toolCallLabel, repairToolCall, l'assemblage des
appels (badArgs, ids de secours) et l'encodage json.Marshal de Go.

L'état global Go (REASONING, preset externe, REASONING_ECHO, retry_patterns,
noteEnginePrompt, codeWriteGuard, tReq) devient des paramètres explicites
(StreamConfig). HTTP, reprises, compactage et exécution restent à la boucle.

Tests Go portés un pour un, et les flux enregistrés des tests de bout en
bout rejoués sur le parseur.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 09:07:09 +02:00
MichaelandClaude Opus 5.5 4c615d131f Compactage : politique d'écho et comptes de wire.rs plutôt qu'une copie
Le port du compactage et celui de la charge utile avaient chacun leur
version d'echoPolicy / echoTokens / echoable / hasBody. Une seule reste,
celle de wire.rs ; les 111 tests du chat passent inchangés.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 09:01:02 +02:00
MichaelandClaude Opus 5.5 e862ac834e Lot 6 : estimation du contexte et compactage de l'historique en Rust
Port fidèle de chat_compact.go et chat_compact_cont.go dans
chat/compact.rs : estimation des jetons (raisonnement renvoyé compris),
seuils et garde de marge de génération, bornes tête/torse/queue, archivage
recall, torse dégraissé, résumé sur transcription ou en continuation,
demande en cours réinjectée, garantie de réduction de 20 %, pré-contrôle,
mémoire des refus et des échecs de la continuation, filet shrinkToFit.
Prompts et marqueurs repris à l'octet près.

L'état global Go devient explicite : CompactConfig (configuration, fenêtre,
modèle, politique d'écho), trait Summarizer (appel au moteur, tampon du
slot, appel d'outil écrit en texte), trait RecallArchive, CompactMemory,
critères du mode Code en paramètre, vue d'envoi du tour fournie par
fermeture. Tests Go portés un pour un avec un faux moteur.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 09:00:08 +02:00
MichaelandClaude Opus 5.5 7defedf141 Lot 6 : ce qui part vers le moteur porté en Rust (chat/wire.rs)
Port fidèle du côté envoi du client de chat : wireMessages et ses étapes
(normalisation du système, contexte projet en tête du premier message
utilisateur, retrait des marques img_relay/ctx_upd, consigne de fin quand
les outils sont neutralisés), le point de sortie REASONING_ECHO
(politique, écho, comptage, mémoire des refus, erreur de gabarit), le
repli de reasoning_effort (llm_effort.go) et turnReasoning,
l'échantillonnage du preset, buildChatPayload, chatEndpoint et
completionsURL.

L'état global Go devient paramètre : configuration, clé locale, mémoire
des traductions d'intensité et des refus d'écho, verdict de la sonde de
gabarit, lecture des images rangées par référence. Aucune copie de
l'historique quand rien ne change (Cow), comme les tranches Go.

Tests Go portés un pour un (normalisation, consigne de fin, écho,
intensité, échantillonnage, endpoint, images, corps identique à
l'ancien assemblage).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:54:49 +02:00
MichaelandClaude Opus 5.5 0c1760c31a Lot 6 : journal d'affichage du chat porté en Rust (logique pure)
Port fidèle de la partie déterministe de chat_conversation.go et des trames
SSE de web_chat.go : ajout au journal avec plafond de 200 000 événements,
compaction de fin de tour (compactLog : blocs texte fusionnés avec toks, ts0
et seq0, seul le done=true des outils gardé, aperçu navigateur retiré),
rejeu coalescé (coalesceReplay, replace quand le client a vu le début du
bloc), pagination (historyCut, historyHead, 20 échanges par défaut), plan
d'abonnement (padding 2048 / 16384, reset de discussion périmée, caught_up,
ctx_used), direct par dichotomie sur le Seq (decorateEvent), et trames SSE
avec l'échappement HTML de json.Marshal et le regroupement 128 Kio / 256
événements.

Ni goroutine, ni verrou, ni base : la discussion active, le contexte utilisé
et l'horloge arrivent en paramètres, pour bâtir l'acteur de conversation
dessus. Les tests Go correspondants sont portés un pour un.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:50:17 +02:00
MichaelandClaude Opus 5.5 461ef8030d Lot 6 : clients Rust du chiffrement et de l'exécution des outils
Sceller / ouvrir un blob par le Go (mémoire verrouillée : erreur dédiée,
rien d'écrit en clair) et faire exécuter un appel d'outil par sa boucle
— les deux ponts dont la conversation Rust aura besoin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:49:56 +02:00
MichaelandClaude Opus 5.5 c38b1ac43e API interne de chiffrement pour la conversation tenue par Rust
Les discussions sont chiffrées en base quand la mémoire l'est, avec une
clé de données qui ne vit qu'en mémoire du Go après déverrouillage. Une
conversation tenue par la façade fera sceller et ouvrir ses blobs ici :
la clé ne sort jamais. Mémoire verrouillée : 423, jamais d'écriture en
clair ni de perte — l'appelant attend le déverrouillage, comme le Go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:49:08 +02:00
MichaelandClaude Opus 5.5 f62271b7fe Lot 6 : transport HTTP vers le moteur en Rust
Envoi de la requête de complétion (clé de CET endpoint seulement, jamais
celle du serveur local vers une API externe), découpe du flux en lignes
comme bufio.Scanner (8 Mio au plus par ligne, arrêt net au-delà), et les
primitives de reprise de llm_retry_net.go : statuts rejouables, délai
800 ms doublé et plafonné à 5 s, classement des pannes réseau et messages
de friendlyLLMError / streamCutError à l'identique. La décision de rejouer
reste à la boucle.

HTTPS pour les presets externes : reqwest + rustls (ring), derrière une
fonctionnalité Cargo « tls » activée par défaut — le contrôle croisé Linux
sous Windows s'en passe, faute d'en-têtes libc Linux. L'étape Alpine de
l'image installe gcc pour ring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:47:53 +02:00
MichaelandClaude Opus 5.5 b2fc40d48e Lot 6 : types partagés du chat en Rust et modules réservés
Message, ToolCall, Tool, événements de flux, statistiques, journal :
sérialisation JSON identique aux types Go (omitempty, contenu any). Socle
commun des ports parallèles qui suivent (charge utile, flux, compactage,
journal).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:42:25 +02:00
MichaelandClaude Opus 5.5 c744b744ee Chat : l'exécution des outils sort de runChatLoop (execTool)
Lot 6, étape 1, sans changement de comportement : le switch des outils de
la boucle de chat (une cinquantaine d'outils) devient execTool, extrait
tel quel ; seule la question « ask », qui émettait un événement, le rend
désormais à l'appelant qui l'émet au même moment.

C'est la frontière par laquelle une boucle de chat écrite en Rust fera
exécuter ses outils par le Go tant qu'ils ne seront pas portés : API
interne /internal/v1/tool/exec, même fonction, donc mêmes garde-fous
(commande dangereuse refusée avant exécution, cible distante, écriture
hors du dossier de travail) ; la coupure de la connexion interrompt la
commande comme le bouton stop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:40:59 +02:00
MichaelandClaude Opus 5.5 99e456b9a2 Spec (brouillon) du lot 6 : carte du cœur chat et stratégie de portage
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:12:48 +02:00
MichaelandClaude Opus 5.5 73db4459d7 Lot 4 : /api/status, /api/service/log, /api/paths, /api/model/layers en Rust
L'en-tête de l'interface interroge /api/status en boucle : il est
désormais assemblé par la façade, à partir des aides portées (état du
service par le fichier PID, sonde /health, preset externe, cause d'un
échec de chargement lue dans le journal, pourcentage de chargement, preset
actif, alertes de volume et de FUSE). Sur une base réelle, sa réponse est
octet pour octet celle du Go.

Journal du moteur, emplacements et nombre de couches du modèle suivent.
L'état du moteur s'appuie sur la résolution des modèles de gguf.rs plutôt
que sur sa propre copie.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:57:53 +02:00
MichaelandClaude Opus 5.5 d805d9f1ba Lot 4 : lecteur GGUF, couches du modèle et emplacements portés en Rust
gguf.rs reprend backend_gguf.go à l'identique : en-tête, clés et table des
tenseurs lus sous les mêmes bornes (chaînes, compteurs, profondeur des
tableaux, alignement, premier octet du dernier tenseur), v1/v2/v3 et
gros-boutiste, modèles en tranches (shardFamily porté avec), cache par
empreinte taille/date, mêmes messages d'erreur. Un panic éventuel reste
rattrapé en « GGUF illisible ». Tous les tests Go sont portés un pour un,
avec le même constructeur de GGUF synthétique.

Pour /api/model/layers : resolveModelPath / resolveServeModelPath et leurs
dossiers de modèles (models/, state/model_dirs, LOKI_MODEL_DIRS), la liste
stockée étant passée par l'appelant qui la lit via le store ; le corps JSON
de handleModelLayers est reproduit tel quel.

paths.rs sert /api/paths avec le même JSON que lokiPaths. Le « binaire en
cours » reste celui du serveur Go supervisé, passé par l'appelant, comme le
projet actif (state/active_project) et LOKI_WORKSPACE. Les équivalents de
filepath.Clean/Join/Dir/Abs/IsAbs garantissent les mêmes chaînes que le Go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:53:39 +02:00
MichaelandClaude Opus 5.5 0d75e90386 Lot 4 : état du moteur porté en Rust (engstatus)
Les aides qui nourrissent /api/status et /api/service/log passent dans
loki-rs/src/engstatus.rs, à l'identique du Go :

- model_load_error_from / is_backtrace_frame (modelLoadErrorFrom) : même
  ordre des cas, même sensibilité à la casse cas par cas, libellés
  identiques à l'octet près ;
- service_log_tail : la fin du journal du moteur en conteneur ;
- engine_load_pct (Linux, -1 ailleurs) : /proc/<pid>/io et VmRSS rapportés
  à la taille du modèle, tranches comprises ; la résolution de MODEL= est
  portée localement (models/, $LOKI_MODEL_DIRS, et les dossiers de la base
  via engine_load_pct_dirs) en attendant crate::paths ;
- llm_port, is_external, health_check (sonde /health sur localhost, 3 s,
  prêt seulement sur un 200, preset externe toujours prêt).

Tests Go repris un pour un (LOAD_GUARD, Strata, OOM avant la pile
d'appels, preset externe, tranches), plus une sonde contre un serveur
hyper local : 200, 503, port fermé, connexion muette.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:53:39 +02:00
MichaelandClaude Opus 5.5 b98ee5c3e7 Lot 4 : /api/ram et /api/vram servis par Rust
RAM lue dans /proc/meminfo, jauges des cartes par un seul nvidia-smi
partagé entre tous les onglets (cache de deux secondes, une minute quand
nvidia-smi est absent), comme le Go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:48:58 +02:00
MichaelandClaude Opus 5.5 dc6a001581 Lot 4 : mesures système et diagnostics de stockage portés en Rust
Le module sysinfo de loki-rs reprend, à l'identique, ce dont les routes
/api/ram, /api/vram et le bandeau de /api/status ont besoin côté Go.

La RAM se lit dans /proc/meminfo (MemTotal moins MemAvailable, en Mo) ;
hors Linux la façade rend (0, 0) et l'interface masque le bloc. Les jauges
GPU interrogent nvidia-smi, bornées à dix secondes, et passent par un cache
partagé : deux secondes de validité, une minute quand le binaire est
introuvable, et un verrou tenu pendant la requête pour que les onglets
simultanés attendent la même lecture au lieu d'en lancer chacun une. La
structure GpuStat sérialise name, used, total, util et temp, les noms que
handleVram envoie déjà.

L'alerte « /data n'est PAS un volume monté » et le conseil fuse.shfs
d'Unraid gardent leurs textes, leur règle du montage le plus long et leur
cache d'une minute. Les dossiers de modèles venant de la base, le conseil
reçoit leur lecture en fermeture asynchrone, appelée seulement quand le
cache expire.

Les tests Go de sys_storagefuse_test.go et la partie cache de
web_vram_test.go sont portés un pour un ; le test hors conteneur, qui
touche à l'environnement, vit seul dans tests/sysinfo.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:48:18 +02:00
MichaelandClaude Opus 5.5 2fcafa4ffe Presets : SHA-1 de la crate sha1 plutôt qu'une implémentation maison
Même empreinte (vecteurs officiels et empreinte du Go toujours vérifiés par
les tests), cinquante lignes de cryptographie en moins à maintenir.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:48:18 +02:00
MichaelandClaude Opus 5.5 a4b0885a22 Lot 4 : la liste des presets passe à Rust (lecture seule)
Portage fidèle de ListPresets, configFingerprint et activePresetID
(backend_presets.go, web_sysprompt.go) dans loki-rs/src/presets.rs, pour
que /api/status puis GET /api/presets puissent se servir côté façade.

Le Go lisait lui-même LOKI_HOME, la configuration active et l'ordre choisi
par glisser-déposer ; ici tout arrive en paramètres : home, la config
(Store::config) et l'ordre (seau state, clé preset_order, décodé par
parse_preset_order comme le faisait getJSON). Le reste suit le Go à la
lettre : création du dossier presets, fichiers illisibles sautés, tri par
nom puis ordre stocké, clés « appareil » exclues de l'empreinte.

L'empreinte reste un SHA-1 identique octet pour octet à celle du Go, car
elle est aussi stockée ailleurs (réglage auto, résultats de tune). Faute
de crate SHA-1 disponible, une implémentation directe, vérifiée sur les
vecteurs officiels et sur une empreinte calculée côté Go.

presetCtx (pastille de contexte de /api/presets) vient avec, ainsi que
les tests Go correspondants : empreinte insensible à la mise en forme,
ordre des presets, tailles de contexte, preset actif après bascule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:47:40 +02:00
MichaelandClaude Opus 5.5 c9c9ac1ddd Lot 5 : /v1 relayé par la façade directement au moteur
La surface compatible OpenAI ne fait plus le détour par le Go : la façade
authentifie avec la clé des complétions (préflight CORS exempté), réécrit
le corps vers le second slot quand il est en service (id_slot à 1, valeurs
gardées telles quelles), retire les X-Forwarded-* et relaie au moteur sans
tampon — Host d'origine, sauf pour Strata.

Les décisions restent au Go, qui tient l'état de la conversation : la
façade lui demande où router, ouvre un ticket « en vol » avant de relayer
(refus pendant un benchmark ou une optimisation, avec Retry-After) et le
rend quand le flux se termine, que le client parte ou que le moteur soit
injoignable. Sans ticket obtenu, la requête est refusée : le Go pourrait
sinon effacer le slot en plein calcul.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:47:40 +02:00
MichaelandClaude Opus 5.5 451770bfcf Lot 5 (Go) : décisions de la surface /v1 exposées à la façade
La façade Rust va relayer /v1 directement au moteur. Les décisions restent
ici, parce qu'elles dépendent de l'état de la conversation que ce process
tient en mémoire : route (port, Strata, clé des complétions, second slot
en service), begin (requête « en vol » : le slot de la discussion n'est
pas effacé pendant ce temps, préchauffage annulé si besoin ; refus pendant
un benchmark ou une optimisation ; trace de perte de cache), end.

Mêmes étapes, même ordre qu'oaiHandler. Un ticket jamais rendu (façade
tuée en plein flux) expire au bout de six heures, sans quoi le compteur
de requêtes en vol resterait bloqué.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:45:12 +02:00
MichaelandClaude Opus 5.5 7017b66c08 Lot 4 : /api/config et /api/engine/cmdline servis par Rust
Deux routes de pilotage de plus en Rust, lues dans la base à travers le
Go. La configuration sort clés triées, comme json.Encoder le fait d'une
map Go. Faux serveur Go de test à store en mémoire, réutilisable par les
lots suivants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:43:15 +02:00
MichaelandClaude Opus 5.5 bea29bc6a9 Lot 4 (socle) : store accessible à Rust, routeur /api côté façade
Le Go expose sa base à la façade par l'API interne : lecture, écriture,
suppression d'une clé et lecture d'un bucket entier. Il reste l'unique
propriétaire du fichier bbolt ; les écritures passent par putBytes/update
et invalident son cache comme une écriture locale. Base illisible : 503,
pour que Rust distingue « rien » de « pas pu lire ».

Côté Rust : client du store (seul point de passage, que la migration
finale de la base ne touchera qu'ici), routeur /api avec contexte partagé
et garde d'authentification réutilisable ; /api/ping y emménage. Modules
réservés aux aides portées ensuite (système, état du moteur, presets,
chemins, GGUF).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:40:29 +02:00
MichaelandClaude Opus 5.5 a672760f3c Façade Rust, lot 3 : la supervision du moteur passe à loki-rs
En conteneur, start/stop/restart/status du moteur passent par
`loki-rs engine <action>`, port à l'identique de la supervision Go :
`loki serve` lancé chef de session, fichier PID et journal aux mêmes
chemins (serviceIsActive, l'UI et `loki logs` lisent les mêmes fichiers),
contrôle à 2 s avec la fin du journal en cas d'échec, arrêt de tout
l'arbre — session, groupe et filiation, pour le moteur de Strata qui prend
sa propre session — avec les mêmes délais (SIGTERM, 4 s, SIGKILL, 20 s).

Le Go garde ses garde-fous (optimisation en cours, preset externe) puis
délègue au loki-rs voisin de son binaire ; la dernière ligne d'erreur du
superviseur devient l'erreur montrée par l'UI. LOKI_ENGINE_SUPERVISOR=go
revient à l'ancienne supervision. `loki serve`, qui construit la ligne de
commande du moteur, ne change pas : il aura son propre lot.

Le contrôle « démarré ? » regarde si l'enfant est sorti plutôt que le
signal 0 : un enfant mort reste un zombie tant que son parent vit, et un
zombie répond encore au signal 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 07:38:37 +02:00
MichaelandClaude Opus 5.5 d4fa4488cc Façade Rust, lot 2 : interface, authentification et /api/ping
L'interface (index, marked, service worker, manifeste, polices) est
désormais servie par loki-rs, embarquée par build.rs depuis la même source
que le go:embed — les deux binaires d'un build servent la même UI. Mêmes
en-têtes de cache que le Go, gzip à la première demande puis gardé en
mémoire, jamais sur les polices ni en HEAD.

L'authentification de pilotage est portée à l'identique (requireWebAuth,
crossSiteReject, localHostName, Bearer en temps constant, mêmes messages,
mêmes codes) ; les cas de TestCrossSiteReject sont repris un pour un. La
clé est demandée à l'API interne du Go à chaque requête ; si elle ne
répond pas proprement, la façade ferme (503). /api/ping est la première
route authentifiée servie par Rust.

Le jeton de l'API interne est tiré au hasard à chaque démarrage et passé
au Go par l'environnement. La façade rend 404 sur /internal/* venu de
l'extérieur et efface X-Loki-Internal entrant avant tout relais.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:40:10 +02:00
MichaelandClaude Opus 5.5 a99d988e70 API interne pour la façade Rust : clé de pilotage à la demande
Lot 2 de la migration : la façade va authentifier elle-même ses routes
/api/*. La clé de pilotage vit dans la base bbolt, verrouillée par ce
process ; la façade la demande donc à GET /internal/v1/auth, à chaque
requête — un changement de clé prend effet tout de suite, comme avant.

La réponse contient un secret, d'où trois verrous : route branchée
uniquement sur l'écoute TCP directe (le tunnel du relais sert le mux nu),
jeton tiré au hasard par la façade à chaque démarrage et exigé en
temps constant, 404 sans rien révéler sinon. Base illisible : 503, la
façade fermera l'accès plutôt que de l'ouvrir.

Spec du lot 2 jointe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:35:00 +02:00
MichaelandClaude Opus 5.5 4b859c4eb6 Conteneur : façade Rust en frontal, binaires mis à jour au démarrage
L'image embarque loki-rs (étape Alpine, binaire musl statique) et
l'entrypoint en fait la porte d'entrée : `loki-rs serve` sur le port
public, le Go derrière lui sur 127.0.0.1:8091. LOKI_FACADE=0 revient à
l'ancien schéma si la façade pose problème.

Au démarrage, le loki-rs de l'image installe la dernière release
« rolling » dans /data/bin/<build>/ ; l'entrypoint prend ensuite loki et
loki-rs du même dossier (which-bin), et un lien dans /usr/local/sbin fait
que `docker exec loki loki …` vise lui aussi la version installée. C'est
toujours le loki-rs de l'image qui met à jour : un binaire publié
défectueux ne peut pas bloquer le correctif suivant.

CI : job Rust (fmt, clippy -D warnings, tests). Publication : la release
« rolling » part à chaque push sur main avec le même numéro de run que
l'image (LOKI_BUILD) ; IMAGE_ABI, source unique, empêche d'installer des
binaires qui exigent une image plus récente. manifest.json est envoyé en
dernier.

Message corrigé quand l'image est plus récente que la release : rien à
tirer dans ce cas.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rolling
2026-10-13 07:33:18 +02:00
MichaelandClaude Opus 5.5 0a6914c936 Façade Rust loki-rs : proxy devant le Go et mise à jour au démarrage
Premier lot de la migration vers Rust. Le binaire loki-rs :

- serve : prend le port public (8090), lance `loki web 8091` en écoute
  locale et lui relaie tout — HTTP, flux SSE trame par trame, WebSocket.
  Les en-têtes de bout en bout passent intacts (Host, Authorization,
  Origin, Sec-Fetch-*, X-Forwarded-Proto : le Go en tire ses contrôles
  d'accès) ; X-Loki-Via entrant est effacé, un client ne doit jamais
  pouvoir se faire passer pour le tunnel. Go mort : relancé avec attente
  croissante, 502 lisible entre-temps.
- self-update : lit la release « rolling », installe loki et loki-rs dans
  $LOKI_HOME/bin/<build>/ après vérification SHA-256, bascule par
  réécriture atomique de release.json. Les versions s'ordonnent par numéro
  de run CI ; une ABI d'image différente n'est jamais installée. Toute
  erreur laisse l'état précédent, le démarrage n'est jamais bloqué.
  Le HTTPS passe par curl (présent dans l'image) : pas de pile TLS ici.
- which-bin : dossier d'où l'entrypoint prend les deux binaires, toujours
  ensemble.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 07:30:09 +02:00
MichaelandClaude Opus 5.5 342f787eff Web : adresse d'écoute réglable par LOKI_WEB_BIND
La façade Rust du conteneur prend le port public et lance `loki web`
derrière elle : le Go doit alors n'écouter qu'en local, sinon il resterait
joignable en direct sans passer par la façade. Défaut inchangé (0.0.0.0).

Plan du lot 1 de la migration Rust joint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:31:28 +02:00
MichaelandClaude Opus 5.5 f1f73cfc50 Spec : migration Rust, lot 1 — façade et mise à jour au démarrage
La réécriture en Rust se fait par lots, Rust en production dès le premier :
une façade loki-rs prend le port 8090 et proxifie vers le Go, qui passe en
écoute locale sur 8091. Chaque lot suivant lui retire des routes.

Le même lot fait que le conteneur prend la dernière version au démarrage :
release GitHub « rolling » publiée à chaque push sur main, binaires vérifiés
par SHA256SUMS et posés dans /data/bin, garde-fou d'ABI d'image, jamais de
boot bloqué.

La façade efface X-Loki-Via à l'entrée : sur l'écoute directe, l'en-tête
pouvait être forgé pour contourner le contrôle d'hôte anti-rebinding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:27:59 +02:00
LogiFlow 6ea661d705 Merge branch 'claude/bold-faraday-11i7lh' : Strata sur un SSD dédié (Unraid)
Strata sur un SSD dédié (Unraid)
2026-10-08 12:30:44 +02:00
Claude e236069c92 Strata sur un SSD dédié : place libre mesurée sur /data/strata, caches pip/HF hors de l'image
- strataDetect mesure l'espace libre de /data/strata (et non /data) : un SSD
  monté à cet endroit est celui qui compte pour l'installation.
- L'installeur reçoit XDG_CACHE_HOME=/data/strata/cache : les caches pip et
  Hugging Face ne remplissent plus /root, c.-à-d. l'image Docker d'Unraid.
- docker-compose.unraid.yml + README : monter un pool SSD en /data/strata.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLbFcZj6mJjDpWvCpcChGA
2026-10-08 09:33:26 +00:00
LogiFlow 6afe07a823 Merge branch 'claude/wizardly-lamport-jfgrq5' : MTP sans recharger le modèle en brouillon
MTP : MODEL_DRAFT égal au modèle principal ignoré (poids chargés deux fois)
2026-10-08 11:18:00 +02:00
Claude 30ce3a4e86 MTP : MODEL_DRAFT égal au modèle principal ignoré (poids chargés deux fois)
Choisir le modèle lui-même comme brouillon dans l'éditeur de preset
écrivait MODEL_DRAFT=<le modèle>, et Loki passait -md vers ce fichier :
common_speculative_init_result charge alors un second exemplaire complet
du modèle, au lieu de greffer le contexte MTP sur le modèle déjà en VRAM.
Swift 27B : « allocating 9628.34 MiB on device 0 » pour le brouillon,
moteur mort ; quand ça passait, couches renvoyées sur le CPU et 5 t/s au
lieu de 22 (prefill 290 t/s au lieu de 890).

probeSpec reconnaît ce cas (os.SameFile), specArgs l'ignore et le dit :
la tête MTP intégrée sert de brouillon sans -md. L'éditeur ne propose
plus le modèle principal dans la liste des brouillons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wfx8uW1un1WwWJ6xzAbkV
2026-10-08 09:13:56 +00:00
LogiFlow a463ccbd9f Merge branch 'claude/wizardly-lamport-jfgrq5' : arrêt du moteur sans course avec la relance, VRAM rendue
Arrêt du moteur : attendre tout l'arbre (VRAM rendue avant la relance)
2026-10-08 10:08:13 +02:00
Claude 393004cb06 Arrêt du moteur : attendre tout l'arbre, pas seulement le chef
En conteneur, userSvcStop n'attendait que le processus `loki serve`. Avec
Strata, le moteur qui tient la VRAM (cache d'experts, ~9 Go) est un autre
processus qui finit de s'arrêter après le serveur Python : la relance
suivante démarrait llama-server sur une carte encore pleine (« cudaMalloc
failed: out of memory » à l'encodeur de vision) et Strata mesurait la RAM
disponible avec l'ancien moteur encore chargé (mode mmap choisi à tort).

stopProcTree relève l'arbre (session, groupe et descendants) avant tout
signal, SIGTERM puis SIGKILL aux survivants, et ne revient qu'une fois
tout parti.

Diagnostic : une pile d'appels citant load_model ne passe plus pour une
nouvelle tentative (la cause mémoire était perdue), et le chat donne
l'échec de chargement au lieu de « il démarre, réessaie ».

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wfx8uW1un1WwWJ6xzAbkV
2026-10-08 07:18:53 +00:00
MichaelandClaude Opus 5.5 7d614324a0 Merge branch 'fix/tune-lock-race' : CI verte, verrou de l'optimiseur sans course
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 15:29:11 +02:00
MichaelandClaude Opus 5.5 853e59c085 Optimiseur : le ménage du verrou ne croise plus sa prise ni son rendu
TestTuneRecoverNeverReapsOwnLock échouait au hasard sous Linux (2 fois sur
5 déjà en v0.15.0, CI rouge). Le ménage (tuneReapStale) déplaçait un verrou
vivant, le relisait puis le remettait en place ; si release() passait entre
les deux, il ne trouvait plus le fichier et rendait l'inscription. Le verrou
remis en place devenait orphelin, et le ménage suivant le prenait pour une
optimisation interrompue : moteur relancé, preset « rétabli » à tort.

Un mutex de processus (tuneFileMu) sérialise désormais les opérations sur
le fichier — prise, réécriture, rendu, déplacement-relecture du ménage.
L'arrêt d'un essai orphelin et le retour du preset restent hors du mutex.
Vérifié sous Linux : 40 passages sans échec, suite complète verte.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 15:29:08 +02:00
MichaelandClaude Opus 5.5 e7fb63b1dd Merge branch 'fix/strata-ram-mmap' : experts de Strata selon la RAM disponible, mode mmap
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:26:54 +02:00
MichaelandClaude Opus 5.5 6aaf9360b4 Strata : experts placés selon la RAM disponible, mode « depuis le disque » (mmap)
Vécu : 62 Go de RAM dont 18 déjà pris par le reste du serveur. Le mode
« carte d'appoint hors RAM » avait été choisi sur la RAM TOTALE, et le
premier lancement, chargé d'écrire experts.bin, a monté le moteur à ~41 Go
de mémoire anonyme : le noyau l'a tué (OOM).

- Placement décidé AU LANCEMENT sur la RAM disponible (MemAvailable), plus
  sur la RAM totale ; le mode retenu est écrit dans le journal du moteur.
- Nouveau mode « disque » : --mmap-experts, les experts sont lus dans les
  fichiers du modèle par le cache système, rien n'est chargé au démarrage.
  Choisi d'office quand la RAM disponible ne suffit pas.
- Plus de premier lancement qui écrit experts.bin : les modes « hors RAM »
  et « experts.bin mappé » ne servent que si le fichier existe déjà.
- Réglage « Experts » dans la fenêtre de Strata : auto, en RAM, ou depuis le
  disque ; la configuration montre le mode du dernier lancement et la RAM
  disponible de la machine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:26:50 +02:00
MichaelandClaude Opus 5.5 c2a2cc5748 Merge branch 'feat/ajean-lot-images' : recherche d'images, aperçu du navigateur, vignettes, journal d'installation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 11:04:54 +02:00
MichaelandClaude Opus 5.5 ac6d8ad144 Journal d'installation : la progression de Strata visible, plus de lignes en double
- Les barres de progression qui se redessinent avec \r (téléchargement du
  modèle par l'installeur de Strata) n'apparaissaient jamais : le journal ne
  découpait que sur \n et restait figé pendant tout le téléchargement. Une
  version toutes les 10 s est montrée ; \r\n reste une fin de ligne.
- Le suivi du job relançait une requête chaque seconde sans attendre la
  précédente : quand le serveur était occupé, deux réponses ajoutaient les
  mêmes lignes au journal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 10:54:09 +02:00
MichaelandClaude Opus 5.5 5016396998 Repris d'AJEAN : recherche d'images, aperçu du navigateur, vignettes, liens d'images
- web_images (amont v0.17.7) : recherche d'images DuckDuckGo, pendant de
  web_search ; les images du web s'affichent dans le fil avec la loupe, sans
  Referer, et disparaissent si le site les refuse. Schéma minimal pour tenir
  le budget du préambule.
- Aperçu en direct du navigateur piloté (cu_autoshot.go) : une capture après
  chaque action browser_* qui change la page, gardée en RAM, jamais envoyée au
  modèle, retirée du journal en fin de tour ; une carte dans le fil, en fondu.
- Vignettes calculées par le serveur (?thumb=, amont v0.17.9) : ~560 px au
  lieu de l'original (7 Ko au lieu de 550 Ko sur une capture), chargées deux
  par deux avec un nouvel essai ; la loupe ouvre l'original.
- Un lien vers une image ([graphique](graph.png)) l'affiche au lieu d'un
  bouton de téléchargement.
- Chrome du computer use tué avec Loki sous Linux (Pdeathsig), même en cas
  d'arrêt brutal : plus de navigateur orphelin dans le conteneur.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 10:30:05 +02:00
MichaelandClaude Opus 5.5 502f23e833 staticcheck : decryptChatStores retirée (remplacée par decryptChatStoresSkipping)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:58:40 +02:00
MichaelandClaude Opus 5.5 634cfb61f3 Merge branch 'feat/moteur-strata' : moteur Strata, correctifs et presets repris d'AJEAN
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:40:10 +02:00
MichaelandClaude Opus 5.5 ecff7de5a3 Presets : glisser-déposer, pastilles contexte et capacités, prompt système dans l'éditeur
Repris d'AJEAN et adapté à Loki :
- liste réordonnable par glisser-déposer (SortableJS 1.15.6, MIT), ordre
  gardé côté serveur (/api/presets/order) et suivi par le sélecteur de
  l'en-tête ; le clic de fin de glissement ne bascule pas de preset
- pastille de contexte (32K, 128K, 40K…) et pastille « capacités » : œil
  quand le preset lit les images (MMPROJ, API externe déclarée multimodale,
  Strata), ampoule + niveau quand il raisonne
- éditeur : prompt système DE CE preset (rangé en base, sysprompt:<id>),
  projecteur vision sur le CPU (--no-mmproj-offload, visible seulement avec
  un mmproj)
- les projecteurs mmproj ne sont plus proposés comme modèle (éditeur et
  sélecteur de l'en-tête)
- l'aide « ? » reste survolable sur un réglage grisé

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:36:59 +02:00
MichaelandClaude Opus 5.5 127013b32b Correctifs repris d'AJEAN : chiffrement, preset externe, heartbeat, commande du moteur
- Désactivation du chiffrement (amont cac0cda, 19918c6) : une valeur
  illisible ne bloque plus tout (quarantaine datée + copie du keyvault), et la
  reprise au démarrage déchiffre aussi les conversations au lieu de retirer la
  clé après les seules pages. Propre à Loki : les images de conversation
  (chatimg/) sont maintenant déchiffrées elles aussi — elles restaient
  chiffrées sans clé. Les .bak chiffrés devenus illisibles sont retirés.
- Preset externe (amont #95) : `loki serve` sort sans erreur (plus de boucle
  systemd « MODEL non défini »), le pré-vol l'accepte, le superviseur du
  conteneur ne le prend plus pour un plantage.
- Unité systemd du moteur : TimeoutStopSec=5 (bascule de preset bloquée 90 s
  en pleine génération).
- Heartbeat SSE (amont #105) : plus d'écriture après le retour du handler.
- Paramètres → Configuration : commande exacte du moteur copiable (clé API
  masquée, Strata compris) et nombre de couches du modèle à côté de NGL, lu
  dans l'en-tête GGUF (amont #108, #43) ; aussi dans l'éditeur de preset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:28:42 +02:00
MichaelandClaude Opus 5.5 4a9a5a1712 Moteur Strata : un second moteur, choisi par preset (ENGINE=strata)
Strata (github.com/Niko1221/Strata, MIT) fait tourner Qwen3.8 Flash Next
(MoE 125 B) sur une carte de joueur en répartissant les experts entre VRAM,
RAM et disque. Intégration reprise d'AJEAN (backend_moe.go, « AJEAN MoE
1.0 ») : paquet figé de la release moe-v1.0, vérifié par SHA-256.

- backend_strata.go : détection de la machine, quant conseillé, installation
  en tâche (même suivi que llama.cpp), preset ENGINE=strata, lancement du
  serveur de Strata par `loki serve`, réglages d'un modèle installé
- pré-vol, vision, liste des presets, journal de chargement : Strata reconnu
- proxy OpenAI et relais : Host local pour Strata (il refuse un Host inconnu)
- benchmark et optimiseur refusés sur Strata (protocole propre à llama-server)
- UI : ligne Strata dans Paramètres → Moteur (visible aussi avec le moteur de
  l'image), fenêtre d'installation et de réglages, moteur actif affiché
- image : python3 + python3-venv ; compose : memlock illimité

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:03:22 +02:00
MichaelandClaude Opus 5.5 5dd14c9de5 Version 0.15.0
Version consacrée à la vitesse sans dénaturer le modèle : les lots 1, 2 et 3
de performance. Ce qui est sans perte est actif d'office ; tout ce qui change
ce que voit le modèle, le placement ou les slots attend une clé.

- Version passée à 0.15.0 (run.go, versioninfo.json, ressources Windows
  régénérées par goversioninfo seul — icônes inchangées).
- Notes de release réécrites : actif d'office, opt-in et leurs clés, ordre de
  test conseillé, ce qui a été écarté (cache-reuse, context-shift, KV
  quantifié par défaut) et pourquoi.
- README : une section « Performance » rassemble toutes les clés (défaut,
  effet, prix ou zone grise), les outils sans clé, l'ordre de test et les
  pistes écartées ; le détail de chaque clé y est déplacé depuis
  « Fonctionnalités », qui n'y renvoie plus que par un lien.
- NOTICE inchangé : rien de ces lots ne reprend OpenFox ni AJEAN.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:32:27 +02:00
MichaelandClaude Opus 5.5 5d37bef0ce Moteur : une bascule plus récente périme la vérification du rendu, plan et retour en POST
La vérification du rendu du gabarit après une mise à jour attend jusqu'à
vingt minutes que le nouveau moteur réponde. Un retour à la version
précédente, une autre version installée ou une seconde mise à jour pendant
cette attente la laissaient courir : elle comparait alors l'ancien moteur à
celui d'une autre bascule, ou écrasait le relevé de la suivante.

- Chaque bascule ouvre une génération ; une vérification périmée s'arrête
  sans rien ranger, et son relevé n'écrase plus le suivant. Retour et
  « utiliser » effacent le relevé, qui parlerait d'un moteur arrêté.
- /api/engine/plan (registre) et /api/engine/rollback (configuration et
  redémarrage) refusent tout autre verbe que POST — l'interface n'utilise
  que POST.
- Tests : vérification annulée en attente, relevé périmé ignoré, GET refusé.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:24:21 +02:00
MichaelandClaude Opus 5.5 5b834d54b2 Optimiseur : la reprise toutes les 30 s ne prend plus un verrou vivant pour périmé
Depuis que le processus web refait la reprise des optimisations
interrompues toutes les 30 s, elle tourne pendant que ce même processus
prend et rend ses propres verrous (bouton « Optimiser… », application d'un
résultat). Trois fenêtres lui faisaient écarter un verrou bien vivant : le
fichier créé avant l'inscription du verrou comme nôtre, l'inscription
retirée avant le fichier (sous Windows, un fichier qu'un autre lit ne se
supprime pas), et le renommage d'un verrou neuf pris entre la lecture et le
ménage. En phase « application », le preset tout juste vérifié était alors
défait, et le moteur relancé.

- Inscription avant la création du fichier, retrait après lui ; un
  identifiant de prise (nonce) distingue deux verrous du même processus dans
  la même seconde.
- Au moment de le rendre, le verrou est réécrit neutre (rien à défaire,
  rien à relancer, aucun essai), puis retiré avec quelques essais.
- Le ménage relit ce qu'il vient de déplacer : un autre verrou, ou un verrou
  vivant, est remis en place par un lien (jamais par-dessus un verrou neuf).
- La relance d'après reprise passe par serviceAction : une optimisation
  neuve qui a pris le verrou entre-temps garde le moteur arrêté.
- Test : 60 prises et rendus en phase « application » sous une reprise en
  boucle, aucune relance ; échoue sur l'ancien code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:24:20 +02:00