Aller au contenu

Espaces de travail et isolation

Un espace de travail est le locataire d’isolation de premier niveau dans Control Center. Il regroupe des agents, des dépôts, des espaces, des tickets, de la mémoire, des pipelines et des membres dans un contexte délimité. Tout ce qui se produit à l’intérieur d’un espace de travail y reste.

Un espace de travail typique correspond à un projet ou à une base de code. Vous pouvez avoir des espaces de travail distincts pour « api-service », « frontend-v2 » et « infrastructure ».

Les lignes d’un espace de travail résident dans le fichier SQLite propre à cet espace, à l’emplacement <dataDir>/<workspaceId>/workspace.db. Chaque espace de travail dispose d’un répertoire plutôt que d’un simple fichier, car un espace de travail accumule plus qu’une base de données — des arbres de travail par conversation, les fichiers d’agents et de skills, les identifiants de robots de chat — et lui donner un dossier signifie que tout cela vit et est supprimé ensemble.

Entité Portée
Agents Appartiennent à exactement un espace de travail
Dépôts Enregistrés dans un espace de travail (voir ci-dessous)
Espaces, conversations et messages Limités à l’espace de travail
Tickets, projets, plans, orchestrations Limités à l’espace de travail
Faits de mémoire, politiques, domaines, autorisations d’accès Limités à l’espace de travail
Exécutions de pipeline, modèles, déclencheurs Limités à l’espace de travail
État des pull requests, espaces de revue, cohortes de revue Limités à l’espace de travail
Journaux d’exécution des agents et mémoire de travail Limités à l’espace de travail
Membres, invitations, autorisations par dépôt Limités à l’espace de travail
Symboles et arêtes du graphe de code Limités par repoId dans le fichier de l’espace de travail

La plupart de ces lignes portent une colonne workspaceId ; ce sont les fichiers de base de données séparés qui font respecter l’isolation. Voir l’invariant d’isolation ci-dessous.

Les dépôts sont limités à l’espace de travail ; l’identité entre espaces de travail repose sur le chemin

Section intitulée « Les dépôts sont limités à l’espace de travail ; l’identité entre espaces de travail repose sur le chemin »

La ligne d’un dépôt vit dans le fichier de base de données propre à son espace de travail — le fichier est la portée, si bien que la table ne comporte ni colonne workspaceId ni table de jointure.

Vous ajoutez un dépôt à un espace de travail dans Paramètres → Espace de travail → Dépôts. Enregistrer la même copie de travail dans deux espaces de travail crée deux lignes indépendantes dotées de deux identifiants ; l’identité d’un dépôt d’un espace de travail à l’autre repose sur le chemin dans le système de fichiers (ou sur le couple propriétaire/nom GitHub), jamais sur l’identifiant.

L’enregistrement est strict : le chemin doit se trouver à l’intérieur d’un arbre de travail git, disposer d’un dépôt distant origin, et ce dépôt distant doit pointer vers une forge prise en charge (github.com, gitlab.com ou bitbucket.org). La forge est lue depuis le dépôt distant et stockée sur le dépôt, si bien qu’un même espace de travail peut contenir des dépôts issus des trois. Tout le reste est refusé. Le navigateur de dossiers dans lequel vous effectuez votre choix affiche le système de fichiers de l’hôte du serveur, limité à la liste d’autorisation --repo-roots du serveur (qui vaut par défaut le répertoire personnel de l’utilisateur du serveur).

Retirer un dépôt d’un espace de travail supprime la ligne purement et simplement et supprime en cascade ses symboles de code, ses arêtes, ses fichiers, ses points de contrôle d’index et ses autorisations par membre. La copie de travail sur le disque n’est pas touchée.

Le graphe de code vit dans le même fichier, indexé par repoId, car des espaces de travail différents peuvent contenir le même dépôt sur des branches différentes avec du code différent.

