Funk-lab/CLAUDE.md
ALI YESILKAYA 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

15 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Présentation du projet

Funk — homelab IA composé d'un cluster Kubernetes (Talos) pour les services applicatifs, d'un hôte d'inférence LLM dédié (gpu-01 / llama-server ROCm), et d'un agent autonome (Hermes Agent) sur storage-01. Stack IaC complète : Ansible + talhelper + ArgoCD.

État actuel (2026-06-10) :

  • Hermes Agent opérationnel — multi-profils (funk-ai/system/monitor/brain)
  • ⚠️ RAG HS depuis 2026-06-05 — Qdrant en crash-loop (segment funk-docs corrompu, voir admin/incidents.md) ; rag-ingest / rag-query indisponibles
  • Monitoring complet — Prometheus + Grafana + AlertManager → webhook Hermes + n8n
  • storage-01 installé — 192.168.10.1 (LAN cluster), 192.168.1.200 (WAN)
  • gpu-01 installé — llama-server GPU (:1234) + CPU (:1236/:1237) + embeddings nomic-embed-text (:1238, GPU) opérationnels, kernel 5.14.0-687.5.3
  • LiteLLM sur storage-01 — proxy multi-modèles + ask-agent (écoute 0.0.0.0:4000)
  • Cluster k8s Talos opérationnel (MetalLB + Traefik + wildcard DNS lab.local)
  • postfix_relay — SMTP sortant via Gmail SMTP + App Password (aliyesilkaya93@gmail.com)
  • Open WebUI opérationnel — openwebui.lab.local, namespace ai, backend LiteLLM
  • n8n opérationnel — n8n.lab.local, namespace ai, 3 workflows actifs (alertes, rapport horaire, Hermes Doc Worker)
  • hermes-auto-improve — analyse quotidienne de admin/ par Hermes à 22h (n8n → trigger :9095), PR sur hermes/daily-work
  • WOL compute nodes non configuré (BIOS) — allumage manuel requis

Journal de progression : PROGRESS.mdprogress/YYYY-MM-DD.md


Architecture des machines

Machine IP OS Rôle
storage-01 192.168.10.1 / 192.168.1.200 AlmaLinux 9.7 Passerelle + bastion admin + données + Hermes Agent — hors cluster
compute-01 192.168.10.11 Talos v1.13 k8s control-plane
compute-02 192.168.10.12 Talos v1.13 k8s worker
compute-03 192.168.10.13 Talos v1.13 k8s worker
gpu-01 192.168.10.20 AlmaLinux 9.7 llama-server dédié (RX 6700XT, ROCm 7.x) — hors cluster

Points clés d'architecture :

  • storage-01 et gpu-01 sont hors du cluster k8s — consommés via ExternalName services ou Endpoints manuels
  • Single control-plane assumé : si compute-01 tombe, kubectl apply ne fonctionne plus mais les workloads continuent
  • Modèles LLM sur storage-01 (/srv/data/models, NVMe depuis 2026-05-29), montés sur gpu-01 via NFS (/mnt/models)

Stack IaC — trois outils, trois cibles

Outil Cible Déclenchement
Ansible storage-01, gpu-01 (AlmaLinux) Push manuel : make apply-storage / make apply-gpu
talhelper compute-01/02/03 (Talos) Push manuel : talosctl apply-config
ArgoCD Workloads k8s Pull automatique depuis Git

Ansible

Commandes via Makefile (depuis la racine du repo)

python3 -m venv .venv  # créer le venv (une seule fois, avant make install)
make install        # pip install + ansible-galaxy (à faire une fois)
make ping           # vérifier la connectivité vers tous les hôtes
make check          # dry-run site.yml (storage-01 + gpu-01)
make apply          # appliquer site.yml complet
make apply-storage  # appliquer uniquement storage-01.yml
make apply-gpu      # appliquer uniquement gpu-01.yml
make vault-view     # lire le vault chiffré
make vault-edit     # éditer le vault (ouvre $EDITOR)
make vault-encrypt  # (ré)chiffrer vault.yml

