Workflows et tâches

Le moteur (workflow-engine.js) enchaine des tâches. Une tâche accepte des paramètres et s'exécute sans écran. C'est la condition pour qu'un enchainement se déroule sans intervention, et la raison pour laquelle les tâches sont déclarées à part au lieu d'être devinées parmi les commandes. Les mêmes tâches constituent les outils de l'assistant IA.

Déclarer une tâche

var jeton = K.workflows.declarerTache({ id: 'phreeqc.saturation', source: 'phreeqc', title: { fr: 'PHREEQC : indices de saturation', en: 'PHREEQC: saturation indices' }, entrees: [ { nom: 'solution', type: 'string', requis: true, libelle: { fr: 'Solution', en: 'Solution' } }, { nom: 'temperature', type: 'number', requis: false, defaut: 25, libelle: { fr: 'Température (°C)', en: 'Temperature (°C)' } } ], sorties: [ { nom: 'especes', type: 'array' }, { nom: 'si', type: 'object' } ], executer: function (args, ctx) { return fetch('/api/phreeqc/equilibrium', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(args) }).then(function (r) { return r.json(); }); } });
ChampFormeDescription
entreesTableau d'objets{nom, type, requis, defaut, libelle}. Converti en JSON Schema pour l'assistant.
sortiesTableau d'objets{nom, type}. Sert à l'enchainement et au résumé.
executerfunction(args, ctx) vers une promesse d'objet de sorties. Aucune modale.
noticestringLu par tool-schema.js et transmis au modèle comme description d'outil. Voir l'encadré.

Une tâche invalide (sans id ou sans executer) est refusée avec une erreur en console, et le jeton rendu est inerte. Toute mutation émet workflow.tasks.changed.

Le champ notice n'est pas décoratif. Le modèle lit la description de l'outil au moment de décider s'il l'appelle, pas le prompt système en entier. Une consigne d'usage placée dans le prompt système est fréquemment ignorée ; la même consigne dans notice est lue à la décision.

Un workflow

{ "id": "correction-blanc-v3", "nom": "Correction blanc v3", "description": "...", "etapes": [ { "id": "e1", "tache": "core.sql", "args": { "requete": "SELECT id, Cr, material_type FROM samples WHERE campaign='GM-2026'" } }, { "id": "e2", "tache": "core.resume", "args": { "element": "Cr", "par": "material_type" } }, { "id": "e3", "tache": "core.graphique", "args": { "type": "bar", "donnees": "${e2.resume}", "y": "moyenne" }, "si": "e2.resume", "continuerSiEchec": false } ] }
Champ d'étapeEffet
idLes sorties de l'étape sont rangées sous cet identifiant dans le contexte.
tacheIdentifiant de la tâche à exécuter.
argsArguments, avec substitution ${etape.sortie}.
siChemin d'une variable. L'étape est sautée si la valeur est fausse.
continuerSiEchecL'échec ou l'absence de la tâche n'interrompt pas la chaine.

Substitution des variables

Il n'y a pas d'eval. Un workflow circule sous forme de fichier et peut arriver par courriel ; évaluer une expression qu'il contient reviendrait à exécuter du code venu de l'extérieur. Une substitution littérale suffit à tout ce qu'un enchainement doit faire.

FormeRésultat
"${e1.lignes}", référence exacte La valeur, pas sa représentation textuelle. Un tableau reste un tableau.
"Campagne ${e1.nom} terminée", référence incluse Une chaine, avec la valeur convertie en texte. Une valeur absente devient une chaine vide.
Les sorties sont rangées sous l'identifiant de l'étape, jamais fusionnées à plat. Deux étapes qui produisent toutes deux lignes s'écraseraient, et la seconde reprendrait les résultats de la première sans qu'aucune erreur ne soit levée.

Ce qui est délibérément absent

L'enchainement est linéaire, avec des conditions mais sans retour arrière. Aucune boucle n'est possible. Un moteur plus complet conduirait à écrire des programmes dans une interface graphique, où ils sont difficiles à relire, à éprouver et à suivre en version.