Les fuites de données entre espaces de travail sont traitées comme des bugs. L’isolation est structurelle et imposée à plusieurs niveaux :

  • Couche base de données. Chaque espace de travail possède son propre fichier SQLite, distribué par WorkspaceDatabaseManager.of(workspaceId). Une base de données d’espace de travail ne déclare ni users, ni workspaces, ni les tables d’aucun autre espace de travail : une lecture entre espaces de travail n’est donc pas un bug que vous pouvez écrire — elle ne compile pas. Les dépôts résolvent leur DAO à chaque appel plutôt que d’en mettre un en cache, et comme chaque méthode de dépôt exige un workspaceId, l’espace de travail ne peut jamais être déduit ni remplacé par une valeur par défaut.
  • L’identifiant d’espace de travail est un segment de chemin. Comme l’identifiant devient un nom de répertoire, il doit correspondre à un motif sûr à segment unique (début alphanumérique, puis alphanumériques, points, traits de soulignement, traits d’union ; pas de ..). Partout dans le produit, les identifiants sont des UUID. Toute autre valeur lève une exception au lieu d’être assainie — c’est là la garde contre la traversée de chemin, et elle échoue bruyamment plutôt que d’écrire les données d’un espace de travail dans un chemin choisi par quelqu’un d’autre.
  • Défense en profondeur. Les colonnes workspaceId sont toujours là et toujours écrites. Redondantes au sein d’un fichier, elles laissent inchangés les déclencheurs du flux de synchronisation, les index FTS et la forme existante des lignes, et elles rendent un fichier auto-descriptif lorsqu’on l’examine isolément — ce sur quoi reposent l’export et l’import.
  • Couche domaine. Les services qui modifient des entités par identifiant valident entity.workspaceId == workspaceId à un point de passage unique avant de poursuivre et lèvent WorkspaceMismatchException en cas de non-concordance — l’échec bruyant pour un identifiant arrivé du mauvais contexte. Refuser bruyamment importe : un no-op silencieux masque le bug, et poursuivre laisse fuiter des données.
  • Couche RPC. Le répartiteur RPC des dépôts est le point de passage. Chaque opération limitée à un espace de travail doit porter workspace_id dans ses propres arguments — il n’existe aucun « espace de travail courant » par session qui pourrait fuiter, et un identifiant inconnu est refusé comme introuvable avant que quoi que ce soit n’ouvre une base de données, si bien qu’un mauvais identifiant ne peut pas matérialiser un workspace.db fantôme. Les opérations réellement globales (le fil d’actualités, la file de la flotte) se déclarent sans portée, par une décision explicite et revue. Les outils MCP suivent la même règle : tout outil qui touche des données limitées à un espace de travail exige workspace_id.
  • La diffusion entre espaces de travail est énumérable. Répondre à une question portant sur plusieurs espaces de travail implique d’ouvrir plusieurs bases de données. Cette diffusion est confinée à un seul utilitaire, CrossWorkspaceQueries, de sorte que la liste complète de ce qui franchit légitimement la frontière tient dans les sites d’appel d’un seul fichier au lieu de se diluer derrière des commentaires de documentation. Ces sites d’appel sont : l’agrégation à l’échelle du serveur, les réconciliateurs de démarrage, les balayages de rétention et de GC, la sauvegarde, les routeurs d’événements — et la recherche d’appartenance ci-dessous.
  • L’accès par identifiant seul ne suffit pas. Retrouver une entité par son UUID ne prouve pas qu’elle appartient à l’espace de travail de l’appelant. La séparation des fichiers rend le mauvais fichier inatteignable ; la validation de la couche domaine intercepte un identifiant erroné qui arrive par un fichier légitime.

C’est l’appartenance, et non la clé d’appairage, qui accorde l’accès

Section intitulée « C’est l’appartenance, et non la clé d’appairage, qui accorde l’accès »

Détenir un identifiant d’appareil authentifie un appareil. Cela n’autorise rien à l’intérieur d’un espace de travail. Chaque opération limitée à un espace de travail résout le rôle d’appartenance de l’appelant et applique un plancher dérivé du type de l’opération — une lecture exige guest, une modification exige member, une opération destructive exige admin — et les opérations qui exposent le contenu d’un dépôt vérifient en plus l’autorisation par dépôt de l’appelant. Une clé d’appairage valide sans appartenance reçoit Not a member of this workspace.

La conséquence pour l’isolation est élégante : les lignes d’appartenance vivent dans le fichier propre à l’espace de travail, si bien que « qui se trouve dans cet espace de travail ? » est une lecture à portée ordinaire. La question inverse — « dans quels espaces de travail suis-je ? » — est par définition répartie sur plusieurs fichiers : c’est donc l’une des rares diffusions CrossWorkspaceQueries autorisées, et c’est ce que le sélecteur d’espace de travail exécute avant qu’un espace de travail ait été choisi.

Voir Multijoueur pour l’échelle des rôles et les autorisations par dépôt.

