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
| Field | Shape | Description |
|---|---|---|
| entrees | Array of objects | {nom, type, requis, defaut, libelle}. Converted to JSON Schema for the assistant. |
| sorties | Array of objects | {nom, type}. Used for chaining and for summarising. |
| executer | function | (args, ctx) returning a promise of an outputs object. No modal. |
| notice | string | Read 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.
A workflow
| Step field | Effect |
|---|---|
| id | The step's outputs are stored under this identifier in the context. |
| tache | Identifier of the task to run. |
| args | Arguments, with ${step.output} substitution. |
| si | Path to a variable. The step is skipped if the value is falsy. |
| continuerSiEchec | Failure 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.
| Form | Result |
|---|---|
| "${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. |
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.
Execution
| Rule | Detail |
|---|---|
| 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
| Event | Payload |
|---|---|
| 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.
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.
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.
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.
| Type | Declaration | Fires |
|---|---|---|
| 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. |
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.
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.
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
| Task | Role |
|---|---|
| core.sql | Read-only query. Returns rows, columns, row count. |
| core.inventaire | Counts by element, material or isotopic ratio across the whole database, without requiring an element as input. |
| core.echantillons | Sample selection. Handles the empty result explicitly. |
| core.resume | n, mean, 2SD, min, max per group, computed over all rows. |
| core.analyses | One aggregated value per sample, 2SD of replicates as the error bar. |
| core.graphique | bar, line, scatter, histogram. Continuous gradient, error bars, units. |
| core.correspondance | Matching. Names are resolved to identifiers in code. |
| core.carte | Map rendered inside a sandboxed iframe. |
| core.formules | Discovery of the catalogue of 24 geochemical formulas. |
| core.calcul | Applies a formula. Parameters passed as a text string. |
| core.rapport_modeles / core.rapport | Available templates, then generation. Refuses a partial report. |
No kernel task deletes data. The automation surface, and therefore the surface exposed to the AI assistant, is exactly the list of declared tasks.