# Correctif routage Bifrost -> OpenCode Go & raccordement hermes-tt/hermes-nabil/DSH (07/09/2026) ## 1. Contexte Le 07/09/2026, Nabil (au travail, sans acces Gemini) signale trois pannes simultanees : - **hermes-tt** et **hermes-nabil** : "provider failed after retries" - **hermes-nyora** : bascule silencieuse sur le modele de secours (deepseek-v4-flash via opencode-go -> openrouter) - **DSH** : `400 MissingSessionID` a chaque tour Diagnostic mene et corrige en direct par Claude (intervention SMART justifiee par l'absence de Gemini, cf. hermes-platform.md). ## 2. Cause racine n°1 : chemin cassé sur le provider `opencode` de Bifrost `config.json` de Bifrost (`providers.opencode.network_config.base_url`) pointait vers `http://opencode-proxy:8080/zen/go`. Ce chemin est un residu de la tentative de sidecar local du 04/09/2026, **deja abandonnee** a l'epoque a cause du validateur SSRF interne de Bifrost qui bloque toute IP privee (voir `migration-vps-bifrost-stub-forwarder-20260904.md`, section 5). En plus d'etre la mauvaise approche, la valeur etait meme cassee techniquement : `opencode-proxy` (VPS) n'ecoute reellement que sur le port 8902, pas 8080 -> connexion refusee a chaque appel. Consequence : tout appel `provider=opencode` via Bifrost echouait systematiquement. hermes-nyora/perso (qui ont un fallback configure) basculaient silencieusement sur un modele degrade sans que Nabil s'en rende compte immediatement ; hermes-tt (sans fallback) et tout consommateur direct echouaient dur. ### Correctif applique Retour a la solution native documentee le 04/09 (relais d'en-tete `x-bf-eh-x-opencode-session` par `bifrost-proxy`, pas de sidecar) : ``` base_url: https://opencode.ai/zen/go ``` Attention : **pas de `/v1` a la fin**. Bifrost ajoute lui-meme `/v1/chat/completions` sur les providers OpenAI-compatibles ; un `base_url` finissant deja par `/v1` produit un double `/v1` et une 404 HTML (page opencode.ai, pas une erreur JSON API) — piege facile a mal diagnostiquer. ### Procedure ```bash # Sur le VPS, dans le conteneur bifrost (root) docker exec -u 0 bifrost sh -c "cp /app/data/config.json /app/data/config.json.bak-" docker exec -u 0 bifrost sh -c "sed -i 's#ANCIENNE_VALEUR#https://opencode.ai/zen/go#' /app/data/config.json" # Valider le JSON avant de redemarrer (le conteneur bifrost n'a ni python3 ni node) docker run --rm -v /home/dsh-agent/bifrost/data:/data:ro alpine:3.20 sh -c 'apk add -q jq; jq empty /data/config.json && echo VALID_JSON' docker restart bifrost ``` ### Test de validation (depuis le NAS, via bifrost-proxy) ```bash curl -s -X POST http://localhost:3086/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{"model":"opencode/mimo-v2.5","messages":[{"role":"user","content":"ping"}],"max_tokens":10}' ``` Une reponse JSON `chat.completion` valide confirme le bon fonctionnement (teste avec succes pour vk-hermes-nyora, vk-hermes-perso, vk-hermes-tt, vk-hermes-nabil, vk-dsh-vps le 07/09/2026). ## 3. Cause racine n°2 : hermes-tt, hermes-nabil et DSH jamais raccordes au pattern bifrost-proxy Ces trois consommateurs appelaient OpenCode Go **en direct** (`https://opencode.ai/zen/go/v1` ou provider `opencode-go`/`opencode-direct` sans passer par `bifrost-proxy`), donc sans l'injection de `x-opencode-session` -> `MissingSessionID` systematique, sans le filet de securite qu'offre Bifrost (fallback, tracking cout, whitelist par cle). ### hermes-tt (NAS, `hermes-agent-tt`, /opt/data/config.yaml) Avant : ```yaml model: provider: opencode-go base_url: '' api_key: '' fallback_providers: [] ``` Apres (aligne sur hermes-nyora/perso) : ```yaml model: provider: custom base_url: http://bifrost-proxy/v1 api_key: sk-bf-26694aba-54d0-4d03-8c4c-a6e3830b6cbe # vk-hermes-tt (existait deja depuis le 18/07, jamais utilisee) fallback_providers: - provider: opencode-go model: mimo-v2.5 key_env: OPENCODE_GO_API_KEY ``` ### hermes-nabil (VPS, /data/config.yaml) Avant : `base_url: https://opencode.ai/zen/go/v1`, cle OpenCode Go brute. Apres : ```yaml model: provider: custom base_url: http://100.86.197.88:3086/v1 # IP Tailscale du NAS, port publie de bifrost-proxy (le hostname docker "bifrost-proxy" n'existe que sur le reseau n8n du NAS, injoignable depuis le VPS) api_key: sk-bf-eb2e9179-c646-4e84-afe7-bc7c61b16f59 # vk-hermes-nabil, creee le 07/09/2026 ``` **Nouvelle cle virtuelle creee** via l'API Bifrost (`POST /api/governance/virtual-keys` sur `localhost:3085`, meme structure `provider_configs` que vk-hermes-perso : google/groq/nvidia_nim/opencode/openrouter). Piege confirme une fois de plus (voir migration-vps-bifrost-stub-forwarder-20260904.md section 4) : l'API cree la cle avec `allow_all_keys=false` malgre le payload envoye a `true` — patch SQL obligatoire puis restart : ```sql UPDATE governance_virtual_key_provider_configs SET allow_all_keys=1 WHERE virtual_key_id=''; ``` ### DSH (VPS, /home/dsh-agent/.dsh/settings.yaml) Cas different : le provider `bifrost` etait **deja correctement configure** (baseURL `http://100.86.197.88:3086/v1`, `BIFROST_API_KEY` = vk-dsh-vps deja valide cote Bifrost) mais `agent-default-model.provider` restait sur `opencode-direct`. Il manquait aussi l'entree `opencode/mimo-v2.5` dans le catalogue de modeles du provider bifrost (seuls deepseek-v4-flash/gemini/gemma/qwen y figuraient). Correctif : bascule de `provider: opencode-direct` -> `provider: bifrost`, `model: mimo-v2.5` -> `model: opencode/mimo-v2.5`, ajout de l'entree modele manquante. ## 4. Perimetre restant (non traite dans cette session) Le volet nyora-doc-api/nyora-convert-api du ticket infra-2026-09-023 (bascule des appels NAS vers les instances VPS pour tous les assistants) n'a pas ete traite ici — uniquement le volet routage LLM/Bifrost. ## 5. Lecon a capitaliser Ne jamais faire confiance a un `base_url` existant dans `config.json` sans verifier qu'il correspond a une architecture **actuellement valide** — un residu d'une tentative abandonnee (meme documentee comme abandonnee ailleurs) peut rester en config sans lever d'alerte jusqu'a ce qu'un consommateur sans fallback tombe dessus. Toujours re-tester en conditions reelles (requete complete, pas juste `/health`) apres toute correction de routage LLM.