Contexte et objectifs
Le projet safe-upgrade vise à automatiser la mise à jour de dépendances dans un dépôt Git tout en garantissant que chaque modification reste compatible avec le code existant. L’enjeu réside dans le fait que les étapes classiques (mise à jour de version, installation, exécution des tests) peuvent masquer des ruptures d’API ou des changements de comportement non couverts par la suite de tests. L’architecture proposée répartit les responsabilités entre trois composants : Jev pour la prise de décision, LangGraph pour l’orchestration du workflow, et Tenuo pour la gestion d’une autorité limitée à chaque tâche.
Mécanisme de décision avec Jev
Jev implémente le System One Model : le développeur fournit l’état du dépôt et une série de questions typées. Le SDK TypeSafe expose une fonction choice qui renvoie un objet contenant le choix sélectionné, une distribution de probabilités et un score de confidence. Exemple de code :
import { choice, TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient();
const criteria = Object.fromEntries(
input.eligibleActions.map(({action, reason}) => [action, reason])
);
const result = await client.systemOne({
state: JSON.parse(JSON.stringify(input)),
questions: {
next: choice(
"Choose the eligible action that most directly reduces the unresolved risk in this upgrade.",
criteria
)
}
});
const answer = result.answers.next;
Le résultat typé ressemble à :
{
"type": "choice",
"choice": "author_tests",
"confidence": 0.82,
"probabilities": {
"author_tests": 0.74,
"assess_verification": 0.18,
"implement": 0.08
}
}
Le workflow valide d’abord que le libellé retourné appartient bien à l’ensemble des actions admissibles, puis compare le score de confiance à un seuil (confidenceThreshold). En dessous du seuil, le système bascule vers une route déterministe (deterministicFallback), assurant que l’absence de décision fiable ne bloque pas le processus.
Contrôle d’autorité via Tenuu
Après la décision, chaque action s’exécute dans une session Tenuu dont le warrant définit les capacités autorisées. Le parent warrant représente le maximum de droits accordés au run complet ; la fonction narrow crée un warrant enfant avec un sous‑ensemble de capacités, une durée de vie limitée (ttlSeconds: 600) et l’interdiction de déléguer à nouveau. Exemple :
const parentSession = tenuo.sessionFromWire({
warrant: process.env.TENUO_RUN_WARRANT!,
holderKey: createTenuo.holderKeyFromEnv("TENUO_RUN_HOLDER_SECRET")
});
const testAuthor = tighten(
pick(ceilings, [...READ_ONLY, "write_test_file", "run_check"]),
"run_check", "kind", oneOf(["test"])
);
const childSession = tenuo.narrow(parentSession, testAuthor, {
terminal: true,
ttlSeconds: 600
});
Le modèle empêche, par exemple, un worker implementer d’utiliser la capacité write_test_file. Toute tentative génère un enregistrement de refus :
TENUO_TOOL_NOT_AUTHORIZED worker: implementer capability: write_test_file
Le journal inclut le nom du worker, la capacité refusée, le hachage des arguments et l’identifiant de session, mais jamais le contenu du fichier, limitant ainsi les fuites d’information.
Orchestration du workflow avec LangGraph
LangGraph conserve l’état du dépôt, les constats, les décisions et les preuves à chaque nœud du graphe. Chaque nœud reçoit un état explicite, exécute son traitement, puis renvoie une mise à jour qui devient l’entrée du nœud suivant. Des points de contrôle (checkpoints) sont placés aux frontières critiques : après la décision Jev, après la création du warrant Tenuu, et après chaque tentative d’action. Cette granularité garantit la reprise possible en cas d’échec et la traçabilité complète du processus.
En combinant une décision probabiliste typée, une autorité fine‑grainée et un graphe d’exécution persistant, l’agent safe-upgrade montre comment séparer jugement, flux de contrôle et droits d’exécution peut réduire les risques liés aux mises à jour automatisées tout en conservant la flexibilité nécessaire aux projets open‑source.