Aller au contenu

Claude et Hermes Workspace — Infrastructure complète

Contexte

Objectif : permettre à Hermes Workspace (Docker sur VPS) de lancer des missions Claude Code CLI exactement comme on le fait en local, avec un système de routage des tâches selon la complexité — et ce de manière générique (non lié à un projet spécifique).

Référence VPS : 178.105.46.57, utilisateur kanmaber, clé SSH ~/.ssh/kanmaber_hetzner.

Contenu

1. Volumes Docker ajoutés (docker-compose.yml)

Fichier : /home/kanmaber/hermes-workspace/docker-compose.yml

volumes:
  - hermes-workspace-data:/home/workspace/.hermes
  - /home/kanmaber/projets:/home/workspace/projets                                        # tous les projets
  - /home/kanmaber/tekdhi-landing:/home/workspace/tekdhi-landing                          # site perso tekdhi.dev
  - /home/kanmaber/.local/share/claude/versions/2.1.139:/usr/local/bin/claude:ro         # Claude CLI ELF v2.1.139
  - /home/kanmaber/hermes-workspace/mission-scripts:/home/workspace/mission-scripts:ro   # scripts de mission
  - /home/kanmaber/hermes-workspace/skills/task-router:/app/skills/task-router:ro        # skill task-router

Pourquoi monter le binaire Claude directement : c'est un binaire ELF autonome (pas un script Node.js), donc montable directement dans le conteneur à /usr/local/bin/claude. #docker

Pourquoi tekdhi-landing séparé : le site perso n'est pas sous /projets/, il a son propre dossier sur le VPS. Le skill le résout avec --project=tekdhi-landing.

2. Mission scripts déployés

Dossier VPS : /home/kanmaber/hermes-workspace/mission-scripts/

Fichier Source locale Rôle
run-mission.sh /home/kanmaber/projets/PsychoEnLigneV2/.claude/run-mission.sh Wrapper mission avec --auto-retry
MISSION_TEMPLATE.md /home/kanmaber/projets/PsychoEnLigneV2/.claude/MISSION_TEMPLATE.md Template XML de mission
CLAUDE_MISSIONS_README.md /home/kanmaber/projets/PsychoEnLigneV2/.claude/CLAUDE_MISSIONS_README.md Documentation du système

Patches appliqués sur run-mission.sh

Le script a été rendu générique (non lié à PsychoEnLigneV2) par deux modifications :

# Ligne 9-10 : PROJET_ROOT dynamique
PROJET_ROOT="${PROJECT_DIR:-$(cd "$(dirname "$0")/../.." && pwd)}"

# Ligne 13 : CLAUDE_BIN configurable
CLAUDE_BIN="${CLAUDE_BIN:-/usr/local/bin/claude}"

