Asa ne répondait qu'à partir d'un contexte figé (RAG doc ou blocs live
pré-câblés, à présélectionner) → les questions à état live (« gpu-01 tourne
bien ? ») tombaient sur « la doc ne le précise pas ». Bascule vers une boucle
de function-calling : le modèle décide quels outils appeler puis répond à
partir de leurs résultats.
- Nouveau contexte `asa` (STT_DEFAULT_CONTEXT=asa, défaut prod) + tools.py
(registre schémas OpenAI + exécuteurs) + brain.ask_with_tools (boucle bornée
STT_TOOL_MAX_ITERS=4, réponse forcée au-delà ; _post factorisé + retry).
- Outils Phase 1, LECTURE SEULE : search_docs (RAG funk-docs), host_health
(gpu-01|storage-01 : up/charge/RAM + llama-server), cluster_status,
prometheus_query (PromQL arbitraire). Réutilisent sources.py / knowledge.py.
- Trace des outils renvoyée dans `context` → visualiseur HUD (un bloc par appel).
- 100 % local (Qwen3-8B) : tool-calling natif llama.cpp validé, survit au
/no_think. LiteLLM transmet bien les tools. PromQL validés contre le vrai
Prometheus in-cluster. 3 tests unitaires de la boucle (hors-ligne).
Web search (SearXNG in-cluster) = Phase 2 ; actions admin pilotées par le LLM
(hermes-exec comme outil) = Phase 3.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Le `dnf upgrade` du rôle common avait bumpé gpu-01 en AlmaLinux 9.8, mais
repo.radeon.com ne publie pas latest/rhel/9.8 (ni 9.5) — seulement 9.4/9.6/9.7.
`make apply-gpu` échouait alors dès `common` sur « Cannot download repomd.xml »
pour le repo amdgpu.
- rocm_rhel_version 9.4 → 9.7 (dernière mineure publiée par radeon) + piège
documenté dans le README rocm (avec workaround live /etc/dnf/vars/amdgpudistro).
- Pin llama_server_commit au commit construit sur g01 : sans pin le rôle clonait
HEAD → rebuild (~15 min) + restart GPU non déterministe à chaque apply.
- Réconciliation du drop-in llama-embed (ubatch 2048) dans l'unit rendu par le
rôle (apply ciblé) ; drop-in temporaire supprimé. RAG validé (rag-query OK).
- Doc : etat-cluster + journal 2026-06-22 mis à jour.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Test sur poste après #45 : Parakeet FR ne transcrit pas « Asa » comme un mot mais comme
« a so » / « à ça » / « ah ça » (deux tokens) → la détection mot-entier ne matchait jamais
(« quand je dis Asa, rien ne se passe »).
- _detect_wake : glisse une fenêtre de 1-2 mots, joint (sans espace) + déaccentue, compare
en égalité exacte à des alias COMPACTS (asa, aso, aca, ahca…). L'exact évite les faux
positifs (« à cause » → « acause » ≠ alias). Extrait la commande après le wake.
- wake_aliases (engine + config) : formes compactes reflétant les rendus ASR réels.
- Log diagnostic [stt.voice] sur stderr/journalctl (transcription + décision), coupable via
STT_VOICE_LOG=0 — précieux pour ajuster les alias selon l'ASR.
- .gitignore : build/ + *.egg-info (artefacts d'install locale du client).
Validé en live (poste) : « Asa » réveille, « Asa stop » endort, partiels en direct.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(rag): ubatch=2048 pour llama-embed + retry rag-ingest + nettoie defaults rag
L'instance llama-embed (:1238) tournait avec l'ubatch llama.cpp par défaut (512).
En mode --embeddings + pooling, toute l'entrée doit tenir dans un seul ubatch →
tout chunk > 512 tokens échouait en HTTP 500 « input too large to process.
increase the physical batch size ». ~46 chunks du RAG (CHUNK_MAX=2000 chars ≈
700 tokens) n'étaient pas indexés.
- llama_server : ajoute llama_embed_ubatch_size (2048 = ctx) + flags
--batch-size/--ubatch-size dans le template llama-embed.service.
- rag-ingest : retry (5×, backoff) sur erreurs transitoires 5xx/réseau de l'embed.
- rag/defaults : aligne embed_url/embed_model sur nomic :1238 (étaient des vars
mortes pointant encore qwen3-8b :1234 — trompeuses).
Résultat : ré-ingestion 436/436 chunks, 0 erreur.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(cluster): aligne la doc sur l'état réel (relevé 2026-06-21) + snapshot
Passe de vérification terrain (SSH s01/g01 + kubectl) pour corriger les écarts
doc↔réalité, et ajout d'un document de référence vérifié.
- admin/ops/etat-cluster.md (nouveau) : snapshot daté — machines, services+ports
par hôte, workloads k8s, namespaces, IngressRoute, bases PG, modèles LiteLLM,
RAG/Qdrant, + procédure pour régénérer le relevé.
- OS AlmaLinux 9.7 → 9.8 (s01/g01) ; précise k8s v1.33.1 (CLAUDE/README/install/talos).
- RAG funk-docs : retire le warning périmé « supprimée 2026-06-17 / à re-ingérer » →
reconstruite (436 chunks, nomic-embed-text :1238 dim 768, CPU). Maj rag.md,
incidents.md, stt.md.
- Namespace sacrifice (hors-repo, hors ArgoCD) + bases PG grafana/sacrifice +
services ai via IngressRoute + registry in-cluster (CLAUDE.md, k9s.md).
- Services de résilience (llm-heartbeat, llama-watchdog/embed) et instances CPU
manuelles :1236/:1237 (souvent éteintes) documentés.
- Journal progress/2026-06-21.md : section relevé + RAG.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(stt): transcription partielle live + veille/réveil vocal (« Asa » / « Asa stop »)
Deux fonctionnalités sur l'assistant vocal Asa (client 0.13.0 → 0.14.0).
Transcription partielle live :
- en éveil, le buffer en cours est re-transcrit périodiquement (partial_interval_sec) et
poussé au HUD (event `partial`) → le texte se forme en direct dans une bulle dédiée,
remplacée par la bulle « user » finale.
- auto-activée seulement avec l'ASR onnx (Parakeet, rapide CPU) ; bornée min/max ;
forçable via [voice].partial_transcription.
Veille / réveil vocal (machine à états asleep ↔ éveil) :
- veille : Asa transcrit chaque énoncé UNIQUEMENT pour repérer le wake word, ne répond à
rien d'autre (HUD : portrait atténué, « SOMMEIL »).
- « Asa » (mot entier, alias ASR tolérés) réveille ; « Asa, <question> » exécute la question.
- « Asa stop » (ou stop/dors/au revoir… = sleep_words) remet en veille ; timeout aussi.
- wake word par défaut `asa` (était `hermes`) ; détection durcie en mot entier (plus de
sous-chaîne — « asa » matchait « casa », « phrase »…).
Validé : tests unitaires (wake/sleep/alias) + Playwright (rendu asleep, bulle partielle mise
à jour en place puis remplacée, nettoyage au changement d'état). Doc admin/ia/stt.md + journal.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(stt): active les actions via Hermes (STT_ACTIONS_ENABLED=true)
hermes-exec déployé sur storage-01 (:9096) + secret k8s stt-server-secrets/hermes-exec-token
en place → on active le contexte « agent ». Après déploiement ArgoCD, le contexte
🤖 Agent apparaît dans le sélecteur et les actions (avec confirmation) sont opérationnelles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs: journal 2026-06-21 + état STT (portail, contextes, intents, actions Hermes)
- progress/2026-06-21.md : récap complet de la journée (portail + santé live, contextes
visualiseur + presets live, intents vocaux, jeton Ghostfolio unifié, actions via Hermes,
ASR Parakeet) + l'incident de merge des PR empilées et sa leçon.
- PROGRESS.md : ligne du jour.
- CLAUDE.md : « État actuel » daté 2026-06-21, ligne STT enrichie.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(vault): ajoute vault_hermes_exec_token (jeton actions hermes-exec, chiffré)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Asa peut AGIR sur le homelab (l'autre moitié de la vision), via le vrai agent Hermes,
profil par défaut, tous ses outils — avec confirmation et jeton.
Découverte : Hermes n'a pas d'API HTTP (appli TUI), mais un mode one-shot `hermes -z
"<prompt>" --yolo`. On s'appuie dessus.
storage-01 — exécuteur :
- tools/hermes-exec/server.py : service HTTP qui lance `hermes -z --yolo` en user hermes,
derrière un jeton Bearer (compare_digest), timeout + audit, une action à la fois.
- rôle Ansible hermes_exec : systemd (User=hermes, env hermes-agent), jeton via
EnvironmentFile 0640 (Vault vault_hermes_exec_token) ; ajouté au playbook storage-01.
STT-server (0.5.0 → 0.6.0) :
- agent.py : pont vers hermes-exec (jeton) + détection confirme/annule.
- contexts.py : contexte « agent » (court-circuite le LLM).
- app.py : flux dédié — handshake 2 temps par session (pending action) → sur « confirme »,
appelle hermes-exec ; « annule » annule. /v1/contexts masque « agent » si désactivé.
- config : STT_ACTIONS_ENABLED (opt-in, défaut false) + URL + jeton (secret k8s).
- deployment : env actions + secret stt-server-secrets/hermes-exec-token (optionnel).
Sécurité : opt-in désactivé par défaut ; jeton obligatoire (sinon contexte caché + exécuteur
refuse tout) ; --yolo atteint seulement jeton+confirmation en main ; audit storage-01.
Client : AUCUN changement (le contexte « agent » apparaît tout seul dans le sélecteur).
Validé en local : exécuteur (401 sans/mauvais jeton, exec avec bon jeton via echo),
handshake serveur (TestClient : confirme→exécute, annule→annule, agent masqué si OFF).
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Deux features mergées n'avaient pas atteint main (effet de bord des PR empilées :
#37 a été mergé dans sa branche de base supprimée, jamais propagé). Recollées proprement
sur main à jour, en fusionnant engine._respond / app.py avec le travail contextes (#39).
Intents vocaux (lecture seule, court-circuitent le LLM) :
- portal/intents.py + ghostfolio.py (récupérés) + exports __init__.
- engine.py : intent_router AVANT le LLM ; cohabite avec context_provider
(pas de contexte émis sur un intent local).
- app.py : construit le routeur (services, ouverture, santé, Ghostfolio).
- config : section [ghostfolio] + doc example.toml.
Affichage version :
- app.py : meta_msg poussé à la connexion.
- hud : case 'meta' + setVersion + ligne « Version installée » (section Service).
- bump 0.11.0 → 0.12.0.
Validé : compile, routage intents, coexistence intent/visualiseur dans _respond
(intent → mode portail sans contexte ; LLM → contexte émis), HUD (meta/version).
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Moitié client de la fonctionnalité contextes (le serveur #38 est déployé). Tu choisis
un contexte pour Asa et tu vois exactement ce qu'on lui envoie.
- Sélecteur (Réglages → « Contexte d'Asa ») : liste peuplée depuis GET /v1/contexts ;
le choix est envoyé en settings{context}, persisté en localStorage, et réappliqué au
backend à la connexion. Masqué si le serveur ne supporte pas (dégradation).
- Visualiseur (icône ▤ de la barre) : drawer affichant le contexte assemblé renvoyé
après chaque tour — consigne (system prompt), données live injectées, doc RAG, mémoire.
- api.py : ServerClient envoie `context`, expose last_context + contexts().
- engine.py : context_provider → émet {type:context} après chaque réponse.
- app.py : récupère /v1/contexts au démarrage, diffuse à la connexion, applique le
changement de contexte (client.context).
- bump 0.10.0 → 0.11.0.
Validé via Playwright : sélecteur (5 contextes), changement → settings{context},
visualiseur (consigne + bloc live Ghostfolio + mémoire), drawer partagé. Rendu conforme.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Asa n'est plus bloqué sur le seul contexte « doc cluster grounding-strict ». Le client
choisit un contexte par requête ; le serveur change le system prompt ET injecte les
données live du domaine, puis renvoie le contexte assemblé pour le visualiseur du HUD.
- contexts.py : profils funk / ghostfolio / grafana / alerting / cluster (system prompt
+ sources) + assemble() (prompt final + structure de visualisation).
- sources.py : fetchers live best-effort (Ghostfolio auth+details, Alertmanager alerts
hors Watchdog, Prometheus cluster/metrics), env-config, dégradation propre.
- brain.py : ask() reçoit le system prompt déjà assemblé (assemblage remonté).
- app.py : /v1/ask accepte `context`, renvoie context_id + le contexte assemblé ;
nouveau GET /v1/contexts ; RAG doc conditionné au profil.
- config.py : URLs sources + STT_GHOSTFOLIO_TOKEN + STT_DEFAULT_CONTEXT.
- deployment : env in-cluster (Prometheus/Alertmanager monitoring, Ghostfolio ai),
jeton via secret optionnel stt-server-secrets/ghostfolio-token.
- bump 0.3.1 → 0.4.0.
Validé en local : assemblage (blocs+RAG+mémoire), parsing des sources (mock),
endpoints /v1/contexts et /v1/ask (LLM mocké) — context_id, visualiseur, fallback
contexte inconnu → funk.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute la gestion du service STT directement depuis l'interface (Réglages →
section « Service ») et deux commandes CLI.
- CLI : `stt --start` (démarre le service) et `stt --restart` (recharge la config :
modèle ASR, wake word…). Garde-fou si le unit n'est pas installé.
- HUD : boutons Redémarrer / Mettre à jour / Arrêter. Le HUD tournant DANS le
service, les actions sont lancées en process détaché (start_new_session) → le
redémarrage va à son terme même quand le process courant est tué (systemd
possède le job). « Mettre à jour » = `stt --update` puis `stt --restart`.
- bump 0.8.0 → 0.9.0.
Validé : py_compile, --start/--restart dans l'aide, et HUD via Playwright
(3 boutons émettent service-restart/-update/-stop, toast, aucune erreur JS,
rendu conforme).
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute un panneau « Services » au HUD du client STT : une tuile par service du
homelab (Ghostfolio, Grafana, ArgoCD, n8n, Open WebUI, Prometheus, Alertmanager,
Traefik), un clic ouvre l'URL dans le navigateur habituel.
- stt/portal/ : registre piloté par [[services]] de stt.toml (id, name, url, icon,
aliases) + résolution floue (registry.match_service) prête pour la voix.
Ajouter un service = quelques lignes de config, zéro code.
- config.py : défauts homelab + doc dans stt.example.toml.
- ui/app.py : pousse la liste au HUD à la connexion, action de contrôle
open-service → _open_url_external (xdg-open, session navigateur normale) + toast.
- hud/index.html : bouton header, drawer Services (voile partagé avec Réglages),
grille de tuiles, toast de confirmation.
- bump 0.7.0 → 0.8.0.
Validé : registre (fuzzy-match FR/fautes/négatif), wiring backend, et HUD via
Playwright (rendu tuiles + clic émet open-service + toast, aucune erreur JS).
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Hermès répondait « la doc ne le précise pas » à « combien de nœuds K8s + OS »
alors que talos.md § Architecture contenait l'info — mais en TABLE : nomic embed
mal une table face à une question en langage naturel (pas de « 3 »/« trois », OS
implicite), donc le chunk ne remontait pas dans le top-k.
Fix = phrase en prose dense en tête de la section (compte explicite + OS + rôles +
les 2 hôtes AlmaLinux hors cluster). Le chunk passe #1 (score 0.743 vs ~0.69 avant).
Validé après re-ingest :
- « combien de nœuds K8s + OS » → « 3 nœuds, tous sous Talos Linux v1.13.0 » (docs=6)
- « combien de machines + OS » → réponse nuancée (3 Talos + 2 AlmaLinux hors cluster)
Leçon RAG : préfixer toute table dense d'une phrase en prose pour l'ancrage des
questions naturelles « combien / quel ».
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Le heartbeat embed (#28) gardait le slot :1238 chargé mais docs=0 revenait :
latence bimodale 16ms / 9-12s. Cause = contention GPU, pas cold-start. nomic
(:1238, --n-gpu-layers 99) partageait la RX 6700XT avec le chat qwen3-8b (:1234) ;
ROCm sérialise → un embed lancé pendant une génération chat attend ~10s → dépasse
le budget recall 4s du STT-server → docs=0 intermittent.
- llama_embed_n_gpu_layers: 99 → 0 (CPU). nomic = 137M → ~100ms déterministe,
découplé du slot chat. Le template lit déjà la variable.
- heartbeat embed conservé comme sonde de vivacité (commentaire ajusté).
- incidents-llm-gpu.md : section dédiée + README rôle mis à jour.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sur un sujet absent de la doc Funk, le 8B comblait le vide en devinant
(« j'ai un switch ? » → tantôt « Oui » tantôt « Non », fausse assurance).
Le grounding marche (docs=6) mais la doc n'a rien à ancrer.
- config.py : system_prompt durci — si la doc ne mentionne PAS explicitement
l'élément, répondre « la doc Funk ne le précise pas », ne jamais affirmer
oui/non par déduction ni inventer de détail (modèle, marque, nombre, nom).
- admin/infra/reseau.md : section « Topologie physique — switch cluster »
(switch présent, modèle à confirmer) → comble le gap doc qui causait
l'hallucination sur le matériel réseau.
- bump 0.3.0 → 0.3.1
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Le grounding RAG du STT ratait par intermittence (docs=0 → réponses génériques)
parce que l'instance d'embedding :1238 partait à froid après une pause : la 1ʳᵉ
requête mettait 5-8 s et dépassait le timeout 4 s du STT-server (recall + RAG doc).
llama-embed-heartbeat : service systemd local sur gpu-01 qui envoie un petit embed
à :1238 toutes les 20 s → le slot reste résident/chaud → la 1ʳᵉ vraie requête est
rapide → docs>0 fiable. Pendant du llm-heartbeat (slot chat :1234), pour le slot embed.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sans contexte, le modèle (qwen3-8b) hallucinait sur le homelab (« stockage
décentralisé », « worker nodes »…) car il ignore tout du cluster réel. Le
cerveau interroge désormais la collection funk-docs (la doc admin/ indexée) et
injecte les passages pertinents au prompt → il répond à partir de la vraie doc.
Coût ~nul : funk-docs et stt-memory partagent le même embedder nomic (:1238,
768 dim) → le vecteur de requête calculé pour le recall mémoire est réutilisé
pour la recherche doc (une embed, deux recherches). Dégrade en silence si
Qdrant/embed down (réponse sans doc plutôt qu'échec).
Module knowledge.py (STT_DOCS_RAG, TOPK=6, MIN_SCORE=0.45). Log /v1/ask
expose docs=<n>. Serveur 0.2.1 → 0.3.0.
Validé : « c'est quoi le nom des nœuds ? » → avant « worker/master nodes »
(inventé) ; après « Kubernetes ; nœuds storage-01, compute-01, compute-02 »
(tiré de la doc).
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
`_running_stt_pids` ne regardait que argv[0], or pipx lance
`python -E /…/bin/stt --window app` → argv[0] est l'interpréteur, pas `stt`
(et le `-E` décale encore le chemin). Du coup `stt --stop` ne trouvait jamais
l'instance vocale (« Aucun service ni instance en cours ») alors qu'elle
tournait — confirmé en conditions réelles.
Désormais on cherche un token qui est un *chemin* vers `stt` (slash + basename
`stt`) dans toute la ligne de commande, ce qui écarte aussi le `stt` nu d'un
`journalctl -u stt` (faux positif), et on ignore toujours les sous-commandes
utilitaires.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
funk-docs utilisait le modèle de CHAT qwen3-8b (:1234, 4096 dim) comme
embedder → similarités quasi indiscernables (tous les scores ~0.96, ranking
médiocre). Bascule sur l'instance dédiée nomic-embed-text (:1238, 768 dim) —
la même que la mémoire STT — déjà identifiée comme roadmap dans le README.
rag-ingest ET rag-query alignés (même modèle/dim). Seuil rag-query abaissé
0.60→0.40 (nomic étale les scores plus bas). Collection recréée en 768.
Mesure : scores désormais étalés 0.61-0.74, et les bons docs ressortent en
tête (dnsmasq→dnsmasq.md, nftables→phase gateway, wedge→llama_server.md).
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(rag): exclure hermes/builtin de l'index funk-docs
Les rapports auto-générés par hermes-auto-improve (admin/hermes/builtin/,
"ne pas éditer") représentaient ~84% des points de la collection et noyaient
la vraie doc → rag-query remontait du bruit. On les élague à l'ingestion
(os.walk), surchargeable via RAG_EXCLUDE. Collection re-bâtie : 403 points
propres (0 builtin), rag-query remonte de nouveau les bons documents.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(stt-client): boutons stop réponse + mute micro (HUD) + `stt --stop`
HUD : deux contrôles dans la barre du haut.
- « stop » coupe la réponse en cours — interrompt la lecture TTS (kill aplay)
et saute la synthèse si pressé pendant la génération (Event _interrupt).
- « micro » coupe/réactive l'entrée audio (la boucle VAD ignore les trames) ;
l'état fait foi côté backend, renvoyé au HUD via {"type":"mic"} (ré-émis à
la connexion d'un nouveau client).
Protocole WS : nouveau message {"type":"control","action":"stop|mute|unmute"}.
CLI : `stt --stop` éteint le service — systemd --user (stt.service) si actif,
sinon SIGTERM aux process vocaux trouvés via /proc (s'exclut + ignore les
sous-commandes utilitaires).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(litellm): heartbeat anti-502 + request_timeout 20s
Cause racine du 502 « 1ʳᵉ demande après une pause » : la connexion keep-alive
LiteLLM↔llama-server devient inactive → llama-server la ferme → LiteLLM garde le
socket mort → la requête suivante part dans le vide → timeout → 502.
- llm-heartbeat : service systemd qui appelle hermes-default toutes les 15s
(max_tokens:1, /no_think → ~10ms GPU) → la connexion n'est jamais inactive,
jamais périmée. Logge les échecs → sert aussi de sonde (vraie génération).
- request_timeout 60→20s : un socket périmé échoue vite, dans la fenêtre où
num_retries:2 peut rejouer sur une connexion neuve (sinon le client abandonnait
avant le retry → 502 sec).
- Doc : admin/incidents-llm-gpu.md (fix racine) + README rôle.
⚠️ heartbeat appelle hermes-default en continu → si bascule sur Claude (facturé),
mettre llm_heartbeat_enabled: false. request_timeout global → remonter si Claude.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(llama_server): watchdog auto-réparation du wedge ROCm
Service systemd local sur gpu-01 qui sonde une vraie génération sur :1234
(pas juste /health, qui ment quand le slot d'inférence est figé). Sur N
échecs consécutifs → systemctl restart llama-server en local (root, sans
SSH/sudo distant). Gère le 503 "Loading model" post-restart sans le compter
comme échec.
Complète le llm-heartbeat (rôle litellm) : le heartbeat empêche la péremption
par inactivité de la connexion ; le watchdog répare le figeage du serveur lui-même.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Cause du burst de 502 « cold-start » : après un restart de llama-server, LiteLLM
garde des connexions keep-alive mortes dans son pool → la requête suivante hang
jusqu'au timeout. Or request_timeout valait 600s → hang jusqu'à 10 min, sans log
(LiteLLM attendait encore). Fix :
- request_timeout 60s : borne le hang d'une connexion morte
- num_retries 2 : rejoue → évince le socket périmé, repart sur une connexion neuve
Doc : section dédiée dans admin/incidents-llm-gpu.md (mécanisme + application +
reste à faire = watchdog qui bounce llama-server ET litellm).
Application : ansible-playbook -i inventory.yml playbooks/storage-01.yml --tags litellm
(redémarre LiteLLM → applique le fix ET purge le pool d'un coup).
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Suite au déploiement 0.2.0, deux points observés en pilotant l'app en direct :
- Le log de timing (`ask … recall/gen/total`) n'émettait pas : uvicorn ne
configure que ses propres loggers → on attache un handler INFO sur `stt_server`.
- La chaîne LiteLLM→llama-server (ROCm gfx1031) se fige par à-coups (~1-2 min,
502 pour tous les appels, cf. incidents-llm-gpu.md). Résilience côté STT :
brain tente 2× (fermeture du client httpx entre les essais → connexion neuve si
le pool est vicié) et le timeout passe à 30s PAR tentative (≤ ~2× au pire).
Un blip transitoire devient une requête récupérée au lieu d'un 502.
bump serveur 0.2.0 → 0.2.1
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Cause de la latence 30-45s : l'endpoint d'embeddings (gpu-01:1238) peut se
geler ; recall ET remember l'attendaient ~20s chacun (timeout → dégradation
silencieuse), s'ajoutant à la réponse. Refactor :
- store (ex-remember) en BackgroundTasks → APRÈS la réponse, hors latence perçue ;
suppression de `?wait=true` (pas d'attente du flush Qdrant)
- recall renvoie aussi le vecteur de la requête → store le réutilise (1 embed/tour
au lieu de 2, le 2ᵉ portait sur le même texte)
- timeout recall serré (4s, STT_MEMORY_RECALL_TIMEOUT) : un embed lent/mort dégrade
vite (souvenirs vides) au lieu de bloquer ; store tolère 20s en arrière-plan
- clients httpx persistants (pooling/keep-alive) côté brain + longterm, fermés via
lifespan (plus de handshake TCP par appel)
- log de timing par requête (recall/gen/total/mem) pour diagnostiquer
- bump serveur 0.1.0 → 0.2.0
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(stt): auto-start via `stt --install-service` (phase 6)
Ajoute `stt --install-service` / `--uninstall-service` : génère et active un
service systemd --user (lié à graphical-session.target) qui lance le HUD + voix
à l'ouverture de session. Le kiosk existant (ui.kiosk) fournit l'« écran Jarvis ».
- cli : _service_unit (génération du unit, fonction pure testable),
_cmd_install_service (écrit le unit + import-environment DISPLAY/WAYLAND +
daemon-reload/enable/start), _cmd_uninstall_service.
- Docs : admin/ia/stt.md (phase 6 → ✅, section Auto-start + caveat DISPLAY),
stt/README.
- Bump client 0.2.1 → 0.3.0.
Phase 6 de la roadmap STT. Reste : test sur poste, outils Hermes (7).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa
* feat(stt): app de bureau (fenêtre type Discord) + auto-update
Rend STT lançable comme une application de bureau et ajoute la mise à jour intégrée.
- Mode fenêtre « app » : _open_browser ouvre le HUD en fenêtre chromeless
(--app=, type Discord) en plus du kiosk plein écran ; support Brave ajouté.
Sélection via --window {app,kiosk,none} ou [ui].window_mode.
- stt --install-desktop / --uninstall-desktop : entrée .desktop + icône
(stt/hud/icon.svg, aussi favicon du HUD) → STT dans le menu/dock, lance --window app.
- stt --update : pipx reinstall depuis la source git (via clé SSH), affiche
« ancienne → nouvelle » ; stt --version.
- Docs (admin/ia/stt.md, stt/README) + config (window_mode) + bump 0.3.0 → 0.4.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa
* fix(stt): détection navigateur cross-distro (AlmaLinux/Fedora + Debian/Ubuntu)
_open_browser tente les binaires natifs (rpm + deb : brave-browser / chromium /
chromium-browser / google-chrome…) puis Flatpak (com.brave.Browser,
org.chromium.Chromium, com.google.Chrome…), sinon repli sur le navigateur par
défaut. systemd --user, lanceurs .desktop et pipx sont déjà identiques sur les
deux familles.
Doc : note de compatibilité distros. Bump 0.4.0 → 0.4.1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa
* docs(stt): journal 2026-06-18 + PROGRESS + ligne STT du CLAUDE.md
- progress/2026-06-18.md : HUD avancé, auto-start, app de bureau, auto-update, cross-distro
- PROGRESS.md : entrée du jour
- CLAUDE.md : ligne STT enrichie (HUD, auto-start, app de bureau, auto-update)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa
* feat(stt): fenêtre app autonome (profil + WM class dédiés, hors session Brave)
Le mode app s'attachait à la session Brave de l'utilisateur (même profil/icône) →
« ça sent le Brave ». On lance désormais une instance séparée : --class=STT-Funk
+ --user-data-dir dédié (~/.local/share/stt/app-profile), et le .desktop déclare
StartupWMClass=STT-Funk → fenêtre autonome avec sa propre icône de barre des tâches.
(Flatpak : --class seul, sandbox.) Une vraie fenêtre native (pywebview) reste une
option ultérieure. Bump 0.4.1 → 0.4.2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(stt): HUD avancé — design Claude Design câblé au backend
Remplace le HUD minimal (cercle CSS) par le design Claude Design : avatar
portrait réactif (anneau + ping d'écoute + spinner de réflexion), transcript
à bulles (tag modèle), indicateur de connexion + reconnexion auto, et un
drawer Réglages.
- HUD (stt/hud/index.html) : HTML/CSS/JS sans dépendance externe, DEMO=false
branché sur le websocket existant (états idle/listening/thinking/speaking,
user/assistant/error). Portraits chargés depuis hud/avatars/ (repli « あ »
si absent — voir avatars/README.md).
- Backend (ui/app.py) : le HUD renvoie {"type":"settings",...} ; le serveur
applique à chaud reset / mode cerveau (modèle LiteLLM) / mot de réveil via
_apply_settings (best-effort, non bloquant).
- engine/cli : tag du modèle courant ajouté aux réponses (events assistant).
- Docs : hud/README + avatars/README + admin/ia/stt.md (phase 4 ✅) + config.
Phase 4 de la roadmap STT. Reste : test sur poste + portraits réels.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa
* fix(stt): HUD servi sans cache navigateur (anti ancienne version)
Le HUD est un dashboard live servi en statique : sans en-tête anti-cache, le
navigateur garde l'ancien index.html après mise à jour (« je vois encore la
version bleue »). Ajoute Cache-Control: no-store + Pragma: no-cache via un
handler dédié, et silence le log HTTP par requête.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa
* chore(stt): bump client 0.1.0 → 0.2.0 (HUD avancé)
La version inchangée faisait que pipx/uv réutilisait l'ancien build en cache
lors d'un réinstall (HUD bleu persistant malgré le nouveau code). Le bump force
une résolution propre vers la nouvelle version (HUD design Claude Design).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa
* fix(stt): cache-bust de l'URL HUD en kiosk + bump 0.2.1
Ouvre le HUD avec ?v=<timestamp> (URL unique par lancement) pour que le
navigateur ne puisse jamais servir une version cachée de l'ancien HUD.
Complète Cache-Control: no-store côté serveur.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa
---------
Co-authored-by: Claude <noreply@anthropic.com>
* docs(readme): ajouter le switch réseau (Netgear GS308EV4) à l'architecture
Le diagramme d'architecture du README ne représentait pas le commutateur
réseau qui relie physiquement les machines. Ajout du switch Netgear GS308EV4
(8 ports Gigabit, managé Plus, VLAN 802.1Q) entre storage-01 et les nœuds du
cluster, avec le détail du câblage (storage-01 : WAN enp4s0 → port 2/VLAN 1,
LAN enp6s0f3u2c2 → port 7/VLAN 10) déjà présent dans ansible/group_vars.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa
* docs(readme): corriger la topologie du switch (Freebox + storage-01 ×2 VLAN)
Le switch GS308EV4 est le point central : y sont branchés la Freebox (VLAN 1)
et storage-01 par ses DEUX interfaces (enp4s0 port 2/VLAN 1 et enp6s0f3u2c2
port 7/VLAN 10). storage-01 fait passerelle/NAT entre VLAN 1 (domestique) et
VLAN 10 (cluster). Le diagramme précédent montrait à tort Freebox → storage-01
→ switch en série.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa
* docs(readme): détailler le câblage du switch (ports, VLAN, mode Access)
Reprise du mapping noté à l'origine (archive/Funk/HOMELAB.md §5.4 +
progress/2026-05-09) : ajout des numéros de ports au diagramme (port 1 Freebox,
2/7 storage, 3-6 cluster) et d'un tableau de câblage complet des 8 ports
(destination, IP, VLAN). Précision : tous les ports en mode Access (pas de
trunk), storage-01 sur deux NIC physiques (une par VLAN).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa
---------
Co-authored-by: Claude <noreply@anthropic.com>
Aligne la documentation sur l'état réel du cluster au 2026-06-18 :
- Qdrant : crash-loop résolu le 17/06 (statut « HS / EN COURS » corrigé dans
CLAUDE.md, README, admin/ia/rag.md, admin/incidents.md, admin/ia/stt.md).
Précision : collection funk-docs supprimée lors de la réparation → re-ingest
RAG encore à faire (pas de surévaluation du statut).
- STT (assistant vocal « Jarvis ») : ajouté partout (était déployé mais non documenté).
- Ghostfolio : ajouté partout (était déployé mais non documenté).
- Embeddings dédiés nomic-embed-text :1238 : marqués opérationnels (roadmap
README disait encore « TODO »).
- llama-server : ×3 → ×4 (instance embeddings) ; gap IaC précisé (embeddings géré
par le rôle, seules les 2 instances CPU restent manuelles).
- Structure k8s/ corrigée dans CLAUDE.md : ajout stt/ + ghostfolio/, suppression
des sous-dossiers traefik/ + metallb/ inexistants (installés via helm au bootstrap).
- Namespaces et liste des secrets Vault (vault_pg_ghostfolio_password) complétés.
- admin/README.md : index complété (~10 docs manquants : email, n8n, open-webui,
ghostfolio, k9s, stt, rag, hermes-voice/souls/auto-improve, alertmanager-webhook).
- Funk/ (conception d'origine : LM Studio + agents Goose, obsolète) déplacé sous
archive/ avec bandeau « obsolète » + archive/README.md (historique git préservé).
Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa
Co-authored-by: Claude <noreply@anthropic.com>
* feat(stt): mémoire long-terme sémantique via Qdrant (5b)
Serveur : longterm.py — collection Qdrant stt-memory (embeddings Qwen3 gpu-01, dim auto,
Cosine), recall top-k injecté au prompt, remember des tours user. Tout dégrade proprement
si Qdrant/embeddings injoignables (la mémoire court-terme tient). Env STT_MEMORY_LONGTERM,
STT_QDRANT_URL, STT_EMBED_URL, STT_MEMORY_TOPK.
Testé en process : dégradation OK (Qdrant down → mem=0, pas de crash, court-terme tient).
Qdrant réparé le 17/06 (5c). Recherche sémantique réelle à valider sur cluster.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT
* feat(stt): endpoint /v1/memory/health + upsert Qdrant synchrone
- /v1/memory/health sonde activement embeddings + Qdrant + collection et
expose les erreurs (recall/remember dégradent en silence → indébogables).
Permet de diagnostiquer la mémoire long-terme sans kubectl exec.
- remember() : upsert avec ?wait=true → le souvenir est immédiatement
cherchable (sans wait, Qdrant met l'écriture en file → un recall
cross-session immédiat pouvait le rater).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT
* docs(stt): 5b mémoire long-terme validée en prod + backlog nomic-embed-text
- Rappel cross-session confirmé (« Felix » retrouvé dans une nouvelle session),
points_count vérifié via /v1/memory/health.
- Note du fix upsert ?wait=true et de l'endpoint de diagnostic.
- Roadmap : 5d (nomic-embed-text dim 768) en backlog qualité ; états haut/bas
du doc mis à jour (déployé + validé sur cible).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT
* feat(stt): embeddings dédiés nomic-embed-text sur gpu-01 (:1238) + migration auto
Remplace Qwen3 (chat réutilisé, dim 4096, peu discriminant) par un modèle
d'embedding spécialisé nomic-embed-text (dim 768) pour la mémoire long-terme.
Ansible (rôle llama_server) :
- nouvelle instance optionnelle `llama-embed` (llama_embed_enabled) servant un
modèle d'embedding dédié sur :1238, GPU ; télécharge le GGUF si absent.
- activée sur gpu-01 (host_vars) : nomic-embed-text-v1.5 f16.
STT-server :
- STT_EMBED_URL → :1238, STT_EMBED_MODEL → nomic-embed-text (deployment + config).
- _ensure_collection détecte le changement de dimension (4096→768) et recrée
automatiquement la collection stt-memory (anciens vecteurs incomparables) —
pas de drop manuel.
Docs : llama_server README, rag.md, stt.md (5d ✅), CLAUDE.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT
* fix(stt): désactiver le mode thinking de Qwen3 (/no_think) — content vide / 502
Qwen3 est un modèle « thinking » : il dépensait tout le budget max_tokens en
reasoning_content → content vide, ou partait en raisonnement long → timeout (502
"upstream LiteLLM : " avec message vide). Diagnostic : appel direct LiteLLM en
0.87s mais content="" et tout dans reasoning_content.
- brain.py : ajoute le token de contrôle `/no_think` au system prompt
(configurable STT_DISABLE_THINKING, défaut true ; inoffensif pour non-Qwen).
- brain.py : filet de sécurité — si content vide, récupère reasoning_content
au lieu de renvoyer "".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT
* docs(stt): documenter le fix /no_think Qwen3 + instance embeddings :1238
- admin/ia/stt.md : caveat « thinking » Qwen3 (content vide / timeout 502) +
fix /no_think + fallback reasoning_content ; roadmap 5e ; bannière.
- stt/server/README.md : env STT_DISABLE_THINKING + section caveat Qwen3.
- admin/ia/llama_server.md : 4e instance llama-embed :1238 (nomic, dim 768),
double source d'embeddings (1234 Qwen3 / 1238 nomic), caveat thinking,
correction dim Qwen3 (3584 → 4096).
- admin/ia/litellm.md : cross-ref caveat thinking (concerne tous les
consommateurs de hermes-default/qwen3).
- ci : exclure **/*.md du trigger build-stt-server (doc-only ne rebuild plus).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(stt): mémoire long-terme sémantique via Qdrant (5b)
Serveur : longterm.py — collection Qdrant stt-memory (embeddings Qwen3 gpu-01, dim auto,
Cosine), recall top-k injecté au prompt, remember des tours user. Tout dégrade proprement
si Qdrant/embeddings injoignables (la mémoire court-terme tient). Env STT_MEMORY_LONGTERM,
STT_QDRANT_URL, STT_EMBED_URL, STT_MEMORY_TOPK.
Testé en process : dégradation OK (Qdrant down → mem=0, pas de crash, court-terme tient).
Qdrant réparé le 17/06 (5c). Recherche sémantique réelle à valider sur cluster.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT
* feat(stt): endpoint /v1/memory/health + upsert Qdrant synchrone
- /v1/memory/health sonde activement embeddings + Qdrant + collection et
expose les erreurs (recall/remember dégradent en silence → indébogables).
Permet de diagnostiquer la mémoire long-terme sans kubectl exec.
- remember() : upsert avec ?wait=true → le souvenir est immédiatement
cherchable (sans wait, Qdrant met l'écriture en file → un recall
cross-session immédiat pouvait le rater).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT
* docs(stt): 5b mémoire long-terme validée en prod + backlog nomic-embed-text
- Rappel cross-session confirmé (« Felix » retrouvé dans une nouvelle session),
points_count vérifié via /v1/memory/health.
- Note du fix upsert ?wait=true et de l'endpoint de diagnostic.
- Roadmap : 5d (nomic-embed-text dim 768) en backlog qualité ; états haut/bas
du doc mis à jour (déployé + validé sur cible).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT
* feat(stt): embeddings dédiés nomic-embed-text sur gpu-01 (:1238) + migration auto
Remplace Qwen3 (chat réutilisé, dim 4096, peu discriminant) par un modèle
d'embedding spécialisé nomic-embed-text (dim 768) pour la mémoire long-terme.
Ansible (rôle llama_server) :
- nouvelle instance optionnelle `llama-embed` (llama_embed_enabled) servant un
modèle d'embedding dédié sur :1238, GPU ; télécharge le GGUF si absent.
- activée sur gpu-01 (host_vars) : nomic-embed-text-v1.5 f16.
STT-server :
- STT_EMBED_URL → :1238, STT_EMBED_MODEL → nomic-embed-text (deployment + config).
- _ensure_collection détecte le changement de dimension (4096→768) et recrée
automatiquement la collection stt-memory (anciens vecteurs incomparables) —
pas de drop manuel.
Docs : llama_server README, rag.md, stt.md (5d ✅), CLAUDE.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT
* fix(stt): désactiver le mode thinking de Qwen3 (/no_think) — content vide / 502
Qwen3 est un modèle « thinking » : il dépensait tout le budget max_tokens en
reasoning_content → content vide, ou partait en raisonnement long → timeout (502
"upstream LiteLLM : " avec message vide). Diagnostic : appel direct LiteLLM en
0.87s mais content="" et tout dans reasoning_content.
- brain.py : ajoute le token de contrôle `/no_think` au system prompt
(configurable STT_DISABLE_THINKING, défaut true ; inoffensif pour non-Qwen).
- brain.py : filet de sécurité — si content vide, récupère reasoning_content
au lieu de renvoyer "".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(stt): mémoire long-terme sémantique via Qdrant (5b)
Serveur : longterm.py — collection Qdrant stt-memory (embeddings Qwen3 gpu-01, dim auto,
Cosine), recall top-k injecté au prompt, remember des tours user. Tout dégrade proprement
si Qdrant/embeddings injoignables (la mémoire court-terme tient). Env STT_MEMORY_LONGTERM,
STT_QDRANT_URL, STT_EMBED_URL, STT_MEMORY_TOPK.
Testé en process : dégradation OK (Qdrant down → mem=0, pas de crash, court-terme tient).
Qdrant réparé le 17/06 (5c). Recherche sémantique réelle à valider sur cluster.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT
* feat(stt): endpoint /v1/memory/health + upsert Qdrant synchrone
- /v1/memory/health sonde activement embeddings + Qdrant + collection et
expose les erreurs (recall/remember dégradent en silence → indébogables).
Permet de diagnostiquer la mémoire long-terme sans kubectl exec.
- remember() : upsert avec ?wait=true → le souvenir est immédiatement
cherchable (sans wait, Qdrant met l'écriture en file → un recall
cross-session immédiat pouvait le rater).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT
* docs(stt): 5b mémoire long-terme validée en prod + backlog nomic-embed-text
- Rappel cross-session confirmé (« Felix » retrouvé dans une nouvelle session),
points_count vérifié via /v1/memory/health.
- Note du fix upsert ?wait=true et de l'endpoint de diagnostic.
- Roadmap : 5d (nomic-embed-text dim 768) en backlog qualité ; états haut/bas
du doc mis à jour (déployé + validé sur cible).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT
* feat(stt): embeddings dédiés nomic-embed-text sur gpu-01 (:1238) + migration auto
Remplace Qwen3 (chat réutilisé, dim 4096, peu discriminant) par un modèle
d'embedding spécialisé nomic-embed-text (dim 768) pour la mémoire long-terme.
Ansible (rôle llama_server) :
- nouvelle instance optionnelle `llama-embed` (llama_embed_enabled) servant un
modèle d'embedding dédié sur :1238, GPU ; télécharge le GGUF si absent.
- activée sur gpu-01 (host_vars) : nomic-embed-text-v1.5 f16.
STT-server :
- STT_EMBED_URL → :1238, STT_EMBED_MODEL → nomic-embed-text (deployment + config).
- _ensure_collection détecte le changement de dimension (4096→768) et recrée
automatiquement la collection stt-memory (anciens vecteurs incomparables) —
pas de drop manuel.
Docs : llama_server README, rag.md, stt.md (5d ✅), CLAUDE.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(stt): mémoire long-terme sémantique via Qdrant (5b)
Serveur : longterm.py — collection Qdrant stt-memory (embeddings Qwen3 gpu-01, dim auto,
Cosine), recall top-k injecté au prompt, remember des tours user. Tout dégrade proprement
si Qdrant/embeddings injoignables (la mémoire court-terme tient). Env STT_MEMORY_LONGTERM,
STT_QDRANT_URL, STT_EMBED_URL, STT_MEMORY_TOPK.
Testé en process : dégradation OK (Qdrant down → mem=0, pas de crash, court-terme tient).
Qdrant réparé le 17/06 (5c). Recherche sémantique réelle à valider sur cluster.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT
* feat(stt): endpoint /v1/memory/health + upsert Qdrant synchrone
- /v1/memory/health sonde activement embeddings + Qdrant + collection et
expose les erreurs (recall/remember dégradent en silence → indébogables).
Permet de diagnostiquer la mémoire long-terme sans kubectl exec.
- remember() : upsert avec ?wait=true → le souvenir est immédiatement
cherchable (sans wait, Qdrant met l'écriture en file → un recall
cross-session immédiat pouvait le rater).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT
---------
Co-authored-by: Claude <noreply@anthropic.com>
Serveur : longterm.py — collection Qdrant stt-memory (embeddings Qwen3 gpu-01, dim auto,
Cosine), recall top-k injecté au prompt, remember des tours user. Tout dégrade proprement
si Qdrant/embeddings injoignables (la mémoire court-terme tient). Env STT_MEMORY_LONGTERM,
STT_QDRANT_URL, STT_EMBED_URL, STT_MEMORY_TOPK.
Testé en process : dégradation OK (Qdrant down → mem=0, pas de crash, court-terme tient).
Qdrant réparé le 17/06 (5c). Recherche sémantique réelle à valider sur cluster.
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT
Co-authored-by: Claude <noreply@anthropic.com>