Workflows and tasks

The engine (workflow-engine.js) chains tasks. A task takes parameters and runs without a screen. That is the condition for a chain to run unattended, and the reason tasks are declared separately instead of being guessed from among the commands. The same tasks are the tools of the AI assistant.

Declaring a task

var token = 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(); }); } });
FieldShapeDescription
entreesArray of objects{nom, type, requis, defaut, libelle}. Converted to JSON Schema for the assistant.
sortiesArray of objects{nom, type}. Used for chaining and for summarising.
executerfunction(args, ctx) returning a promise of an outputs object. No modal.
noticestringRead by tool-schema.js and passed to the model as the tool description. See below.

An invalid task (no id, or no executer) is refused with a console error, and the returned token is inert. Every mutation emits workflow.tasks.changed.

The notice field is not decorative. The model reads the tool description at the moment it decides whether to call it, not the system prompt as a whole. A usage instruction placed in the system prompt is frequently ignored; the same instruction inside notice is read at decision time.

A workflow

{ "id": "correction-blanc-v3", "nom": "Correction blanc v3", "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 } ] }
Step fieldEffect
idThe step's outputs are stored under this identifier in the context.
tacheIdentifier of the task to run.
argsArguments, with ${step.output} substitution.
siPath to a variable. The step is skipped if the value is falsy.
continuerSiEchecFailure or absence of the task does not break the chain.

Variable substitution

There is no eval. A workflow travels as a file and can arrive by email; evaluating an expression it contains would mean running code from outside. Literal substitution is enough for everything a chain needs to do.

FormResult
"${e1.lignes}", exact reference The value, not its textual form. An array stays an array.
"Campaign ${e1.nom} done", embedded reference A string, with the value converted to text. A missing value becomes an empty string.
Outputs are stored under the step identifier, never merged flat. Two steps both producing lignes would overwrite each other, and the second would take over the first one's results with no error raised.

What is deliberately absent

Chaining is linear, with conditions but no going back. No loop is possible. A more complete engine would lead to writing programs inside a graphical interface, where they are hard to read back, to test and to keep under version control.

Anything that needs a loop needs a script, and IsoFind already runs Python and R. See the Notebook and Console panels.

Execution

K.workflows.executer(flow, initialVariables); // Promise(run) K.workflows.annuler(); K.workflows.enCours(); // the current run, or null K.workflows.executions(); // the last 40 runs
RuleDetail
One workflow at a time A second run is rejected, naming the one already running.
Unknown task State absente, not failed: this is a workflow written for another installation, or for a plugin that is not present. Saying so avoids hunting for a bug where there is only a missing dependency.
Cancellation Raises a flag; it does not interrupt. A task in flight that writes to the database must finish its write: cutting it in half would leave the database in a state the registry could not describe.

Step states

encours, ok, sautee (the si condition was false), absente, echec. The run itself is encours, ok, annule or echec.

Events

EventPayload
workflow.started{passage}
workflow.step{passage, etape}, emitted on entering and on leaving each step
workflow.finished{passage}, including on failure or cancellation
workflow.changed{id}, when a flow is saved or deleted
workflow.tasks.changed{id}, when a task is declared or withdrawn

A saved workflow becomes a command

K.workflows.enregistrer(def) persists the flow under isofind_workflows and registers a workflow.<id> command in the workflow category. It therefore shows up in the palette and the Actions panel, and can be given a shortcut.

The registry does not tell a workflow from a native command: a saved chain is therefore reached by the same paths as a feature shipped with the software, instead of staying in a submenu.

Parameters

A saved workflow may declare parameters. They enter the run's initial context and are referenced like any step output, through ${param.name}. Re-running a run reuses its parameters, and a run's menu lets you copy them as they were.

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

Nesting

A core.workflow task launches another workflow and waits for it. Its outputs are stored under the step identifier, like any other task. The name is accepted as readily as the identifier: forcing someone to find wf-lx8k2 in local storage would make composition impractical.

A workflow cannot call itself, directly or through a third one. The engine keeps a stack and refuses. Nesting is bounded, otherwise an accidental recursion would fill the database with writes no operator asked for.

The core.workflow task is declared as a write by precaution: the called workflow may write, and this task does not know what it does. Declaring it read-only would open, in conversation mode, an indirect path to every write the model is not allowed to call directly.

Simulation mode

K.workflows.simuler(flow, params) runs a pass without writing. Read tasks run normally; write tasks report what they would do without doing it. A simulated run is marked as such in the history, and re-running it from a menu launches another simulation, never a real write: without that rule, replaying a pass from the history would trigger writes the user was not expecting.

Triggers and automation

A saved workflow may declare a trigger, but a declared trigger is not enough: the workflow must also be armed. The separation is deliberate. Importing a workflow that declares a schedule must not make it fire on its own the following night on the machine of whoever received it.

TypeDeclarationFires
Schedule { type: 'horaire', heure, jours } At the stated time, on the chosen days (or every day if the list is empty).
Event { evenement, si } After lims.pull.done, lims.pull.error or workflow.finished, subject to the si condition.
Manual No trigger Fires only on user action.
An armed workflow fires with nobody in front of the screen. It therefore carries an armed mark everywhere it appears, list and detail, without needing to open the editor. Without that mark, a change affecting forty samples overnight would be hard to trace back to its cause.

The preview screen, before arming

Before arming a workflow, especially one received from a third party, a preview screen states what it does. The screen states first the number of steps that write to the database, under the user's name, in the signed registry, each one named. A read-only workflow says so too, just as clearly.

3 step(s). 2 WRITE to your database, under your name, in the signed registry: Correct blank, Mark validated. It declares a trigger (schedule 02:00). IT WILL NOT BE ARMED: arm it yourself if you want it. Tasks missing on this installation: phreeqc.saturation. Those steps will fail.

Workflows can be shared as signed documents. A received workflow carries its signer, checked against the key ring. A few weeks later nothing on screen tells a received workflow from one written on the spot, and that information cannot be recovered any other way.

Inspection by the assistant

The AI assistant has read-only inspection tasks to diagnose automation without ever modifying it: core.workflow_liste, core.workflow_detail, core.workflow_passages and core.workflow_simuler. It can explain why a workflow failed overnight; it cannot arm it, run it, or modify it.

A workflow is an agent

Every task receives an agent field in its execution context, set to workflow: <name>. The signed registry distinguishes the actor, who decides, from the agent, which executes.

Colin Ferrari, via workflow "Correction blanc v3"

Faced with a correction applied to forty samples, the first question raised in an audit is whether it was a repeated gesture or an automatic rule, since the two do not carry the same responsibility. The actor and agent fields carry that distinction in every entry.

Kernel tasks

TaskRole
core.sqlRead-only query. Returns rows, columns, row count.
core.inventaireCounts by element, material or isotopic ratio across the whole database, without requiring an element as input.
core.echantillonsSample selection. Handles the empty result explicitly.
core.resumen, mean, 2SD, min, max per group, computed over all rows.
core.analysesOne aggregated value per sample, 2SD of replicates as the error bar.
core.graphiquebar, line, scatter, histogram. Continuous gradient, error bars, units.
core.correspondanceMatching. Names are resolved to identifiers in code.
core.carteMap rendered inside a sandboxed iframe.
core.formulesDiscovery of the catalogue of 24 geochemical formulas.
core.calculApplies a formula. Parameters passed as a text string.
core.rapport_modeles / core.rapportAvailable templates, then generation. Refuses a partial report.
The parameters of core.calcul are passed as a text string ("delta_0=0.15; epsilon=-1.4") so that they survive the engine's substitution system, which works on values rather than nested structures.

No kernel task deletes data. The automation surface, and therefore the surface exposed to the AI assistant, is exactly the list of declared tasks.