Ce qui demande une boucle demande un script, et IsoFind exécute déjà du Python et du R. Voir les panneaux Notebook et Console.

Exécution

K.workflows.executer(flux, variablesInitiales); // Promise(passage) K.workflows.annuler(); K.workflows.enCours(); // le passage courant, ou null K.workflows.executions(); // les 40 derniers passages
RègleDétail
Un seul workflow à la fois Un second lancement est rejeté, avec le nom de celui qui tourne.
Tâche inconnue État absente, et non échec : c'est un workflow écrit pour une autre installation, ou pour un plugin qui n'est pas là. Le dire ainsi évite de chercher un bogue là où il n'y a qu'une dépendance manquante.
Annulation Lève un drapeau, n'interrompt pas. Une tâche en vol qui écrit dans la base doit finir son écriture : la couper au milieu laisserait la base dans un état que le registre ne saurait pas décrire.

États d'une étape

encours, ok, sautee (condition si fausse), absente, echec. Le passage lui-même vaut encours, ok, annule ou echec.

Évènements

ÉvènementCharge utile
workflow.started{passage}
workflow.step{passage, etape}, émis à l'entrée et à la sortie de chaque étape
workflow.finished{passage}, y compris en échec ou annulation
workflow.changed{id}, à l'enregistrement ou la suppression d'un flux
workflow.tasks.changed{id}, à la déclaration ou au retrait d'une tâche

Un workflow enregistré devient une commande

K.workflows.enregistrer(def) persiste le flux sous isofind_workflows et inscrit une commande workflow.<id>, catégorie workflow. Elle apparait donc dans la palette, dans le panneau Actions, et peut recevoir un raccourci.

Le registre ne distingue pas un workflow d'une commande native : un enchainement enregistré s'atteint donc par les mêmes chemins qu'une fonction livrée avec le logiciel, au lieu de rester dans un sous-menu.

Paramètres

Un workflow enregistré peut déclarer des paramètres. Ils entrent dans le contexte initial du passage et se réfèrent comme n'importe quelle sortie d'étape, par ${param.nom}. Rejouer un passage réutilise ses paramètres, et le menu d'un passage permet de les copier tels quels.

K.workflows.executer(flux, { element: 'Sr', campagne: 'GM-2026' });

Imbrication

Une tâche core.workflow lance un autre workflow et attend qu'il finisse. Ses sorties se rangent sous l'identifiant de l'étape, comme pour toute autre tâche. Le nom est accepté autant que l'identifiant : obliger à retrouver wf-lx8k2 dans le stockage local rendrait la composition impraticable.

Un workflow ne peut pas s'appeler lui-même, directement ou en passant par un troisième. Le moteur tient une pile et refuse. L'imbrication est bornée, sans quoi une récursion involontaire remplirait la base d'écritures qu'aucun opérateur n'a demandées.

La tâche core.workflow est déclarée en écriture par précaution : le workflow appelé peut écrire, et cette tâche ne sait pas ce qu'il fait. La déclarer en lecture ouvrirait, en mode conversation, un chemin indirect vers toutes les écritures que le modèle n'a pas le droit d'appeler directement.

Mode simulation

K.workflows.simuler(flux, params) exécute un passage sans écrire. Les tâches en lecture s'exécutent normalement ; les tâches en écriture rapportent ce qu'elles feraient sans le faire. Un passage de simulation est marqué comme tel dans l'historique, et le rejouer depuis un menu relance une simulation, jamais une écriture réelle : sans cette règle, rejouer un passage depuis l'historique déclencherait des écritures que l'utilisateur n'attendait pas.

Déclencheurs et automatisation

Un workflow enregistré peut déclarer un déclencheur, mais un déclencheur déclaré ne suffit pas : il faut encore armer le workflow. La séparation est délibérée. Importer un workflow qui déclare un horaire ne doit pas le faire partir tout seul la nuit suivante sur la machine de celui qui l'a reçu.

