Commit graph

237 commits

Author SHA1 Message Date
9a46f6cbcb
feat(stt): actions cluster via Hermes — contexte « agent » + exécuteur hermes-exec (#42)
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>
2026-06-21 04:00:14 +02:00
github-actions[bot]
5af51f8f66 ci(stt): image → sha-e49b4fc [skip ci] 2026-06-21 01:29:29 +00:00
e49b4fc3ba
refactor(stt): unifie le jeton Ghostfolio — source client unique, appel côté serveur (#41)
Avant : jeton Ghostfolio en double (config client pour l'intent vocal + secret k8s pour
le contexte serveur). Désormais une SEULE source — la config client — transmise au
serveur, qui fait tous les appels Ghostfolio (un seul code, lecture seule).

Serveur :
- sources.py : cœur _ghostfolio_fetch(token) partagé ; ghostfolio_block (contexte) et
  ghostfolio_phrase (intent) acceptent un jeton par requête (priorité client > env).
- app.py : /v1/ask accepte `secrets` (jetons client) passés aux sources ; nouvel
  endpoint POST /v1/portfolio {token} → valeur du portefeuille.
- bump 0.4.0 → 0.5.0.

Client :
- api.py : ServerClient.secrets transmis dans /v1/ask ; méthode portfolio() → /v1/portfolio.
- cli.py : _make_client renseigne secrets["ghostfolio_token"] depuis [ghostfolio].
- ui/app.py : l'intent « combien ghostfolio » appelle client.portfolio (serveur).
- supprime portal/ghostfolio.py (client) — l'appel API vit côté serveur.
- config : [ghostfolio] = juste access_token (transmis au serveur).
- bump 0.12.0 → 0.13.0.

Le secret k8s STT_GHOSTFOLIO_TOKEN devient un simple fallback. Validé bout-en-bout
contre une instance serveur locale + Ghostfolio réel : « …vaut environ 16 520 EUR. »

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 03:28:52 +02:00
ae4d5bd20e
fix(stt-client): restaure les intents vocaux (#37) + l'affichage version perdus de main (#40)
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>
2026-06-21 03:05:03 +02:00
de4b66ed56
feat(stt-client): sélecteur de contexte + visualiseur dans le HUD (#39)
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>
2026-06-21 02:57:03 +02:00
github-actions[bot]
89aeb418ff ci(stt): image → sha-1faf50a [skip ci] 2026-06-20 21:49:59 +00:00
1faf50a9be
feat(stt-server): contextes présélectionnables + sources live + contexte assemblé (visualiseur) (#38)
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>
2026-06-20 23:49:29 +02:00
d4b053622c
feat(stt-client): pages détail par service + santé live (probe + Prometheus + Alertmanager) (#36)
Transforme le portail (tuiles ouvrant un lien) en mini-pages par service, avec état
de santé temps réel — comme demandé.

Page détail (clic sur une tuile) : icône+nom, pilule d'état (en ligne/dégradé/hors
ligne + latence), description, composants (pastilles), alertes actives, infos (URL),
bouton « Ouvrir ». Tuiles : pastille de santé live + badge global d'alertes.

- registry.py : Service enrichi (description, health_url, components[{name,prom}],
  alerts_match) — toujours config-driven.
- health.py : StatusPoller en thread. Probe HTTP (up/down+latence) + Prometheus
  /api/v1/query (composants via up{}/kube-state-metrics) + Alertmanager /api/v2/alerts
  (hors Watchdog). Parallélisé (ThreadPoolExecutor), pousse portal-status au HUD.
- config.py : métadonnées homelab vérifiées en live (7 services ; traefik retiré —
  URL injoignable) + section [portal] (intervalle, URLs, timeouts).
- app.py : démarre le poller, diffuse portal-status (+ dernier état au connect).
- HUD : vues liste⟷détail, pastilles, badge alertes, mise à jour live.
- bump 0.9.0 → 0.10.0.

Validé : StatusPoller en direct contre le homelab (cycle 0.12s, 7 services ok,
composants Prometheus résolus, 0 alerte) ; HUD via Playwright (pastilles ok/dégradé/
hors-ligne, page détail complète, alerte rendue, bouton Ouvrir → open-service).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 23:17:27 +02:00
3b6099ffa0
feat(stt-client): contrôles du service dans le HUD + commandes stt --start/--restart (#35)
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>
2026-06-20 22:37:04 +02:00
9c335d169b
feat(stt-client): portail de services dans le HUD — tuiles cliquables + registre config-driven (#34)
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>
2026-06-20 22:20:11 +02:00
a7b5c44e82
docs(rag): chunk dense « nœuds + OS » pour fermer le trou de grounding (cas Q3) (#33)
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>
2026-06-20 21:41:13 +02:00
4d8cc1968d
feat(stt-client): ASR enfichable — backend onnx Parakeet/Nemotron en plus de whisper (#32)
Phase 0 (abstraction) + Phase 1 (backend onnx), additif et rétrocompatible.

- voice/asr/ : protocole ASRBackend (load + transcribe) + factory make_backend
  pilotée par cfg["asr_engine"]. Imports lourds lazy → stt --help/config OK sans
  aucun moteur installé.
- whisper.py : faster-whisper extrait tel quel (défaut, comportement inchangé).
- onnx_parakeet.py : onnx-asr (Parakeet/Canary/Nemotron ONNX, multilingue FR,
  ~600M, CPU rapide / GPU AMD via onnxruntime-rocm). Providers configurables ;
  message d'install actionnable si onnx-asr absent.
- engine.py : délègue load()/_transcribe() au backend (le reste — VAD, wake word,
  TTS — inchangé).
- config : [voice] asr_engine="whisper" (défaut), onnx_model, onnx_providers.
- pyproject : extras optionnels onnx / onnx-rocm ; bump 0.6.1 → 0.7.0.

Bascule à chaud par config, rollback = asr_engine="whisper". Streaming = phase 2
(spike séparé). admin/ia/stt.md mis à jour.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 14:28:35 +02:00
470c69881c
chore(gpu-01): retire les serveurs llama CPU system/monitor/dev (inutilisés) (#31)
Trois instances llama-server CPU manuelles (hors Ansible) tournaient sur gpu-01
sans servir : déjà orphelines de LiteLLM (alias absents de config.yaml, d'où le
bruit "Invalid model name"). Suppression complète :

gpu-01 (opérationnel, déjà fait) :
- systemctl disable --now + rm des units llama-server-{system,monitor,dev}
- ports firewall 1235/1236/1237 fermés

Repo :
- monitoring : scrape jobs (values.yaml) + alertes LlamaServer{System,Monitor}Down
  (alerts-ai.yaml) retirés → plus de "target down"/alertes fantômes ; panneaux de
  statut dashboard-infrastructure recyclés (system→llama-embed, monitor supprimé).
- docs admin/ + hermes-skills (souls funk-ai/brain, SKILL agent-delegation) mises à
  jour : inventaire modèles, profils Hermès system/monitor sans backend (à repointer
  sur qwen3-8b), litellm, ops, monitoring.

Les profils Hermès system/monitor existent toujours mais sans backend dédié.
Dashboards Grafana dashboard-ai (panneaux graphiques CPU) laissés tels quels
(cosmétique, séries vides, aucune alerte).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 13:25:50 +02:00
ef15ba25e0
fix(llama_server): embed nomic sur CPU — supprime la contention GPU (docs=0) (#30)
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>
2026-06-20 12:38:08 +02:00
github-actions[bot]
1d3e1e6eb8 ci(stt): image → sha-d982308 [skip ci] 2026-06-20 10:18:01 +00:00
d982308137
feat(stt-server): grounding strict — dire "doc ne le précise pas" au lieu d'inventer (#29)
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>
2026-06-20 12:17:29 +02:00
1b85d4ee65
feat(llama_server): heartbeat embed — garde chaud le slot nomic :1238 (#28)
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>
2026-06-19 22:17:23 +02:00
github-actions[bot]
11693e2abf ci(stt): image → sha-1db84e9 [skip ci] 2026-06-19 19:54:11 +00:00
1db84e9041
feat(stt-server): grounding RAG — le cerveau répond depuis la doc Funk (#27)
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>
2026-06-19 21:53:30 +02:00
767a94b1d0
fix(stt-client): stt --stop détecte le process lancé via pipx (#26)
`_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>
2026-06-19 21:53:15 +02:00
5855bd0b29
feat(rag): embedder dédié nomic-embed-text (768) au lieu de qwen3-8b (#25)
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>
2026-06-19 21:52:52 +02:00
9feb02ed70
feat(stt): boutons stop/mute (HUD) + stt --stop + RAG funk-docs nettoyé (#24)
* 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>
2026-06-19 21:17:32 +02:00
577d61ebaa
feat: anti-502 LLM — heartbeat (litellm) + watchdog auto-réparation (llama_server) (#23)
* 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>
2026-06-19 20:52:29 +02:00
d6ec83af9d
fix(litellm): request_timeout 600→60s + num_retries 2 (anti-connexion périmée) (#22)
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>
2026-06-19 16:00:29 +02:00
github-actions[bot]
a580b04c66 ci(stt): image → sha-681b94e [skip ci] 2026-06-19 13:48:18 +00:00
681b94e268
fix(stt-server): retry brain + timeout par tentative + log timing effectif (#21)
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>
2026-06-19 15:46:35 +02:00
github-actions[bot]
ced79658cf ci(stt): image → sha-914942d [skip ci] 2026-06-19 13:29:32 +00:00
914942de73
perf(stt-server): mémoire long-terme hors chemin de réponse + résilience embed (#20)
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>
2026-06-19 15:27:59 +02:00
7025d7ae70
feat(stt): entrée texte HUD + finitions client + doc incident LLM/GPU (#19)
Regroupe le travail postérieur à la PR #18 (squash-mergée), rebasé proprement
sur main :

- HUD : composer texte (chat clavier sans micro) — {"type":"text"} →
  VoiceEngine.respond_text (hors boucle audio), lock _respond (voix ⟂ texte)
- App autonome : fenêtre app/kiosk via profil + WM class dédiés (hors Brave)
- Auto-update non destructif : `stt --update` = pipx upgrade (ne laisse plus
  l'install cassée en cas d'échec réseau)
- Terminal propre : --log-level=3 + sortie navigateur vers DEVNULL
- Docs : admin/incidents-llm-gpu.md (journal d'inférence dédié) — incident
  2026-06-19 llama-server gelé (/health OK mais génération bloquée → 502),
  + HUD README / stt.md (composer), bump 0.4.x → 0.5.1

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 14:56:36 +02:00
5f1e34dad9
feat(stt): lancement & cycle de vie du client — auto-start, app de bureau, auto-update (#18)
* 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>
2026-06-19 00:05:02 +02:00
2658f92c2f
feat(stt): HUD avancé (phase 4) — design Claude Design câblé au backend (#17)
* 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>
2026-06-18 22:03:39 +02:00
7821651cce
docs(readme): ajouter le switch réseau (GS308EV4) à l'architecture (#16)
* 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>
2026-06-18 18:37:42 +02:00
c023ab0e58
docs: passe de cohérence documentaire (STT, Ghostfolio, embeddings, Qdrant) (#15)
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>
2026-06-18 11:16:34 +02:00
github-actions[bot]
e1ec26d546 ci(stt): image → sha-870c28b [skip ci] 2026-06-17 20:33:37 +00:00
870c28b43d
docs(stt): fix /no_think Qwen3 + instance embeddings :1238 (doc complète) (#14)
* 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>
2026-06-17 22:32:43 +02:00
github-actions[bot]
703d6a9015 ci(stt): image → sha-a1dd9d7 [skip ci] 2026-06-17 20:22:01 +00:00
a1dd9d7c44
fix(stt): désactiver le thinking de Qwen3 (/no_think) — corrige content vide / 502 (#13)
* 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>
2026-06-17 22:20:26 +02:00
github-actions[bot]
9f06bbb13d ci(stt): image → sha-9fef555 [skip ci] 2026-06-17 19:55:40 +00:00
9fef555cc3
feat(stt): embeddings dédiés nomic-embed-text sur gpu-01 (:1238) + migration auto (#12)
* 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>
2026-06-17 21:54:41 +02:00
github-actions[bot]
30942f6478 ci(stt): image → sha-1c3128a [skip ci] 2026-06-17 18:32:52 +00:00
1c3128a319
feat(stt): /v1/memory/health + upsert Qdrant synchrone (debug 5b) (#11)
* 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>
2026-06-17 20:32:04 +02:00
github-actions[bot]
a670784e4d ci(stt): image → sha-1986ab5 [skip ci] 2026-06-17 14:54:20 +00:00
1986ab56d8
feat(stt): mémoire long-terme sémantique via Qdrant (5b) (#10)
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>
2026-06-17 16:52:33 +02:00
70499b7d84
ci(stt): images taguées par SHA + bump auto du manifest (fin du :latest) (#9)
La CI build l'image en sha-<commit> et, sur main, met à jour k8s/apps/stt/deployment.yaml
avec ce tag (commit [skip ci]). ArgoCD redéploie alors une image immuable et déterministe
— fini les races de timing et les 'kubectl rollout restart' manuels.

permissions: contents write (pour le bump). :latest gardé en parallèle sur main (fallback).


Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-17 16:39:14 +02:00
815beaf37c
feat(stt): mémoire court-terme de session côté serveur (5a) (#8)
Serveur : SessionStore (historique borné + TTL, en mémoire), /v1/ask accepte session_id
et injecte l'historique dans l'appel LLM, /v1/reset l'efface. Dockerfile en 1 worker
(cohérence mémoire process).

Client : session_id généré par run (uuid), envoyé à chaque requête ; commande /reset
en mode texte.

Testé en process (TestClient) : historique croît 0→2→4, reset→0, sessions isolées,
sans session_id = sans état.

Mémoire long-terme Qdrant (5b) + réparation Qdrant (5c) à suivre.


Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-17 16:15:57 +02:00
bcdb4f8b2a
feat(stt): mode texte + sélection de modèle (client→serveur par requête) (#7)
Serveur : /v1/ask accepte un model optionnel (validé contre STT_ALLOWED_MODELS),
nouvel endpoint GET /v1/models (défaut + alias autorisés). Pas de switch global.

Client : 'stt --text' (chat texte simple sans micro/HUD), '--model hermes|claude|qwen|opus'
(noms courts → alias LiteLLM), commandes /model et /models en mode texte. Le modèle
choisi est envoyé au serveur à chaque requête (voix comme texte).


Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-17 13:36:06 +02:00
47936b0088
fix(stt): joindre LiteLLM en IP directe (192.168.10.1:4000) (#6)
L'indirection litellm-ext (Service sans sélecteur + Endpoints manuel) ne routait pas
('All connection attempts failed'). open-webui joint LiteLLM en IP directe — on copie
ce pattern éprouvé et on supprime litellm-external.yaml.


Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-17 13:19:51 +02:00
35f37bd293
fix(stt): imagePullSecret ghcr-pull pour l'image privée ghcr (#5)
* fix(stt): imagePullSecret ghcr-pull pour l'image privée

Le Deployment référence un secret de pull ghcr-pull (ns ai) — image ghcr privée.
Secret créé manuellement (même pratique que ghostfolio-secret, non versionné).
Doc d'install dans stt/README.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT

* fix(stt): securityContext conforme PodSecurity restricted (ns ai)

runAsNonRoot + runAsUser 1000 + seccompProfile RuntimeDefault (pod),
allowPrivilegeEscalation false + drop ALL caps (conteneur).

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>
2026-06-17 12:40:34 +02:00
e390ddef12
feat(stt): assistant vocal Jarvis — client pipx + STT-server in-cluster (#4)
* feat(stt): cadrage + squelette assistant vocal Jarvis

Conception validée du projet STT — assistant vocal/HUD du homelab Funk :
- HUD web sur-mesure + STT/TTS local (faster-whisper + Piper)
- Packaging commande pipx (stt), démarrage auto systemd --user
- Cerveau 3 modes + auto-détection LAN : hermes / local-direct / claude-direct
- Mémoire 3 tiers : SQLite local + Qdrant s01 + GitHub (distillée, versionnée)

Réutilise tools/hermes-voice, LiteLLM, Hermes Agent. Squelette + doc admin/ia/stt.md,
implémentation par phases (roadmap dans le doc).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT

* feat(stt): phase 1-2 — commande, backend vocal, routeur cerveau, HUD MVP

- cli.py : commande `stt` (--setup, --mode, --no-tts)
- config.py : défauts embarqués + ~/.config/stt/stt.toml
- voice/engine.py : refactor de hermes-voice en classe avec callbacks d'état
- brain/router.py : 3 modes (hermes SSH / local LiteLLM / claude API) + auto-détection LAN
- server/app.py : HTTP statique (HUD) + websocket (états → HUD)
- memory/store.py : tier local SQLite (Qdrant + sync GitHub = phase 4)
- hud/index.html : HUD MVP (visualiseur d'état + conversation)

Vérifié hors-LAN : py_compile, --help, config, routeur (→ claude), mémoire SQLite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT

* fix(stt): embarquer le HUD dans le package (404 après pipx install)

Le HUD était à la racine du projet (stt/hud/) donc absent du package installé
par pipx → HTTP 404 sur /. Déplacé dans le package (stt/stt/hud/) + package-data,
HUD_DIR ajusté. Vérifié : le wheel contient bien stt/hud/index.html.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT

* refactor(stt): pivot client-serveur — STT-server in-cluster + client pipx

Sépare STT en deux :
- stt/client/ : commande `stt` (pipx), voix locale (Whisper/Piper) + HUD ; envoie
  le texte au serveur via api.py (ServerClient → POST /v1/ask). URL serveur paramétrable,
  pas de cerveau local (suppression du routeur 3 modes).
- stt/server/ : STT-server FastAPI (conteneur), /healthz + /v1/ask → LiteLLM (Qwen3/Claude).

Déploiement cluster :
- k8s/apps/stt/ : Deployment, Service, IngressRoute (stt.lab.local), litellm-ext
  (Service + Endpoints → 192.168.10.1:4000 pour joindre LiteLLM hors cluster)
- k8s/apps-of-apps/apps/stt.yaml : Application ArgoCD (depuis main)
- .github/workflows/build-stt-server.yml : build/push image → ghcr.io/alkatrazz24/funk-stt-server

Inférence/chat seulement (outils Hermes 'agir sur Funk' = phase ultérieure, API :8080 à spécifier).
Vérifié : py_compile client+serveur, YAML manifests, ServerClient.

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>
2026-06-17 12:08:58 +02:00
22d40023e1 docs(n8n): rapports portefeuille — news en FR + piège apostrophe JS + édition via API
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 13:24:21 +02:00