Voir aussi : Conformité VFS · Référence CLI · Retour à l'index
ADR-008 — Les chemins virtuels sont la seule monnaie versée aux agents
Statut : Accepté · Date : 2026-08-25
· Portée : Orkeon.Domain/FileSystem, Orkeon.Hosting, Orkeon.Host, Orkeon.Scripting.Cli, Orkeon.Studio.*
Contexte
La doctrine de système de fichiers d'Orkeon a été fixée tôt et tenue fermement : la règle Q3,
« chemins virtuels partout » — aucune API, propriété ou DTO ne porte de chemin physique hors de
FileSystemService lui-même — appuyée par l'analyseur Orkeon.Compliance.Vfs, par un MountInfo
délibérément sans BasePath, et par des tests vérifiant qu'un message de refus ne nomme jamais un
chemin disque. Quand un flag CLI a un jour accepté un chemin réel (--prebuild-index <real-path>),
il a été retiré plutôt que toléré : la codebase se monte via -m ../path:/src:ro.
La doctrine tenait à l'intérieur du framework. Elle ne tenait pas à la frontière où les montages se créent.
Le code du framework calcule des chemins physiques absolus avant que le VFS n'existe — le dossier
de définition du crew, le dossier --llm-log. Ces chemins doivent ensuite être lisibles à
travers le VFS. Le raccourci pris a été de les monter 1:1, physique égal virtuel :
{configDir}:{configDir}:ro # RunnerExecution
{llmLogPath}:{llmLogPath}:rw # RunnerExecution, RunCommand
{crewDir}:{crewDir}:ro # HostCrewMounts, par crew hébergé
pour que le chemin absolu déjà en main se résolve tel quel, « sans gymnastique de préfixe » comme
le disait le code. Pour que ces chaînes se parsent, FileSystemMount.IsValidVirtualPath avait été
élargi pour accepter un chemin de lecteur Windows comme chemin virtuel. La brèche était donc
dans le contrat du VFS lui-même, pas chez un appelant.
Les conséquences n'étaient pas théoriques. Un montage issu d'une mount-string reçoit
MountVisibility.AgentFacing — Parse n'a aucun moyen de dire autre chose — donc ces dossiers
étaient listés aux agents :
list_mountsretournaitC:\Users\…\mon-equipecomme chemin de montage ;- tout message de refus nomme les montages disponibles, et
FileSystemService.CollectBasePathsexemptait délibérément les montages identité de la rédaction (les rédiger aurait produitAvailable mounts: [REDACTED]), donc le garde-fou était désarmé pour exactement les montages qui en avaient besoin ; - la réécriture de chemins de
ShellCommandTooltravaillait sur la même liste ; - là où la table de montages est rendue dans un prompt système, un chemin disque absolu apparaissait sous la phrase « All file operations must use these virtual paths. Absolute or unmounted paths are not allowed. »
Studio reflétait ensuite tout cela fidèlement — y compris dans deux écrans que lit un novice :
les chips de dossiers du Composer et, pire, la ligne « Sur quel dossier » de l'éditeur d'agent,
qui joignait les mount-strings physique:virtuel:droits brutes sous un libellé parlant de ce que
cet agent peut voir.
Le raccourci n'a jamais eu de nécessité technique. Le chemin scripté montait déjà son entrée en
/script:ro, le banc de la forge monte /workspace, /forge et /output, et le chargeur de
crew est agnostique : il lit le chemin qu'on lui donne via IFileSystemService.
Décision
Un chemin physique n'est jamais un chemin virtuel. Concrètement :
Les runners montent sous un nom. Le dossier du crew est
/crew(un crew mono-fichier s'adresse en/crew/<fichier>.yaml), les crews d'un démon sont/crews,/crews-1, …, le dossier d'un script reste/script. Le chargeur reçoit l'orthographe virtuelle.IsValidVirtualPathest resserré au seul/…. Un chemin virtuel à lettre de lecteur est refusé avec un message qui nomme le remède. Le segment physique garde son traitement de lettre de lecteur — ce côté-là est légitimement un chemin disque.Les montages d'infrastructure sont invisibles.
Orkeon:FileSystem:InternalMountsporte les montages enregistrés enMountVisibility.Internal: résolubles par le VFS, absents deGetAvailableMounts()et donc delist_mounts, des tables de montages des prompts et des messages de refus — un message de refus est lu par le LLM, doncFileSystemRegistryconstruit ses listes « Available mounts » et « Mounts granting … » à partir de l'ensemble agent, pas de tous les montages. Le journal d'échanges LLM y vit — le VFS doit l'atteindre, aucun agent n'a à l'adresser. C'est une clé de configuration plutôt qu'un service hébergé parce que les runners ne démarrent jamais l'hôte : unIHostedServicene se déclencherait jamais sous--validateou--list-tools.Internalest une frontière, et il a fallu un second accesseur pour que ce soit vrai.Parsen'avait aucun moyen de dire « caché », si bien que la première version de cette décision retirait seulement le montage de toutes les listes : il restait résoluble, etIFileSystemServicene sait pas qui l'appelle. Les noms sont documentés —/llm-logscontient chaque prompt et chaque réponse de l'exécution — donc un seulfile_read /llm-logs/llm-exchanges-….jsonlremettait tout l'historique des échanges à un agent.FileSystemRegistry.ResolveAndCheckRightsrefuse désormais un montage Internal par défaut, dans les deux sens (ToVirtualPathne le nomme pas davantage, pour qu'une réécriture physique→virtuel d'un outil ne le fuite pas), et le signale exactement comme un chemin inexistant. Les deux composants qui écrivent légitimement sous une racine interne — le journal d'échanges et les bacs à sable de code — demandentPrivilegedFileSystemAccesspar son nom : un enregistrement DI distinct plutôt qu'un drapeau sur l'interface que tout le monde tient déjà, de sorte qu'un outil ne peut pas l'obtenir par accident et qu'un relecteur trouve chaque détenteur en cherchant le type.La règle des écrans est délimitée, pas absolue. Aucun chemin physique dans un contexte agent ou novice. Les surfaces expertes — la table des montages effectifs, l'aperçu de mount-string du sélecteur — continuent d'afficher les chaînes réelles : leur raison d'être est d'énoncer la ligne de commande exacte.
Un
--mountutilisateur revendiquant une racine réservée est refusé avec une ligne actionnable, au lieu de remonter en exception « chemins virtuels dupliqués » depuis une fabrique DI.
La dérogation de rédaction survit pour le seul cas restant : un montage Unix écrit pareil des deux
côtés (/output:/output:rw, la convention conteneur). Elle ne couvre plus l'injection du runner.
Conséquences
- Un agent ne peut plus apprendre l'organisation disque de l'opérateur via le VFS, et les messages de refus sont rédigés sans condition pour tout dossier injecté par le runner.
- Le journal d'échanges cesse d'être annoncé aux agents comme un montage inscriptible — il contient les prompts complets et les charges utiles des API.
- Rupture :
--mountn'accepte plus de chemin virtuel à lettre de lecteur ; le dossier du crew est/crewet les crews hébergés/crews*. Rien dans le dépôt ne reposait sur l'ancienne orthographe, et aucun crew publié ne le peut : les mount-strings sont fournies par lancement, jamais stockées dans un crew. - Activer
--llm-logne décale plusOrkeon:FileSystem:Mounts:{i}, le journal ayant sa propre clé. La prédiction d'index de Studio se simplifie et reste vraie. - Studio duplique les trois racines virtuelles (il ne peut pas référencer
Orkeon.Hosting) ; un test de dérive les épingle surRunnerMounts.
Ce que cet ADR ne tranche délibérément pas
Un crew ne peut toujours pas lier les dossiers dont il a besoin. Depuis VFS-90 il les
nomme : un bloc mounts: (/output, ou <ulid>|/output pour épingler une entrée des settings
quand plusieurs déclarent cette racine) liste les racines virtuelles que le crew utilise, sur le
modèle du bloc links: existant, et les runners le résolvent face aux settings avant tout host —
une racine que rien ne fournit, ou un identifiant qu'aucune entrée ne porte, est refusé en une
ligne plutôt qu'au premier appel d'outil. Le bloc sélectionne et valide ; le liage
virtuel→physique lui-même reste fourni depuis l'extérieur de l'artefact portable — par une entrée
des settings (désormais dotée d'une identité), par un argument --mount, ou par le sidecar de
Studio. C'est ce qui referme le trou qui permettait de lancer une équipe promue sans aucun
/output alors que ses propres tâches déclaraient deliverable: /output/… : forge promote écrit
le bloc, et un orkeon run crew/ nu refuse au lieu d'écrire nulle part.
Un bloc filesystem: qui lierait des dossiers depuis le crew — des chemins physiques dans
l'artefact portable — reste hors périmètre, et délibérément : le dossier derrière un nom est
l'affaire de la machine, ce qui est tout le propos de cet ADR.
L'appelant privilégié que cet ADR laissait ouvert a depuis été construit :
PrivilegedFileSystemAccess est ce second accesseur, et MountVisibility.Internal refuse
désormais un agent qui adresse le montage par son nom au lieu de simplement l'omettre des
listages. Ce qui reste hors périmètre, c'est une granularité plus fine qu'un bit — des droits
par appelant, une capacité remise à un outil précis — qui toucherait le contrat du Domain bien
plus profondément qu'un booléen sur le registre.