Ainsi, le script fonctionne : - En mode workspace : PROJECT_DIR="/home/workspace/projets/monprojet" bash run-mission.sh ... - En mode local : comportement original (derive le path depuis l'emplacement du script)

3. Skill task-router

Fichier : /home/kanmaber/hermes-workspace/skills/task-router/SKILL.md
Monté dans le conteneur à : /app/skills/task-router/SKILL.md

Syntaxe

/task-router --mode=<mode> --project=<nom> <description de la tâche>

Résolution du projet

if [ "<nom>" = "tekdhi-landing" ]; then
  PROJECT_DIR="/home/workspace/tekdhi-landing"
else
  PROJECT_DIR="/home/workspace/projets/<nom>"
fi

En l'absence de --project : Hermes analyse la description et détecte le projet depuis ls /home/workspace/projets/.

3 modes de routage

Mode Usage Comportement quota
claude-only Tâche très complexe Attend le reset de quota et relance automatiquement (--auto-retry). Jamais de fallback Hermes.
claude-first Tâche modérée Claude en premier. Si quota épuisé → Hermes lit le checkpoint et continue sans attendre.
hermes-only Tâche simple Hermes traite directement. Pas de fichier mission, pas de Claude.

Procédure claude-only et claude-first (création mission)

# 1. Créer le dossier mission
HORODATAGE=$(date +%Y%m%d_%H%M)
NOM_COURT="<slug kebab-case max 20 chars>"
MISSION_DIR="$PROJECT_DIR/.claude/missions/${HORODATAGE}_${NOM_COURT}"
mkdir -p "$MISSION_DIR"

# 2. Adapter MISSION_TEMPLATE.md (remplacer les placeholders)
python3 -c "
import os
src = open('/home/workspace/mission-scripts/MISSION_TEMPLATE.md').read()
src = src.replace('/home/kanmaber/projets/PsychoEnLigneV2', '$PROJECT_DIR')
src = src.replace('YYYYMMDD_HHMM_nom_court', '${HORODATAGE}_${NOM_COURT}')
src = src.replace('TITRE_MISSION', '$TITRE_MISSION')
open('$MISSION_DIR/lePrompte.md', 'w', encoding='utf-8').write(src)
"

# 3. Lancer
PROJECT_DIR="$PROJECT_DIR" \
CLAUDE_BIN="/usr/local/bin/claude" \
bash /home/workspace/mission-scripts/run-mission.sh "${HORODATAGE}_${NOM_COURT}" [--auto-retry]

Procédure hermes-only

cat "$PROJECT_DIR/CLAUDE.md" 2>/dev/null | head -150
# traiter directement
git -C "$PROJECT_DIR" add -A && git commit -m "feat: ..." && git push

4. Règles communes du skill

  • Toujours lire CLAUDE.md avant toute modification de code
  • Utiliser python3 open() pour écrire dans .claude/ — jamais heredoc
  • Chemins absolus obligatoires
  • Après modification TypeScript : npx tsc --noEmit doit retourner 0
  • Fichier prompt mission : toujours lePrompte.md (jamais mission.xml)

5. Vérification finale

# Dans le conteneur
ls /usr/local/bin/claude          # → binaire présent
ls /home/workspace/projets/       # → liste des projets
ls /home/workspace/tekdhi-landing # → site perso
ls /home/workspace/mission-scripts/run-mission.sh
ls /app/skills/task-router/SKILL.md

6. Utilisation depuis Hermes

Le skill fonctionne depuis toutes les interfaces Hermes (Telegram, workspace, WebUI) — elles envoient toutes les messages au même moteur Hermes qui charge les skills.

Taper directement dans le champ de message :

/task-router --mode=<mode> --project=<nom> <description libre>

Exemples concrets

Correction ciblée (hermes-only) :

/task-router --mode=hermes-only --project=PsychoEnLigneV2 corrige le message d'erreur dans AccountSettingsPage.tsx ligne 42

Implémentation d'un use case (claude-first) :

/task-router --mode=claude-first --project=PsychoEnLigneV2 implémente le use case ModifierProfilPatient avec ServiceValidation et handler HTTP

Module entier (claude-only) :

/task-router --mode=claude-only --project=PsychoEnLigneV2 implémenter tout le module Questionnaires (domain + application + infrastructure + routes) depuis zéro

Site perso tekdhi.dev (hermes-only) :

/task-router --mode=hermes-only --project=tekdhi-landing ajoute une section Projets avec 3 cartes

Choisir le bon mode

La tâche touche... Mode
Un fichier, une fonction, un bug précis hermes-only
Un use case complet, un module partiel claude-first
Un module entier, une refonte, une architecture claude-only

7. Notifications Telegram pendant une mission

Pendant l'exécution d'une mission (claude-only ou claude-first), Hermes est bloqué à attendre la fin de run-mission.sh — il ne peut pas envoyer de messages intermédiaires. Les notifications viennent donc du script lui-même via l'API Telegram.

Bot token : 8786472081:AAHyPKvi7QT3oGGW04r2EbvtL-g6FYjbLPc (dans .env Hermes) Chat ID : 1945031566 (TELEGRAM_HOME_CHANNEL)

Notifications envoyées automatiquement à 5 moments clés :

Moment Message reçu
Démarrage Mission <nom> lancee - projet : <projet>
Succès Mission <nom> terminee avec succes (<durée>s)
Échec Mission <nom> echouee - exit <code> - voir logs
Quota épuisé Quota Claude epuise - relance a <heure> Lagos (<Xh Ym>)
Surcharge 529 API surchargee (529) - relance dans 10 min (tentative X/3)

Implémenté dans run-mission.sh via la fonction :

notify_telegram() {
  curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
    --data-urlencode "chat_id=${TELEGRAM_CHAT_ID}" \
    --data-urlencode "text=$1" \
    > /dev/null 2>&1 || true
}

8. Suivi des logs en temps réel

Le script écrit les logs dans le dossier .claude/logs/ du projet, accessible depuis le VPS host car /home/kanmaber/projets est monté :

/home/kanmaber/projets/<projet>/.claude/logs/<mission>/
  ├── <timestamp>.jsonl      ← sortie brute Claude CLI
  ├── <timestamp>.log        ← version lisible (parsée)
  └── <timestamp>.summary.md ← résumé après exécution

Pour suivre en temps réel depuis une deuxième fenêtre (la session Hermes qui lance la mission est bloquée) :

Depuis ttyd (terminal browser sur le VPS) :

tail -f /home/kanmaber/projets/<projet>/.claude/logs/<mission>/<timestamp>.log

Depuis un second SSH :

ssh -i ~/.ssh/kanmaber_hetzner kanmaber@178.105.46.57 \
  "tail -f \$(ls -t /home/kanmaber/projets/<projet>/.claude/logs/*/*.log | head -1)"

Depuis une deuxième session Hermes :

lis-moi le dernier log de la mission <nom> du projet <projet>
Hermes lira : ls -t /home/workspace/projets/<projet>/.claude/logs/*/*.log | head -1

Checkpoint en cours (mis à jour après chaque étape par Claude) :

cat /home/kanmaber/projets/<projet>/.claude/missions/<mission>/checkpoint.md

9. Workflow validé — déclencher une mission depuis Hermes WebUI

Leçon clé : le terminal de Hermes WebUI tourne sur le host VPS (pas dans le conteneur). Les chemins /home/workspace/ n'existent pas — il faut les chemins host /home/kanmaber/.

Hermes est capable de trouver les bons chemins tout seul si on lui donne une commande avec les mauvais chemins — il fait un find et adapte. Mais il vaut mieux lui donner directement les bons chemins.

Méthode recommandée — create-and-run-mission.sh

Script créé par Hermes lors du premier test réel (20260516). Chemin : /home/kanmaber/projets/create-and-run-mission.sh

Usage : envoyer dans Hermes WebUI :

Lance cette commande bash :

bash /home/kanmaber/projets/create-and-run-mission.sh "description de la tâche" nom-court

Le script : 1. Génère un MISSION_ID horodaté (YYYYMMDD_HHMM_nom-court) 2. Crée le dossier mission dans .claude/missions/ 3. Copie et adapte MISSION_TEMPLATE.mdlePrompte.md 4. Lance run-mission.sh automatiquement

Exemple concret :

Lance cette commande bash :

bash /home/kanmaber/projets/create-and-run-mission.sh "Corriger les écarts backend-frontend identifiés dans le rapport /home/kanmaber/projets/PsychoEnLigne/.claude/missions/.../rapport.md" corriger-ecarts

Outil complémentaire — claude-wrapper.sh

Script créé par Hermes. Chemin : /home/kanmaber/projets/claude-wrapper.sh

# Lancer une mission existante
bash /home/kanmaber/projets/claude-wrapper.sh <MISSION_ID> --auto-retry

# Prompt direct sans mission
bash /home/kanmaber/projets/claude-wrapper.sh --direct "question ou tâche courte"

Pourquoi pas le skill task-router ?

Le skill task-router est lu par le LLM comme des instructions textuelles — il ne déclenche pas mécaniquement l'exécution des commandes bash. Le LLM peut faire la tâche lui-même (mode hermes-only effectif) ou ignorer le protocole de création de mission. Pour déclencher Claude CLI, toujours passer par une commande bash directe.

10. Système d'orchestration complet — orchestrate-mission.sh

Point d'entrée principal pour lancer une mission avec suivi automatique.

Usage

bash /home/kanmaber/projets/orchestrate-mission.sh "description de la tâche" nom-court [PROJECT_DIR]

PROJECT_DIR est optionnel — défaut : /home/kanmaber/projets/PsychoEnLigne

Chaîne d'exécution

orchestrate-mission.sh
  ├── create-mission-prompt.sh  → crée lePrompte.md (Phase 1 : template, Phase 2 : Claude génère le contenu)
  ├── execute-mission.sh        → appelle $PROJECT_DIR/.claude/run-mission.sh
  └── monitor-mission.sh        → surveille le log, notifie à chaque fin d'étape

PROJECT_DIR est propagé à tous les sous-scripts via le 3e argument.

Flux de notifications (stdout + Telegram simultanément)

Chaque notify_telegram() affiche aussi dans le terminal avec horodatage :

Moment Message
Prompt créé Mission creee : MISSION_ID - description
Exécution lancée Execution demarree : MISSION_ID
Monitor prêt Surveillance demarree : MISSION_ID
Fin de chaque étape Etape N terminee : description [MISSION_ID]
Étape finale atteinte Etape finale atteinte : MISSION_ID
Fin surveillance Surveillance terminee : MISSION_ID
Résultat final Mission terminee avec succes : MISSION_ID ou Mission echouee : MISSION_ID (exit N)

Phase 2 de create-mission-prompt.sh

Claude génère le vrai contenu de lePrompte.md (étapes réelles, commandes bash, contexte projet) avant l'exécution. Latence : ~15 min pour un gros projet.

Règle critique : la sortie de Claude doit aller vers stderr avec >&2, sinon elle pollue MISSION_ID=$(...) :

/home/kanmaber/.local/bin/claude \
  --print \
  --dangerously-skip-permissions \
  --add-dir "$PROJECT_DIR" \
  < "$BOOTSTRAP" >&2   # ← OBLIGATOIRE

Bug documenté et résolu (20260516)

Sans >&2, MISSION_ID capture tout le texte d'explication de Claude → noms de fichiers tronqués (File name too long), mission introuvable, échec immédiat.

Décision / Conclusion

L'architecture retenue est Option A : skill Hermes qui orchestre le routage et appelle run-mission.sh ou agit directement. Pas de daemon Telegram intermédiaire — le workflow est déclenché depuis Hermes WebUI ou Hermes Dashboard directement.

Le skill est générique : ne contient aucun chemin ni référence à un projet spécifique. La résolution du projet se fait dynamiquement à l'exécution.

Le run-mission.sh local est également réutilisable : les patches sont rétrocompatibles (le comportement local est inchangé grâce aux fallbacks ${VAR:-valeur_par_defaut}).

À faire

  • [ ] Tester /task-router --mode=claude-only --project=PsychoEnLigneV2 <description> depuis Hermes
  • [ ] Tester /task-router --mode=hermes-only --project=tekdhi-landing <description>
  • [ ] Tester orchestrate-mission.sh sur une vraie mission après le fix >&2 (20260516)
  • [x] Vérifier que le conteneur tourne avec les nouveaux volumes — confirmé
  • [x] Notifications Telegram dans run-mission.sh — implémentées (5 points)
  • [x] orchestrate-mission.sh — chemins absolus + notify_telegram + PROJECT_DIR propagé
  • [x] monitor-mission.sh — notifications Telegram + stdout à chaque checkpoint
  • [x] Bug MISSION_ID pollué par stdout Claude — corrigé avec >&2

Liens connexes