TypeDéclarationDéclenchement
Horaire { type: 'horaire', heure, jours } À l'heure dite, les jours retenus (ou tous les jours si la liste est vide).
Évènement { evenement, si } Après lims.pull.done, lims.pull.error ou workflow.finished, sous réserve de la condition si.
Manuel Aucun déclencheur Ne part que sur action de l'utilisateur.
Un workflow armé se déclenche sans que personne soit devant l'écran. Il porte donc une marque armé partout où il apparait, liste et détail, sans qu'il faille ouvrir l'éditeur. Sans cette marque, une modification portant sur quarante échantillons pendant la nuit serait difficile à rattacher à sa cause.

L'écran d'aperçu, avant d'armer

Avant d'armer un workflow, en particulier un workflow reçu d'un tiers, un écran d'aperçu énonce ce qu'il fait. L'écran indique d'abord le nombre d'étapes qui écrivent dans la base, sous le nom de l'utilisateur, dans le registre signé, chacune nommée. Un workflow en lecture seule le dit aussi, tout aussi clairement.

3 étape(s). 2 ÉCRIVENT dans votre base, sous votre nom, dans le registre signé : Corriger le blanc, Marquer validé. Il déclare un déclencheur (horaire 02:00). IL NE SERA PAS ARMÉ : à vous de l'armer si vous le voulez. Tâches absentes de cette installation : phreeqc.saturation. Ces étapes échoueront.

Les workflows peuvent se partager en documents signés. Un workflow reçu porte son signataire, vérifié contre le carnet de clés. Quelques semaines plus tard, rien ne distingue à l'écran un workflow reçu d'un workflow écrit sur place, et cette information ne se retrouve pas autrement.

Inspection par l'assistant

L'assistant IA dispose de tâches d'inspection en lecture seule pour diagnostiquer l'automatisation sans jamais la modifier : core.workflow_liste, core.workflow_detail, core.workflow_passages et core.workflow_simuler. Il peut expliquer pourquoi un workflow a échoué cette nuit ; il ne peut ni l'armer, ni le lancer, ni le modifier.

Le workflow est un agent

Chaque tâche reçoit dans son contexte d'exécution un champ agent, valorisé à workflow: <nom>. Le registre signé distingue l'acteur, qui décide, de l'agent, qui exécute.

Colin Ferrari, via workflow « Correction blanc v3 »

Devant une correction appliquée à quarante échantillons, la première question posée en audit est de savoir s'il s'agit d'un geste répété ou d'une règle automatique, les deux n'engageant pas la même responsabilité. Les champs acteur et agent portent cette distinction dans chaque entrée.

Tâches du noyau

TâcheRôle
core.sqlRequête en lecture. Rend lignes, colonnes, nombre de lignes.
core.inventaireComptage par élément, matériau ou rapport isotopique sur toute la base, sans exiger d'élément en entrée.
core.echantillonsSélection d'échantillons. Gère explicitement le résultat vide.
core.resumen, moyenne, 2SD, min, max par groupe, calculés sur la totalité des lignes.
core.analysesUne valeur agrégée par échantillon, 2SD des réplicats en barre d'erreur.
core.graphiquebar, line, scatter, histogram. Dégradé continu, barres d'erreur, unités.
core.correspondanceRecherche de correspondances. Les noms sont résolus en identifiants dans le code.
core.carteCarte rendue dans une iframe cloisonnée.
core.formulesDécouverte du catalogue de 24 formules géochimiques.
core.calculApplication d'une formule. Paramètres en chaine texte.
core.rapport_modeles / core.rapportModèles disponibles, puis génération. Refuse un rapport partiel.
Les paramètres de core.calcul passent en chaine texte ("delta_0=0.15; epsilon=-1.4") pour traverser le système de substitution du moteur, qui opère sur des valeurs et non sur des structures imbriquées.

Aucune tâche du noyau ne supprime de données. La surface d'automatisation, et donc la surface exposée à l'assistant IA, est exactement la liste des tâches déclarées.