Aller au contenu principal

Référence des variables d'environnement

Variables de déploiement, valeurs par défaut, secrets et services concernés par chaque réglage.

67 min de lecture

Cette référence indique les variables de déploiement, leurs valeurs par défaut et les processus concernés. Le fichier .env du projet est une source possible ; l’environnement des conteneurs et un gestionnaire de secrets peuvent aussi fournir les valeurs. Le fichier d’exemple contient la configuration correspondante.

Après une modification, recrée les services concernés avec ta procédure de déploiement. docker compose restart conserve l’environnement existant. Les fichiers de configuration d’organisation ont un cycle distinct.

Comment lire cette page

Les tableaux indiquent le nom, la valeur par défaut et le rôle de chaque variable. Les valeurs requises doivent atteindre le service concerné ; certaines sont générées par le déploiement. Les variables facultatives peuvent rester absentes. Une valeur par défaut peut venir de Compose plutôt que du processus lui-même.

Consulte aussi les commentaires du fichier d’exemple et vérifie l’environnement effectif du service lorsqu’une valeur manque.

Identité de domaine (obligatoire au premier boot)

NomDéfautDescription
HOSTlocalhostObligatoire. Nom d'hôte sans protocole. Utilisé pour le réseau Docker et le mail sortant.
SITE_URLhttps://localhostObligatoire. URL canonique complète incluant le schéma et tout port non standard. Les callbacks d'auth l'utilisent.
ADDITIONAL_SITE_URLSnon définiOptionnel. Autres origines sur lesquelles le même déploiement répond, séparées par des virgules ou des espaces (ex. https://a.example,https://b.example). Chacune est une entrée complète. Voir TLS et domaines.
TOTP_CLIENT_NAMEnon définiClient que nomment les nouvelles entrées de l’application d’authentification et les codes de secours téléchargés, lu par platform, backend-api et le rôle all : 1–40 lettres, chiffres, espaces ou & ' . + -. Les espaces aux extrémités sont supprimés. Avec Acme, les entrées portent le nom Acme Tale Platform ; sans valeur ou avec Tale, elles gardent Tale Platform. Un nom invalide empêche le backend de démarrer.
TOTP_ENVIRONMENTnon définiEnvironnement que nomment les nouvelles entrées de l’application d’authentification et les codes de secours téléchargés, lu par platform, backend-api et le rôle all : 1–32 lettres, chiffres, tirets bas ou traits d’union. Les espaces aux extrémités sont supprimés et les lettres passent en majuscules. pr ou une valeur absente n’ajoute aucun environnement ; avec te, Acme Tale Platform devient Acme Tale Platform TE, et les codes de secours se téléchargent sous le nom acme-tale-platform-te-backup-codes.txt. Une valeur invalide empêche le backend de démarrer.
BASE_PATHnon définiOptionnel. Préfixe de chemin pour les déploiements en sous-chemin derrière un reverse proxy (ex. /app). Laisse vide pour la racine.
DOCS_URLhttps://docs.<HOST>Origine publique de l’hôte de documentation distinct dans le proxy. Le service docs doit aussi faire partie du déploiement.

SITE_URL désigne l’origine publique canonique. Protocole, hôte et port doivent correspondre à l’adresse du navigateur et aux callbacks enregistrés. BASE_PATH ajoute le préfixe de chemin ; le proxy normalise une barre oblique finale. Les adresses supplémentaires sont des origines sans chemin dans ADDITIONAL_SITE_URLS. Une origine supplémentaire invalide empêche le backend de démarrer.

La documentation utilise sa propre origine. Sur l’origine de la plateforme, /docs ouvre la référence API interactive et /openapi.json fournit son schéma. DOCS_URL change l’hôte de documentation du proxy ; cette variable n’installe pas le service docs et ne réécrit pas les liens des clients déjà compilés. TALE_DOCS_URL dans les outils de compilation SEO et le préfixe DOCS_BASE_URL du service docs, utilisé à la compilation comme à l’exécution, sont des réglages distincts.

Une modification de TOTP_CLIENT_NAME ou de TOTP_ENVIRONMENT s’applique aux nouveaux codes QR et URI de configuration, y compris lorsqu’un secret existant est affiché à nouveau, ainsi qu’aux codes de secours téléchargés ensuite. Elle ne change ni les secrets ni les noms des entrées déjà enregistrées sur ton appareil ; tu peux renommer ces entrées dans ton application d’authentification.

TLS

NomDéfautDescription
TLS_MODEselfsignedUn de selfsigned, letsencrypt, external. Voir TLS et domaines.
TLS_EMAILnon définiE-mail de contact pour les notifications Let's Encrypt. Optionnel mais recommandé en production.
TRUSTED_PROXIESprivate_rangesAvec TLS_MODE=external, les adresses dont le proxy accepte les en-têtes transférés : plages CIDR séparées par des espaces, ou private_ranges. Les autres modes l’ignorent. Voir TLS et domaines.

selfsigned crée un certificat Caddy local. Fais confiance à sa CA uniquement pour ta propre installation contrôlée. letsencrypt exige un domaine public et les ports 80/443 accessibles. Avec external, Caddy sert HTTP derrière un proxy TLS.

Secrets de sécurité (obligatoire)

NomDéfautDescription
BETTER_AUTH_SECRETvaleur d'exemple dans le fichierSecret d’authentification commun aux réplicas backend. Génère une valeur aléatoire robuste, par exemple avec openssl rand -base64 32, et garde-la stable. Un changement peut invalider les sessions et les connexions en cours.
ENCRYPTION_SECRET_HEXvaleur d'exemple dans le fichierRacine de chiffrement de 32 octets en hexadécimal ; génère-la avec openssl rand -hex 32. Conserve la valeur correspondant aux secrets existants. La remplacer ne migre pas les données chiffrées : rétablis la bonne clé ou saisis les secrets concernés par leur parcours prévu.
INSTANCE_SECRETvaleur d'exemple dans le fichierObligatoire. Le secret racine de l’instance : 64 caractères hex, généré par tale init (à la main : openssl rand -hex 32). Au démarrage, Tale en dérive la clé HMAC des mots de passe d’app WebDAV (WEBDAV_APP_PASSWORD_HMAC_KEY) sauf si tu définis cette clé toi-même, et les jetons éphémères avec lesquels les sessions sandbox récupèrent des blobs sont signés par une sous-clé de la même dérivation. Garde-le stable entre les déploiements : une rotation re-dérive la clé et invalide chaque mot de passe d’app WebDAV.
SANDBOX_TOKENvaleur d'exemple dans le fichierObligatoire. Secret HMAC partagé entre le backend et le spawner sandbox : le backend signe chaque appel au spawner avec, et le spawner rejette les appels non signés. Sans lui, le spawner refuse de démarrer — il tient le socket docker de l’hôte, il n’a donc pas de mode non signé. tale init et bun run dev le génèrent ; une stack que tu composes toi-même le pose avant le premier boot (openssl rand -hex 32). Une rotation veut dire redémarrer le backend et le spawner ensemble — ils doivent s’accorder.
SANDBOX_LLM_GATEWAY_ADMIN_PASSWORDnon définiRequis pour les appels d’environnement d’agent sandbox. Le backend utilise cet identifiant d’administration pour créer les clés de session. Donne la même valeur au gateway : tant qu’il n’a pas de compte d’administration, il n’en crée un que pour un appelant qui présente ce secret, et c’est ainsi que le backend prend la main sur le gateway à la première utilisation. L’image du gateway prend toujours cette valeur comme jeton de configuration (BIFROST_SETUP_TOKEN), à la place de celui qu’on aurait donné au conteneur. Le gateway conserve ensuite une empreinte du mot de passe dans llm-gateway-data. Garde le secret correspondant ou utilise la procédure de récupération/rotation prise en charge par le gateway. Ne supprime pas son état comme réparation habituelle. Le nom par défaut est admin (SANDBOX_LLM_GATEWAY_ADMIN_USERNAME).

Remplace les valeurs livrées dans .env.example avant d'exposer l'instance — ce sont des espaces réservés volontairement non sûrs.

Base de données

Tale utilise tale_app pour les données applicatives et tale_knowledge pour les fragments, embeddings et pages web. Le déploiement standard conserve les deux dans le service Postgres db, avec l’alias knowledge-db. Tu peux configurer des connexions externes séparément.

NomDéfautDescription
DB_PASSWORDtale_password_change_meRequis pour Postgres fourni. Mot de passe commun aux bases applicative et documentaire dans le déploiement standard. Remplace l’exemple avant la production.
DATABASE_URLconstruit depuis DB_PASSWORDOptionnel. URL de connexion de la base opérationnelle. Pose-la pour diriger le backend vers un Postgres que tu exploites toi-même : il n’a besoin d’aucune extension ni d’un superutilisateur, seulement d’une base et d’un rôle qui peut créer des schémas. Relue à chaque démarrage.
DATABASE_POOL_MAX10Optionnel. Nombre maximal de connexions par pool de la base opérationnelle. Chaque réplique de backend-api ou backend-worker utilise normalement jusqu’au double de cette valeur (pool applicatif et file de jobs). Les opérations sur les sandboxes peuvent temporairement ouvrir une connexion supplémentaire par place occupée du pool applicatif, soit un pic égal au triple de cette valeur par réplique. Compare ce pic au max_connections de la base.
AUTH_DATABASE_POOL_MAX5Optionnel. Connexions du pool par lequel chaque réplique de backend-api résout les sessions de connexion ; chaque requête authentifiée lit par ce pool. Augmente-le sur les répliques qui servent des milliers de requêtes par seconde, et compte-le par réplique dans le max_connections de la base, à côté de DATABASE_POOL_MAX.
POSTGRES_CA_FILEnon définiOptionnel. Chemin d’un bundle PEM auquel toutes les connexions Postgres font confiance : la base opérationnelle, le corpus de connaissances et les bases que les organisations apportent elles-mêmes. Nécessaire dès qu’une URL demande sslmode=verify-ca ou verify-full face à un fournisseur dont la racine n’est pas livrée avec Node — Amazon RDS est le cas courant. Si tes bases utilisent des fournisseurs différents, concatène leurs racines dans un seul fichier.
KNOWLEDGE_DATABASE_URLpostgresql://tale:${DB_PASSWORD}@knowledge-db:5432/tale_knowledgeURL de connexion du corpus documentaire par défaut. La modifier sélectionne une autre base, sans migrer les fragments ni les vecteurs existants.
KNOWLEDGE_DB_POOL_MAX10Optionnel. Connexions qu’un processus backend ouvre vers le corpus documentaire. Chaque job d’indexation en occupe une pendant qu’il écrit une tranche de fragments ; si WORKER_CONCURRENCY autorise plus de jobs simultanés que cela, ils attendent le pool : augmente les deux ensemble. Comme DATABASE_POOL_MAX, cette valeur compte par réplique dans le max_connections de la base du corpus.
KNOWLEDGE_DB_NAMEtale_knowledgeNom de la base documentaire créée par l’initialisation du service de base fourni.
KNOWLEDGE_INDEX_REPAIR_INLINE_MAX_BYTES1073741824Optionnel. Taille maximale (en octets) d'un index de recherche BM25 que le backend reconstruit de façon synchrone au démarrage quand il le trouve corrompu ; au-delà, un job d'arrière-plan le reconstruit pendant que les écritures vers ce corpus sont refusées. Voir Architecture des conteneurs.
KNOWLEDGE_INDEX_REPAIR_DISABLEDnon défini1 ou true désactive la vérification et la réparation BM25 au démarrage. Cela ne corrige aucune corruption ; les échecs de lecture ou d’écriture nécessitent une enquête et une réparation contrôlée.
KNOWLEDGE_CRAWL_DOCUMENT_MAX_BYTES104857600Optionnel. Taille maximale (en octets) d’un document (PDF, DOCX, XLSX, PPTX, ODT) que le crawler de sites télécharge et lit ; au-delà, la page est notée comme trop volumineuse. Les pages HTML ont leur propre budget de 2 Mio. Chaque récupération reste en mémoire pendant l’extraction du texte, et jusqu’à cinq sites sont analysés en même temps : dimensionne la mémoire du backend en conséquence. Une valeur inférieure à 1 Mio, ou qui n’est pas un entier, retombe sur la valeur par défaut.

L’URL générée est postgresql://tale:${DB_PASSWORD}@db:5432/tale_app ; APP_DB_NAME change le nom de base. Le corpus utilise les schémas private_knowledge et public_web. Une organisation peut choisir ses propres connexions sous Paramètres > Résidence des données. Enregistrer une autre URL ne migre aucune donnée. Consulte Résidence des données.

Deux points à connaître avant de diriger l’une des deux bases vers ta propre infrastructure :

  • Le corpus de connaissances exige pgvector déjà installé. Tale crée lui-même ses schémas et ses tables sur une base vide, mais n’installe jamais d’extension — la table des chunks porte une colonne vector, donc CREATE EXTENSION vector; doit avoir été lancé sur la base cible. Le pg_search de ParadeDB reste optionnel : sans lui la recherche retombe sur le vectoriel seul au lieu d’échouer. La base opérationnelle, elle, n’a besoin d’aucune extension.
  • Utilise une connexion Postgres directe ou compatible avec les sessions. Notifications de tâches, verrous de migration et requêtes préparées dépendent de ces propriétés. Vérifie tout proxy de connexion géré selon ces exigences.

Store d'objets

Les fichiers et médias utilisent un stockage compatible S3. Ces variables règlent la connexion par défaut. Une connexion propre à l’organisation est prioritaire ; une panne du stockage par défaut ne signifie pas que les buckets de toutes les organisations sont inaccessibles.

NomDéfautDescription
OBJECT_STORE_ACCESS_KEYnon définiClé d’accès de la connexion S3 par défaut. Pour MinIO fourni, configure la même valeur dans MINIO_ROOT_USER. Sans identifiants, aucune connexion par défaut n’est créée ; une organisation peut néanmoins avoir sa propre connexion valide.
OBJECT_STORE_SECRET_KEYauto-généré par tale initClé secrète du stockage par défaut. Pour MinIO fourni, elle doit correspondre à MINIO_ROOT_PASSWORD. Coordonne la rotation du stockage et du backend, puis teste lecture et écriture. Changer le mot de passe ne déplace pas les fichiers et ne les rend pas intrinsèquement orphelins.
OBJECT_STORE_BUCKETtale-blobsBucket où vivent les blobs. Le backend le crée s’il est absent et que la clé y est autorisée ; s’il existe déjà, il l’utilise tel quel.
OBJECT_STORE_ENDPOINThttp://object-store:9000 dans le compose livréEndpoint du stockage vu par le backend. AWS S3 n’utilise pas d’endpoint personnalisé ; retire ou vide explicitement celui du stockage fourni dans l’environnement Compose effectif. MinIO, R2 ou un autre service compatible utilisent une URL dédiée.
OBJECT_STORE_REGIONus-east-1Région de signature. Signifiante sur AWS ; arbitraire mais exigée par le signataire pour un store auto-hébergé.
OBJECT_STORE_FORCE_PATH_STYLEtrue avec endpoint, false sansAdresse le bucket en endpoint/bucket/key plutôt qu’en bucket.endpoint/key. Le défaut suit l’endpoint et convient donc aux deux cas ; ne le pose que pour un store qui déroge à sa propre forme.
OBJECT_STORE_PREFIXnon définiPréfixe de clés dans le bucket, pour que les blobs de Tale partagent un bucket avec d’autres données. Vide signifie la racine du bucket.
OBJECT_STORE_PUBLIC_ENDPOINT${SITE_URL} (posé par la CLI)Où le navigateur atteint le store. Le proxy publie le store embarqué sous /<bucket>/* et transfère les URLs présignées telles quelles, donc téléversements et téléchargements se font directement navigateur↔store. Si ce point d’accès est l’une des origines du déploiement, un lien destiné à un navigateur sur une autre origine configurée est signé pour cette origine. Laisse-le vide pour un bucket que le navigateur atteint déjà.

Le proxy fourni expose la route des objets au navigateur sans publier le port d’administration du stockage. Un stockage externe peut utiliser son propre endpoint public.

Comment ces variables arrivent dans le déploiement en cours

Au démarrage, le backend rapproche default/object-storage/connection.json de son environnement. Recrée backend-api et backend-worker avec les nouvelles valeurs. Les messages de démarrage distinguent les résultats suivants :

LigneSignification
object store (seeded)il n’y avait aucune connexion ; une a été écrite depuis l’environnement
object store (reconciled)l’environnement a changé ; la connexion a été mise à jour
object store (adopted)une connexion écrite par une version antérieure a été reconnue et est désormais suivie
object store (ignored)la connexion porte "managedBy": "operator", ces variables ne font donc rien
object store (skipped)Aucune paire d’identifiants fournie pour créer la connexion par défaut. Vérifie si une connexion existante ou propre à l’organisation reste utilisable.
(rien)déjà aligné — l’état normal

Si tu préfères gérer le store à la main, mets "managedBy": "operator" dans connection.json : le backend ne touchera plus jamais ce fichier. Un fichier sans managedBy du tout — écrit avant ce comportement — n’est repris que s’il nomme encore le même bucket sur le même endpoint que l’environnement ; si tu l’avais repointé à la main, ta modification reste.

Droits sur le bucket : le backend vérifie l’existence du bucket avec HeadBucket et ne le crée que s’il est absent. Une clé qui peut lire, écrire et supprimer des objets sans pouvoir créer de bucket suffit donc, à condition que tu crées le bucket toi-même. Les téléversements et téléchargements présignés passent par le navigateur : un bucket externe a donc aussi besoin d’une politique CORS autorisant l’origine de ton déploiement en GET, PUT et HEAD — voir Résidence des données.

Confidentialité du journal d'audit

Un pepper pseudonymise les données personnelles enregistrées lors des connexions échouées. Les versions précédentes généraient aussi TALE_AUDIT_SIGNING_KEY et TALE_AUDIT_SIGNING_KEY_PREVIOUS. Plus rien ne les lit : tu peux conserver ou supprimer une valeur existante.

NomDéfautDescription
TALE_AUDIT_PEPPERauto-généré par tale initAu moins 16 caractères pour pseudonymiser les échecs de connexion : HMAC-SHA256 de l’e-mail et de l’adresse IP tronquée. Sans valeur, ces champs d’audit restent en clair et le backend avertit. La rotation empêche de corréler les nouveaux identifiants avec les anciens ; la conservation suit la politique appliquée de l’organisation.

Voir Intégrité du journal d'audit pour le modèle de vérification.

Observabilité

NomDéfautDescription
SENTRY_DSNnon définiDSN Sentry pour le suivi d'erreurs. Laisse vide pour désactiver. Compatible avec GlitchTip et Bugsink auto-hébergés.
SENTRY_ENVIRONMENTnon définiLibellé commun aux erreurs du navigateur, du backend et de la sandbox. Les déploiements gérés utilisent leur name existant par défaut ; une référence d’environnement explicite peut définir un libellé canonique sans renommer le déploiement.
SENTRY_TRACES_SAMPLE_RATEnon définiTaux d’échantillonnage facultatif des traces du navigateur (0.0–1.0), indépendant de celui du backend.
BACKEND_SENTRY_TRACES_SAMPLE_RATE0Taux d’échantillonnage des spans HTTP et des tâches du backend (0.0–1.0). Nécessite SENTRY_DSN et une destination acceptant les transactions Sentry ; 0 désactive les spans.
METRICS_BEARER_TOKENnon définiJeton Bearer pour les routes /metrics/* du proxy. Sans jeton configuré, elles répondent 401. Restreins séparément l’accès réseau aux endpoints internes des processus.
UMAMI_URLnon définiOrigine HTTPS de la passerelle de collecte authentifiée. HTTP est accepté uniquement pour les tests locaux sur localhost, 127.0.0.1 ou [::1]. Exige un identifiant de site et un jeton valides ; aucun chemin, paramètre de recherche ni identifiant de connexion dans l’URL.
UMAMI_WEBSITE_IDnon définiUUID du site Umami. Une valeur absente ou invalide désactive la collecte. Utilise un identifiant distinct par déploiement.
UMAMI_PROXY_TOKENnon définiJeton Bearer de la passerelle, réservé au serveur : 16 à 256 lettres ASCII, chiffres ou caractères de ._~-. Ne l’injecte jamais dans la configuration du navigateur.

Définir METRICS_BEARER_TOKEN expose les endpoints de métriques derrière le token : /metrics/platform, /metrics/backend (les métriques du backend applicatif) et /metrics/sla-rules. Voir Configuration d'observabilité pour la configuration de scrape.

Chiffrement des secrets de fournisseur

SOPS protège les fichiers de secrets de configuration compatibles. Les identifiants actuels des fournisseurs en base utilisent ENCRYPTION_SECRET_HEX, décrit avec les secrets de sécurité plus haut.

NomDéfautDescription
SOPS_AGE_KEYnon définiUne clé privée age directe. Prime sur le fichier de clés.
SOPS_AGE_KEY_FILEnon définiChemin accessible au processus, avec une ou plusieurs clés privées age, une par ligne. Monte le fichier dans chaque conteneur concerné.

Sans clé age, le module SOPS écrit les fichiers compatibles en clair avec les permissions 0600. Les fichiers déjà chiffrés exigent toujours leur clé. Lis Secrets avec SOPS avant de modifier ces variables.

Les identifiants de fournisseurs peuvent référencer une variable préfixée par TALE_PROVIDER_KEY_ (40 caractères maximum). Les courtiers d’abonnement utilisent le préfixe distinct TALE_TOKEN_SOURCE_ (60 maximum). Ces champs contiennent des noms de variables, pas les secrets. Injecte les valeurs dans les processus backend et recrée les conteneurs concernés après modification. Fournisseurs décrit ce fonctionnement.

Applications OAuth des connectors

Les connectors OAuth (Gmail, Google Drive, Outlook, Teams, Slack, …) résolvent leur application fournisseur d’abord par organisation : une app configurée sous Paramètres > Connectors > Apps OAuth gagne pour cette organisation. L’environnement fournit la valeur par défaut du déploiement en dessous (et reste la seule source pour Slack, dont la vérification de signature des événements s’exécute avant qu’aucune organisation ne soit connue). Pour chaque slug de connector :

NomDéfautDescription
CONNECTOR_OAUTH_<SLUG>_CLIENT_IDunsetIdentifiant client OAuth pour ce connector. Slug en majuscules, tirets remplacés par des tirets bas (gmail → GMAIL).
CONNECTOR_OAUTH_<SLUG>_CLIENT_SECRETunsetSecret client correspondant.
CONNECTOR_SLACK_SIGNING_SECRETunsetLe secret de signature de l’app Slack. L’endpoint d’événements entrants vérifie chaque livraison avec lui et renvoie 503 tant qu’il manque.

Enregistre ${SITE_URL}${BASE_PATH}/api/connectors/oauth2/callback sur l’application fournisseur, et pour Slack aussi ${SITE_URL}${BASE_PATH}/api/connectors/slack/events comme Events Request URL. Détails : Connectors (développement).

Import cloud Knowledge (Documents)

Les autorisations OneDrive / Google Drive par utilisateur pour Connaissances → Documents sont distinctes des connectors d’organisation et de la connexion. Ici aussi, une app d’organisation configurée sous Paramètres > Connectors > Apps OAuth prime — l’entrée google-drive est partagée avec la voie connector, et OneDrive / SharePoint (import de connaissances) a sa propre entrée ; les chaînes ci-dessous s’appliquent partout où l’organisation n’en a pas configuré. Enregistre cette URI de redirection sur l’app Microsoft (ou Google) :

${SITE_URL}${BASE_PATH}/api/cloud-import/oauth2/callback

Résolution des identifiants pour OneDrive (premier match gagne) :

NomDescription
CLOUD_IMPORT_MICROSOFT_CLIENT_ID / _SECRETApp dédiée à l’import Knowledge (préférée).
CLOUD_IMPORT_MICROSOFT_TENANT_IDID d’annuaire (tenant) de cette app.
AUTH_MICROSOFT_ENTRA_ID_ID / _SECRETApp de connexion Microsoft.
AUTH_MICROSOFT_ENTRA_ID_TENANT_IDID d’annuaire (tenant) de l’app de connexion.

Les inscriptions d’app Entra mono-tenant exigent une URL d’autorisation propre au tenant — /common échoue avec AADSTS50194. Définis l’ID de tenant (ou organizations / common pour une app multi-tenant). S’il est absent, Tale reprend le tenant de l’issuer SSO Entra de l’organisation s’il est configuré.

L’écran de consentement Microsoft demande Graph Files.Read et Sites.Read.All (liste/téléchargement OneDrive et SharePoint), User.Read (libellé du compte) et offline_access (jeton de rafraîchissement pour la sync). Cette autorisation est intentionnelle et par utilisateur — elle n’est pas attachée à la connexion à Tale.

Google Drive utilise uniquement une app dédiée (pas de repli sur l’app de connexion) :

NomDescription
CLOUD_IMPORT_GOOGLE_DRIVE_CLIENT_ID / _SECRETApp d’import Google Drive Knowledge.

Enregistre la même URI de callback cloud-import sur le client OAuth Google. Le consentement demande drive.readonly et userinfo.email.

Drapeaux de fonctionnalité

Ces variables règlent l’authentification backend, les événements de fichiers et les droits opérateur. Recrée les rôles backend concernés après un changement d’environnement. Modifier seulement le conteneur web ne suffit pas.

NomDéfautDescription
TRUSTED_SECRET_HEADERRemote-Internal-SecretNom de l'en-tête de requête qui porte la clé trusted headers de l'organisation sur la requête de passage.
TRUSTED_EMAIL_HEADERRemote-EmailNom de l'en-tête de requête qui porte l'e-mail de l'utilisateur — l'identité pour laquelle la session est émise.
TRUSTED_NAME_HEADERRemote-NameNom de l'en-tête de requête qui porte le nom affiché. S'il manque, Tale prend la partie locale de l'e-mail.
TRUSTED_ROLE_HEADERRemote-RoleNom de l'en-tête de requête qui porte le rôle d'organisation avec lequel la session agit, plafonné au plafond de l'organisation (member si l'en-tête manque).
TRUSTED_TEAMS_HEADERRemote-TeamsNom de l'en-tête de requête qui porte les appartenances aux équipes, en noms d'équipe séparés par des virgules (les entrées id:name sont aussi acceptées). Absent, les équipes ne bougent pas ; présent, la liste du proxy fait foi pour les appartenances qu'il a accordées (vide les révoque).
TALE_FILE_EVENTSfalseDiffuse les changements des fichiers de config sous TALE_CONFIG_DIR aux onglets ouverts (/events/file) : un fichier d’agent, de skill ou de branding modifié sur disque apparaît sans recharger. Actif dans le compose de dev, inactif en production.
TALE_DEPLOYMENT_CONFIG_ADMINSnon définiAllowlist de courriels (séparés par des virgules) des opérateurs autorisés à écrire le fichier de configuration du déploiement (deployment.yml, aujourd’hui la section du runtime de la sandbox) via l’API. Vide/non défini = lecture seule pour tous les admins. La résidence des données se configure par organisation et ne dépend pas de cette liste.
TALE_ALLOW_PRIVATE_PROVIDER_HOSTSnon définiAvec 1 dans l’environnement du backend, autorise les destinations privées des fournisseurs de modèles, y compris leur configuration dans la passerelle sandbox. Les métadonnées cloud restent bloquées. Voir Fournisseurs.
TALE_ALLOW_PRIVATE_CRAWL_HOSTSnon définiAvec 1, autorise les cibles intranet et les hôtes privés dans l’imageUrl d’un produit. Les métadonnées cloud restent bloquées.
TALE_ALLOW_OPEN_SIGN_UPnon définiLa valeur exacte true laisse POST /api/auth/sign-up/email ouvert après le premier compte du déploiement. Réservé aux piles de test jetables — l’orchestrateur de développement local et la surcouche compose de développement le définissent eux-mêmes. Un déploiement réel le laisse non défini : chaque compte suivant est créé par une administratrice ou un administrateur.
TALE_ORGANIZATION_CREATORSnon définiListe d’adresses e-mail, séparées par des virgules, des comptes autorisés à créer une organisation, comparées sans tenir compte de la casse. Non définie, toute personne connectée peut en créer une. Définie, toute autre personne est refusée avec 403 ORGANIZATION_CREATION_FORBIDDEN dès que le déploiement compte une organisation — la première est toujours autorisée — et l’application lui masque Créer une organisation. Une valeur définie mais vide ferme la création à tout le monde. Un déploiement géré l’écrit à partir de organizations.creators de sa spécification ; voir Installer la CLI tale.

L’autorisation des cibles privées concerne deux contrôles : l’enregistrement des sites et les requêtes du crawler, ainsi que la validation de l’imageUrl d’un produit. Sans elle, une cible de site privée renvoie 400 WEBSITE_DOMAIN_NOT_CRAWLABLE ; une URL d’image privée renvoie 400 INVALID_BODY. Pour un produit, seule la chaîne du nom d’hôte est contrôlée, sans télécharger l’image ni résoudre le DNS. L’enregistrement d’un site et le crawler vérifient aussi les adresses résolues. Active cette variable uniquement si le déploiement a besoin de ces destinations privées. Elle est distincte de l’autorisation des fournisseurs privés.

Topologie du déploiement

Ces valeurs façonnent les rôles applicatifs d’un déploiement de workspace : le nombre de réplicas, que tale deploy lit dans l’environnement du projet et ramène dans la plage autorisée avec un avertissement, et la quantité de travail qu’un réplica de worker prend en charge à la fois.

NomDéfautDescription
TALE_PLATFORM_REPLICAS1Replicas de l'étage web qui sert la coquille de l'app. Plage 1–16.
TALE_BACKEND_API_REPLICAS1Replicas de l'API — chaque porte applicative, l'auth et le flux de hints. Plage 1–16.
TALE_BACKEND_WORKER_REPLICAS1Replicas du runner de jobs : ingestion, crawls, automations, tours d'agent. Plage 1–16.
UV_THREADPOOL_SIZE4Optionnel. Threads du processus Node.js que se partagent les lectures de fichiers, les résolutions DNS et le hachage des mots de passe ; Node lit la valeur au démarrage. Un réplica backend-api vérifie les mots de passe sur la moitié d’entre eux au plus en même temps (2 par défaut) et répond par un 503 avec le code PASSWORD_CHECK_BUSY et un Retry-After, dès qu’environ 16 vérifications par place attendent ou que l’une a attendu quatre secondes. Une vague de connexions ne ralentit ainsi qu’elle-même au lieu de passer devant toutes les autres requêtes. Augmente la valeur (8, par exemple) sur un réplica qui dispose de cœurs CPU libres et sert beaucoup de connexions à la fois ; une vérification en cours occupe environ 32 Mio. Les métriques tale_backend_password_checks_waiting et tale_backend_password_checks_refused_total montrent une telle vague pendant qu’elle se produit.
WORKER_CONCURRENCY5Jobs qu’un réplica backend-worker exécute en même temps dans chaque file de jobs — ingestion, crawls, automations et tours d’agent en reçoivent chacun autant. Les démarrages de tours d’agent en reçoivent au moins 8 et les fenêtres de traitement des tours d’agent en cours au moins 16 par file, car chacun attend surtout sa sandbox (AGENT_START_SLOTS, AGENT_DRIVE_SLOTS). Les réponses de chat pour les clés d’API en reçoivent au moins 16, car chacune attend surtout le modèle. Le processus worker le lit lui-même ; plage 1–64. C’est le levier à actionner avant d’ajouter des réplicas de worker quand un retard s’accumule. Chaque job d’indexation en cours écrit via le pool du corpus, donc augmente KNOWLEDGE_DB_POOL_MAX en même temps.
AGENT_START_SLOTSWORKER_CONCURRENCY, au moins 8Démarrages de tours d’agent qu’un réplica backend-worker exécute en même temps, pour les exécutions de tâches et pour les étapes d’automatisation chacun. Un démarrage occupe sa place pendant la création de la sandbox et la première fenêtre de 90 secondes du tour. Plage 1–256.
AGENT_DRIVE_SLOTSWORKER_CONCURRENCY, au moins 16Fenêtres de traitement des tours d’agent en cours qu’un réplica backend-worker exécute en même temps, par voie. Un réplica garde environ 2,5 fois ce nombre de tours en cours traités par voie ; au-delà, les fenêtres attendent assez longtemps pour que la reprise intervienne. Augmente-le (ou ajoute des réplicas) pour plus de tours à la fois. Plage 1–256.
AUTOMATION_ORG_CONCURRENCY8Étapes d’automatisation d’une même organisation qui s’exécutent en même temps sur l’ensemble des réplicas backend-worker. Ainsi, une organisation qui lance beaucoup d’exécutions d’un coup n’occupe pas toutes les places des workers pendant que les exécutions des autres organisations attendent. 0 supprime la limite. Des réplicas qui récupèrent du travail au même instant peuvent chacun démarrer une étape : lors d’un afflux, une ou deux de plus peuvent brièvement s’exécuter. Un réplica seul avec la valeur par défaut de WORKER_CONCURRENCY ne l’atteint jamais ; si ton déploiement ne sert qu’une organisation et offre plus de places pour les étapes que cette valeur, augmente-la ou mets 0. Plage 0–256.
AUTOMATION_RUNNER_PROCESSES2 pour backend-api ; pour backend-worker, un par cœur de CPU moins un, au plus 4Nombre de processus dans lesquels un réplica backend évalue le code des automatisations : les templates que lit le runner et le code des étapes transform. Un réplica en démarre un et n’en ajoute un autre que lorsque tous les siens sont occupés ; un processus au-delà du premier s’arrête après cinq minutes sans travail. Chacun occupe environ 75 Mo lorsqu’il tourne. Baisse la valeur sur un réplica à court de mémoire ; augmente-la sur un worker dont les automatisations exécutent beaucoup de code transform. Plage 1–16.
SHUTDOWN_DRAIN_MS15000 pour backend-api, 90000 pour backend-workerDurée, en millisecondes, pendant laquelle un réplica backend qui s’arrête attend ses jobs avant de quitter. Un worker transmet d’abord ses exécutions d’automatisation à un autre : chaque exécution continue à sa prochaine étape, et une étape qui travaille encore au bout de 20 secondes est interrompue et s’exécute à nouveau sur le réplica suivant, sauf si elle envoyait quelque chose à un service externe — l’exécution attend alors qu’une personne décide. Une réponse de chat encore en cours de streaming à la fin de ce délai — une réponse de l’app dans un backend-api qui s’arrête, une réponse pour une clé d’API dans un backend-worker qui s’arrête — échoue aussitôt comme interrompue par un redémarrage : la personne peut renvoyer son message au lieu de voir tourner un indicateur jusqu’à ce que le balayage des générations y mette fin dix minutes plus tard. Garde le stop_grace_period du conteneur au moins 15 secondes au-dessus de cette valeur ; les fichiers compose fournis donnent 30 secondes à backend-api et 120 à backend-worker, et tale deploy les porte tous deux à 15 secondes au-dessus d’une valeur plus longue. Plage 1000–600000.
TALE_BACKEND_URLhttp://backend-api:3005Où l'étage web atteint le backend applicatif : la page publique /status le sonde et le serveur web lui demande les réponses que seule une base de données peut donner. Le Compose livré et l'entrypoint du conteneur prennent l'alias in-compose par défaut ; définis la variable seulement si ton service backend porte un autre nom. Lue uniquement par le service platform.

Un déploiement de workspace fait temporairement tourner les deux couleurs. Prévois cette capacité supplémentaire. Augmente le rôle qui limite le débit selon tes mesures ; davantage de réplicas consomment aussi plus de connexions et de mémoire. Consulte Mises à jour.

Sessions

NomDéfautDescription
SESSION_IDLE_TIMEOUT_MINUTESnon définiOptionnel. Déconnecte une session après ce nombre de minutes d'inactivité (1–1440). La fenêtre glisse à chaque activité et est appliquée côté serveur — sessions e-mail/mot de passe, SSO et trusted headers.
SESSION_COOKIE_CACHE_SECONDSnon définiOptionnel. Permet à une requête de réutiliser sa session résolue depuis un cookie signé pendant au plus ce nombre de secondes (1–300), au lieu de lire la base. Sur une instance très sollicitée, cela économise la plupart des lectures d’authentification. En contrepartie, les changements de la session ou du compte n’atteignent les requêtes ordinaires qu’à l’expiration de la copie en cache (déconnexion sur un autre appareil, ou sessions fermées quand un mot de passe change ou qu’un admin révoque le passkey d’un membre ou réinitialise son authentification à double facteur). L’appartenance aux organisations suit AUTH_REQUEST_CACHE, et les flux de mise à jour en direct ouverts revérifient la session en 15 secondes. Si SESSION_IDLE_TIMEOUT_MINUTES est défini, le cache est limité au quart de cette fenêtre ; la révocation pour inactivité en tient compte, si bien que le délai d’inactivité d’une organisation peut s’appliquer jusqu’à ce nombre de secondes plus tard.
AUTH_REQUEST_CACHEactivéOptionnel. Chaque processus API garde les sessions et les appartenances aux organisations qu’il a résolues, si bien que les requêtes ordinaires évitent ces lectures en base. Un changement — une déconnexion sur un autre appareil, un nouveau mot de passe, un passkey révoqué, un rôle modifié ou un membre retiré — atteint chaque processus en une seconde environ : la base enregistre chaque changement, et chaque processus lit ces enregistrements cinq fois par seconde. Un processus qui ne peut pas les lire répond depuis la base jusqu’à ce qu’il le puisse. Le prolongement glissant d’une session a toujours lieu une fois par minute. Avec off, chaque requête lit la base. Si SESSION_COOKIE_CACHE_SECONDS est défini, les sessions viennent plutôt de ce cookie signé, et ce cache ne garde que les appartenances. Au-delà de quelques réplicas API, envoie les requêtes d’une même personne toujours vers le même réplica (sessions persistantes sur le cookie de session) : chaque réplica a son propre cache.

Laisse-le non défini pour conserver la durée de session par défaut. Si défini, une session inactive expire côté serveur une fois la fenêtre écoulée, tandis qu'une session active continue de glisser à chaque requête. Les Administrateurs d'organisation peuvent raccourcir la fenêtre effective par organisation — jamais l'allonger au-delà de ce plafond — via la politique de gouvernance du délai d'inactivité de session ; les sessions inactives sous cette politique sont révoquées par une passe qui tourne environ toutes les cinq minutes.

Lien vers le support

NomDéfautDescription
TALE_CONTACT_SUPPORT_URLhttps://tale.dev/contactOptionnel, lu par le service platform. Cible du lien contacter le support sur les écrans d’erreur de l’application. Une URL absolue http:// ou https://.

Indique ici ton propre service d’assistance pour que les personnes confrontées à une erreur joignent l’équipe qui exploite ton déploiement. Quand l’écran d’erreur connaît l’organisation, le lien ajoute organizationId=<id> à la chaîne de requête, après les paramètres que l’URL contient déjà, et remplace un paramètre organizationId qu’elle porte déjà. Toute autre valeur, par exemple mailto: ou une URL sans schéma, est ignorée avec un avertissement dans le journal du service platform, et le lien garde sa valeur par défaut.

Point d’accès MCP

NomDéfautDescription
TALE_MCP_ALLOWED_ORIGINSnon définiOptionnel, lu par backend-api et le rôle all. Les origines de navigateur depuis lesquelles le point d’accès MCP accepte, en plus de SITE_URL et ADDITIONAL_SITE_URLS, une requête qui porte un en-tête Origin. Séparées par des virgules ou des espaces, chacune au format scheme://host[:port] sans chemin, comme l’origine propre d’un éditeur de bureau, vscode-file://vscode-app. Une entrée mal formée empêche le démarrage du backend.
TALE_MCP_ORIGIN_ENFORCEfalseOptionnel, lu par backend-api et le rôle all. Avec true, le point d’accès refuse une requête de toute autre origine avec 403 et ORIGIN_FORBIDDEN ; avec false, il se contente de la journaliser.
TALE_MCP_CLIENT_METADATA_DOCUMENTStrueOptionnel, lu par backend-api et le rôle all. Indique si un agent de code peut se connecter au point d’accès MCP en désignant un document de métadonnées client qu’il publie sur son propre hôte HTTPS, comme le font Claude Code et Codex. Le backend récupère ce document : il lui faut donc un accès HTTPS sortant vers l’hôte de l’agent. false désactive cette voie. Voir Laisser les agents de code se connecter.
TALE_MCP_CLIENT_METADATA_HOSTSnon définiOptionnel, lu par backend-api et le rôle all. Les seuls hôtes dont un tel document peut provenir, sous forme de noms d’hôte séparés par des virgules ou des espaces, comme claude.ai. Non défini : n’importe quel hôte HTTPS public. Une entrée mal formée empêche le démarrage du backend.
TALE_MCP_DYNAMIC_CLIENT_REGISTRATIONtrueOptionnel, lu par backend-api et le rôle all. Indique si un agent de code peut s’enregistrer lui-même auprès du serveur de connexion de Tale, comme le font Cursor, VS Code et Gemini CLI. L’enregistrement est limité en débit, et une application qui s’est enregistrée elle-même apparaît comme non vérifiée sur la page où la personne autorise l’accès. false désactive cette voie ; avec cette variable et TALE_MCP_CLIENT_METADATA_DOCUMENTS toutes deux à false, les agents ne peuvent pas se connecter. Voir Laisser les agents de code se connecter.
TALE_MCP_DCR_WEB_REDIRECT_HOSTSclaude.ai,claude.com,chatgpt.com,vscode.dev,insiders.vscode.devOptionnel, lu par backend-api et le rôle all. Les hôtes web vers lesquels un agent qui s’est enregistré lui-même peut renvoyer la personne après sa connexion, sous forme de noms d’hôte séparés par des virgules ou des espaces. Tout autre agent doit revenir par l’ordinateur de la personne (localhost, 127.0.0.1 ou [::1]) ou par une application. Une entrée mal formée empêche le démarrage du backend.

Les agents de code lancés dans un terminal et les clients côté serveur n’envoient pas d’Origin : la vérification ne s’applique jamais à eux. Une page web d’un autre site en envoie une, et c’est avec une clé API placée dans une telle page que ce site atteindrait le point d’accès. Tant que la vérification n’est pas imposée, chaque requête venant d’une origine hors de la liste est journalisée comme [mcp] origin-mismatch, avec l’origine, l’organisation et la personne. Lis ces lignes pour savoir lesquels de tes clients envoient une origine avant de définir TALE_MCP_ORIGIN_ENFORCE=true, et ajoute leurs origines à TALE_MCP_ALLOWED_ORIGINS.

Les quatre réglages en bas du tableau appartiennent à la connexion des agents de code, qui permet à un agent de se connecter au point d’accès MCP sans clé API. Tant qu’elle n’est pas complète, les agents se connectent avec une clé API et les deux voies restent désactivées. Pour lire un document de métadonnées client, le backend a besoin d’un accès HTTPS sortant vers l’hôte de l’agent. Il ne récupère jamais de document depuis une adresse privée ou depuis le déploiement lui-même, et ne suit jamais de redirection. L’enregistrement autonome n’accepte que les portées des agents de code, et son débit est limité par adresse source et par déploiement.

Infrastructure sandbox

Le spawner sandbox lit les paramètres ci-dessous. Transmets-les dans son environnement et recrée ce service après une modification. SANDBOX_MAX_SESSIONS fixe la capacité partagée par toutes les organisations. Le total des trois limites de travail d’une organisation se recalcule automatiquement ; tu ne peux pas enregistrer ces limites s’il dépasse cette capacité. Gère les limites dans Sandboxes, où les environnements réellement actifs et les mesures de l’hôte apparaissent séparément des allocations.

NomDéfautDescription
SANDBOX_MAX_SESSIONSdimensionnée selon la mémoire et les CPU de l’hôte sur un hôte Docker local (au moins 8, au plus 256) ; 8 ailleursNombre maximal de sessions actives ou au démarrage de toutes les organisations sur l’hôte Docker ou dans le namespace Kubernetes, y compris les conteneurs inactifs conservés pour être réutilisés. Cette capacité ne réserve ni CPU ni mémoire. Sans valeur, sur un hôte Docker dont le spawner peut lire la mémoire, elle est dimensionnée selon cette mémoire : une session par tranche de 768 MiB restant après SANDBOX_MIN_FREE_MEMORY, ou de 1,5 GiB quand les sessions d’agent font tourner Docker dans la sandbox, et au plus deux sessions par CPU que le daemon Docker annonce. Si la mémoire ne peut pas être lue au démarrage, la capacité reste à 8 jusqu’à ce qu’elle le soit. Une valeur explicite la fixe et désactive le contrôle adaptatif de la mémoire au démarrage des sessions. Tu dois alors laisser assez de mémoire aux autres activités de l’hôte. Le seuil d’espace disque et le contrôle de la pression CPU (SANDBOX_CPU_PRESSURE_PERCENT) restent actifs. Des réplicas Kubernetes concurrents l’appliquent au mieux ; utilise ResourceQuota pour imposer des limites strictes aux ressources du namespace.
SANDBOX_MIN_FREE_MEMORYun dixième de la mémoire de l’hôte, au moins 1gAvec SANDBOX_MAX_SESSIONS non défini, mémoire qu’une nouvelle session ou une session libérée qui reprend du travail doit laisser libre sur un hôte Docker local, en plus de ce que les sessions encore au démarrage ou tout juste démarrées vont bientôt occuper. Si un démarrage en laissait moins, le spawner arrête d’abord une session libérée inactive ; si la mémoire manque encore, il répond « occupé » et le travail attend que des sessions en cours en libèrent. Accepte des tailles comme 2g ou 1536m. La réserve doit être inférieure à la mémoire totale de l’hôte. Si celle-ci est lisible au démarrage, une réserve impossible empêche le démarrage avec une erreur de configuration. 0 supprime la réserve, pas le contrôle de la mémoire nécessaire par session. Sans effet avec une valeur explicite de SANDBOX_MAX_SESSIONS, sous Kubernetes ou avec un daemon Docker distant.
SANDBOX_CPU_PRESSURE_PERCENT60Pression CPU sur un hôte Docker local, en pourcentage, à partir de laquelle les nouvelles sessions et les sessions libérées qui reprennent du travail démarrent une à une : la part des dix dernières secondes pendant laquelle une tâche a attendu un CPU, telle que la rapporte l’information de blocage par pression (PSI) du noyau (some avg10 de /proc/pressure/cpu). Au-delà, le spawner admet un démarrage toutes les dix secondes, dans l’ordre des demandes, et les autres attendent avec une réponse « occupé » ; le travail en cours n’est pas ralenti. 0 désactive ce contrôle. Un noyau sans PSI (compilé sans, ou démarré avec psi=0) laisse le CPU hors de l’admission, ce que le spawner journalise une fois. Sans effet sous Kubernetes ou avec un daemon Docker distant.
SANDBOX_MIN_FREE_DISKun vingtième de chaque disque, au moins 2g, au plus 20gEspace libre requis pour les sessions nouvelles et réactivées sur le système de fichiers des espaces de travail et celui, vérifié, des données Docker. Sous ce seuil, le travail attend ; les caches de build arrêtés et inutilisés sont supprimés en premier. Accepte 10g ou 4096m ; 0 désactive le contrôle. Kubernetes dépend de sa classe de stockage et du provisionnement des volumes.
SANDBOX_CRITICAL_FREE_DISKun quart du seuil SANDBOX_MIN_FREE_DISK, au moins 1g et jamais au-dessusEspace libre sous lequel le disque des sessions devient critique : ce que les sessions en cours y écrivent est sur le point d’échouer. En dessous, les sessions libérées et inactives avec Docker dans la sandbox sont arrêtées aussitôt, ce qui supprime leur stockage d’images interne, et le spawner journalise les trois plus grands espaces de travail au plus toutes les dix minutes. Accepte 2g ou 2048m ; une valeur au-dessus du seuil compte comme le seuil ; 0 désactive ce palier, tout comme un seuil de 0. Backend Docker uniquement.
SANDBOX_DOCKER_WORKLOADSproject,workflowTypes de travail autorisés à utiliser Docker dans leur sandbox, lorsque le déploiement le permet. Avec project, les agents de workflow démarrent sans Docker ; avec workflow, ce sont les agents de projet ; avec none, les deux. Le réglage s’applique aux nouvelles sessions ; les sessions existantes conservent leurs capacités. Une demande de session peut renoncer à Docker avec docker: false, mais ne peut pas dépasser cette liste ni le réglage du déploiement.
SANDBOX_AGENT_CPUS2Limite de CPU par session d’agent. Tiens compte des builds simultanés et des autres tâches de l’hôte pour choisir le nombre de sessions.
SANDBOX_AGENT_CPU_SHARES256Poids CPU d’une session d’agent et des conteneurs auxiliaires du cache de build de son organisation tant que les CPU de l’hôte sont saturés, face à la valeur Docker par défaut de 1024 que gardent les services de la plateforme ; les sessions du profil default (exécutions de code, rendus de pages) utilisent 128. Les sessions chargées laissent alors le CPU à la base de données et au backend au lieu de les bloquer ; sur un hôte où il reste du CPU, une session utilise toujours jusqu’à SANDBOX_AGENT_CPUS. Un nombre entier de 2 à 262144. S’applique aux nouvelles sessions ; un conteneur auxiliaire en cours prend une nouvelle valeur aussitôt.
SANDBOX_AGENT_MEMORY4g ; 8g avec Docker dans la sandboxLimite de mémoire par session d’agent, partagée avec son daemon Docker interne et les conteneurs qu’il lance. Une valeur explicite remplace ces deux valeurs par défaut et s’applique aux nouvelles sessions.
SANDBOX_AGENT_PROFILEagentLu par l’API backend et le worker. Profil des nouveaux espaces d’agents et de workflows : agent prend en charge Docker interne ; agent-light conserve les outils de développement et l’espace persistant sans démarrer Docker ni les services de cache de build. Les espaces existants conservent leur profil enregistré.
TALE_SANDBOX_CLAUDE_EFFORTnon défini (hérite de la valeur de l’environnement d’agent)Lu par l’API backend et le worker. Effort de raisonnement de Claude Code : low, medium, high ou max. Les valeurs inférieures retirent l’instruction Ultrathink automatique et définissent l’effort par exécution, tout en gardant la réflexion adaptative. Les autres environnements d’agent et les modèles tiers restent inchangés. Compare des tâches représentatives avant de réduire l’effort ; des exécutions plus courtes ne sont pas garanties.
SANDBOX_SESSION_CREATE_TIMEOUT_MS180000Budget total de démarrage, dont le cache de build facultatif, le conteneur ou Pod, la disponibilité du daemon et l’envoi de l’environnement. L’annulation d’une requête interrompt le démarrage de sa session ; le nettoyage arrête le calcul en conservant l’espace de travail. La préparation du cache partagé a sa propre durée maximale.
SANDBOX_DOCKER_DATA_ROOTnon définiChemin hôte facultatif correspondant au DockerRootDir de Docker. La CLI ajoute un montage en lecture seule au service sandbox. Avec Compose directement, ajoute le montage ci-dessous.
SANDBOX_DOCKER_DATA_PATH/var/lib/tale-sandbox/docker-data si la racine est définieEmplacement de ce montage en lecture seule dans le spawner. L’admission vérifie ce système de fichiers ainsi que celui des espaces de travail. Un montage configuré impossible à vérifier ou à lire bloque l’admission.
SANDBOX_BUILDKITD_CPUSSANDBOX_AGENT_CPUSLimite de CPU du conteneur auxiliaire du cache de build d’une organisation, qui exécute les builds d’images de toutes ses sessions d’agent quand Docker tourne dans la sandbox. Les conteneurs auxiliaires en cours prennent une nouvelle valeur aussitôt. Le nombre d’étapes parallèles du solveur BuildKit correspond à cette limite CPU arrondie à l’entier inférieur, avec un minimum de un ; cette limite s’applique à la recréation d’un conteneur auxiliaire inactif.
SANDBOX_BUILDKITD_MEMORYle double de SANDBOX_AGENT_MEMORYLimite de mémoire du conteneur auxiliaire du cache de build d’une organisation, partagée par les builds que ses sessions d’agent lancent en même temps. Accepte des tailles comme 16g ou 12288m. Un conteneur auxiliaire en cours prend une nouvelle valeur quand il est recréé, dès qu’aucun build ne tourne.
SANDBOX_BUILDKITD_IDLE_CACHE5gCache de build que garde le conteneur auxiliaire du cache de build d’une organisation quand il s’arrête faute de sessions d’agent. Juste avant cet arrêt, le spawner réduit le cache à cette taille, en commençant par les entrées inutilisées depuis le plus longtemps : le nettoyage propre au cache ne tourne que tant que le conteneur auxiliaire tourne. Accepte des tailles comme 5g ou 2048m ; 0 ne garde rien.
SANDBOX_BUILDKITD_MAX_CACHEun dixième du disque des sessions, au moins 1g, au plus 20gCache de build maximal que garde le conteneur auxiliaire du cache de build d’une organisation ; au-delà, les entrées inutilisées depuis le plus longtemps partent en premier. Sans valeur, la limite suit la taille du disque où se trouvent les espaces de travail des sessions, afin que de nombreuses organisations qui construisent des images n’en occupent pas ensemble plus qu’une part. Le plancher sous lequel le manque d’espace disque ne réduit jamais le cache vaut un dixième de cette limite, au plus 2 GiB. Accepte des tailles comme 8g ou 40960m, au moins 1g. Un conteneur auxiliaire en cours prend une nouvelle valeur quand il est recréé, dès qu’aucun build ne tourne.
SANDBOX_BUILDKITD_PROVISION_TIMEOUT_MS5000Budget total en millisecondes pour préparer le cache de build partagé facultatif lors de la création d’une session, y compris les appels Docker en attente et les miroirs de registres. Plage 100–60000. À expiration du budget de préparation partagé, les appels en attente sont annulés et ne peuvent plus lancer de conteneurs auxiliaires ; la session utilise son builder local.
SANDBOX_BUILDKITD_CACHE_RETENTION14dDurée pendant laquelle les conteneurs auxiliaires arrêtés du cache de build d’une organisation gardent leurs caches. Une fois le builder arrêté depuis plus longtemps, et si aucune session de l’organisation ne peut l’utiliser, le spawner supprime ses conteneurs auxiliaires, son réseau et ses volumes de cache, comme le fait la suppression d’une organisation ; son prochain build repart sans cache. Accepte des jours ou des heures entiers comme 14d ou 336h ; off (ou 0) garde les caches jusqu’à la suppression de l’organisation.
SANDBOX_BUILDKITD_IDLE_MS600000 (10 min)Durée pendant laquelle les conteneurs auxiliaires du cache de build d’une organisation continuent de tourner une fois qu’aucune session d’agent de l’organisation qui construit des images ne peut plus les utiliser. Ils s’arrêtent ensuite et gardent le réseau et le volume de cache du builder, tandis que les miroirs de registre sont supprimés avec leurs caches ; le prochain build les relance, en redémarrant le conteneur arrêté s’il a été lancé avec l’image et les réglages actuels, et en le recréant sinon. Minimum 60000.
SANDBOX_PACKAGE_CACHE_RETENTION14dDurée pendant laquelle les caches de paquets d’une organisation survivent à leur dernière utilisation : les caches de téléchargement de pip, uv, npm et bun que ses sandboxes partagent sur Docker (les sandboxes avec Docker à l’intérieur et les sandboxes légères ne les utilisent pas). Une fois qu’aucune sandbox de l’organisation ne les a utilisés depuis ce délai, le spawner les supprime lors de sa prochaine vérification horaire ; la prochaine sandbox de l’organisation démarre avec des caches vides, et les installations prennent plus de temps jusqu’à ce qu’ils se remplissent à nouveau. Les caches antérieurs à ce réglage comptent à partir de la première vérification après la mise à jour. Accepte des jours ou des heures entiers comme 14d ou 336h ; off (ou 0) garde les caches jusqu’à la suppression de l’organisation.
SANDBOX_SESSION_MAX_IDLE_MS1800000 (30 min)Délai d’inactivité avant l’arrêt des sessions non épinglées. Les conteneurs auxiliaires du cache de build d’une organisation suivent plutôt SANDBOX_BUILDKITD_IDLE_MS.
SANDBOX_SESSION_RELEASED_IDLE_MS300000 (5 min)Délai d’inactivité d’une session libérée, c’est-à-dire dont la tâche ou l’exécution est terminée et qu’aucun autre travail n’occupe plus. Une session libérée et non épinglée, inactive pendant ce délai, est arrêtée sans attendre SANDBOX_SESSION_MAX_IDLE_MS ; le tour suivant la reprend sur son espace de travail conservé. Les sessions d’agent avec Docker dans la sandbox gardent le délai complet dès que leur daemon Docker interne a tourné, car à chaque reprise il repart d’un stockage d’images vide ; une session dont le daemon n’a jamais démarré reçoit aussi ce délai plus court.
SANDBOX_EXEC_STALL_MINUTES45Durée pendant laquelle une commande d’une session peut ne rien afficher alors que ses processus utilisent moins de 1 % d’un CPU, avant que la sandbox ne l’arrête comme bloquée. Un agent bloqué, qui attend une connexion qui ne répond jamais ou une saisie que personne n’envoie, occuperait sinon sa session jusqu’à la limite de temps de son exécution. Le travail que la commande confie à des conteneurs qu’elle a démarrés dans la sandbox compte comme utilisation du CPU. Minutes entières jusqu’à 1440 ; 0 désactive la vérification. S’applique aux nouvelles sessions.
SANDBOX_RUNTIME_IMAGEtale-sandbox-runtime:latestOptionnel, lu par le spawner. L'image dont sort chaque conteneur de session. Le défaut est le tag que la stack de développement construit localement ; un hôte qui tire ses images pose donc le nom de la registry : ghcr.io/tale-project/tale/tale-sandbox-runtime:<version>, aligné sur le reste de la stack. tale deploy le pose pour toi.
SANDBOX_DIND_INNER_POOLnon défini (automatique)Pool d’adresses optionnel du daemon Docker interne des sessions d’agent, sur Docker ou Kubernetes. Choisis un /16 IPv4 privé RFC1918 sous forme canonique, hors de tes réseaux de Pods, de Services et de VPC. Le runtime refuse tout chevauchement avec les réseaux et adresses qu’elle détecte.
SANDBOX_K8S_DOCKER_STORAGE_SIZE_LIMIT20GiKubernetes uniquement. La taille du stockage du Docker interne d’une session d’agent qui exécute Docker dans sa sandbox. Une session qui le dépasse est évincée ; dimensionne-le donc pour les plus grosses images que tes agents téléchargent et construisent. Les autres paramètres Kubernetes figurent dans Déployer sur Kubernetes.
SANDBOX_K8S_EPHEMERAL_STORAGE_REQUEST / SANDBOX_K8S_EPHEMERAL_STORAGE_LIMIT256Mi / 2GiKubernetes uniquement. L’espace disque du nœud qu’un Pod de session demande, et ce qu’il peut y écrire en dehors de son workspace temporaire et du stockage du Docker interne. La limite du Pod y ajoute ces stockages : une session qui la dépasse est évincée seule au lieu de remplir le nœud.
SANDBOX_K8S_NODE_SELECTOR / SANDBOX_K8S_TOLERATIONSnon définiKubernetes uniquement. Les labels de nœud auxquels chaque Pod de session doit correspondre, sous forme d’objet JSON comme {"tale.dev/sandbox":"true"}, et les taints qu’il tolère, sous forme de tableau JSON de tolérances de Pod : les sessions tournent ainsi sur leurs propres nœuds plutôt qu’à côté de la base de données et de la plateforme. Une valeur mal formée empêche le spawner de démarrer.
SANDBOX_K8S_PRIORITY_CLASSnon définiKubernetes uniquement. La PriorityClass de chaque Pod de session. Une classe inférieure à celle de la plateforme permet au planificateur de préempter des sessions pour placer les Pods de la plateforme.
SANDBOX_EGRESS_MAX_CLIENTS2000Lu par le service sandbox-egress, pas par le spawner : recrée ce service après un changement. Connexions que le proxy egress sert en même temps pour toutes les sessions réunies ; avec la sortie transparente, chaque connexion sortante d’une session, de ses builds et de ses installations de paquets passe par lui. Au-delà de la limite, le proxy refuse les nouvelles connexions, et les installations et chargements de pages échouent sur des connexions réinitialisées. Chaque connexion occupe un thread et deux fichiers ouverts, donc les fichiers Compose fournis dimensionnent les limites de processus et de fichiers ouverts du proxy pour la valeur par défaut ; une valeur plus haute demande aussi des limites plus hautes, et le proxy avertit au démarrage quand sa limite de fichiers ouverts est trop basse. Au démarrage, le proxy relève sa limite de fichiers ouverts à ce dont les connexions ont besoin, dans la mesure où la limite stricte du conteneur le permet. Sous Kubernetes, pose-la sur le Deployment egress ; une spécification de Pod ne fixe aucune limite de processus, donc le podPidsLimit du nœud doit laisser la place d’un thread par connexion.
SANDBOX_EGRESS_MAX_CONNECTIONS_PER_SESSION256Lu par le service sandbox-egress, pas par le spawner : recrée ce service après un changement. Connexions qu’une session peut garder ouvertes en même temps vers le proxy egress, comptées par son adresse, pour qu’une seule session ne prenne pas aux autres toute la réserve de SANDBOX_EGRESS_MAX_CLIENTS ; les conteneurs auxiliaires du cache de build de chaque organisation ont leurs propres adresses. Une connexion au-delà de la limite est refusée aussitôt par une réinitialisation, et les autres connexions de la session continuent. 0 désactive la limite, et une valeur qui n’est pas un nombre entier empêche le proxy de démarrer. Le proxy installe la limite comme règle de pare-feu au démarrage ; sur un noyau sans la correspondance connlimit, il démarre sans elle et consigne un avertissement. Sous Kubernetes, pose-la sur le Deployment egress ; là où le réseau du cluster réécrit les adresses source des sessions, de sorte qu’elles atteignent le proxy depuis une même adresse, elles partagent une seule limite, alors mets 0 dans ce cas.

Les réglages backend ci-dessus doivent parvenir à l’API et au worker. Recrée ces services après une modification. Le spawner vérifie aussi la mémoire et le disque lors de la reprise d’une session libérée : un espace déjà démarré peut donc attendre de la capacité.

Les appareils connectés appliquent les mêmes contrôles de mémoire et d’espace disque qu’un hôte Docker local, tout en conservant leur plafond de sessions configuré.

Un daemon Docker arrêté volontairement reste prêt pour la prochaine commande ; les contrôles de santé ne le démarrent pas. Un contrôle qui échoue bloque immédiatement le nouveau travail, mais ne suffit pas à arrêter la session. La reprise après des contrôles défaillants exige au moins trois contrôles terminés en échec sur une période d’au moins cinq secondes. Un contrôle réussi ou un nouveau moteur efface cet historique. Un échec de démarrage confirmé ou un arrêt inattendu du moteur peut déclencher la reprise immédiatement. Après une vérification atomique de l’activité, le spawner peut alors arrêter une session inactive et non épinglée, en gardant son espace de travail pour le prochain démarrage. Sur Docker, l’arrêt de toute la session supprime son stockage Docker interne ; l’arrêt automatique du seul moteur interne inactif le conserve. Les sessions occupées ou épinglées restent en place et refusent de nouveaux démarrages jusqu’à ce que Docker réponde de nouveau ou qu’un opérateur résolve le problème.

Lors d’une mise à niveau progressive, garde chaque ancien spawner sur la référence fixe de son image runtime actuelle jusqu’à son remplacement. Ne déplace pas un tag runtime encore utilisé par un ancien spawner : il ne distingue pas les erreurs de contrôle passagères des pannes confirmées. Un nouveau spawner avec un ancien runtime refuse le nouveau travail quand Docker est indisponible et conserve les limites habituelles d’inactivité et de durée de vie.

Un spawner en conteneur contrôle automatiquement le système de fichiers des données Docker lorsqu’il peut vérifier son montage /etc/hostname existant auprès du daemon. Si cette détection est indisponible, le contrôle des espaces de travail reste actif et /health indique unavailable pour la surveillance du disque Docker. Un montage explicite en lecture seule du répertoire de données est prioritaire ; s’il ne peut être vérifié ou lu, l’admission attend qu’il soit disponible. Pour le configurer, consulte docker info --format '{{.DockerRootDir}}' et utilise ce chemin réel sur l’hôte. Docker Desktop et les daemons distants peuvent nécessiter une autre configuration. Avec Compose directement, ajoute la surcharge suivante et transmets les deux variables au service sandbox :

yaml
services:
  sandbox:
    environment:
      SANDBOX_DOCKER_DATA_ROOT: ${SANDBOX_DOCKER_DATA_ROOT}
      SANDBOX_DOCKER_DATA_PATH: /var/lib/tale-sandbox/docker-data
    volumes:
      - ${SANDBOX_DOCKER_DATA_ROOT}:/var/lib/tale-sandbox/docker-data:ro

Un seuil d’admission n’est pas un quota de stockage par session. Les volumes nommés Docker n’ont pas de limite de taille portable, et le respect de la taille demandée par un PVC Kubernetes dépend du stockage utilisé. Pour des quotas stricts, choisis un système de fichiers ou une classe de stockage qui les applique. Surveille séparément les espaces de stockage de containerd et les répertoires de volumes montés à part, ainsi que les volumes gérés par un pilote externe, en plus des espaces de travail, des couches d’images et des caches de build.

Les sessions Kubernetes utilisent /readyz pour le démarrage et /livez pour la vivacité du daemon. Un daemon Docker interne arrêté volontairement est considéré comme prêt au démarrage ; un daemon Docker en cours d’exécution doit répondre à sa sonde de santé. La sonde de vivacité ne vérifie que le daemon de la sandbox, afin qu’une dépendance défaillante ne fasse pas redémarrer une session occupée. Un daemon bloqué peut redémarrer même si sa session est épinglée. Les diagnostics Docker et egress apparaissent dans /healthz.

Une génération déjà active n’est pas comptée une seconde fois. Un workflow sans Docker interne garde l’identité et les outils de son agent, mais ne démarre aucun daemon Docker et ne maintient pas les conteneurs auxiliaires du cache de build en activité. Sans valeur explicite pour SANDBOX_AGENT_MEMORY, chaque session a une limite par défaut de 4g sans Docker interne ou de 8g avec Docker, même si d’autres sessions du déploiement utilisent Docker. Une valeur explicite s’applique aux deux modes. Ces limites sont des plafonds, pas de la mémoire réservée.

À pleine capacité, le spawner peut arrêter une session inactive, libérée et non épinglée avant le délai d’inactivité pour accepter un nouveau travail, en commençant par celle qui est inactive depuis le plus longtemps. Le daemon doit confirmer qu’aucun travail n’est en cours ; les sessions occupées ou dont l’état est inconnu restent protégées. L’arrêt conserve le répertoire ou le volume persistant de l’espace de travail. Sans session à récupérer en toute sécurité, la capacité du déploiement continue de bloquer les nouveaux démarrages. Il en va de même lorsque la mémoire libre de l’hôte passerait sous SANDBOX_MIN_FREE_MEMORY. Tant que le disque qui contient les espaces de travail des sessions reste sous SANDBOX_MIN_FREE_DISK, le nouveau travail attend sans que des sessions inactives soient arrêtées : une session arrêtée garde son espace de travail. Ce n’est que sous SANDBOX_CRITICAL_FREE_DISK que le spawner arrête aussitôt les sessions libérées et inactives avec Docker dans la sandbox, car leur arrêt supprime leur stockage d’images interne.

Les enregistrements du protocole des environnements d’agent, y compris les lignes inachevées, sont limités à 8 Mio. Une sortie trop volumineuse ou incomplète provoque un échec explicite ; l’aperçu de progression borné n’est jamais considéré comme un résultat structuré complet. Les réponses finales complètes sont conservées séparément de cet aperçu.

La reprise d’une session libérée encore en mémoire vérifie la même réserve et comptabilise son besoin mémoire prévu avant de reprendre le travail. Les reprises simultanées partagent ce calcul. Une réponse « occupé » permet une nouvelle tentative : elle ne supprime pas l’espace de travail et n’interrompt pas une session déjà occupée. Ces réservations ne limitent pas la croissance ultérieure des outils actifs ou des builders de l’organisation.

Lorsque Docker est activé dans les sandboxes d’agent, le daemon démarre automatiquement à la première commande Docker. Les agents n’ont aucun mode à choisir. Après cinq minutes sans client connecté, il s’arrête uniquement si aucun conteneur n’est en cours d’exécution, de redémarrage ou en pause et si toutes les règles de redémarrage automatique sont désactivées ; un état inconnu le maintient actif. La commande suivante le relance avec les mêmes images, volumes et espace de travail. Si des métadonnées de conteneurs existent déjà au démarrage du conteneur de session, le daemon démarre immédiatement pour respecter leurs règles de redémarrage. L’isolation, les privilèges et les limites mémoire du déploiement restent inchangés.

Dimensionner la capacité des sessions

Sans SANDBOX_MAX_SESSIONS sur un hôte Docker, le spawner dimensionne la capacité selon la mémoire et les CPU de l’hôte (une session par tranche de 768 MiB au-delà de la réserve, au plus deux par CPU : un hôte de 16 GiB avec 8 CPU obtient 16 sessions, un hôte de 64 GiB avec 16 CPU en obtient 32 ; un hôte de 16 GiB en obtient 9 quand les sessions d’agent font tourner leur propre daemon Docker) et n’admet une nouvelle session que si l’hôte garde SANDBOX_MIN_FREE_MEMORY de libre, en comptant ce que les sessions encore au démarrage ou tout juste démarrées vont bientôt occuper. Un pic de nouvelles sessions qui surchargerait l’hôte attend donc au lieu de démarrer ; ce contrôle n’empêche pas les sessions déjà lancées de continuer à grandir. Quand le CPU de l’hôte est sous pression (SANDBOX_CPU_PRESSURE_PERCENT), les nouvelles sessions démarrent de même une à une au lieu d’alourdir la charge. Chaque exécution d’agent de projet qui travaille en même temps qu’une autre prend sa propre session, un worker d’agent : un agent qui travaille sur trois tâches à la fois fait tourner trois sessions, chacune avec sa propre limite SANDBOX_AGENT_MEMORY. Ces deux réglages bornent donc la quantité de travail d’agent menée en parallèle. Fixe toi-même la capacité sous Kubernetes, avec un daemon Docker distant ou si l’hôte fait aussi tourner d’autres charges gourmandes : teste alors les tâches que ton déploiement exécutera en même temps. Le rendu dans le navigateur et les builds Docker n’ont pas les mêmes pics de charge ; inclus les agents, les workflows et l’exploration de toutes les organisations. Une place libre ne garantit pas des ressources suffisantes.

Sous Docker, relève la consommation pendant que des tâches représentatives s’exécutent en parallèle. Répète cette commande pendant l’exécution ; une mesure au repos ne montre pas les pics des tâches :

bash
docker stats --no-stream --format 'table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}'

Retire de la mémoire de l’hôte la part nécessaire au système, aux services de la plateforme, aux bases de données, aux conteneurs auxiliaires de build et à une marge de sécurité. Divise le reste par le pic mesuré par session active, puis arrondis à l’entier inférieur. Par exemple, un hôte de 32 GiB avec 8 GiB mis de côté et un pic mesuré de 3 GiB par session donne (32 - 8) / 3 = 8 sessions. C’est un exemple de calcul, pas un benchmark. Pour des tâches différentes, additionne leurs pics simultanés et vérifie la saturation du CPU ainsi que la durée des tâches avant d’augmenter la limite.

Les 4 GiB ou 8 GiB d’un agent sont un plafond de mémoire ; cette mémoire n’est pas réservée au démarrage. Huit agents qui exécutent des builds Docker peuvent donc consommer bien plus que huit sessions surtout inactives. Mesure avec une charge représentative et garde une marge. Passe à 16 ou plus seulement si l’hôte tient cette charge dans la durée. Avant de réduire la capacité, abaisse les totaux d’organisation qui dépassent la nouvelle valeur. Les limites par défaut d’une organisation totalisent 6 ; une capacité inférieure demande aussi des limites d’organisation plus basses.

Appliquer un changement de capacité

Ajoute ou modifie cette ligne dans le .env du déploiement, en conservant les autres entrées. Les valeurs explicites restent en vigueur après une mise à niveau ; sans valeur, un hôte Docker dimensionne la capacité selon sa mémoire comme décrit plus haut, et Kubernetes utilise 8.

dotenv
SANDBOX_MAX_SESSIONS=8

Pour une stack Compose que tu gères toi-même, recrée uniquement le service sandbox avec son image locale existante :

bash
docker compose up -d --no-deps --no-build --pull never sandbox

Utilise le même projet, les mêmes fichiers -f et les mêmes options de fichiers d’environnement que la stack active. Un redémarrage seul ne charge pas un .env modifié. Pour une installation gérée par la CLI, suis la procédure de déploiement dans Mises à niveau. Sous Kubernetes, définis la variable dans le Deployment du spawner sandbox, puis déploie cette nouvelle configuration.

Vérifie la nouvelle capacité du déploiement dans Sandboxes. Avec des limites d’organisation de 2/2/2 et une capacité de 8, le total affiche 6 / 8. Les réglages existants des organisations sont conservés ; leur total doit toujours respecter la capacité actuelle pour être enregistré. Ce changement n’augmente pas les limites de CPU ou de mémoire des conteneurs.

Après une mise à niveau

La capacité par défaut était de 16, et les organisations créées avant cette version ont reçu les limites 2/4/4, soit un total de 10. Un déploiement qui n’a jamais défini SANDBOX_MAX_SESSIONS démarre donc la nouvelle version avec une capacité de 8 et des organisations dont le total enregistré la dépasse. Le travail en cours n’est pas touché, et chaque organisation continue d’admettre du travail selon ses limites enregistrées ; seul l’enregistrement de la page Sandboxes reste bloqué tant que le total ne rentre pas, et abaisser une limite s’enregistre toujours. Définis SANDBOX_MAX_SESSIONS=16 explicitement pour conserver la capacité précédente, ou demande à chaque organisation concernée d’abaisser une limite.

À partir de cette version, un déploiement Docker qui n’a jamais défini SANDBOX_MAX_SESSIONS obtient une capacité dimensionnée selon la mémoire de son hôte au lieu de 8, jamais moins de 8. Il peut admettre plus de sessions à la fois qu’avant, et seulement tant que l’hôte a de la mémoire disponible. Une valeur explicite fixe la capacité et désactive le contrôle adaptatif de la mémoire ; choisis-la selon les pics de mémoire mesurés. Le seuil d’espace disque reste actif.

Cette capacité dimensionnée compte désormais aussi au plus deux sessions par CPU : un hôte avec beaucoup de mémoire et peu de CPU obtient donc moins de sessions qu’avant (un hôte de 16 GiB avec 4 CPU : 8 au lieu de 19). Quand le CPU de l’hôte est sous pression, les nouvelles sessions démarrent en outre une à une (SANDBOX_CPU_PRESSURE_PERCENT, 0 désactive ce comportement). Une valeur explicite de SANDBOX_MAX_SESSIONS reste inchangée.

Caches de build Docker

La préparation du cache de build partagé utilise SANDBOX_BUILDKITD_PROVISION_TIMEOUT_MS (5 secondes par défaut). Chaque session attend au plus cette durée ou un quart de son budget total de démarrage si ce délai est plus court, puis utilise son builder local. La préparation partagée a son propre budget borné : annuler l’attente d’une session n’annule pas la préparation destinée aux autres sessions, et un résultat tardif ne se rattache pas à une session déjà passée au builder local. Les miroirs de registry sont préparés en parallèle. Le nombre d’étapes simultanées du builder partagé correspond à sa limite CPU arrondie à l’entier inférieur, avec un minimum de un ; sa limite de mémoire reste applicable. Le temps de préparation est déduit du budget de démarrage restant de la session.

Les caches de build Docker sont isolés par organisation. Chaque organisation utilise un builder privilégié et trois miroirs de registre sans privilèges élevés. Dès qu’aucune session ne peut encore les utiliser, les conteneurs auxiliaires s’arrêtent après SANDBOX_BUILDKITD_IDLE_MS (10 minutes), et les miroirs de registre sont supprimés avec leurs caches, car le cache du builder garde les couches de base que ses builds ont utilisées. Le prochain build redémarre le builder avec son volume de cache conservé et recrée les miroirs. Juste avant l’arrêt du builder, le spawner réduit son cache à SANDBOX_BUILDKITD_IDLE_CACHE, car son nettoyage ne tourne que tant qu’il tourne. Le cache d’une organisation arrêtée n’est plus réduit ensuite ; après SANDBOX_BUILDKITD_CACHE_RETENTION (14 jours) sans build, ses conteneurs auxiliaires et ses caches sont supprimés, et sinon ils partent avec l’organisation. Tant que le disque des sessions reste sous SANDBOX_MIN_FREE_DISK, les caches des organisations arrêtées partent plus tôt, en commençant par celles arrêtées depuis le plus longtemps. Le réseau et le volume de cache du builder restent disponibles pour être réutilisés. Les sessions Kubernetes utilisent leur propre builder Docker interne ; la réconciliation Kubernetes n’appelle pas la CLI Docker pour ces conteneurs auxiliaires.

Le spawner remplit le premier pool d’adresses Docker disponible avec les réseaux d’organisation avant de passer au suivant. Par défaut, chaque réseau reçoit un /23 de 512 adresses ; un /16 entièrement libre peut donc accueillir 128 réseaux d’organisation. Les sous-réseaux plus petits configurés dans Docker gardent leur taille. Le spawner exclut les réseaux Docker existants, les routes et les adresses des serveurs DNS de l’hôte du daemon Docker, ainsi que 172.31.0.0/16 pour les anciennes images de runtime, puis vérifie le réseau créé.

Pour observer l’hôte du daemon même avec Docker à distance, le spawner lance brièvement l’image BuildKit configurée dans le namespace réseau de l’hôte. Ce conteneur utilise un système de fichiers en lecture seule, sans capacités Linux ni montages. Si cette observation échoue ou qu’aucun sous-réseau sûr ne reste disponible, les sessions construisent localement sans cache partagé. Le spawner recrée un réseau inutilisé qui lui appartient si son sous-réseau est invalide ; il conserve les réseaux utilisés ou appartenant à un autre propriétaire.

Une mise à niveau crée des caches d’organisation vides et conserve pour l’instant les anciennes données globales. Les anciens conteneurs auxiliaires s’arrêtent automatiquement dès qu’aucune session active n’en dépend. Une fois qu’ils sont arrêtés depuis plus de SANDBOX_BUILDKITD_CACHE_RETENTION (14 jours) et qu’aucune session n’en dépend toujours, le spawner supprime ces conteneurs auxiliaires et les quatre anciens volumes de cache (tale-buildkitd-cache ainsi que tale-buildkitd-mirror-cache-docker-io, -ghcr-io, -quay-io), chacun seulement tant qu’il porte l’ancienne étiquette du cache de build sans organisation, et journalise chaque suppression ; off les garde. Laisse se terminer les anciennes sessions épinglées ou arrête-les pour achever la transition ; jusque-là, l’ancien service de cache partagé reste accessible. L’automatisation du navigateur utilise Chromium sans interface graphique. La vue en direct et la prise de contrôle manuelle ont été retirées.

Images du moteur Docker interne

Quand le cache de build partagé est actif, le moteur Docker interne d’une session télécharge les images Docker Hub via le miroir de registre docker.io de son organisation, le même cache que ses builds, et revient à Docker Hub quand ce miroir ne tourne pas. Les images des autres registres sont téléchargées directement. Un moteur interne qui devient inactif avec plus de 10 GiB d’images et de cache de build supprime d’abord ses images orphelines et réduit son cache de build aux 5 GiB utilisés le plus récemment ; les images nommées, les conteneurs et les volumes restent. Sur Kubernetes, c’est la limite de taille du volume du stockage interne qui le borne quand elle est inférieure à 10 GiB.

Réseaux Docker internes

Sur Docker et Kubernetes, le choix automatique vérifie les routes IPv4 et leurs passerelles dans toutes les tables de routage, les adresses et préfixes des interfaces, les serveurs DNS et les adresses résolues des hôtes de proxy et de passerelle configurés dans l’environnement au démarrage du conteneur. Les hôtes transmis plus tard pendant un tour d’agent échappent à cette observation initiale. Il tient aussi compte du réseau d’organisation Docker raccordé ensuite. Le runtime privilégie 172.31.0.0/16 s’il est libre, puis essaie d’autres plages privées /16. Le premier /24 sert à docker0 ; les réseaux Compose internes utilisent des blocs /24 du même pool. En mode automatique, une observation indisponible ou l’absence de plage libre empêche le démarrage de la session.

Un Pod ne peut pas déduire tous les CIDR des Pods, des Services et du VPC du cluster depuis son propre namespace réseau. Pour DinD sur Kubernetes, définis SANDBOX_DIND_INNER_POOL avec un /16 privé que tu as vérifié contre l’ensemble de ces réseaux. Un pool explicite reste refusé si le runtime détecte un chevauchement ou une valeur invalide. Si certaines observations manquent, il les indique dans un avertissement et peut continuer avec ce pool ; c’est à toi de tenir compte des plages invisibles depuis le Pod.

Après avoir modifié ce pool, redémarre le spawner et recrée les sessions existantes pour leur appliquer la valeur. Redémarrer le conteneur runner dans le même Pod Kubernetes conserve l’environnement du Pod et le stockage Docker interne.

Le proxy egress autorise les requêtes DNS vers les adresses IP de serveurs de noms validées dans son /etc/resolv.conf, y compris le DNS privé du cluster. Chaque exception se limite à cette IP exacte et au port de destination 53 en UDP/TCP. Les autres destinations privées et le transfert entre réseaux raccordés restent bloqués.

Protection du transfert IPv6

Garde sandbox, sandbox-egress et SANDBOX_RUNTIME_IMAGE sur la même version lors d’une mise à niveau. Avant de raccorder le réseau de build Docker d’une organisation, le spawner vérifie la protection de la session contre le transfert de paquets. Compose et les conteneurs de session Docker générés désactivent IPv6 avec net.ipv6.conf.all.disable_ipv6=1 et net.ipv6.conf.default.disable_ipv6=1. Conserve les deux valeurs dans tes propres définitions Docker.

Les Pods Kubernetes ne reçoivent pas automatiquement de sysctls non sûrs. Le proxy egress a besoin d’un pare-feu IPv6 fonctionnel ou d’IPv6 désactivé dans son namespace réseau. Si le pare-feu IPv6 est indisponible, l’entrypoint tente cette désactivation locale, puis vérifie la valeur par défaut et chaque interface. Un /proc/sys en lecture seule ou des droits insuffisants peuvent l’en empêcher ; IPv6 encore actif sans protection bloque le démarrage. Configure le Pod egress avant le déploiement avec les paramètres réseau autorisés par ton cluster.

Accès SSH aux repositories

La sandbox native contient OpenSSH et netcat-openbsd. Accorde une clé remplaçable limitée au repository comme secret nommé de l'agent, puis charge-la dans ssh-agent via stdin pendant le tour autorisé. Garde ses octets privés dans l'environnement ; ne crée jamais de fichier de clé et ne les affiche pas. Un tour lancé par un Membre ne reçoit aucun secret d'agent. Les commits Git utilisent le nom et l'e-mail du propriétaire du workspace même sans autorisation de connector GitHub.

Les sessions internes utilisent leur HTTP_PROXY existant pour les connexions sortantes. Le SSH direct ne peut pas compter sur le DNS externe dans ce réseau. Utilise le tunnel HTTP CONNECT du proxy et un port autorisé. GitHub prend en charge SSH sur ssh.github.com:443 ; vérifie l'hôte avec les empreintes publiées par GitHub, comme décrit dans SSH sur le port 443. Prépare un fichier known-hosts public contenant la clé vérifiée indépendamment, puis configure la commande après le chargement de la clé privée par l'agent :

python
import os
import shlex
import subprocess
from urllib.parse import urlsplit

proxy = urlsplit(os.environ["HTTP_PROXY"])
if proxy.scheme != "http" or not proxy.hostname or not proxy.port or proxy.username or proxy.password:
    raise ValueError("Expected the existing unauthenticated HTTP egress proxy")
connect = shlex.join(["nc", "-X", "connect", "-x", f"{proxy.hostname}:{proxy.port}", "%h", "%p"])
environment = os.environ.copy()
environment["GIT_SSH_COMMAND"] = shlex.join([
    "ssh", "-o", f"ProxyCommand={connect}", "-o", "StrictHostKeyChecking=yes",
    "-o", "UserKnownHostsFile=/tmp/repository-known-hosts",
    "-o", "GlobalKnownHostsFile=/dev/null", "-o", "BatchMode=yes",
    "-o", "ConnectTimeout=20",
])
subprocess.run(["git", "ls-remote", "--exit-code", "ssh://git@ssh.github.com:443/org/repository.git", "HEAD"], env=environment, check=True, timeout=30)

Le fichier known-hosts contient uniquement la clé publique de l'hôte du fournisseur ; la clé privée du repository reste dans ssh-agent. La politique egress existante et la portée d'accès de la clé du repository continuent de s'appliquer. Aucune autorisation de connector GitHub plus large n'est nécessaire.

Appareils de sandbox

Les organisations peuvent exécuter leurs sandboxes sur leurs propres machines, qu’elles connectent dans Paramètres > Sandboxes. Un appareil se connecte en HTTPS à <SITE_URL><BASE_PATH>/sandbox/tunnel et garde un WebSocket ouvert. Le proxy fourni transmet ce chemin, et uniquement celui-ci, au hub d’appareils du spawner ; l’API signée du spawner reste sur le réseau interne. Les appareils ont besoin du backend Docker : avec Kubernetes, le hub reste désactivé.

NomDéfautDescription
SANDBOX_HUB_PORT8004Lu par le spawner. Port du hub d’appareils, qui ne répond qu’à une mise à niveau WebSocket authentifiée par ticket et à une vérification d’état. 0 désactive les appareils : Ajouter un appareil est alors indisponible. Si tu changes le port, fais aussi pointer SANDBOX_HUB_UPSTREAM dessus.
SANDBOX_DEVICE_TUNNEL_URL<SITE_URL><BASE_PATH>/sandbox/tunnel en wss://Facultatif, lu par le backend. Où les appareils se connectent, lorsqu’un proxy placé devant Tale publie le hub sous un autre hôte ou chemin. Une adresse https:// est utilisée comme wss://.
SANDBOX_DEVICE_IMAGE_REGISTRYGHCR_REGISTRY, sinon ghcr.io/tale-project/taleFacultatif, lu par le backend. D’où les appareils téléchargent les images de sandbox de la version du serveur.
SANDBOX_HUB_UPSTREAMsandbox:8004Facultatif, lu par le proxy. Où le proxy transmet /sandbox/tunnel, lorsque le spawner n’est pas le service sandbox.

Un appareil exécute toujours la version du serveur. Il apprend la version à chaque renouvellement de sa connexion et remplace ses propres conteneurs dès que le serveur change de version, sauf s’il a été connecté avec --no-auto-update. Il ne conserve aucune donnée sur le serveur : ses espaces de travail restent sur la machine. Les sandboxes d’un appareil joignent les points d’accès sandbox du backend et la passerelle de modèles par la connexion de l’appareil, sur les mêmes chemins que les sessions sur le serveur ; l’API d’administration de la passerelle n’est jamais accessible par cette voie. Un appareil conserve les adresses du backend et de la passerelle reçues lors de sa connexion : après avoir modifié SANDBOX_HTTP_API_BASE_URL ou EXTERNAL_AGENT_GATEWAY_URL, lance tale sandbox update sur chaque appareil.

Un appareil doit faire confiance au certificat TLS du site. Un déploiement avec TLS_MODE=selfsigned ne peut pas accueillir d’appareils.

Si tu utilises ton propre proxy au lieu du proxy fourni, transmets /sandbox/tunnel au port SANDBOX_HUB_PORT du spawner, conserve les mises à niveau WebSocket et l’en-tête Authorization, et autorise des connexions qui restent ouvertes pendant des heures.

Tours d'agent en sandbox

NomDéfautDescription
TALE_EXTERNAL_TURN_DEADLINE_MS1800000 (30 min)Optionnel. Combien de temps un tour d’agent de code en sandbox (Claude Code, OpenCode, Codex) peut rester sans que personne ne lise sa sortie avant que le daemon de la sandbox ne le récupère. Une fenêtre glissante, relancée chaque fois que la plateforme se rattache à la sortie — pas un plafond absolu sur le tour. Un tour d’agent en cours attend aussi un service sandbox injoignable avant d’échouer : un tiers de cette valeur, 10 minutes au plus (exactement 10 minutes avec la valeur par défaut). Si tu baisses cette valeur, cette attente raccourcit. En millisecondes.
SANDBOX_LLM_GATEWAY_STREAM_IDLE_TIMEOUT_SECONDS600 (10 min)Optionnel. Combien de temps la passerelle de modèles de la sandbox attend l’octet suivant d’un modèle en amont resté silencieux avant d’interrompre le flux, y compris pendant qu’un modèle local lent traite encore un long prompt. Le backend lit cette valeur et s’en sert pour configurer la passerelle. Les tours Claude Code et Codex attendent au moins aussi longtemps avant d’abandonner un flux silencieux et de renvoyer la requête. Tu peux donc l’augmenter pour un modèle local lent sans que l’un d’eux envoie un tour en double. En contrepartie, un modèle en amont bloqué retient aussi un tour plus longtemps avant que la passerelle n’interrompe le flux. Une valeur supérieure à 600 relève aussi d’autant le délai de requête de la passerelle : il borne une réponse entière non diffusée en flux, sur laquelle un agent se replie quand un flux s’interrompt, ainsi que le temps qu’un modèle en amont peut mettre à commencer une réponse en flux. En secondes.
SANDBOX_LLM_GATEWAY_PROVIDER_CONCURRENCY512Optionnel. Combien de requêtes la passerelle de modèles de la sandbox envoie à la fois à un fournisseur intégré (OpenAI, Anthropic, OpenRouter et les autres fournisseurs que la passerelle implémente elle-même), que toutes les organisations partagent. Le backend lit cette valeur et s’en sert pour configurer la passerelle. La passerelle garde autant de workers pour chaque fournisseur, qu’il soit appelé ou non : une valeur plus élevée coûte donc de la mémoire à la passerelle. Une requête qui trouve tous les workers occupés attend dans une file 16 fois plus longue. Un nombre entier de 1 à 5 000.
SANDBOX_LLM_GATEWAY_CUSTOM_PROVIDER_CONCURRENCY64Optionnel. La même chose pour un fournisseur personnalisé. Le backend en crée un dans la passerelle pour chaque organisation et chaque modèle, le nombre de ces fournisseurs croît donc avec les organisations et les modèles, et chacun garde ses workers comme un fournisseur intégré. La voie Anthropic d’un fournisseur intégré pour les environnements d’agent (Claude Code via l’endpoint Anthropic d’OpenRouter) reçoit son propre fournisseur par organisation et par modèle, dimensionné par cette valeur. Les sessions sandbox d’une organisation ont rarement plus de quelques appels de modèle en cours à la fois sur un même modèle. Augmente cette valeur quand les clés API d’une organisation envoient plus de requêtes à la fois à un même modèle personnalisé. Un nombre entier de 1 à 5 000.
SANDBOX_LLM_GATEWAY_LOG_RETENTION_DAYS3Optionnel. Combien de jours la passerelle de modèles de la sandbox conserve son journal des requêtes : une ligne par appel de modèle avec le modèle, les jetons, le coût et la durée, jamais un prompt ni une réponse. Tale ne lit pas ce journal. Les budgets, les dépenses et l’utilisation viennent des compteurs de budget des clés de la passerelle, donc un journal plus court n’en change aucun. Le backend transmet la valeur à la passerelle, qui l’applique à son premier redémarrage après que le backend la lui a transmise : elle supprime alors les lignes plus anciennes, puis environ une fois par jour. Un tale deploy redémarre la passerelle avant que le nouveau backend ne transmette la valeur ; une valeur modifiée s’applique donc à partir du déploiement suivant. Pour l’appliquer plus tôt, redémarre une fois le service sandbox-llm-gateway après le démarrage de la première session sandbox. Le stockage réutilise leur place au lieu de continuer à grossir. Un nombre entier de jours, au moins 1 ; le backend ignore toute autre valeur et l’indique par un avertissement dans son journal.

Cherche pourquoi la sortie n’est plus lue avant d’augmenter ce délai. Il limite les flux de sortie abandonnés, pas la durée totale d’une tâche. Recrée les rôles backend concernés après avoir changé l’environnement.

Un nombre de workers modifié atteint chaque fournisseur la prochaine fois qu’une organisation qui l’utilise démarre une session sandbox ou appelle les endpoints de modèles. Un nombre plus bas atteint aussi tous les autres fournisseurs que la passerelle garde : après un redémarrage, le backend les redimensionne deux à cinq minutes après sa première session sandbox ou sa première requête aux endpoints de modèles.

Un fournisseur dont l’hôte en amont ne se résout plus, comme celui d’un connecteur supprimé ou d’un hôte que tu as retiré de ton réseau, fait exception : la passerelle refuse de le réécrire, il garde donc ses workers, et le backend journalise après chaque redémarrage un avertissement qui le nomme. Supprime un tel fournisseur de la passerelle. Un fournisseur encore utilisé est recréé à sa prochaine session sandbox ou à sa prochaine requête aux endpoints de modèles. Envoie la requête depuis un conteneur backend, qui joint la passerelle et connaît son mot de passe administrateur ; sur un hôte tale deploy, son nom contient -backend-api :

bash
docker exec <backend-api-container> sh -c 'curl -fsS -X DELETE -u "${SANDBOX_LLM_GATEWAY_ADMIN_USERNAME:-admin}:$SANDBOX_LLM_GATEWAY_ADMIN_PASSWORD" "${SANDBOX_LLM_GATEWAY_URL:-http://sandbox-llm-gateway:8080}/api/providers/<name>"'

Sur Kubernetes, utilise kubectl exec -n tale deploy/backend-api -- à la place de docker exec <backend-api-container>.

La passerelle démarre les workers de tous ses fournisseurs pendant son démarrage, avant que le backend puisse la joindre. Des fournisseurs enregistrés avec plus de workers que ces valeurs (la valeur par défaut de la passerelle elle-même est de 1 000) peuvent demander plus de mémoire que sa limite, 512 Mio avec tale deploy et dans le manifeste Kubernetes, et la passerelle est alors arrêtée à chaque démarrage. Ne supprime pas son volume, qui contient son compte administrateur et les clés qu’elle a émises. Augmente plutôt la limite jusqu’à ce que la passerelle démarre : sur un hôte tale deploy, lance docker update --memory 2g --memory-swap 2g <container> pour le conteneur dont le nom se termine par -sandbox-llm-gateway ; sur Kubernetes, augmente resources.limits.memory de son Deployment. Démarre ensuite une session sandbox ou appelle les endpoints de modèles, puis attends la ligne [llm-gateway] provider worker resize finished dans le journal du backend, qui arrive deux à cinq minutes plus tard. Elle compte les fournisseurs que le backend a redimensionnés, ceux que la passerelle a refusés et ceux que le backend n’a pas pu confirmer. Un fournisseur refusé ou non confirmé garde ses workers, et un avertissement avant cette ligne le nomme ; un backend redémarré réessaie un fournisseur non confirmé. Avant de rétablir la limite, supprime comme décrit plus haut un fournisseur refusé dont l’hôte en amont ne se résout plus. Rétablis ensuite la limite avec docker update --memory 512m --memory-swap 1g <container> puis docker restart <container>, ou, sur Kubernetes, la valeur d’origine du Deployment.

Ingestion de liens vidéo (yt-dlp)

Le worker utilise ces valeurs pour récupérer les transcriptions vidéo. Son image comprend yt-dlp et un plugin de jetons PO. Importation vidéo distingue les restrictions de source, les problèmes de sortie réseau et les sessions autorisées. Recrée le worker après une modification de son environnement ; relire une variable dans un processus ne recharge pas .env.

NomDéfautDescription
VIDEO_INGEST_PROXY_URLnon définiProxy des requêtes yt-dlp. Protocoles admis : http, https, socks4, socks4a, socks5, socks5h ; le dernier résout le DNS de destination au proxy. Utilise un service de sortie approuvé.
VIDEO_INGEST_POT_PROVIDER_URLhttp://bgutil-provider:4416 (intégré)URL du fournisseur de jetons PO. Le sidecar fourni est utilisé par défaut lorsque le plugin est présent dans l’image. Les jetons peuvent aider la récupération, sans autoriser du contenu privé ni garantir le succès.
VIDEO_INGEST_FETCH_POTalways dès qu'un fournisseur est branchéQuand demander des jetons : never, auto ou always. Le parcours fourni utilise always. Choisis never pour désactiver délibérément cette source de jetons.
VIDEO_INGEST_YTDLP_PLUGIN_DIRS/opt/yt-dlp/plugins (intégré)Répertoire depuis lequel yt-dlp charge les plugins — chaque plugin imbriqué un niveau plus bas (<dir>/<nom>/yt_dlp_plugins/…). Par défaut, le répertoire de plugins bgutil intégré quand il est présent ; ne le remplace que pour ajouter tes propres plugins.
VIDEO_INGEST_COOKIES_FILEnon définiChemin dans le worker vers un fichier de cookies Netscape. Protège-le comme des identifiants de compte et utilise une session autorisée. Le pool de sessions par organisation décrit dans le guide vidéo permet un import et une révocation gérés.
VIDEO_INGEST_PLAYER_CLIENTdefault,tv_simplyListe de repli des clients de lecture YouTube, séparés par des virgules. Quand un fournisseur de PO tokens est branché, la valeur par défaut s'élargit à default,mweb,tv_simply (mweb exige un token GVS) ; définis-la explicitement pour forcer une liste.
VIDEO_INGEST_PO_TOKENnon définiPO token défini manuellement (CLIENT.CONTEXT+TOKEN). Surtout pour les tests — les tokens sont liés à l'ID de la vidéo et éphémères ; privilégie le fournisseur.
VIDEO_INGEST_IMPERSONATEnon définiCible d'imitation TLS/JA3 du navigateur (p. ex. safari). Nécessite curl_cffi dans l'image ; à laisser non défini sauf disponibilité connue.
VIDEO_INGEST_BIN_DIRnon définiRépertoire ajouté en tête du PATH du processus enfant yt-dlp/ffmpeg, pour qu'un yt-dlp auto-provisionné (et son runtime Deno) installé hors des répertoires bin intégrés soit trouvé en premier. L'image du backend intègre yt-dlp dans le PATH, donc laisse-le non défini là ; définis-le sur un hôte ou une machine de dev avec sa propre toolchain.
VIDEO_INGEST_FFMPEG_LOCATION/usr/bin/ffmpegChemin absolu vers le ffmpeg que yt-dlp utilise pour la post-production (conversion des sous-titres, extraction audio). À surcharger quand ffmpeg vit ailleurs — p. ex. le /opt/homebrew/bin/ffmpeg de Homebrew sur une machine de dev macOS.