Commandes directes (depuis ansible/)

ansible-playbook -i inventory.yml playbooks/storage-01.yml --tags litellm
ansible-playbook -i inventory.yml playbooks/gpu-01.yml --tags llama_server

Les tags correspondent aux noms de rôles. Tous les rôles sont idempotents.

Inventaire et connectivité

  • storage-01 : accès direct SSH via 192.168.1.200 (WAN)
  • gpu-01 : accès via ProxyJump sur storage-01 (configuré dans inventory.yml)
  • Vault password dans .vault_pass (ignoré par git, chargé automatiquement via ansible.cfg)

Rôles — storage-01

hermes_user, common, gateway (nftables + NAT), dnsmasq, nfs_server, qdrant, postgresql, minio (⚠️ rôle vide, service non déployé), litellm, hermes_agent, rag, node_exporter, alertmanager_webhook, postfix_relay, hermes_auto_improve (worker doc Hermes + trigger server :9095)

Rôles — gpu-01

hermes_user, common, rocm, nfs_client, llama_server, node_exporter

Chaque rôle a un README.md (variables, caveats, gaps IaC). lm_studio est présent dans ansible/roles/ mais n'est plus utilisé dans aucun playbook (déprécié, voir son README). ⚠️ Gap IaC : les instances llama-server CPU :1236/:1237 sur gpu-01 sont des services manuels, non gérés par le rôle llama_server (voir ansible/roles/llama_server/README.md).

Caveat premier déploiement — hermes_agent

Le rôle hermes_agent installe le binaire via curl | bash seulement si le binaire est absent. La configuration (.env, config.yaml) est ensuite conditionnelle à hermes_binary.stat.exists. Sur un hôte vierge, il faut deux runs consécutifs : le premier installe, le second configure.

Variables par hôte

ansible/host_vars/<hostname>/vars.yml contient les variables spécifiques à chaque machine :

  • storage-01 : UUID RAID5, exports NFS, chemins des services sous /srv/data
  • gpu-01 : chemin du modèle GGUF (llama_model_path), alias modèle, llama_ctx_size, montages NFS

Variables globales (non-secrètes) dans ansible/group_vars/all/vars.yml : admin_user, dns_domain, ntp_servers, cluster_network, wan_gateway.

Secrets Ansible Vault

Variables chiffrées dans ansible/group_vars/all/vault.yml :

  • vault_anthropic_api_key — clé Anthropic pour le profil brain
  • vault_pg_hermes_password, vault_pg_litellm_password, vault_pg_n8n_password, vault_pg_openwebui_password
  • vault_postfix_relay_password — App Password Gmail (relay SMTP)
  • vault_n8n_api_key — clé API n8n (label "claude") pour gérer les workflows via /api/v1

talhelper (nœuds Talos)

cd talos/
talhelper genconfig                                        # régénérer configs nœuds
talhelper gencommand apply --node compute-02 | bash        # appliquer sur un nœud
talhelper gencommand bootstrap --node compute-01 | bash    # bootstrap (1ère fois seulement)
talhelper gencommand kubeconfig | bash                     # récupérer kubeconfig

talos/clusterconfig/ est généré — gitignored, ne pas committer. talos/talsecret.sops.yaml chiffré avec SOPS+age (clé dans .config/sops/, ignorée par git).


ArgoCD (workloads k8s)

Pattern "App of Apps" : modifier un manifest → git commit + git push → ArgoCD poll automatiquement (~3 min).

git add k8s/apps/open-webui/
git commit -m "open-webui: bump to v0.4.0"
git push

ArgoCD utilise le repo git@github.com:Alkatrazz24/Funk-lab.git branche main.

Structure k8s/

