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.mdavant toute modification de code - Utiliser
python3 open()pour écrire dans.claude/— jamais heredoc - Chemins absolus obligatoires
- Après modification TypeScript :
npx tsc --noEmitdoit retourner 0 - Fichier prompt mission : toujours
lePrompte.md(jamaismission.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>
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.md → lePrompte.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.shsur 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_IDpollué par stdout Claude — corrigé avec>&2
Liens connexes¶
- shared/architecture/hermes-workspace-migration — migration initiale hermes-telegram → Hermes Workspace
- shared/architecture/vps-ports-services — cartographie complète ports/services VPS
- shared/references/hermes-webui-guide — utilisation Hermes WebUI : sessions, skills, slash commands
- shared/references/modeles-primaires-fallback-hermes — modèles OpenRouter pour le mode hermes-only
- shared/references/guide-mise-en-place-nouveau-projet — templates pour reproduire le workflow sur un nouveau projet
- PsychoEnLigneV2/architecture/workflow-developpement-deploiement — workflow local : /cc Telegram vs session Claude Code