Tout n’est pas limité à l’espace de travail. Une seconde base de données, <dataDir>/global.db, contient ce qui est réellement global au serveur :

  • le registre des espaces de travail, pour que le sélecteur d’espaces puisse lister chaque espace de travail sans ouvrir le moindre fichier d’espace de travail — et pour que le nom, le logo, le propriétaire, l’ordre manuel et le marqueur de suppression logique d’un espace de travail vivent en dehors du fichier qu’ils décrivent ;
  • l’identité : les utilisateurs, les préférences utilisateur et les appareils appairés, car un humain correspond à un seul utilisateur dans tous les espaces de travail et un appareil appairé survit à la suppression d’un espace de travail ;
  • la file de la flotte (workers, tâches, journal de placement), dont l’ordonnanceur confronte la file entière à chaque worker à chaque cycle ;
  • le fil d’actualités, qui est propre à l’utilisateur (et non à l’espace de travail) — un fil ajouté vous suit dans tous les espaces de travail ;
  • workspace_routes, l’index d’avant-authentification qui répond à « quel espace de travail possède cette clé ? » pour les points d’entrée qui arrivent sans rien d’autre qu’un secret ou qu’un identifiant opaque (un hachage d’invitation, un jeton de webhook, un lien profond) et sans espace de travail. Une absence y donne un introuvable ; il n’existe délibérément aucun repli par balayage ;
  • les paramètres et l’identité à l’échelle de l’installation : l’identifiant de l’installation, les paramètres au niveau de l’hôte, les connexions SSO et les politiques d’action gérées (un verrou à l’échelle de l’installation qui ne peut que resserrer les garde-fous d’un espace de travail).

Le démarrage n’ouvre que ce fichier. Les fichiers d’espaces de travail s’ouvrent paresseusement au premier accès, si bien que le coût de démarrage reste constant quelle que soit l’histoire que les espaces de travail accumulent, et le quick_check de chaque fichier est payé à la première utilisation plutôt que sur le chemin menant à la bannière de disponibilité.

Un fichier d’espace de travail ne contient qu’une workspace_meta à une seule ligne, pour s’auto-identifier (son identifiant, l’installation qui l’a créé, la version de schéma avec laquelle il a été créé, et quand). Tout ce qui décrit un espace de travail se trouve dans le registre.

Créer un espace de travail ne se limite pas à insérer une ligne dans le registre. En une seule opération, le serveur inscrit l’utilisateur créateur comme ownerUserId, enregistre l’appartenance de cet utilisateur au rôle de propriétaire et publie un événement de domaine WorkspaceCreated. Les écouteurs de cet événement amorcent ensuite, de façon idempotente et en arrière-plan :

  • un agent CEO et quatre spécialistes — qa, architect, engineer, librarian — les spécialistes étant rattachés au CEO ;
  • les modèles de pipeline intégrés et leurs déclencheurs (les déclencheurs événementiels et manuels sont activés d’origine ; les déclencheurs planifiés sont à activer, sauf le balayage quotidien de GC des arbres de travail) ;
  • les suites d’évaluation de démarrage.

Cela se produit pour chaque espace de travail, pas seulement le premier. Les agents amorcés sont créés sans adaptateur : ils s’exécutent donc sur Control Center (built-in) avec le modèle par défaut d’Anthropic (claude-opus-4-8 sauf si vous en épinglez un autre) jusqu’à ce que vous définissiez un adaptateur et un modèle pour chaque agent dans Paramètres → Espace de travail → Agents. Voir Le modèle d’agent.

L’écriture de l’appartenance du propriétaire compte plus qu’il n’y paraît : sans elle, l’espace de travail tout juste créé n’aurait aucun membre et chaque appel limité à un espace de travail que ferait ensuite le créateur serait refusé.

Supprimer un espace de travail marque la ligne du registre et dissocie le répertoire. L’espace de travail disparaît de toutes les listes et de toutes les recherches. La ligne du registre subsiste (supprimée logiquement) afin que l’identifiant reste connu de la sauvegarde et de la maintenance, mais workspace.delete supprime immédiatement le répertoire de cet espace de travail — base de données, arbres de travail, fichiers d’agents et le reste — via WorkspaceDatabaseManager.dropAndClose. Seul le propriétaire de l’espace de travail peut le supprimer.

Parce qu’un espace de travail tient dans un seul fichier, transmettre un espace de travail est une opération sur fichier plutôt qu’un vidage table par table :

  • workspace.export se réduit à un seul VACUUM INTO — un instantané cohérent et défragmenté, pris sous une transaction de lecture pendant que le serveur continue de servir.
  • workspace.import adopte un tel fichier, en remplaçant tout ce que contient actuellement l’espace de travail cible. La table workspace_meta intégrée permet à un import de distinguer « mon propre fichier, ré-adopté » d’« un fichier venu d’une autre installation » (ce dernier cas est autorisé, mais journalisé plutôt qu’invisible).
  • server.backupNow capture l’installation entière dans un répertoire horodaté qui reflète le répertoire de données actif — manifest.json, global.db et un <workspaceId>/workspace.db par espace de travail — si bien que restaurer revient à le recopier.
  • server.listBackups relit ce répertoire, du plus récent au plus ancien, avec la taille et la liste des espaces de travail de chaque instantané. Un instantané dont le manifeste est absent ou qui nomme des fichiers inexistants est tout de même signalé, marqué comme incomplet — le masquer est la façon dont un opérateur finit par croire qu’il dispose d’une sauvegarde qu’il n’a pas.