k8s/
├── apps-of-apps/
│   ├── root.yaml            # Application ArgoCD racine (pointe vers apps-of-apps/apps/)
│   └── apps/                # Un fichier Application par workload — ArgoCD les crée automatiquement
│       ├── n8n.yaml
│       ├── open-webui.yaml
│       ├── monitoring.yaml
│       └── nfs-provisioner.yaml
├── apps/                    # Manifests raw k8s par application
│   ├── n8n/                 # namespace: ai
│   ├── open-webui/          # namespace: ai
│   └── {external-services}/ # ExternalName/Endpoints pointant vers storage-01 ou gpu-01
└── infra/                   # Helm releases (HelmRelease + values.yaml)
    ├── monitoring/           # kube-prometheus-stack 85.0.2
    ├── nfs-provisioner/
    ├── traefik/
    └── metallb/

Ajouter un nouveau workload k8s

  1. Créer k8s/apps/<nom>/ avec les manifests (Deployment, Service, Ingress, PVC…)
  2. Créer k8s/apps-of-apps/apps/<nom>.yaml (ArgoCD Application CRD) — copier n8n.yaml comme modèle
  3. git commit + git push → ArgoCD prend en charge automatiquement

Namespaces actifs : argocd, infra (Traefik, MetalLB, NFS-provisioner), monitoring (Prometheus/Grafana), ai (n8n, open-webui).


Stack IA — Hermes Agent

Profils

Profil Modèle Port Usage
funk-ai Qwen3-8B Q4_K_M GPU :1234 Agent principal (~35 tok/s)
system Qwen3-1.7B Q4_K_M CPU :1236 Calculs, formatage, résumés
monitor Qwen3-1.7B Q4_K_M CPU :1237 Analyse logs, métriques, état services
brain Claude Sonnet 4.6 API Anthropic Raisonnement complexe — facturé

Hermes écoute sur le port 8080 (gateway). Dashboard web sur le port 9119 (accessible uniquement depuis 192.168.1.10).

Switcher le modèle de Hermes à chaud

hermes-switch status    # modèle actuel de hermes-default
hermes-switch qwen      # → Qwen3-8B sur gpu-01 (local, gratuit)
hermes-switch claude    # → Claude Sonnet 4.6 via API Anthropic (facturé)

hermes-switch modifie /etc/litellm/config.yaml puis redémarre litellm. L'alias hermes-default dans LiteLLM est ce que Hermes consomme — changer cet alias ne nécessite pas de reconfigurer Hermes.

ask-agent (délégation inter-agents)

Binaire dans /usr/local/bin/ask-agent sur storage-01. Appel LiteLLM direct (:4000), réponse en ~2-5s.

Règle : ask-agent reçoit du texte, jamais des commandes shell. Toujours 2 étapes :

# Étape 1 — collecter les données
journalctl -u litellm -n 20 --no-pager --output=cat
# Étape 2 — passer le texte à ask-agent
ask-agent monitor "logs litellm : <résultat étape 1> — erreurs ?"

RAG — Documentation Funk

⚠️ HS depuis 2026-06-05 — Qdrant en crash-loop (segment funk-docs corrompu). Réparation : supprimer la collection corrompue puis relancer rag-ingest (reconstructible depuis admin/).

Qdrant collection funk-docs indexée depuis admin/ (339 chunks, score min 0.60).

rag-query "comment relancer dnsmasq ?"
rag-query "nftables port cluster configuration"
rag-query "hermes profils litellm modèles" --top 3

Scripts installés sur storage-01 : /usr/local/bin/rag-query, /usr/local/bin/rag-ingest

Après modification de admin/, re-déployer via la commande directe (voir section Ansible) :

ansible-playbook -i inventory.yml playbooks/storage-01.yml --tags rag

Le script admin/index_knowledge.py est l'ingesteur Python brut.

Hermes Skills — hermes-skills/

Versionnés dans ce repo, déployés par le rôle hermes_agent vers ~hermes/.

hermes-skills/
├── funk/
│   ├── agent-delegation/SKILL.md   # usage de ask-agent (2 étapes, agents dispo)
│   └── rag-docs/SKILL.md           # usage de rag-query
└── souls/                          # prompts système par profil
    ├── funk-ai.md    # profil principal (agent sur storage-01)
    ├── system.md     # profil calculs/formatage
    ├── monitor.md    # profil analyse logs
    └── brain.md      # profil Claude API

