Aller au contenu principal
Version: 1.0.0

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.

Cycle de reprise
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 :

BesoinParcoursÉ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 serveurPile 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-checkpointBranches 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 interrompueProjection /operations/{id}/recovery, puis demande explicite de repriseProfil 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 :

touba.manifest.json
{
"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, le run_id et 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 runTentative interrompueNouvelle réservationRésultat
15 000 ms10 000 ms10 000 msrefus avant le handler
40 000 ms10 000 ms10 000 msré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 :

État de reprise
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é :

Requête locale
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 restaurableréinjecte le résultat sans handler
effet applied sans résultat restaurablerefuse avec EffectResultUnavailable
issue inconnue et idempotency_keynouvelle tentative, même effect_id, nouveau call_id
issue inconnue et reconcileexige un rapprochement explicite
issue inconnue et retry nonerefuse avec EffectOutcomeUnknown
tool kind: none dont le résultat est perdupeut 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​

info
  • 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_approval intermé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 SIGKILL de 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 reste unknown et suit le mode idempotency_key, reconcile ou none ; 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.