Reprise durable bornée
Une reprise durable continue une même opération après l'arrêt du processus. Ce n'est ni un nouveau run, ni un rejeu aveugle : Touba relit un checkpoint, revalide l'autorité courante et rapproche chaque effet externe avec son journal avant de continuer.
run -> checkpoint -> interruption -> demande explicite -> validation -> suite
Choisir le mécanisme de reprise
Le mot « checkpoint » recouvre plusieurs parcours. Choisissez celui du point interrompu, sans relancer le programme depuis le début :
| Besoin | Parcours | État et frontière |
|---|---|---|
| Attendre une décision humaine | --human-checkpoint-out, puis --human-resume-checkpoint et une décision exacte ; attentes durables via le serveur | Pile source/agent, boucles et continuations parallèles conservées ; décision propre à une occurrence. |
Persister la progression d'un groupe parallel | --parallel-checkpoint-out, puis --parallel-resume-checkpoint | Branches terminées restaurées sans nouvel appel, journaux privés et budgets conservés ; sauvegarde aux frontières du groupe, pas de toute instruction après sa jointure. |
| Reprendre une opération séquentielle interrompue | Projection /operations/{id}/recovery, puis demande explicite de reprise | Profil direct et acyclique détaillé ci-dessous, checkpoint touba-run-checkpoint-v1 et journal d'effets. |
Conservez la même build et les artefacts compatibles, les checkpoints privés et le journal d'effets. Un état incomplet ou altéré est refusé, pas converti. Une issue externe inconnue doit être rapprochée selon le contrat d'effet, jamais supposée réussie. Les handlers restent responsables de l'idempotence réelle du service externe. Voir Validation humaine, CLI et SDK.
Profil séquentiel de reprise d'opération
Le profil décrit dans la suite de cette page couvre un graphe
d'entrée direct, séquentiel et acyclique composé de bind, de call_tool
directs et d'un terminal give ou stop. Une décision humaine peut être
consommée seulement si elle est déjà disponible et encore valide au départ.
Rendre un parcours éligible
Chaque tool atteignable doit utiliser un manifest V1 et déclarer son contrat d'effet, y compris lorsqu'il ne produit aucune écriture externe :
{
"manifest_version": "touba-manifest-v1",
"bindings": {
"tool:examples.orders/CreateOrder": {
"handler": {
"kind": "process",
"command": "node",
"args": ["handlers/create-order.handler.mjs"],
"cwd": "."
},
"grants": ["network"],
"timeout_ms": 5000,
"max_payload_bytes": 1048576,
"effect_contract": {
"kind": "external_write",
"retry_mode": "idempotency_key"
}
}
}
}
Le package doit réellement transmettre l'effect_id au service externe
comme clé idempotente. Une déclaration dans le manifest ne rend pas à elle
seule le fournisseur idempotent.
Dans cette première tranche, la map inputs du runtime doit être vide. Le
runtime refuse d'activer RESUME-1 avant le premier checkpoint si un hôte lui
fournit à la fois des entrées et un observer durable : sans référence opaque
explicite, une chaîne pourrait être un prompt ou un credential que le
checkpoint ne doit pas sérialiser. Le serveur projet conserve la compatibilité
des runs avec entrées en les exécutant normalement, mais sans les rendre
reprenables par RESUME-1. Utilisez des constantes AIR non sensibles et les
résultats validés des nœuds admis lorsque la reprise est nécessaire.
Touba refuse le parcours avant le premier handler s'il contient une branche, une boucle, un fallback, un appel imbriqué de function, skill ou agent, une interaction utilisateur, ou une approbation humaine encore absente. Ces cas ne sont pas convertis silencieusement en une continuation générale.
Ce que conserve le checkpoint
Le checkpoint local touba-run-checkpoint-v1 conserve les informations
nécessaires à la continuation :
- l'
operation_id, lerun_idet la portée d'effet stables ; - le nœud courant et la phase de réservation ;
- les digests des artefacts qui ont autorisé l'exécution ;
- les valeurs JSON validées accumulées sur le chemin déjà exécuté ;
- les budgets et le temps actif déjà consommés ;
- l'identité d'une tentative en cours et l'ordinal de son effet ;
- le ledger borné des décisions consommées ;
- le digest du préfixe de trace et le prochain index de séquence.
Il ne conserve ni valeur de secret, ni jeton d'autorité, ni prompt brut. Au redémarrage, les secrets sont résolus de nouveau depuis leurs handles et les artefacts d'autorité sont revalidés.
Retrouver une opération après une coupure HTTP
Pour un client réseau, créez d'abord une session par POST /interactions/sessions
et conservez son session_id. Envoyez ensuite le travail par
POST /interactions/sessions/{session_id}/messages, avec un message_id
unique choisi et conservé par votre application.
Si la réponse est perdue, GET /interactions/sessions/{session_id} fournit
les opérations et leur message_id. Retrouvez l'operation_id correspondant,
puis lisez GET /operations/{operation_id}/recovery. Renvoyer exactement le
même message avec la même identité retrouve aussi son opération existante.
Une nouvelle demande métier utilise un nouveau message_id.
Le serveur enregistre cette corrélation avant de lancer le travail. Après redémarrage, la récupération passe par ces API authentifiées ; aucune lecture du stockage SQLite interne n'est nécessaire. Les droits restent ceux du serveur projet. L'application hôte gère l'accès de ses utilisateurs.
La session est une vue bornée : 100 sessions, 100 messages et 50 opérations par session. Conservez vos identifiants dans votre application et récupérez rapidement les réponses perdues. Le résultat d'admission HTTP 202 annonce la prise en charge ; l'opération porte le résultat d'exécution.
Dimensionner le budget après une panne
Avant un handler, Touba réserve son timeout_ms. Après un arrêt brutal,
cette réservation reste consommée : la durée exacte de la tentative n'a pas
pu être confirmée. Une nouvelle tentative réserve à nouveau son timeout.
| Budget du run | Tentative interrompue | Nouvelle réservation | Résultat |
|---|---|---|---|
| 15 000 ms | 10 000 ms | 10 000 ms | refus avant le handler |
| 40 000 ms | 10 000 ms | 10 000 ms | réservation possible, avec marge pour la suite |
Ajoutez le temps actif déjà confirmé, les autres opérations et une marge.
La somme doit rester strictement inférieure à max_run_ms.
Le temps passé serveur éteint ne consomme pas de temps actif.
Le refus RunTimeBudgetExceeded fournit activeElapsedMs,
reservationMs et remainingMs dans ses détails. L'événement
dispatch_refused_budget_exceeded porte les champs correspondants
active_elapsed_ms, reservation_ms, remaining_ms et limit_value.
Ces valeurs expliquent le refus sans modifier les budgets déjà réservés.
Demander la reprise
Le serveur projet détecte une opération interrompue au redémarrage, mais ne la reprend jamais automatiquement. Un client commence par lire la projection produit du serveur :
GET /operations/operation%3Aexample/recovery
Le contrat touba-operation-recovery-v1 fournit l'état, la fraîcheur, les
corrélations, un résumé non sensible des effets, les actions permises et le
motif structuré d'un refus. Le client ne déduit pas une action depuis un bouton
visible ou un ancien cache.
La demande doit ensuite identifier exactement le checkpoint observé :
POST /runs/sandbox/resume
Content-Type: application/json
{
"schema_version": "touba-operation-resume-request-v1",
"operation_id": "operation:example",
"checkpoint_revision": 4,
"checkpoint_digest": "sha256:...",
"resume_request_id": "resume:example:1"
}
La lecture exige executions.read et la reprise exige executions.run. La version
schema_version est obligatoire ; les requêtes sans contrat ou d'un ancien
format sont refusées avant toute reprise.
Quand le serveur utilise un control plane d'identité dynamique, il revalide la permission effective juste avant chaque lancement de handler. Une permission retirée entre la demande de reprise et ce lancement arrête donc l'opération sans dispatch. Le jeton courant sert uniquement à cette vérification en mémoire ; il n'est conservé ni dans le checkpoint, ni dans le journal.
Le même resume_request_id appliqué à la même révision est coalescé. Une autre
demande concurrente ou une révision obsolète reçoit
DurableResumeConflict. Une opération annulée, une autorité révoquée ou un
artefact modifié est refusé avant le handler.
Comportement après une interruption
Touba compare le checkpoint et le journal d'effets avant toute nouvelle tentative :
| État retrouvé | Comportement |
|---|---|
effet applied avec résultat restaurable | réinjecte le résultat sans handler |
effet applied sans résultat restaurable | refuse avec EffectResultUnavailable |
issue inconnue et idempotency_key | nouvelle tentative, même effect_id, nouveau call_id |
issue inconnue et reconcile | exige un rapprochement explicite |
issue inconnue et retry none | refuse avec EffectOutcomeUnknown |
tool kind: none dont le résultat est perdu | peut rejouer avec un nouveau call_id et un nouveau coût de budget |
Une tentative interrompue reste comptée. Une restauration depuis un reçu
applied ne consomme pas un nouvel appel de tool.
La projection produit classe cette situation par effects.reconciliation :
retry_safe, provider_required, unavailable ou not_required. Elle
n'invente pas une route générique de rapprochement : cette opération appartient
au package ou au fournisseur qui sait établir le résultat externe.
Une entrée de journal plus avancée ne répare le checkpoint que si elle
correspond au même call_id, à la même tentative et au même ordinal déjà
budgétés dans sa réservation pending_dispatch. Touba ne relève jamais les
index en sautant vers un call trouvé dans le journal. Sans corrélation exacte,
il refuse la reprise : il ne peut pas reconstruire honnêtement les budgets et
la réservation de temps manquants.
Limites
- Le store et le journal livrés sont locaux, mono-processus et possédés par un seul hôte à la fois.
- La reprise est explicite ; aucun redémarrage ne relance automatiquement une opération.
- Le serveur projet ne compose pas encore ce parcours avec une pause
human_approvalintermédiaire ni avec le service de triggers. - Les entrées runtime brutes excluent la reprise durable. Aucun contrat de référence opaque pour les réinjecter après redémarrage n'est livré dans cette tranche.
- La validation ou la révocation d'une décision humaine prédisponible relève de l'hôte avant de fournir cette décision au runtime.
- Avec un control plane dynamique, l'hôte revalide aussi la permission du demandeur avant chaque spawn de handler ; aucun jeton d'autorité n'est persistant.
- Les checkpoints humains historiques restent séparés et ne sont pas migrés
silencieusement vers
touba-run-checkpoint-v1. - Il n'existe ni transaction distribuée, ni coordination multi-hôte, ni
garantie
exactly-once, ni compensation implicite. - Le digest détecte une divergence locale ; sans signature externe, il ne protège pas contre un acteur capable de modifier le fichier et de recalculer son digest.
- Touba peut bloquer une valeur de secret déjà résolue et connue du runtime, mais ne peut pas reconnaître par heuristique un credential qu'un handler retournerait à tort comme simple donnée métier.
- Cette V1 ne calcule pas la vivacité des bindings : une valeur produite peut rester dans les checkpoints suivants même si le reste du graphe ne la relit plus. Le graphe et les budgets bornent cette rétention locale.
- Hors sandbox capable de terminer les descendants avec leur parent, un
handler peut rester en vol pour une durée non bornée après un
SIGKILLde l'hôte. Le timeout parent ne lui survit pas et son environnement, y compris les secrets autorisés, peut rester dans sa mémoire. Son issue resteunknownet suit le modeidempotency_key,reconcileounone; Touba ne revendique pas l'absence universelle de processus orphelins.
Pour le contrat des écritures et des reçus, lire Effets externes et idempotence bornée.