hermes-auto-improve — analyse quotidienne de la doc

Worker qui fait analyser admin/ par Hermes et pousse les rapports sur la branche hermes/daily-work (PR squash-only vers main). Source dans tools/hermes-auto-improve/, déployé par le rôle hermes_auto_improve :

  • /usr/local/bin/hermes-auto-improve — script d'analyse (--all = tous les docs, sinon batch de 5 en rotation ; --daily-pr = crée/met à jour la PR GitHub)
  • /usr/local/bin/hermes-auto-improve-server — trigger HTTP :9095 (POST /trigger/all, /trigger/daily-pr, GET /status)
  • Scheduling : workflow n8n « Hermes Doc Worker — Funk-lab » à 22h (run complet → boucle de polling /status → email + PR). Pas de timer systemd.
  • Rapports écrits dans admin/hermes/builtin/, état dans /var/lib/hermes-auto-improve/, log /var/log/hermes-auto-improve.log

Doc détaillée : admin/ia/hermes-auto-improve.md


Base de connaissance — admin/

Documentation admin versionnée, indexée dans le RAG :

admin/
├── ia/           # hermes, litellm, llama_server, rocm, rag, alertmanager-webhook, hermes-auto-improve
├── infra/        # dnsmasq, nfs, réseau, ssh, email
├── install/      # procédures installation storage-01, gpu-01, k8s
├── k8s/          # argocd, talos, monitoring, n8n, open-webui, k9s
├── ops/          # ansible, cluster, système
├── hermes/builtin/  # rapports générés par hermes-auto-improve (ne pas éditer)
└── incidents.md  # historique des incidents matériel/service avec résolutions

Réseaux

  • CNI Flannel — Pods : 10.42.0.0/16
  • Services : 10.43.0.0/16
  • MetalLB pool : 192.168.10.200-230 (IP virtuelles Traefik/services)
  • DNS wildcard *.lab.local via dnsmasq → Traefik

nftables géré par le rôle gateway sur storage-01. Modifier ansible/roles/gateway/templates/nftables.conf.j2 puis :

ansible-playbook -i inventory.yml playbooks/storage-01.yml --tags gateway

Contrainte GPU critique

La RX 6700XT (gfx1031) n'est pas officiellement supportée par ROCm. Variable obligatoire dans tous les services GPU :

HSA_OVERRIDE_GFX_VERSION=10.3.0

Gérée automatiquement par le rôle llama_server. ROCm tourne directement sur l'hôte AlmaLinux.


Gestion du cluster

Ces commandes sont des fonctions bash sur le poste perso (configurées dans ~/.bashrc) qui SSH sur storage-01 et invoquent /usr/local/bin/funk-cluster.

funk-status                 # état de tous les services (storage-01 + gpu-01 + k8s)
funk-start                  # WOL gpu-01 + démarrage services storage-01
funk-start --k8s            # idem + nœuds k8s (WOL → drain-aware boot)
funk-stop                   # arrêt propre gpu-01 + services
funk-stop --k8s             # idem + drain k8s → NFS check → talosctl shutdown

etcd backup (single control-plane → critique) :

talosctl etcd snapshot /path/to/backup.db --nodes compute-01

Conventions

  • Git : branches main (prod) / feature/* — conventional commits (feat:, fix:, chore:, docs:)
  • Secrets : Ansible Vault (Ansible), SOPS+age (Talos), Sealed Secrets (k8s)
  • Namespaces k8s : argocd, infra, monitoring, ai, external
  • Hermes data : profils/données dans /srv/data/hermes (NVMe, bind-mount /home/data), code dans /opt/hermes/ (SSD OS)
  • RAM compute-02/03 : toujours définir resources.requests + resources.limits (8 GB RAM, OOM-killer actif)