Les opérations de sauvegarde se trouvent dans Paramètres → Serveur → Sauvegarde et restauration : Sauvegarder maintenant prend un instantané de l’installation, la liste indique quels instantanés sont complets et permet au propriétaire de l’installation d’en supprimer un après confirmation, et chaque espace de travail propose l’export et l’import. Le pipeline intégré Workspace backup ne prend que la base de données de cet espace de travail, selon une planification hebdomadaire, et supprime ses propres sauvegardes plus anciennes que l’âge de rétention configuré. Restaurer un espace de travail à partir d’un instantané de l’installation revient à pointer workspace.import sur le <workspaceId>/workspace.db de l’instantané ; restaurer une installation entière reste une recopie du répertoire d’instantanés, serveur arrêté.

Ce qu’aucune d’elles ne transporte, c’est le reste du répertoire de l’espace de travail. Un espace de travail accumule plus qu’une base de données — des images collées, des fichiers de skills et d’agents, des identifiants de chat, des arbres de travail — et l’export et l’import déplacent le fichier, si bien que tout ce qui l’entoure reste où il est. C’est délibéré pour les identifiants et jetable pour les arbres de travail ; pour le reste, c’est une limite à connaître plutôt qu’une fonctionnalité. La référence sur la sauvegarde contient le tableau complet.

Déplacer une sauvegarde entre le serveur et votre appareil

Section intitulée « Déplacer une sauvegarde entre le serveur et votre appareil »

Toutes les opérations ci-dessus s’expriment en chemins sur le serveur, ce qui n’est une réponse complète que lorsque le serveur est votre propre machine. Trois routes HTTP signées — /backup/workspace, /backup/snapshot et /backup/restore — transportent les octets pour toutes les autres topologies, sur la même voie authentifiée que celle déjà utilisée par le proxy multimédia et le magasin d’images. Dans la page, il s’agit d’un bouton Télécharger à côté de chaque export et de chaque instantané (vous choisissez où il atterrit), et de Choisir un fichier et l’envoyer pour restaurer à partir d’un fichier présent sur la machine que vous utilisez. C’est l’envoi qui fonctionne lorsque le serveur n’est pas votre machine : workspace.import exige que le fichier soit déjà sur l’hôte, et c’est ainsi qu’il y arrive.

Trois propriétés méritent d’être connues :

  • Un téléchargement fabrique une copie fraîche et le serveur n’en garde rien. Télécharger ne revient pas à « récupérer le fichier produit par le bouton d’export » — le serveur exporte de nouveau, diffuse le résultat, puis le supprime. La réponse est no-store avec Accept-Ranges: none pour la même raison : une plage reprise recollerait deux exports différents en un fichier qui paraît valide et ne l’est pas.
  • Un envoi est diffusé en flux vers le disque, jamais mis en mémoire tampon, aux deux extrémités, et la copie mise en attente est supprimée sur tous les chemins de sortie — y compris en cas de refus. Une base de données d’espace de travail n’est pas une capture d’écran.
  • Une connexion relayée n’a pas d’origine HTTP, c’est pourquoi les commandes de transfert y sont désactivées, avec une note qui en explique la raison. Tout ce qui s’exprime en chemins côté serveur continue de fonctionner via le relais ; seules les voies d’octets exigent une connexion directe.

Vous pouvez exécuter plusieurs espaces de travail côte à côte. Chacun fonctionne indépendamment : les agents d’un espace de travail ne peuvent pas envoyer de message aux agents d’un autre, ne peuvent pas lire la mémoire d’un autre espace de travail et ne peuvent pas voir ses tickets. La résolution des destinataires pour la messagerie d’agent à agent est exacte et ne franchit jamais un espace de travail.

Il n’existe aucun tableau de bord inter-espaces. La surface d’analyse à l’adresse /workspaces/:workspaceId/observability s’abonne à un flux global borné des exécutions récentes et le restreint immédiatement à l’espace de travail actif, si bien que ce que vous y voyez concerne un seul espace de travail à la fois. Les seules surfaces réellement multi-espaces qu’une personne côtoie sont le sélecteur d’espace de travail et le sélecteur d’espaces.

Une limite pratique à connaître : un espace de travail que quelqu’un utilise reste ouvert — il n’y a pas de LRU. Au-delà de 32 espaces de travail ouverts, le serveur avertit plutôt que de refuser, car chaque fichier ouvert retient un isolat d’arrière-plan et un cache de pages. La diffusion entre espaces de travail ouvre des fichiers de façon transitoire et les referme ensuite si rien d’autre ne les a réclamés.