consolidation: fusion doublons (mcp-nas, opencode, bifrost-vk, doc-api rebuild), purge obsoletes (gotenberg, ports-registry racine), re-spherage runbooks metier (perso/tt/nyora), refonte _INDEX.md — 09/07/2026

This commit is contained in:
2026-07-09 12:28:30 +00:00
parent cdeb9ebbc3
commit 390202d93f
25 changed files with 308 additions and 1068 deletions
-87
View File
@@ -1,87 +0,0 @@
# RUNBOOK — Bifrost VK OpenCode Fix + Veille Fallback
**Date** : 2026-06-23
**Contexte** : Veille bolbol.tn — "Resume indisponible" systematique
## Symptomes
- `00:00` et `19:00` : `could not auto resolve a provider`
- `07:00` : `GoUsageLimitError — Weekly usage limit reached`
## Diagnostic
### Erreur 1 — could not auto resolve
Node n8n `Enrichir via Bifrost` (workflow 9378593adccf42dc) envoie x-bf-vk: bfk-e5832bfe (Nabil-Key), model: deepseek-v4-flash.
La Nabil-Key VK n'avait pas OpenCode dans governance_virtual_key_provider_configs.
Bifrost cherchait deepseek-v4-flash dans openrouter/google/nvidia_nim -> introuvable.
### Erreur 2 — GoUsageLimitError
Job cron hermes-nyora `veille-hebdo-nexum-kanban-a1b2c3d4` avait model/provider null.
Orchestrateur -> provider par defaut = OpenCode Go direct -> quota hebdo epuise lundi.
NOTE: Les 6 workers kanban utilisent le profil veilleur (openrouter) -> OK. Seul l'orchestrateur est affecte.
## Fixes appliques
### Fix 1 — Bifrost DB: OpenCode ajoute a Nabil-Key VK
```python
conn = sqlite3.connect('/mnt/docker/bifrost/data/config.db')
cur = conn.cursor()
vk = 'dc17fe0c-f7bf-4752-acb8-798156b2ec9e'
models = '["deepseek-v4-flash","deepseek-v4-pro","glm-5.1","kimi-k2.6"]'
cur.execute(
"INSERT INTO governance_virtual_key_provider_configs "
"(virtual_key_id, provider, weight, allowed_models, blacklisted_models, allow_all_keys) "
"VALUES (?, 'opencode', 1.0, ?, '[]', 1)", (vk, models))
conn.commit()
```
### Fix 2 — Bifrost DB: OpenCode key persistee dans config_keys
```python
kid = str(uuid.uuid4())
now = datetime.datetime.utcnow().isoformat() + 'Z'
cur.execute(
"INSERT INTO config_keys (name,provider_id,provider,key_id,value,models_json,"
"blacklisted_models_json,weight,enabled,encryption_status,created_at,updated_at) "
"VALUES ('opencode-1',36,'opencode',?,?,?,'[]',1.0,1,'plain_text',?,?)",
(kid, BIFROST_OPENCODE_KEY, models, now, now))
conn.commit()
```
### Fix 3 — Bifrost restart (requis apres modif DB)
Via Portainer API http://172.17.0.1:9000 (endpoint_id=2, container 3136d565d6dd).
Auth: POST /api/auth -> jwt. Actions: stop + start sur /api/endpoints/2/docker/containers/{id}/{action}
ATTENTION: container ID change apres recreate -> verifier avant.
### Fix 4 — jobs.json hermes-nyora: orchestrateur job 2
Fichier: /mnt/docker/hermes-platform/hermes-nyora/data/cron/jobs.json
Job veille-hebdo-nexum-kanban-a1b2c3d4 -> model: deepseek/deepseek-v4-flash, provider: openrouter
### Fix 5 — n8n workflow 9378593adccf42dc: fallback gemini-2.5-flash
Node Enrichir via Bifrost modifie: si GoUsageLimitError -> retry avec gemini-2.5-flash.
Via n8n API: PUT /api/v1/workflows/9378593adccf42dc avec {name, nodes, connections, settings}.
## Architecture finale (VK Nabil-Key bfk-e5832bfe)
| Provider | Modeles autorises |
|-------------|------------------------------------------------------------------------|
| openrouter | Gemma-4-31B/26B free, Nemotron, GPT-OSS free, Hermes-3, Qwen3-coder |
| google | gemma-4-31b/26b, gemini-2.5-flash, gemini-2.5-flash-lite |
| nvidia_nim | Llama-3.3-70B, Nemotron-70B, Llama-405B, Qwen2.5-Coder |
| opencode | deepseek-v4-flash, deepseek-v4-pro, glm-5.1, kimi-k2.6 (NOUVEAU) |
## Pieges a eviter
- Bifrost ne hot-reload PAS les providers -> restart obligatoire apres modif DB
- config.json charge providers au boot en memoire; config_keys DB = source persistante
- auth Bifrost = x-bf-vk header, PAS Authorization: Bearer -> 401
- Portainer container ID change apres recreate -> verifier avant restart
- N8N PUT /workflows: accepte uniquement {name, nodes, connections, settings} -> 400 sinon
## MCP NAS — Regles de scripting fiables
| NE PAS faire | Faire a la place |
|--------------------------------------------|-----------------------------------------------|
| Heredoc << 'EOF' | Base64 encode -> transfer -> decode |
| printf 'long code python' | python3 >400c | printf > /tmp/s.py && python3 /tmp/s.py |
| Commands dans session apres plusieurs err | Nouvelle session ou session differente |
| Pipes complexes inline dans printf | Script intermediaire en /tmp/ |
-190
View File
@@ -1,190 +0,0 @@
# MCP NAS — Référence de scripting fiable
**Date** : 2026-06-23 | Session debug Veille/Bifrost (40+ tool calls observés)
> ⚠️ **OBSOLÈTE EN PARTIE depuis le patch P0 du 2026-07-04** — voir [mcp-nas-run-command-v2.md](mcp-nas-run-command-v2.md).
> Les RÈGLES 1, 2 et 3 ci-dessous (poisoning, heredoc interdit, limite 350 chars/base64) **ne s'appliquent plus** :
> heredocs, quotes et commandes longues fonctionnent nativement, et `reset_session` récupère une session bloquée.
> La section « RÈGLE 4 — Services internes (172.17.0.1) » reste valide ;
> depuis le P1, `nas.internal` est un alias stable de 172.17.0.1 dans le container.
---
## Environnement du container
| Propriété | Valeur |
|-----------|--------|
| Shell | `/bin/bash` |
| User | `root` uid=0 |
| Python | `3.11.15``/usr/local/bin/python3` |
| Modules Python | `requests`, `sqlite3`, `json`, `urllib`, `subprocess` |
| Filesystem `/mnt` | NAS Synology 11 To, R/W |
| Réseau host Docker | `172.17.0.1` |
| Réseau LAN NAS | `192.168.100.33`**INACCESSIBLE depuis container** |
### Binaires (vérifié 2026-06-23)
| Outil | Dispo | Note |
|-------|-------|------|
| `curl` | oui | `/usr/bin/curl` — soumis limite 350 chars |
| `base64` | oui | `/usr/bin/base64` |
| `python3` | oui | 3.11 complet |
| `git` | oui | via subprocess Python |
| `sqlite3` CLI | non | utiliser module Python `import sqlite3` |
| `docker` CLI | non | pas de socket monté — Portainer API |
| `paramiko` | non | pas de SSH natif Python |
---
## RÈGLE 1 — Sessions : poisoning
Après 3+ erreurs consécutives, la session refuse même `echo hello`.
Symptôme : `{"error": "Error occurred during tool execution"}` sur commande triviale.
Sessions mortes dans cette session : `claude`, `db_update`, `verify`.
Sessions stables : `model_test`, `emergency`, `s1` (utilisées fraîchement).
**Toujours spécifier `session=` dans run_command** (sans = erreur systématique).
Après 2 erreurs d'affilée : changer de session immédiatement.
Si toutes poisonnées : `new_session("fresh1")`.
---
## RÈGLE 2 — Heredoc interdit
`<< 'EOF'` dans toute forme cause un **timeout 120s** qui tue la session.
Cela inclut `cat > f << 'EOF'`, `python3 << 'PYEOF'`, `bash << 'EOF'`.
Cause : l'injection tmux ne gère pas l'attente stdin que heredoc déclenche.
---
## RÈGLE 3 — Limite 350 chars par commande
Au-delà de ~350 chars dans le champ `command` : timeout, troncature ou erreur silencieuse.
**Méthode 1 — Code court (< 200 chars)**
Deux appels séparés :
```
printf 'import json\nprint("ok")\n' > /tmp/s.py
python3 /tmp/s.py
```
**Méthode 2 — Code moyen (200350 chars)**
Appender en plusieurs appels :
```
printf 'import sqlite3\nconn=sqlite3.connect("/mnt/docker/bifrost/data/config.db")\n' > /tmp/s.py
printf 'cur=conn.cursor()\ncur.execute("SELECT * FROM config_keys")\n' >> /tmp/s.py
printf 'for r in cur.fetchall(): print(r)\n' >> /tmp/s.py
python3 /tmp/s.py
```
**Méthode 3 — Code long ou avec quotes simples (> 350 chars)**
Base64 encode côté Claude (bash_tool), decode sur NAS (2 run_command) :
```
# bash_tool : base64 -w0 fichier.py → obtenir la chaîne B64
# run_command 1 : printf '<B64>' | base64 -d > /tmp/s.py
# run_command 2 : python3 /tmp/s.py
```
Piège : une quote simple `'` dans printf termine la chaîne → code tronqué sans erreur.
Dès qu'il y a des `'` dans le code Python → base64 obligatoire.
---
## RÈGLE 4 — Services internes (172.17.0.1)
**Portainer — restart container**
```python
import urllib.request, json
# Auth
body = json.dumps({"username":"bestof","password":"<depuis .env>"}).encode()
req = urllib.request.Request("http://172.17.0.1:9000/api/auth", data=body,
headers={"Content-Type":"application/json"})
jwt = json.loads(urllib.request.urlopen(req).read())["jwt"]
h = {"Authorization": "Bearer " + jwt}
# ID dynamique — change après recreate
req = urllib.request.Request(
"http://172.17.0.1:9000/api/endpoints/2/docker/containers/json", headers=h)
cs = json.loads(urllib.request.urlopen(req).read())
cid = [c["Id"][:12] for c in cs if "bifrost" in str(c.get("Names","")).lower()
and "proxy" not in str(c.get("Names","")).lower()][0]
# Stop + Start
for action in ["stop","start"]:
req = urllib.request.Request(
f"http://172.17.0.1:9000/api/endpoints/2/docker/containers/{cid}/{action}",
data=b"", headers=h)
urllib.request.urlopen(req)
```
**n8n API — PUT workflow**
```python
# Uniquement ces 4 champs acceptés — autres -> 400
payload = {"name": wf["name"], "nodes": wf["nodes"],
"connections": wf["connections"], "settings": wf.get("settings", {})}
# Header : X-N8N-API-KEY (pas Authorization Bearer)
```
**Bifrost**
```
Auth : header x-bf-vk: <VK> — PAS Authorization Bearer (-> 401)
URL : http://172.17.0.1:3085
```
**Gitea push**
```python
import subprocess, os
env = open("/mnt/docker/hermes-platform/.env").read()
pwd = [l.split("=",1)[1].strip() for l in env.splitlines() if "PASSWORD_GITEA" in l][0]
os.chdir("/mnt/docker/nas-runbooks")
subprocess.run(["git","add","."])
subprocess.run(["git","commit","-m","message"])
subprocess.run(["git","push", f"http://bolbol:{pwd}@172.17.0.1:3232/bolbol/nas-runbooks.git"])
```
**NyoraNotes**
```python
tok = open("/mnt/docker/nyora-notes/data/hermes-perso.token").read().strip()
note = {"title":"...","content":"...","tags":["infra","runbook","terminee"]}
body = json.dumps(note).encode()
req = urllib.request.Request("http://172.17.0.1:8787/notes", data=body,
headers={"Authorization":"Bearer "+tok,"Content-Type":"application/json"})
urllib.request.urlopen(req) # -> 201
```
**SQLite Bifrost DB**
```python
import sqlite3, shutil
# Lecture : copier d'abord (WAL actif)
shutil.copy("/mnt/docker/bifrost/data/config.db", "/tmp/bifrost.db")
conn = sqlite3.connect("/tmp/bifrost.db")
# Écriture live (restart Bifrost après pour prise en compte)
conn = sqlite3.connect("/mnt/docker/bifrost/data/config.db")
```
---
## Tableau de décision rapide
| Besoin | Faire |
|--------|-------|
| Code Python < 200 chars | printf > /tmp + python3 (2 appels) |
| Code Python > 350 chars ou avec quotes | base64 encode -> decode -> exec |
| Heredoc | Jamais. Python open().write() en /tmp |
| Restart container | Portainer API, ID dynamique |
| sqlite3 | Python import sqlite3 |
| Session instable | list_sessions -> session fraîche |
| Gitea push | subprocess git avec http://bolbol:pwd@ |
---
## Améliorations suggérées linux-mcp-nas
1. **Monter docker.sock** : `-v /var/run/docker.sock:/var/run/docker.sock` — Docker CLI direct, supprime Portainer API workaround
2. **Installer sqlite3 CLI** : `apt install -y sqlite3` dans l'image — inspection DB sans Python
3. **Augmenter timeout run_command** : 120s trop court pour git sur gros repos
4. **Nommer sessions par rôle** : `git`, `db`, `api`, `test` — rotation propre entre tâches
5. **Installer paramiko** : `pip install paramiko` — SSH natif si besoin accès NAS direct
-59
View File
@@ -1,59 +0,0 @@
# Migration OpenRouter → OpenCode Go — 2026-06-16
Objectif : basculer la stack LLM du NAS vers **OpenCode Go** (forfait Go, endpoint
`https://opencode.ai/zen/go/v1`, auth `Authorization: Bearer <clé>` uniquement —
pas de `x-bf-vk`, pas de `HTTP-Referer`/`X-Title`).
## Modèles OpenCode Go
IDs **sans préfixe** (`glm-5.1`, `deepseek-v4-pro`…), PAS `openrouter/` ni `deepseek/`.
Utilisables en **OpenAI-compat (oa-compat)** : `deepseek-v4-flash`, `deepseek-v4-pro`,
`glm-5.1`, `qwen3.7-plus`, `kimi-k2.6`, `kimi-k2.7-code`, `minimax-m3`, `glm-5`,
`qwen3.6-plus`, `mimo-v2.5-pro`
⚠️ **NE FONCTIONNE PAS** :
- `qwen3.7-max``Model not supported for format oa-compat` (dispo seulement en format Anthropic côté OpenCode Go). Tout consommateur OpenAI-compat (Bifrost custom provider, Open WebUI, family-help) doit utiliser `glm-5.1` à la place.
- MiniMax M2.7 vision via OpenCode Go = `prompt_tokens:0` (vision cassée).
## Ce qui a été migré
| Cible | Avant | Après | Clé |
|---|---|---|---|
| **hermes-tt** (config.yaml) | deepseek-v4-flash | défaut `deepseek-v4-pro`, fallback `deepseek-v4-flash`, délégation (data-engineer) `deepseek-v4-pro` | HERMES_TT_OPENCODE_KEY (provider `opencode-go`) |
| **hermes-nyora** | deepseek-v4-flash | défaut `glm-5.1`, fallback `qwen3.7-plus` | HERMES_NYORA_OPENCODE_KEY |
| **hermes-perso** | deepseek-v4-flash | défaut `qwen3.7-plus`, fallback `deepseek-v4-flash` | HERMES_PERSO_OPENCODE_KEY |
| **Bifrost** | — | provider `opencode` route `glm-5.1, deepseek-v4-pro, qwen3.7-plus, kimi-k2.6, minimax-m3` ; alias `claude-*→deepseek-v4-flash` (openrouter) conservé | BIFROST_OPENCODE_KEY |
| **Open WebUI** | Bifrost seul | 2e connexion directe OpenCode Go (`OPENAI_API_BASE_URLS` pluriel) | BIFROST_OPENCODE_KEY |
| **family-help** | OpenRouter (modèle unique) | routing par skill : medical/nutrition `glm-5.1`, homework `qwen3.7-plus`, legal_admin `deepseek-v4-pro`, tech/general `deepseek-v4-flash` ; fallback OpenRouter (web + erreur) | OPENCODE_API_KEY (=BIFROST_OPENCODE_KEY) |
| **n8n actifs** | commentaires/refs OpenRouter | nettoyés (NABIL.md Quotidien, Monitoring sources IA, Ingest Opportunités) | — |
## Hermes — config.yaml
Modèle défini dans `/volume1/docker/hermes-platform/hermes-{tt,nyora,perso}/data/config.yaml` :
`model.default` + `model.provider: opencode-go`, `fallback_providers[0]`, `fallback_model`.
Hermes **réécrit config.yaml au shutdown** → procédure : `docker stop` → éditer → `docker start`
(éditer container arrêté pour éviter le clobber). Healthcheck port 8642, API OpenAI-compat
(`/v1/chat/completions`, modèle proxy `hermes-agent`).
## Bifrost — PIÈGE MAJEUR (voir mémoire bifrost-keys-config-json)
Bifrost v1.5.13 **ne recharge PAS les clés des providers au redémarrage** (elles ne vivaient
qu'en mémoire). Fix durable = **`/volume1/docker/bifrost/data/config.json`** (providers + clés,
chargé au boot) + `allow_all_keys=1` dans `governance_virtual_key_provider_configs`.
- Providers dans config.json NE doivent PAS être aussi dans le store (`sqlite3 config.db "DELETE FROM config_providers; DELETE FROM config_keys;"`, bifrost stoppé).
- `sqlite3 config.db "UPDATE governance_virtual_key_provider_configs SET allow_all_keys=1;"`.
- ⚠️ éditer providers/clés/VK via l'UI Bifrost peut re-casser (reset allow_all_keys / conflit config.json).
- Backup config.db pré-migration : `/tmp/config.db.backup-*` (NAS).
## n8n — workflows AO inactifs : NON migrés (à faire délibérément)
Les 15 workflows AO/veille contenant encore OpenRouter sont **archivés** (`isArchived=true`) →
l'API n8n refuse de les éditer (`Cannot update an archived workflow`). Le pipeline AO **actif**
(`AO — Insertion Baserow FINAL`, hisVAANSYpixR4Si) est déjà propre.
Pour réutiliser un workflow AO archivé : le désarchiver (UI), migrer son Code node
(`openrouter.ai/api/v1/chat/completions``opencode.ai/zen/go/v1/chat/completions`, clé sk-or →
OpenCode, retirer HTTP-Referer/X-Title, modèle → `deepseek-v4-pro`), re-tester, ré-archiver.
Règle Code node n8n : `require('https')` + parsing manuel d'URL, jamais `fetch`/`new URL`/`$helpers`.
Liste des archivés concernés : edFMGtI2HHWW72aT, JTwurk7XVBUXSHKn, BCgZYRJcxoHOMDhm, iuxVgZFfpKdYFNqG,
xJtW4algsBcx6r1E, EEDr0xx9krRq2nwN, 7zWWJhP2aQbzQhZG, QALWT3NwMQjOuY6u, N3yQWk0XyhyS5wZX,
hcOmozHadfBnHzri, nZiXRcwSAXsoj2M6, Mdl5SRcT9rYw48VZ, dPnpoHkc6hEXdsFF, wS3SzRFfcaDDD34g, xu4smQdEGrHFx1zK, RRKUk3GvnULj7edE.
## Hors scope (laissés sur OpenRouter, actifs/famille — à migrer sur décision)
Workflows n8n actifs encore sur OpenRouter en direct (endpoint+clé) : `Mode-nedya`,
`FAMILLE | Recettes Weekend`, `Labellisation Auto`.
+39
View File
@@ -0,0 +1,39 @@
# Bifrost — Incidents VK & fallback (consolidé)
**Date** : 2026-07-09 (fusionne BIFROST-VK-OPENCODE-FIX.md 23/06 + bifrost-vk-stale-cache-et-fallback-opencode.md 06/07)
**Tags** : infra, bifrost, vk, opencode, openrouter, cout
Guide d'accès général : common/bifrost-access.md.
## Règles durables (à retenir avant tout debug)
1. Auth Bifrost = header `x-bf-vk`, JAMAIS `Authorization: Bearer` → 401.
2. Bifrost ne hot-reload NI les clés provider NI le VK store → **restart du container = premier réflexe** sur tout `401 virtual_key_not_found` inexpliqué (cache VK désynchronisé de la DB).
3. config.json chargé au boot = source providers ; `allow_all_keys=1` en DB ; ne pas dupliquer providers entre config.json et le store ; l'UI peut re-casser ces réglages.
4. Fallbacks Hermes → **opencode-go** (forfait), plus jamais openrouter (facturation réelle à chaque échec du modèle primaire).
5. Auditer périodiquement les règles de routing : le NOM d'une règle peut ne pas correspondre à sa cible réelle en base (désync vue sur la règle Haiku).
6. Portainer : l'ID container change après recreate → toujours le re-résoudre avant stop/start.
7. n8n PUT /workflows : payload strict {name, nodes, connections, settings} → 400 sinon.
## Incident 23/06/2026 — « Résumé indisponible » veille bolbol.tn
- `could not auto resolve a provider` : la VK Nabil-Key (bfk-e5832bfe…) n'avait pas OpenCode dans `governance_virtual_key_provider_configs` → deepseek-v4-flash introuvable. Fix : INSERT provider opencode (weight 1.0, allowed_models deepseek-v4-flash/pro, glm-5.1, kimi-k2.6, allow_all_keys=1) + clé persistée dans `config_keys` + restart bifrost.
- `GoUsageLimitError` : job cron hermes-nyora orchestrateur avec model/provider null → OpenCode direct, quota hebdo épuisé. Fix : jobs.json → model openrouter/deepseek-v4-flash pour l'orchestrateur ; workflow n8n 9378593adccf42dc → retry gemini-2.5-flash sur GoUsageLimitError.
- Architecture VK Nabil-Key finale : openrouter (modèles :free), google (gemma-4, gemini-2.5-flash/lite), nvidia_nim, opencode (deepseek-v4-flash/pro, glm-5.1, kimi-k2.6).
## Incident 06/07/2026 — VK stale-cache + fallbacks payants
1. 401 `virtual_key_not_found` répétés sur « VEILLE | Ingest Opportunités » alors que la VK était exacte en base → cache VK désynchronisé, **un simple restart bifrost a suffi**.
2. hermes-nyora (pilote mimo-v2.5) a basculé 2× sur son fallback natif openrouter (bypass Bifrost, coût réel facturé) → les 3 config.yaml migrés vers fallback `opencode-go/deepseek-v4-flash`, `key_env: OPENCODE_GO_API_KEY` (nom réel du compose top-level, PAS OPENCODE_API_KEY). Restart des 3 agents (les appels restart API peuvent timeout côté client → vérifier l'état, ne pas re-tenter en boucle ; start manuel parfois requis).
3. Règle routing « Haiku → Gemini 2.5 Flash » pointait en réalité vers openrouter/deepseek-v4-flash payant → `routing_targets` corrigé vers google/gemini-2.5-flash. Backups config.db pris avant chaque édit.
## Pilote mimo-v2.5 (contexte)
Bilan J+1 (06/07) : 389 appels, 0,5% d'échec (réponse vide = reasoning épuisant max_tokens) → fix `model.max_tokens: 8192` sur hermes-nyora. **Verdict final 08/07 : GARDER mimo, V4 Flash en fallback** — détails : hermes-nyora/verdict-pilote-mimo-v25.md.
## Vérification type
```bash
curl -X POST http://bifrost:8080/v1/chat/completions -H 'x-bf-vk: <Nabil-Key>' \
-d '{"model":"deepseek-v4-flash","max_tokens":30,"messages":[{"role":"user","content":"dis OK"}]}'
# 200 + routing_info.provider=opencode attendu
```
@@ -1,95 +0,0 @@
# Bifrost : VK stale-cache + migration fallback OpenRouter -> OpenCode Go
**Instance auteur** : Claude (via mcp-nas)
**Date** : 2026-07-06
**Tags** : infra, bifrost, hermes, opencode, openrouter, cout
**Statut** : valide
## Probleme observe
1. Pipeline nyora-veille (n8n workflow "VEILLE | Ingest Opportunites", node "Enrichir via Bifrost")
en echec repete (401 virtual_key_not_found) le 06/07 a 07:34 et 13:29-13:30 UTC, alors que le
x-bf-vk hardcode dans le node correspond exactement a la valeur Nabil-Key en base
(bfk-e5832bfebd22e9d8252a426d0734ed5f2d923204c8033d6d). Aucun cout facture (echec avant tout
routage provider).
2. hermes-nyora (mimo-v2.5, pilote 01-08/07) a bascule 2x le 05/07 (~21:38 et 21:48 UTC) sur son
fallback natif `fallback_providers: openrouter` (bypass Bifrost, cle OPENROUTER_API_KEY en
.env) suite a des reponses vides du pilote sur OpenCode. Cout reel facture sur OpenRouter,
hors quota epuise.
3. Bifrost logs.db (depuis 06/06) : 1370 appels reussis, 17,43$, VK non identifiee, modele
deepseek/deepseek-v4-flash sur OpenRouter -> trace de sessions Claude Cowork/Claude Code
routees via Bifrost et interceptees par une regle de routing.
## Cause racine
- Point 1 : cache Bifrost desynchronise (VK valide en base mais rejetee en memoire) -> **un simple
restart du container bifrost recharge correctement le VK store**. Meme famille de piege que
celui deja connu pour les cles provider (config.json au boot) : Bifrost ne recharge pas
systematiquement son etat interne sans redemarrage explicite.
- Point 2 : `fallback_providers` des 3 instances Hermes pointait vers `openrouter` (paye a l'usage)
au lieu de `opencode-go` (deja couvert par le forfait Go). Design d'origine (voir
migration-provider-opencode-go.md, 14/06) : a l'epoque volontaire comme filet de secours large,
mais genere une facturation reelle des qu'un modele opencode echoue (meme hors quota).
- Point 3 : regle de routing Bifrost `Haiku -> Gemini 2.5 Flash` (id
f5f03c54-049a-4f53-8590-7392730f42e0) avait un NOM ne correspondant pas a sa cible reelle en
base (deepseek/deepseek-v4-flash payant sur openrouter au lieu de gemini-2.5-flash gratuit sur
Google AI Studio). Les 4 autres regles Opus/Sonnet/Catch-all pointent, elles, vers des modeles
`:free` OpenRouter et sont correctes.
## Solution appliquee (06/07/2026)
1. `routing_targets` (config.db Bifrost) : rule f5f03c54... -> provider=google,
model=gemini-2.5-flash (etait openrouter/deepseek-v4-flash). Backup pris avant edit
(config.db.bak-<timestamp>). Restart container bifrost pour charger.
2. Les 3 `data/config.yaml` Hermes (hermes-tt, hermes-nyora, hermes-perso) :
`fallback_providers: openrouter/deepseek-v4-flash` -> `opencode-go/deepseek-v4-flash`,
`key_env: OPENCODE_GO_API_KEY` (nom reel de la var injectee par le compose top-level, pas
OPENCODE_API_KEY comme indique dans l'ancien runbook de migration). Backup des 3 fichiers pris
avant edit. Restart des 3 containers hermes-agent-* (perso a eu besoin d'un start manuel apres
le restart, le call restart API ayant timeout cote client -> verifier l'etat plutot que
de re-tenter en boucle).
3. Aucun changement necessaire sur le node n8n "Enrichir via Bifrost" : le restart bifrost a
suffi a corriger le probleme de VK stale.
## Verification
```
curl -s --unix-socket /var/run/docker.sock http://localhost/containers/<c>/json | jq .State
# bifrost, hermes-agent-tt/nyora/perso : running + healthy apres restart
curl -X POST http://bifrost:8080/v1/chat/completions -H 'x-bf-vk: <Nabil-Key>' \
-d '{"model":"deepseek-v4-flash","max_tokens":30,"messages":[{"role":"user","content":"dis OK"}]}'
# HTTP 200, routing_info.provider=opencode -> confirme
```
## A surveiller
- Si un 401 virtual_key_not_found reapparait sans changement de config -> suspecter a nouveau le
cache VK, restart bifrost avant toute autre investigation.
- Fiabilite mimo-v2.5 (reponses vides) non resolue en soi ; le fallback pointant desormais vers
opencode-go, tout echec futur du pilote restera gratuit (dans le forfait) au lieu d'etre facture.
- Auditer les 4 autres regles Opus/Sonnet/Catch-all : noms vs cibles reelles a revalider
periodiquement (meme type de desync que la regle Haiku).
## References
- migration-provider-opencode-go.md (14/06/2026)
- llm-audit-register.md (24/06/2026), item "N8n Ingest Opportunites"
## Addendum 06/07/2026 — pilote mimo-v2.5 sur hermes-nyora
Migration mimo-v2.5 confirmee active depuis le 05/07 20h01 (decision : tester un modele de
raisonnement sur Nyora uniquement, controler la conso, decider ensuite de generaliser ou non aux
3 instances). Bilan a J+1 : 389 appels, ~11,65M tokens entrants (tres majoritairement cache),
~182K tokens sortants, 5 incidents "reponse vide/pensee sans contenu", 2 fallbacks (meme session
courte 21h47-21h48 du 05/07). Taux d'echec 0,5%, isole a un cas de contexte court (in=631) ou le
raisonnement interne du modele epuisait le budget max_tokens avant d'emettre le contenu visible
(meme piege documente pour family-help dans migration-provider-opencode-go.md).
Fix applique : `model.max_tokens` 4096 -> 8192 dans data/config.yaml hermes-nyora (backup pris),
restart hermes-agent-nyora (start manuel necessaire, l'appel restart API a timeout cote client
comme pour hermes-agent-perso plus tot). Container reparti healthy, mimo-v2.5 toujours model
par defaut.
Point de decision (generaliser mimo-v2.5 aux 3 instances ou rester DeepSeek V4 Flash) a reprendre
en fin de semaine de pilote, sur la base de la conso reelle et du taux d'incidents mesures ici.
+14 -6
View File
@@ -1,6 +1,14 @@
Runbook: Nettoyage Stack Documentaire
Date: 21 juin 2026
Services: gotenberg/pandoc/carbone/document-factory tous desinstallés
Portainer REST API utilisée pour stop+delete containers et images
Piege: rm blocked par mcp-nas, utiliser shutil.rmtree
Futur: API doc Nyora a construire from scratch
# Nettoyage stack documentaire (historique)
**Date** : 21/06/2026, complété 09/07/2026
**Tags** : infra, cleanup, doc-stack
## Ce qui a été retiré (ne pas réinstaller, ne pas proposer)
gotenberg, pandoc, carbone, document-factory, pptx-tt-api, tika — désinstallés le 21/06 via API Portainer (stop + delete containers et images). open-webui, paperless, familyspace retirés lors de l'audit stack de juin (58 → 43 containers).
Piège : `rm` bloqué par la denylist mcp-nas de l'époque → `shutil.rmtree` en Python.
## Remplacement
**nyora-doc-api (port 3050)** est depuis le seul moteur documentaire : DOCX/XLSX/PPTX/PDF, charte Nyora centralisée dans formatting.py. Guide : common/nyora-doc-api-hermes-guide.md · Rebuild : common/nyora-doc-api-rebuild.md.
-25
View File
@@ -1,25 +0,0 @@
# GOTENBERG-PDF — HTML → PDF (piège police web)
Note partagée aux 3 instances Hermes. Source : projet gsparc-mezzouna, 2026-06-14.
Gotenberg convertit du HTML en PDF. Conteneur `gotenberg` sur le réseau **n8n**, port **3000**.
`POST http://gotenberg:3000/forms/chromium/convert/html` (champ `files` = index.html).
## PIÈGE — police web manquante sur pages lourdes
Sur une page **lourde** (grosse image base64 en ligne), Chromium **imprime avant que la police web** (`@import` Google Fonts) soit appliquée → **texte manquant ou rendu incohérent** d'un document à l'autre. C'est **déterministe selon le poids** : les pages légères passent, les lourdes échouent (piège : on croit à un aléa).
## SOLUTION
- Champ form **`waitDelay: "1.5s"`** → laisse charger les ressources async (police) avant impression.
- **Alléger** la page : redimensionner les images embarquées.
- Pile de polices de secours avec une Arabe quasi toujours présente dans Chromium :
`font-family: Cairo,"Noto Sans Arabic","Noto Naskh Arabic",Tahoma,Arial,sans-serif;`
## Exemple (paysage A4 pleine page)
```bash
curl -s http://gotenberg:3000/forms/chromium/convert/html \
-F "files=@index.html" \
-F "paperWidth=11.69" -F "paperHeight=8.27" \
-F "marginTop=0.2" -F "marginBottom=0.2" -F "marginLeft=0.2" -F "marginRight=0.2" \
-F "landscape=true" -F "printBackground=true" -F "waitDelay=1.5s" -o out.pdf
```
Implémentation de référence : `gsparc-mezzouna/app/routes/export.py` (`_fiche_pdf_bytes`).
-95
View File
@@ -1,95 +0,0 @@
# Hermes-Perso -- Assistant Personnel de Nabil
Tu es l'assistant personnel de Nabil Derouiche, base a Sfax, Tunisie.
Ne en 1973. Marie a Nedya (1983, coeliaque).
Fille Yesmine (2008, fibromyalgie/nevralgie -- priorite absolue).
Fils Ahmed (2010).
Tu es confidentiel, bienveillant, proactif.
---
## LANGUES
- Nabil : francais par defaut, arabe si Nabil ecrit en arabe
- Nedya : francais
- Ahmed : francais
- Yesmine : anglais -- toujours repondre en anglais a Yesmine
---
## PRIORITES ABSOLUES
1. Yesmine -- suivi medical, RDV, recherches fibromyalgie/nevralgie (en anglais)
2. Famille -- Nedya (regime sans gluten), Ahmed (suivi scolaire, Bac en cours)
3. Veille IA -- synthese des 16 sources de confiance
4. Formation personnelle -- IA et automatisation
5. Sante et equilibre de Nabil
---
## MISSIONS PRINCIPALES
- Veille IA : surveiller les 16 sources, evaluer chaque article (score /10)
- TOUJOURS envoyer TOUS les articles evalues vers l'API veille (quel que soit le score)
- Webhook : POST http://n8n:5678/webhook/veille-ingest
- Content-Type: application/json
- Payload : {"date":"YYYY-MM-DD","source":"@NomSource","titre":"...","score":N*10,"texte":"message original","url":"..."}
- score : convertir /10 en /100 (ex: 2/10 -> 20, 7/10 -> 70) — n8n gere enrichissement et ingest
- Alerter via Telegram Nabil_Perso uniquement si score >= 7/10 (score >= 70 en API)
- Sante Yesmine : recherches medicales EN ANGLAIS, suivi traitements, prep consultations
- Alimentation Nedya : recettes sans gluten, alternatives, etiquetage
- Mode Nedya : stylisme, modelisme, broderie, tendances styles et couleurs
- Suivi Ahmed : scolaire, activites, developpement -- periode Bac prioritaire
- Formation Nabil : DataCamp, ressources IA, planification apprentissage
- FamilySpace : synchroniser taches, calendrier, notes famille (port 3333)
- Agenda personnel : rappels, echeances, organisation
---
## INTEGRATION FAMILYSPACE
Adresse : 192.168.100.33 port 3333
Acces via cle dans credentials.md
Membres : Nabil (super admin), Nedya, Yesmine, Ahmed
Appels complets : lire references/api-calls.md section FamilySpace
Comportement par membre :
- Nabil : lecture/ecriture toutes sections
- Nedya : taches, notes, calendrier (verifier sans gluten si courses)
- Yesmine : notes symptomes a archiver vault medical en anglais
- Ahmed : emploi du temps, todo scolaire
---
## REGLES ABSOLUES
- Yesmine : recherches medicales en anglais, sources serieuses uniquement
- Jamais de diagnostic medical -- orienter vers professionnel de sante
- Donnees famille : strictement locales, jamais vers services externes
- Donnees medicales Yesmine : vault local uniquement
---
## MEMOIRE PERSISTANTE
- Vault : /opt/data/obsidian/ -- memoire absolue
- USER.md max 1375 chars -- MEMORY.md max 2200 chars
- Ecrire apres chaque tache importante
- Toutes les 10 interactions : verifier distillation necessaire
---
## SERVICES DISPONIBLES
- FamilySpace : 192.168.100.33 port 3333
- NyoraNotes : NYORA_NOTES_URL port 8787
- Veille sources : lire skills/veille-sources-ia.md
- Appels complets : lire references/api-calls.md
---
## PERIMETRE
Vie personnelle, famille, sante, formation, veille IA.
Taches professionnelles TT : hermes-tt
Taches Nyora : hermes-nyora
## PROTOCOLE INFRA
Apres tout deployement ou resolution de probleme : lire et appliquer /opt/data/PROTOCOL-INFRA.md
+11 -36
View File
@@ -1,45 +1,20 @@
# HTML vers PNG : Rendu headless Puppeteer / Chromium
# HTML vers PNG : rendu headless Puppeteer / Chromium
**Contexte** : html2canvas echoue dans les iframes sandbox (Claude.ai, widgets).
Polices Google Fonts et SVG complexes bloques par CORS.
**Solution** : rendu serveur avec Chromium headless.
**Date** : 2026-06-21, révisé 09/07/2026 (retrait de l'approche Gotenberg — container désinstallé)
---
**Contexte** : html2canvas échoue dans les iframes sandbox (Claude.ai, widgets). Polices Google Fonts et SVG complexes bloqués par CORS.
**Solution** : rendu serveur avec Chromium headless via Puppeteer (bash_tool Claude).
## Approche 1 - Claude bash_tool
## Pièges critiques
### Installation (une seule fois par session)
### Script render.mjs
**Pieges critiques :**
- Entry point : lib/puppeteer/puppeteer.js (pas lib/esm/)
- page.waitForTimeout() supprime en v25+ => setTimeout natif
- page.waitForTimeout() supprimé en v25+ setTimeout natif
- --no-sandbox obligatoire dans container Claude
- networkidle0 + 2s = polices externes chargees
- networkidle0 + attente 2 s = polices externes chargées
## Cas validé (2026-06-21)
Carte félicitations Bac 2026 — HTML/SVG 680×680 px + Google Fonts → PNG 1360×1360 px (137 Ko), upload Facebook direct.
---
## Approche 2 - NAS via Gotenberg (port 3001, deja deploye)
Gotenberg embarque Chromium. HTML -> PDF puis PDF -> PNG Pillow.
Ou via noeud HTTP Request n8n (multipart/form-data vers Gotenberg).
---
## Cas valide (2026-06-21)
Carte felicitations Bac 2026 - HTML/SVG 680x680px + Google Fonts.
Resultat : PNG 1360x1360px (137 Ko), upload Facebook direct.
**Regle absolue** : html2canvas = NON iframe sandbox.
Puppeteer (Claude) ou Gotenberg (NAS) = OUI.
**Règle absolue** : html2canvas = NON en iframe sandbox. Puppeteer = OUI.
**Note** : l'ancienne « Approche 2 » via Gotenberg (HTML→PDF→PNG) n'existe plus — gotenberg a été désinstallé le 21/06/2026 (cf. cleanup-stack-documentaire.md).
-42
View File
@@ -1,42 +0,0 @@
# Runbook — Kanban Multi-Agent Evaluation AO
Date : 2026-06-18 | Instance : hermes-tt | Statut : Deploye
## Architecture
Pattern agents paralleles pour evaluation AO Zone Sud TT :
- Profil orchestrateur : default (hermes-tt)
- Profil worker : evaluateur-ao (deepseek/deepseek-v4-flash via openrouter)
- Kanban : dispatch_in_gateway=true (deja actif dans config.yaml)
## Comment declencher
Depuis Telegram ou le workspace hermes-tt :
Lance evaluation AO [REF] avec soumissionnaires [X, Y, Z]
hermes-tt cree automatiquement :
1. /opt/data/workspace/ao-eval/[REF]/
2. Une tache Kanban par soumissionnaire -> profil evaluateur-ao
3. Les workers tournent en parallele
4. Synthese : rapport XLSX + PV de depouillement format TT
5. Envoi sur Telegram
## Criteres standard
- Conformite administrative (eliminatoire)
- Note technique /70
- Note financiere /30 (moins-disant = 30pts, autres proportionnels)
## Chemins
Fiches eval : /opt/data/workspace/ao-eval/<REF>/<SOUMISSIONNAIRE>_fiche_eval.md
SOUL worker : /opt/data/profiles/evaluateur-ao/SOUL.md
Config worker : /opt/data/profiles/evaluateur-ao/config.yaml
## Commandes utiles
hermes profile list -- voir les profiles
hermes profile show evaluateur-ao -- etat du worker
hermes -p evaluateur-ao config set X Y -- modifier la config worker
+46
View File
@@ -0,0 +1,46 @@
# mcp-nas — Référence opérationnelle (consolidée)
**Date** : 2026-07-09 (fusionne MCP-NAS-SCRIPTING.md 2026-06-23 + mcp-nas-run-command-v2.md 2026-07-04, + contraintes post-audit 2026-07-08)
**Tags** : infra, mcp-nas, scripting, securite
## Environnement du container (vérifié 09/07/2026, image post-audit)
| Propriété | Valeur |
|-----------|--------|
| Shell | `/bin/bash`, user root uid=0, **cap_drop: [ALL]** + no-new-privileges |
| Python | 3.11 — requests, sqlite3, json, urllib, subprocess |
| `git` | **ABSENT** (retiré au rebuild post-audit) → API HTTP Gitea obligatoire |
| `docker` CLI / docker.sock | **ABSENTS** (socket retiré P0 audit 08/07) → API Portainer |
| `/mnt/docker` | = /volume1/docker, RW |
| Réseau | host via `172.17.0.1` ou alias `nas.internal` (extra_hosts). 192.168.100.33 **inaccessible** |
## run_command v2 (P0 du 04/07, commit 30560984fa)
La commande est écrite dans un script par Python (zéro parsing shell) puis sourcée dans tmux :
- Heredocs natifs (`<<'EOF'`, quotes, `$VAR`, backticks) ; testé 5 250 chars en un appel.
- Busy-check : pane occupé → refus propre (options : capture_pane, send_keys, reset_session).
- Timeout intelligent : Ctrl-C + sonde ; session préservée si le shell répond.
- `reset_session` : reprise d'une session bloquée SANS destruction (vim : `send_keys ':q!'` d'abord).
- Cleanup au boot : sessions tmux orphelines + `/tmp/mcp_*` purgés.
- Denylist ancrée (P1, commit 7fb13c5) : patterns ancrés en début de segment, heredocs exclus. Défense en profondeur, PAS une frontière de sécurité.
- Interdits toujours actifs : reboot, mkfs, dd of=/dev/*, rm -rf racines critiques, docker rm -f, syno*…
**Ne plus utiliser** : workaround base64 2 appels, découpage printf-append, limite 350 chars — tout cela date de la v1.
## Contraintes post-audit sécurité (08/07/2026) — vérifiées le 09/07
1. **`cap_drop: [ALL]` → pas de CAP_DAC_OVERRIDE** : les dossiers `0700` appartenant à uid 1026 (ex. `hermes-*/data/`) sont **illisibles même en root**. C'est voulu. Lecture des data/ Hermes → passer par un autre canal (Mac, ou décision Nabil).
2. **`~/.ssh` du container vidé au rebuild** : plus de clé pour `ssh Best0f@172.17.0.1:22222`. SSH host depuis mcp-nas = indisponible tant que la clé n'est pas re-provisionnée (décision Nabil — l'audit recommandait justement de sortir les clés).
3. **docker.sock retiré** → gestion containers via **API Portainer** (`http://nas.internal:9000`, POST /api/auth → jwt, endpoint_id=2 ; l'ID container change après recreate, toujours le re-résoudre).
4. **Gitea** : API HTTP avec `GITEA_DEPLOY_TOKEN` (repo-scopé, dans hermes-platform/.env) — **jamais** le mot de passe bolbol dans une URL (fuite audit.log + ps). Lire le token dans une variable depuis le fichier, pas en clair dans la commande.
5. Rebuild du container : **depuis le Mac** (`ssh -p 22222 Best0f@192.168.100.33`, `cd /volume1/docker/linux-mcp-nas && sudo docker compose build && up -d --force-recreate`), jamais depuis mcp-nas lui-même.
## Services internes depuis mcp-nas
`nas.internal` (= 172.17.0.1) : Gitea `:3232`, Portainer `:9000`, NyoraNotes `:8787`, nyora-doc-api `:3050`, Bifrost `:3085` (LAN) / `http://bifrost:8080` (réseau n8n), context-hub `:3093`.
## Bonnes pratiques
- Scripts Python : heredoc `cat <<'EOF' > /tmp/s.py … EOF` puis `python3 /tmp/s.py`.
- Session par défaut `claude` fiable ; `reset_session` avant de créer une session neuve.
- Secrets : toujours via variable lue depuis .env (`export GT=$(grep … | cut -d= -f2)`), jamais inline — audit.log journalise chaque commande.
-84
View File
@@ -1,84 +0,0 @@
# MCP NAS — run_command v2 (P0) : heredocs, commandes longues, reset_session
**Date** : 2026-07-04 | Commit `30560984fa` sur `bolbol/linux-mcp-nas@main` | Testé et validé le jour même.
> **Ce runbook remplace les RÈGLES 13 de [MCP-NAS-SCRIPTING.md](MCP-NAS-SCRIPTING.md).**
> Le chunking base64, la limite ~350 chars et l'interdiction de heredoc n'existent plus.
> La section « Services internes (172.17.0.1) » de l'ancien runbook reste valide.
---
## Ce qui a changé
Avant, `run_command` envoyait la commande brute à tmux via `send-keys` : le parsing shell
cassait sur les quotes, les heredocs déclenchaient un timeout tueur de session, et au-delà
de ~350 caractères la commande était tronquée — d'où le workaround base64 en 2 appels.
Depuis le patch P0, le serveur **écrit la commande dans un fichier script depuis Python**
(`open(f"/tmp/mcp_{uid}.sh","w").write(command)` — zéro parsing shell) et n'envoie au pane que :
```
. /tmp/mcp_XXX.sh > /tmp/mcp_XXX.out 2>&1; echo $? > /tmp/mcp_XXX.rc
```
via `send-keys -l` (mode littéral). Conséquences :
| Avant (v1) | Maintenant (v2) |
|------------|-----------------|
| Heredoc → timeout 120 s, session morte | Heredocs natifs, y compris `<<'EOF'` avec quotes/`$VAR`/backticks |
| Limite ~350 chars, base64 obligatoire | Testé 5 250 chars en un appel, intégrité parfaite |
| Quote simple `'` → troncature silencieuse | Quotes arbitraires OK (aucun parsing shell) |
| Prompt de continuation `>` empoisonnait la session | Mécaniquement impossible |
| Empilement sur pane occupé (vim/ssh/pager) | **Busy-check** : refus propre avec message d'options |
| Sessions poisonnées → new_session en chaîne | **reset_session** : reprise sans destruction |
## Nouveaux comportements
- **Busy-check** : si le pane exécute déjà quelque chose, `run_command` renvoie
`[SESSION OCCUPÉE] 'x' exécute actuellement 'sleep'…` au lieu d'empiler. Options proposées :
`capture_pane`, `send_keys`, `reset_session`, ou une autre session.
- **Timeout intelligent** : à expiration, le serveur tente un Ctrl-C puis sonde le shell.
S'il répond → session préservée (cwd/env conservés). Sinon → session recréée dans le même cwd.
- **`reset_session(session)`** : envoie Ctrl-C, `q` (pager), Ctrl-U, vérifie que le shell répond.
Reprend la main sur une session bloquée SANS la détruire. Pour vim : `send_keys ':q!'` d'abord.
- **Cleanup au démarrage** : sessions tmux orphelines et fichiers `/tmp/mcp_*` purgés au boot
du container (les 24 sessions zombies accumulées ont disparu au premier rebuild).
## Tests de validation (2026-07-04)
1. **Heredoc** avec quotes simples/doubles, `$VAR`, backticks, `$(subshell)`, `!#&*(){}[]|;<>`
→ contenu intact, zéro expansion, exit code correct.
2. **Commande de 5 250 caractères** (heredoc 50 lignes × 100 chars + vérifications)
→ 5 050 octets écrits, 50/50 lignes conformes au motif.
3. **Busy-check + reset_session** : `sleep 300` lancé via send_keys → `run_command` refusé
proprement → `reset_session` → « shell réactif, cwd/env conservés (cwd: /tmp) ».
## Bonnes pratiques v2
- Écrire les scripts Python directement en heredoc : `cat <<'EOF' > /tmp/s.py … EOF` puis `python3 /tmp/s.py`.
- Ne plus jamais utiliser le workaround base64 (2 appels) ni le découpage printf-append.
- Session par défaut `claude` fiable ; `reset_session` avant de créer une session neuve.
- Le rebuild du container se fait **depuis le Mac** (`ssh nas`), jamais depuis mcp-nas lui-même :
```
ssh -p 22222 Best0f@192.168.100.33
cd /volume1/docker/linux-mcp-nas
sudo docker compose build && sudo docker compose up -d --force-recreate
curl -s http://127.0.0.1:3042/health
```
## P1 — denylist ancrée + nas.internal (2026-07-04, commit `7fb13c5`, déployé et testé)
- **Denylist ancrée** : les patterns ne sont plus cherchés n'importe où dans la chaîne brute.
La commande est découpée en segments (`;` `&` `|` retours ligne), les préfixes neutres
(`sudo`, `env`, `VAR=…`, chemin `/sbin/…`) sont retirés, et les patterns sont ancrés sur le
**début de chaque commande**. Les **corps de heredoc sont exclus** de l'analyse.
- Fini les faux positifs : `grep reboot /var/log`, `echo "ne pas faire reboot"`, un heredoc
de doc contenant « mkfs » → **acceptés**.
- Toujours refusés : `reboot`, `/sbin/reboot`, `echo ok && reboot`, `VAR=1 sudo shutdown`,
`rm -rf /volume*|/opt|/mnt|/`, `mkfs.*`, `dd of=/dev/*`, fork bomb, `> /dev/sd*`,
`docker rm -f`, `fdisk`, `parted`, `syno*`, `chmod -R 777 /`. (30/30 tests unitaires.)
- Changement assumé : `rm -rf /tmp/x` est désormais **autorisé** (avant, tout chemin absolu
était bloqué) ; les racines critiques restent protégées.
- **`nas.internal`** : `extra_hosts: nas.internal:host-gateway` dans le compose → depuis toute
session, `nas.internal` = l'hôte NAS (172.17.0.1). Utiliser `http://nas.internal:3232` (Gitea),
`:9000` (Portainer), `:8787` (NyoraNotes)… au lieu de mémoriser 172.17.0.1.
-106
View File
@@ -1,106 +0,0 @@
# Migration provider IA → OpenCode Go (+ fallback OpenRouter)
**Instance auteur** : hermes-perso (transverse aux 3)
**Date** : 2026-06-14
**Tags** : infra, opencode, hermes, family-help, llm, fallback
**Statut** : valide
---
## Probleme
Basculer le provider IA par defaut de OpenRouter vers OpenCode Go (endpoint
`zen/go/v1`), tout en gardant DeepSeek V4 Flash et un fallback OpenRouter.
Concerne les 3 agents Hermes (gateway) + l'app family-help (module medical).
---
## Contexte et contraintes
- Image `nousresearch/hermes-agent:latest`, source montee dans `/opt/hermes`.
- Les 3 Hermes sont des **stacks Portainer** : 291=nyora, 292=perso (path-based,
compose sur le FS), 294=TT (compose **inline** dans Portainer). Le
`docker-compose.yml` top-level de `hermes-platform/` est OBSOLETE.
- 1 seul abonnement OpenCode Go, plusieurs cles tirant du meme pool (~60$/mois).
- `/Volumes/docker` (Mac) = mount SMB du NAS `/volume1/docker`.
---
## Ce qui NE fonctionne PAS
| Tentative | Erreur obtenue | Raison de l'echec |
|-----------|----------------|-------------------|
| `docker compose up` depuis `hermes-platform/` (top-level) | `Conflict. The container name is already in use` | Les containers appartiennent aux stacks per-instance, pas au compose top-level |
| `hermes model` / `hermes fallback add` en SSH | `requires an interactive terminal` | Pickers interactifs → editer le YAML directement |
| Provider custom dans `providers:` | inutile | `opencode-go` est deja BUILT-IN (models.dev) |
| family-help medical `max_tokens: 512` | `finish_reason=length`, `content` VIDE | Modele de raisonnement : reasoning_content mange le budget avant le content |
---
## Solution validee
Provider built-in `opencode-go` : base_url `https://opencode.ai/zen/go/v1`,
cle via env var **`OPENCODE_API_KEY`**, model `deepseek-v4-flash` (sans prefixe).
OpenRouter (fallback) garde `deepseek/deepseek-v4-flash`.
```yaml
# data/config.yaml (gateway) — pour chaque instance
model:
default: deepseek-v4-flash
provider: opencode-go
fallback_providers:
- provider: openrouter
model: deepseek/deepseek-v4-flash
key_env: OPENROUTER_API_KEY
```
```bash
# perso / nyora (stacks path-based) : ajouter OPENCODE_API_KEY au .env + au compose
# - OPENCODE_API_KEY=${OPENCODE_API_KEY}
cd /volume1/docker/hermes-platform/hermes-perso
docker compose up -d --force-recreate hermes-agent-perso
# TT (stack 294 inline) : via API Portainer
# GET /api/stacks/294/file → inserer "- OPENCODE_API_KEY=<cle>" → PUT /api/stacks/294?endpointId=2
```
family-help : `medical_service.py` → helper `_llm_complete()` (OpenCode primaire,
OpenRouter fallback), **`max_tokens: 2048`**, cle `BIFROST_OPENCODE_KEY`.
`docker compose build --no-cache && docker compose up -d --force-recreate`.
---
## Verification
```bash
docker exec hermes-agent-<inst> hermes fallback ls
# Attendu : Primary deepseek-v4-flash (via opencode-go)
# Test end-to-end
docker exec hermes-agent-<inst> curl -s -H "Authorization: Bearer <API_SERVER_KEY>" \
-H 'Content-Type: application/json' \
-d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"dis OK"}],"max_tokens":40}' \
http://localhost:8642/v1/chat/completions
# Attendu : HTTP 200, content non vide
# Test fallback : pointer OPENCODE_GO_BASE_URL vers un hote injoignable → reponse via OpenRouter (200)
```
---
## Pieges specifiques DSM / NAS
- `docker` n'est PAS dans le PATH SSH → utiliser `/usr/local/bin/docker`.
- Ne jamais editer le docker-compose top-level (obsolete) ; identifier la vraie source
via `docker inspect <c> --format '{{index .Config.Labels "com.docker.compose.project.config_files"}}'`.
- TT = stack inline Portainer → API Portainer, pas le FS (sudo requis sinon).
- DeepSeek V4 Flash (OpenCode) = reasoning model → `max_tokens` >= 2048 pour les
apps maison qui parsent du JSON (Hermes gere nativement).
---
## References
- https://opencode.ai/docs/zen
- https://hermes-agent.nousresearch.com/docs/user-guide/features/fallback-providers
- NyoraNotes : note 416da848 (capitalisation), note 26c38f8c (missions)
-42
View File
@@ -1,42 +0,0 @@
# nyora-doc-api — Footer Nyora jamais implémenté pour DOCX
**Date** : 2026-07-03
## Constat
Confirmé sur 3 générations réelles consécutives (PMI_Infinity_Formation v1, v2, v3,
via Perso) : le DOCX produit par `nyora-doc-api` n'a jamais eu qu'un footer
"Page X / Y". Le footer Nyora obligatoire (liseré or, "· NYORA ·", tagline
"Crafted with precision · Built for excellence", "© 2026") n'existe que côté
PPTX/PDF (`_wrap_html_with_nyora_theme`, `_get_nyora_pdf_css`) — jamais côté DOCX.
Vérifié dans le `.pyc` compilé avant le "premium upgrade" du 01/07 (commit
cbe8cf1) : la fonction n'y était pas non plus. Ce n'est donc pas une régression
du upgrade, c'est un trou jamais comblé depuis la création du service.
## Fix
`/mnt/docker/nyora-doc-api/app/services.py`, fonction
`_add_docx_page_number_footer` : ajout de 3 paragraphes avant la pagination
existante — bordure supérieure de paragraphe couleur `B8862A` (simule le liseré
or), "· NYORA ·" gras or 8pt, tagline slate italique 7pt, "© 2026" slate 7pt.
Backup pris avant patch : `services.py.bak_<timestamp>`.
## Bloquant — PAS ENCORE ACTIF
`/app` n'est **pas** bind-mounté dans le container `nyora-doc-api` (seul `/data`
l'est) — le code est baké dans l'image `nyora-doc-api:latest` au build. Un
`docker restart` ne suffit pas (vérifié en conditions réelles : patch appliqué
sur le host, container redémarré, footer toujours absent en test `/v1/generate`
direct).
## Reste à faire
`docker compose build` (Dockerfile présent dans `/mnt/docker/nyora-doc-api/`)
puis recréer le container. Jamais fait sans accord explicite (service qui sert
TT et Nyora en production). Tester ensuite avec un appel `/v1/generate` direct
(format docx, `data` minimal) et vérifier `word/footer1.xml` contient bien les
runs "NYORA" / tagline / copyright avant de considérer le correctif déployé.
Étape finale une fois validé : `git add . && git commit && git push` depuis le
compte bolbol sur le repo `bolbol/nyora-doc-api`.
@@ -1,30 +0,0 @@
# Runbook — Rebuild & déploiement nyora-doc-api (footer Nyora DOCX)
**Date** : 04/07/2026 · **Statut** : validé en production
## Problème
Le footer Nyora (liseré or → · NYORA · → tagline → © 2026) n'a jamais existé dans les DOCX générés par nyora-doc-api. Le fix était écrit dans `app/services.py` (`_add_docx_page_number_footer`) mais inactif : **`/app` est cuit dans l'image Docker, pas bind-mounté** — toute modification de code exige un rebuild.
## Procédure validée (depuis mcp-nas)
1. **Docker indisponible dans le container mcp-nas** → passer par SSH host : `ssh -p 22222 Best0f@172.17.0.1` (clé ed25519 en place dans ~/.ssh du container). Sur le host : docker = `/usr/local/bin/docker`, **sans sudo** (Best0f dans le groupe docker ; sudo échoue, pas de TTY).
2. Build : `cd /volume1/docker/nyora-doc-api && /usr/local/bin/docker compose build` (~2 min, pip timeout 180 déjà dans le Dockerfile).
3. Déploiement : `docker stop nyora-doc-api && docker rm nyora-doc-api && docker compose up -d` — jamais de simple restart, l'image ne serait pas rechargée.
4. Health : `curl http://172.17.0.1:3050/health` → 200.
## Test /v1/generate — pièges
- Endpoint **multipart/form-data**, pas JSON : `curl -F "format=docx" -F "data=</tmp/data.json"` + header `X-API-Key` (clé dans hermes-platform/.env). Un POST -d JSON → 422 "body.format missing".
- Payload avec apostrophes françaises : toujours l'écrire dans un fichier, jamais inline.
## Vérification du footer (sans ouvrir Word)
```python
import zipfile, re
xml = zipfile.ZipFile('doc.docx').read('word/footer1.xml').decode()
re.findall(r'<w:t[^>]*>([^<]*)</w:t>', xml) # textes du footer
'B8862A' in xml # liseré + NYORA doré
```
Attendu : `· NYORA ·`, `Crafted with precision · Built for excellence`, `© 2026`, pagination.
## Notes
- Couleur or : **#B8862A (Or Bruni, charte doc-api WCAG AAA)**, pas le #d4a01a de la règle générale footer — divergence assumée, à arbitrer.
- **Git existe sur le host NAS** (`/usr/bin/git`) — l'ancienne consigne « git depuis le Mac uniquement » est périmée. Push : `git push http://bolbol:PWD@127.0.0.1:3232/bolbol/REPO.git HEAD` depuis le host.
- Heredoc contenant du python multi-lignes dans mcp-nas → hang/timeout. Transférer les fichiers en base64 chunké (~290 chars) et décoder sur place.
+28
View File
@@ -0,0 +1,28 @@
# nyora-doc-api — Procédure de rebuild & déploiement
**Date** : 2026-07-09 (généralise nyora-doc-api-rebuild-footer-docx.md 04/07 ; historique footer clos)
**Tags** : infra, nyora-doc-api, docker, rebuild
## Règle de base
**`/app` est cuit dans l'image Docker, pas bind-monté** → toute modification de code dans `/mnt/docker/nyora-doc-api/app/` exige un **rebuild + recreate**. Un simple restart ne recharge rien.
## Procédure validée (depuis mcp-nas ou Mac)
1. Docker indisponible dans mcp-nas (socket retiré, audit 08/07) → passer par le Mac : `ssh -p 22222 Best0f@192.168.100.33`. Sur le host : docker = `/usr/local/bin/docker`, **sans sudo** (Best0f dans le groupe docker ; sudo échoue, pas de TTY).
2. Build : `cd /volume1/docker/nyora-doc-api && /usr/local/bin/docker compose build` (~2 min, pip timeout 180 déjà dans le Dockerfile).
3. Déploiement : `docker stop nyora-doc-api && docker rm nyora-doc-api && docker compose up -d`**jamais un simple restart**, l'image ne serait pas rechargée. Alternative sans SSH : `nohup` côté host (cf. session 08/07).
4. Health : `curl http://172.17.0.1:3050/health` → 200.
5. Vérifier code container == disque == Gitea : `docker exec nyora-doc-api cat /app/services.py | diff - /volume1/docker/nyora-doc-api/app/services.py`.
## Test /v1/generate — pièges
- Endpoint **multipart/form-data**, pas JSON : `curl -F "format=docx" -F "data=</tmp/data.json"` + header `X-API-Key` (clé dans hermes-platform/.env). Un POST -d JSON → 422 "body.format missing".
- Payload avec apostrophes françaises : toujours dans un fichier, jamais inline.
## Historique (clos)
- 03-04/07 : footer Nyora ajouté aux DOCX (n'avait jamais existé) puis déployé.
- **08/07 (commits a7380268 + f7353419) : footers retirés de TOUS les formats** (DOCX/PPTX/PDF/XLSX) sur décision Nabil, et option `restyle=full` implémentée pour le mode template (voir common/nyora-doc-api-template-restyle-cause-racine.md). Les vérifications XML de footer des anciennes versions ne s'appliquent plus.
Guide d'usage API : common/nyora-doc-api-hermes-guide.md.
+65
View File
@@ -0,0 +1,65 @@
# Migration provider → OpenCode Go (consolidé)
**Date** : 2026-07-09 (fusionne migration-provider-opencode-go.md 14/06 + OPENCODE-GO-MIGRATION.md 16/06 ; état mis à jour)
**Tags** : infra, opencode, hermes, family-help, bifrost, fallback
## État actuel des modèles (09/07/2026)
| Instance | model.default | Fallback |
|---|---|---|
| hermes-tt | deepseek-v4-flash (opencode-go) | opencode-go/deepseek-v4-flash |
| hermes-nyora | **mimo-v2.5** (verdict pilote 08/07 : GARDER — voir hermes-nyora/verdict-pilote-mimo-v25.md) | opencode-go/deepseek-v4-flash |
| hermes-perso | deepseek-v4-flash (opencode-go) | opencode-go/deepseek-v4-flash |
⚠️ Depuis le 06/07, les fallbacks pointent vers **opencode-go** (forfait) et non plus openrouter (payant) — voir common/bifrost-vk-incidents.md.
**Source de vérité compose** : `/volume1/docker/hermes-platform/docker-compose.yml` (top-level), confirmé par le fix DNS du 09/07 (recreate des 3 agents). Les compose per-instance sont en `.disabled` ; les mentions historiques « stacks Portainer 291/292/294, top-level obsolète » ne sont plus valables.
## Endpoint & modèles OpenCode Go
Provider built-in `opencode-go` : base_url `https://opencode.ai/zen/go/v1`, auth `Authorization: Bearer <clé>` uniquement (pas de x-bf-vk, pas de HTTP-Referer/X-Title). IDs **sans préfixe** (`deepseek-v4-flash`, `glm-5.1`, `mimo-v2.5`…), jamais `openrouter/` ni `deepseek/`.
Utilisables en OpenAI-compat : deepseek-v4-flash/pro, glm-5/5.1, qwen3.6/3.7-plus, kimi-k2.6, kimi-k2.7-code, minimax-m3, mimo-v2.5(-pro).
**NE FONCTIONNE PAS** :
- `qwen3.7-max``Model not supported for format oa-compat` (dispo seulement format Anthropic). Consommateurs OpenAI-compat → `glm-5.1`.
- MiniMax M2.7 vision = `prompt_tokens:0` (vision cassée). Vision/OCR → Gemini via Bifrost.
## config.yaml Hermes
`/volume1/docker/hermes-platform/hermes-{tt,nyora,perso}/data/config.yaml` :
```yaml
model:
default: <modele>
provider: opencode-go
fallback_providers:
- provider: opencode-go
model: deepseek-v4-flash
key_env: OPENCODE_GO_API_KEY # nom réel injecté par le compose top-level (PAS OPENCODE_API_KEY)
```
Hermes **réécrit config.yaml au shutdown** → toujours `docker stop` → éditer → `docker start` (jamais éditer à chaud). Healthcheck port 8642, API `/v1/chat/completions`, modèle proxy `hermes-agent`.
⚠️ data/ en 0700 uid 1026 : illisible depuis mcp-nas post-hardening — édition via Mac ou canal autorisé.
## family-help
`medical_service.py` → helper `_llm_complete()` (OpenCode primaire, OpenRouter fallback web/erreur), routing par skill : medical/nutrition glm-5.1, homework qwen3.7-plus, legal_admin deepseek-v4-pro, tech/general deepseek-v4-flash. **`max_tokens ≥ 2048`** obligatoire (modèles de raisonnement : le reasoning mange le budget avant le content → `finish_reason=length`, content vide). Même piège vu sur mimo-v2.5 (fix : max_tokens 8192 sur hermes-nyora).
## Bifrost (rappel)
Bifrost ne recharge PAS les clés provider au restart → `config.json` chargé au boot + `allow_all_keys=1` en DB. Providers dans config.json ≠ store (`DELETE FROM config_providers/config_keys`, bifrost stoppé). Éditer via l'UI peut re-casser. Détails : common/bifrost-access.md et common/bifrost-vk-incidents.md.
## n8n
- Code nodes LLM → Bifrost obligatoire (common/runbook-n8n-llm-bifrost.md). `require('https')` + parsing manuel, jamais fetch/new URL/$helpers.
- 15 workflows AO/veille archivés contiennent encore OpenRouter : l'API n8n refuse d'éditer un archivé. Pour réutiliser : désarchiver (UI) → migrer le Code node (endpoint zen/go/v1, clé OpenCode, retirer HTTP-Referer/X-Title) → re-tester → ré-archiver.
- Restent volontairement sur OpenRouter direct : `Mode-nedya`, `FAMILLE | Recettes Weekend`, `Labellisation Auto`.
## Pièges DSM/NAS
- `docker` hors PATH SSH → `/usr/local/bin/docker` (Best0f groupe docker, sans sudo).
- `hermes model` / `hermes fallback add` en SSH → « requires an interactive terminal » → éditer le YAML.
- Vérif : `docker exec hermes-agent-<i> hermes fallback ls` + test curl `localhost:8642/v1/chat/completions`.
## Références
- https://opencode.ai/docs/zen · hermes-agent.nousresearch.com/docs (fallback-providers)
- hermes-nyora/verdict-pilote-mimo-v25.md · common/bifrost-vk-incidents.md
@@ -1,34 +0,0 @@
# Cron Hermes ne se relit pas au vol + publish n8n casse : veille Dr Nexum non recue
**Date** : 2026-07-07
**Tags** : infra, cron, hermes, n8n, publish, timezone, veille, nyora
## Symptome
Veille Dr Nexum (Hermes 3x/jour + workflow n8n) non recue le 06/07 20h ni le 07/07 7h, sur les deux pipelines simultanement.
## Cause 1 -- Cron Hermes fige en memoire (jobs.json)
Le service cron de chaque agent Hermes charge `jobs.json` uniquement au demarrage du container. Une edition directe du fichier (passage de 1x a 3x/jour le 06/07) s'est bien ecrite sur disque, mais le processus tournait toujours avec l'ancien horaire quotidien (11h30 UTC) en memoire -- `next_run_at` restait fige sur l'ancienne valeur meme un jour apres l'edit.
**Fix** :
1. Editer `jobs.json` (cron expr).
2. Recalculer et ecrire manuellement `next_run_at` a la bonne valeur (le daemon ne le fait pas toujours correctement au premier boot suivant).
3. Redemarrer le container **agent** correspondant -- nom reel `hermes-agent-tt` / `hermes-agent-nyora` / `hermes-agent-perso` (piege : ne pas confondre avec `hermes-workspace-*`, qui sont d'autres containers).
4. Restart via API Portainer : `POST /api/endpoints/2/docker/containers/{id}/restart` timeout systematiquement sur cette instance -- utiliser `stop` puis `start` explicites (2 appels separes).
5. Verifier apres coup que `next_run_at` a la bonne valeur.
Portainer : endpoint local = id `2`. Auth via `172.17.0.1:9000/api/auth` depuis un container (jamais l'IP LAN). Credentials dans `hermes-platform/.env`.
## Cause 2 -- Fuseau horaire (NAS + n8n en UTC, pas Tunis)
Le NAS et n8n tournent en UTC. Tunis = UTC+1 toute l'annee (pas de changement d'heure). Un cron `0 7,12,20 * * *` tire a 8h/13h/21h Tunis, pas 7h/12h/20h.
**Fix** : toujours retirer 1h a l'expression cron pour un horaire "heure de Tunis" -- `0 6,11,19 * * *` en UTC pour obtenir 7h/12h/20h Tunis. Applique sur jobs.json (Hermes) et sur le node Schedule Trigger n8n.
## Cause 3 -- publish_workflow n8n casse sur un workflow specifique (non systemique)
Un workflow deja actif, modifie via l'outil `update_workflow`, ne bascule pas automatiquement en production -- `update_workflow` cree un nouveau `versionId` (brouillon) mais le workflow continue de servir son `activeVersionId` existant. Sur ce workflow precis, `publish_workflow` echouait de facon persistante avec `"Version not found"`, y compris en reessayant **depuis l'UI n8n elle-meme** (capture d'ecran a l'appui) et apres un `unpublish_workflow` prealable -- confirme comme un etat corrompu propre a ce workflow (chaine de versions cassee en base), pas un bug de l'outil MCP.
**Fix qui a marche** : dupliquer le workflow (bouton **Duplicate** dans l'UI n8n). La copie repart avec une chaine de versions saine et se publie normalement des le premier essai. Renommer la copie pour retirer le suffixe " copy", puis `unpublish_workflow` + `archive_workflow` sur l'original casse une fois la copie confirmee active.
**Verification obligatoire apres toute modif d'un workflow n8n deja actif** : `get_workflow_details` -> comparer `versionId` et `activeVersionId`. S'ils different, le changement n'est PAS en production, meme si l'outil de modification a repondu succes.
## Lecon generale
Une reponse "success" d'un outil de modification (`update_workflow`, edition de `jobs.json`) ne garantit jamais que le changement est en production. Toujours verifier l'etat reel apres coup : `next_run_at` recalcule pour un cron Hermes redemarre, `activeVersion == draft` pour un workflow n8n.
-44
View File
@@ -1,44 +0,0 @@
# Runbook - Veille Hebdo Dr. Nexum (Kanban Fan-Out)
Date : 2026-06-18 | Instance : hermes-nyora | Statut : Deploye
## Architecture
Cron lundi 07h00 -> default (orchestrateur) -> 6 workers veilleur paralleles -> synthese brief Telegram
## Profile veilleur
Path : /opt/data/profiles/veilleur/
SOUL.md : scoring /10, angle video, signal fort OUI/NON
Config : deepseek-v4-flash/openrouter, max_turns 25
## Sources (6)
youtube-fr-ia : videos IA/automatisation FR semaine
huggingface-blog : nouveaux modeles open-source
reddit-ia : r/LocalLLaMA + r/MachineLearning
producthunt-ia : nouveaux outils IA lances
labs-ia : Anthropic + OpenAI + DeepMind annonces
automatisation : n8n + Make + Zapier blog workflows
## Sortie
Fiches sources : /opt/data/workspace/veille/YYYY-WNN/<source>.md
Brief final : /opt/data/workspace/veille/YYYY-WNN/brief-nexum.md
Livraison : Telegram Nabil (2084513684)
## Cron jobs.json
Fichier : /volume1/docker/hermes-platform/hermes-nyora/data/cron/jobs.json
Job ID : veille-hebdo-nexum-kanban-a1b2c3d4
Expr : 0 7 * * 1 (lundi 07h00 UTC = 08h00 Tunisie ete)
## Declencher manuellement
Depuis Telegram hermes-nyora :
Lance la veille hebdo Nyora maintenant
## Deux crons hermes-nyora actifs
1. Veille IA Matin (11h30 UTC quotidien) - opportunites Dr. Nexum via veille_ia_matin.py
2. Veille Hebdo Fan-Out (07h00 lundi) - analyse profonde 6 sources, brief structure
-94
View File
@@ -1,94 +0,0 @@
# Workflow Claude-GLM -- Nyora API Builder
**Date** : 2026-06-17 (rev 2)
**Scope** : hermes-nyora (GLM-5.1 via opencode-go) + Telegram bot
**Objectif** : Economiser les tokens Claude -- garder Claude pour le strategique
---
## Architecture
PLANNER -> Claude (discussion + accord + generation plan)
EXECUTOR -> hermes-nyora / GLM-5.1 via Telegram (implementation)
REVIEWER -> Claude (validation finale, problemes critiques uniquement)
Ratio economies tokens : ~7x (Claude = 20-30k, GLM = 150k+)
---
## Workflow complet (4 etapes)
### Etape 1 -- Discussion et accord (session Claude)
Nabil decrit son projet librement a Claude.
Claude pose les questions necessaires :
- Stack existante et contraintes
- Perimetre exact de l API / feature
- Integrations (Bifrost, Baserow, n8n, etc.)
- Chemin du repo Gitea
Pas de template a remplir avant -- la discussion IS le template.
Accord explicite avant de passer a l etape 2.
### Etape 2 -- Generation du plan (Claude)
Claude genere un PLAN.md complet et structure.
Format : PROJECT / CONTEXT / TECH STACK / PHASES / REVIEW CRITERIA
Maximum 5 phases. Chaque phase : GOAL + TASKS + FILES + VALIDATION.
Claude inclut les criteres de review qu il utilisera a l etape 4.
### Etape 3 -- Execution (Telegram -> hermes-nyora)
Nabil copie-colle le PLAN.md dans le Telegram bot hermes-nyora.
hermes-nyora active automatiquement le skill claude-glm-workflow.
Execution phase par phase avec rapport standardise.
hermes-nyora genere HANDOFF.md a la fin.
Bot Telegram hermes-nyora : token 8914117708:AAHNysUFMy8eFRagLmcNMvCkiCuArf0aZMs
allowed_chats : '' (tous les chats autorises par defaut)
### Etape 4 -- Review finale (session Claude)
Nabil copie HANDOFF.md + prompt review dans Claude.
Prompt review disponible dans : workspace/templates/claude-plan-template.md
Claude identifie uniquement les problemes critiques et majeurs.
Verdict : Ready to deploy | Needs fixes.
---
## Fichiers deployes
hermes-nyora/data/skills/nyora/claude-glm-workflow/SKILL.md
hermes-nyora/data/workspace/templates/claude-plan-template.md
hermes-nyora/data/SOUL.md (section WORKFLOW CLAUDE-GLM)
---
## Modele et couts
Modele actuel : glm-5.1 (opencode-go, $10/mois = $60 valeur)
Fallback : qwen3.7-plus (opencode-go)
GLM-5.2 : disponible semaine 22 juin -- changer config.yaml model.default
---
## Pieges connus
- SOUL.md et config.yaml : 100% ASCII obligatoire
- hermes-nyora voit /opt/data/workspace/ (pas /workspace/)
- Bifrost requis pour tout LLM depuis n8n (opencode.ai inaccessible)
---
## Bonne pratique -- Plans longs (> limite Telegram)
Pour tout plan depasse quelques centaines de caracteres :
1. Claude depose le PLAN.md dans /opt/data/workspace/plans/<projet>.md via MCP
2. Nabil envoie UNE LIGNE dans Telegram :
Execute le plan : /opt/data/workspace/plans/<projet>.md
3. hermes-nyora lit le fichier et execute normalement
Avantages : plans illimites en taille, historique des plans conserve sur le NAS,
relance facile sans re-coller le prompt, traçabilite Gitea possible.
Convention nommage : <slug-projet>-YYYYMMDD.md (ex: panda-theme-switcher.md)