Les backends terminal de plugin ne restent plus coincés dans /root : default_cwd et supports_streams

Vous branchez un backend terminal de plugin dans Hermes — Cua Spaces, une VM QEMU, ou le service d’exécution interne de votre entreprise. Le plugin se charge, terminal.backend pointe dessus, et la première commande atterrit dans /root alors que le projet vit dans /home/acme/project. Vous demandez à l’agent de faire un cd jusque-là ; la commande suivante repart de /root. Curieusement, read_file résout quand même le chemin du projet. Vous tentez d’installer Bot Desktop dans ce sandbox : il refuse, sans raison claire.
Rien de tout cela n’était la faute de votre plugin. Trois hypothèses codées en dur dans le cœur produisaient ce résultat, et la PR #142225, mergée le 20 octobre 2026, les a transformées en options qu’un provider peut déclarer. Voici le mécanisme, l’API, et les deux pièges à garder en tête au moment de concevoir.
Les trois hypothèses codées en dur
Un : le répertoire de départ était /root, ou rien. Une table _DEFAULT_CWD_BY_BACKEND ne couvrait que ssh et vercel_sandbox. Tous les autres backends retombaient sur /root, et un plugin n’avait aucun moyen d’indiquer où il devait démarrer.
Deux : le garde-fou de conteneur jetait les chemins home du sandbox. Le répertoire de travail de chaque commande vient du cwd enregistré dans la session, à défaut de la valeur par défaut de la config (_resolve_command_cwd) — et execute() préfère cette valeur au env.cwd de l’environnement lui-même. Les commandes continuaient donc de partir dans /root, et quand vous faisiez un cd vers /home/acme/project, le garde-fou classait /home/... comme chemin hôte et le supprimait à la commande suivante. Les outils de fichier, eux, conservaient le chemin : d’où des commandes et des outils de fichier sur deux planètes différentes.
Trois : les backends compatibles streaming formaient un tuple figé. exec_prefix() dans tools/environments/streams.py ne reconnaissait que les classes d’environnement Docker, SSH et Singularity, et tools/bot_desktop/placement.py codait en dur _STREAM_BACKENDS = ("docker", "ssh", "singularity"). Un plugin ne pouvait rejoindre le lot qu’en modifiant le cœur pour y ajouter son nom.
Attribut un : default_cwd
Un provider ajoute une propriété en lecture seule. Retournez un chemin absolu à l’intérieur du sandbox, ou "~" pour signifier « le home propre à chaque sandbox » :
from agent.terminal_env_provider import TerminalEnvironmentProvider
class AcmeBoxProvider(TerminalEnvironmentProvider):
name = "acmebox"
is_container = True
@property
def default_cwd(self):
return "/home/acme" # known home: an absolute path is enough
Pour les sandboxes dont le home n’est connu qu’après la connexion — le cas normal d’un produit d’espaces hébergés — retournez "~" :
@property
def default_cwd(self):
return "~"
Hermes n’étend jamais ce tilde côté hôte. Il passe cwd="~" tel quel à create_environment(), et c’est votre environnement qui le résout vers le home qu’il a réellement obtenu :
class AcmeBoxEnvironment(BaseEnvironment):
def __init__(self, *, cwd, timeout, sandbox):
if cwd == "~":
cwd = sandbox.home() # e.g. /home/acme
super().__init__(cwd=cwd, timeout=timeout)
Hermes inscrit ensuite ce chemin résolu sur l’objet d’environnement : tout cd sous le home du sandbox survit aux commandes suivantes, et les outils de fichier ancrent leurs chemins relatifs au même endroit — la séparation entre le cwd des commandes et celui des outils de fichier disparaît.
Trois détails comptent en pratique. default_cwd sert quand terminal.cwd n’est pas défini, ou quand le garde-fou de conteneur écarte un terminal.cwd qui ressemble à un chemin hôte. Il est lu à chaque commande : gardez-le bon marché, sans appel réseau. Les chemins relatifs sont ignorés, car Hermes les ancrerait sur l’hôte.
Attribut deux : supports_streams et exec_prefix()
L’écran, computer_use et le navigateur suivent le terminal dans un sandbox en y lançant des processus de longue durée, stdin et stdout laissés ouverts. Docker, SSH et Singularity font cela depuis toujours. Un backend de plugin peut désormais s’y joindre :
- Mettez
supports_streams = Truesur le provider.bot_desktop.placement: autoconsidère alors votre backend comme une cible de placement valide, au lieu de refuser tous les backends de plugin. - Donnez à l’environnement une méthode
exec_prefix(*, user=None, interactive=True). Elle renvoie un argv local qui exécute ce qui le suit à l’intérieur du sandbox — la forme dedocker exec -i <container>— ouNonequand ce n’est pas possible sur le moment.
class AcmeBoxEnvironment:
def exec_prefix(self, *, user=None, interactive=True):
return ["acmebox", "exec", "--stdin", self.sandbox_id, "--"]
Trois conventions : Hermes ajoute bash -c <script> en mots séparés après votre argv, donc le client doit les transmettre sans les toucher et le sandbox doit disposer de bash ; retournez une liste, jamais une chaîne de commande unique ; user désigne le compte avec lequel exécuter, ignorez-le si votre backend ne sait pas changer d’utilisateur.
Le côté image a aussi ses exigences : Xvnc, Xfce, Chromium et cua-driver doivent être présents dans le sandbox — nousresearch/hermes-sandbox:desktop les embarque. Bot Desktop et le navigateur gardent leurs fichiers de travail sous get_temp_dir() de l’environnement.
Les deux attributs sont optionnels et désactivés par défaut. Un plugin qui n’en définit aucun se comporte exactement comme avant.
Deux pièges
Le mode isolation avale les deux fonctionnalités. exec_prefix() comme la résolution du home ~ lisent l’objet d’environnement lui-même : ils ne fonctionnent donc que pour les plugins chargés in-process. Sous plugins.isolation: host, l’environnement n’arrive à Hermes que comme un placeholder opaque : Bot Desktop refuse un backend supports_streams avec un message qui nomme le réglage, et default_cwd = "~" reste un tilde littéral. Si votre backend est censé héberger l’écran, ne faites pas tourner ce plugin en isolation.
Ne lui donnez pas de chemins hôte. default_cwd accepte un chemin absolu dans le sandbox ou "~". Passer un chemin hôte comme /Users/you/project ne lève pas d’erreur — la valeur est jugée inutilisable et on retombe sur le défaut. De même, le garde-fou ne conserve un cd que s’il aboutit sous le home que vous avez déclaré : un home de sandbox qui ressemble simplement à /home/... a donc toujours besoin d’un default_cwd.
Peut-on l’utiliser aujourd’hui ?
Le changement est sur main (mergé le 20 octobre 2026) mais dans aucune release : la dernière version publiée est la v0.22.2, antérieure à cette interface. Mettez à niveau au prochain patch, ou suivez main. Pour la vue d’ensemble, lisez le guide du plugin backend terminal ; les commandes d’installation, d’activation et d’isolation des plugins sont dans la référence hermes plugins ; et le travail sur le cycle de vie des plugins est traité dans le billet sur les hooks de cycle de vie.
En bref
Un backend terminal de plugin peut désormais répondre lui-même à deux questions : où démarrent les commandes (default_cwd, chemin absolu ou "~"), et si l’écran peut vivre à l’intérieur (supports_streams plus exec_prefix()). Les deux sont optionnels, avec des défauts inchangés — et le seul point à anticiper vraiment est le mode isolation, où les deux se dégradent en placeholder.