Taxonomie des opérateurs Caméléon
Version : v3 — 07/03/2026 Auteurs : O. Cugnon de Sévricourt · Léa (Recherche)
Principe de classification
Chaque opérateur est défini par une famille et zéro à trois attributs orthogonaux.
La famille est structurelle — elle découle de la topologie (ports, firing policy). Les attributs sont orthogonaux à la famille — ils décrivent le comportement au runtime.
Deux familles
Structurel
Contrôle de flux. Pas de logique métier, pas de transformation de données. La topologie dit tout — l'opérateur route, duplique, synchronise ou filtre.
Comportemental
Logique propre. Traitement de données, interaction, ou production de valeur.
Note terminologique — rapport à la littérature
La distinction Structurel / Comportemental est propre à Caméléon. Elle recouvre des distinctions analogues dans la littérature, sans leur être identique :
- Van der Aalst et al. (2003) distinguent routing constructs (places et
- transitions de contrôle dans les CPN) et activity nodes (transitions portant
- une logique métier). Notre Structurel correspond aux routing constructs, notre
- Comportemental aux activity nodes.
- BPMN (OMG, 2011) distingue gateways (contrôle de flux exclusif, parallèle,
- inclusif) et tasks (unités de travail). La correspondance est directe :
- Gateway ≈ Structurel, Task ≈ Comportemental.
- YAWL (van der Aalst & ter Hofstede, 2005) utilise les termes conditions
- et tasks, où les conditions sont des places de contrôle et les tasks portent
- la logique. Même structure conceptuelle.
Ce qui est original dans Caméléon : les attributs orthogonaux human et live n'ont pas d'équivalent direct dans ces formalismes. human rejoint partiellement le concept de human task dans YAWL et les resource patterns (Russell et al., 2006), mais avec une sémantique plus précise (bloquant + validation unique). live est une contribution originale — ancré dans les open nets (Lohmann et al., 2007) et les langages réactifs synchrones (Caspi et al., 1987), mais inexistant comme attribut d'opérateur dans YAWL, BPMN ou LangChain.
Ces écarts terminologiques sont à documenter explicitement dans le paper v2 — ils ne sont pas des faiblesses mais des contributions.
Références - van der Aalst, W.M.P., ter Hofstede, A.H.M., Kiepuszewski, B., & Barros, A.P. (2003). Workflow Patterns. Distributed and Parallel Databases, 14(3), 5–51. - van der Aalst, W.M.P. & ter Hofstede, A.H.M. (2005). YAWL: Yet Another Workflow Language. Information Systems, 30(4), 245–275. - Russell, N., ter Hofstede, A.H.M., van der Aalst, W.M.P., & Mulyar, N. (2006). Workflow Control-Flow Patterns: A Revised View. BPM Center Report BPM-06-22. - OMG (2011). Business Process Model and Notation (BPMN) 2.0. - Lohmann, N., Massuthe, P., & Wolf, K. (2007). Operating Guidelines for Finite-State Services. TACAS 2007, LNCS 4424. - Caspi, P. et al. (1987). Lustre: A Declarative Language for Programming Synchronous Systems. POPL 1987.
Trois attributs orthogonaux
| Attribut | Signification | Composition |
config | L'opérateur a des paramètres statiques définis avant le run | Fermée |
human | Bloque le flux pendant le run — attend une validation humaine | Fermée |
live | Injecte en continu sur événement utilisateur | Ouverte |
Un opérateur sans attribut s'exécute automatiquement, sans config ni interaction. Les attributs sont cumulables — un opérateur peut être config + human. La présence de live dans une composition la rend ouverte (ADR-035).
Catalogue des opérateurs
Structurels
| Opérateur | Ports | Firing | Attributs | Rôle |
| Fork | 1→N | ANY | — | Duplique une donnée vers N sorties. addPlug() pour N>2 |
| Merge | N→1 | COND | — | XOR-join — ∃ input NEW, EMPTY ignoré. Pièce maîtresse des boucles |
| Barrier | N→N | ALL | — | Barrière — attend que toutes les entrées soient NEW simultanément |
| Switch | 1+1→N | COND | — | Route selon un index dans les données (in_data + in_index). addPlug() pour N>2 |
| Gate | 1+1→2 | COND | — | Alias Switch(N=2) — in_condition: DataBool → out_true / out_false |
| Filter | 1→2 | COND | config | Route selon un prédicat défini en config — out_match / out_no_match |
| Router | 1→N | COND | config | Route selon des prédicats multiples en config — premier vrai gagne. addPlug() pour N>2 |
| Guard (backlog) | 1→2 | COND | config | Sort d'une boucle quand une condition config est remplie |
Comportementaux
| Opérateur | Ports | Firing | Attributs | Rôle |
| Source | 0→N | Spécial | config | Produit une valeur statique au démarrage (fire si sorties EMPTY) |
| FileLoader | 0→N | Spécial | human | Charge un fichier sélectionné par l'utilisateur au démarrage — fire once |
| HumanInput | N→M | ANY | human | Bloque le flux et attend une saisie humaine validée |
| LiveSource | 0→N | Environnement | live | Injecte une valeur à chaque événement utilisateur — ouvre la composition |
| Processor | N→M | ANY | config | Transformation générique — logique définie en config ou dans run() |
| Accumulator | N→M | ANY | config | Traitement par lot — accumule avant de produire |
| Sink | N→0 | ANY | — | Consomme ou termine un flux — déclenche la fin explicite si terminal |
Règle de tir par défaut
ANY + garde EMPTY : tire dès qu'au moins une entrée est NEW, à condition qu'aucune ne soit EMPTY.
Au premier passage, l'opérateur attend que toutes ses entrées aient reçu une donnée. Ensuite il devient réactif — tire dès qu'un input est rafraîchi (NEW), même si les autres sont OLD. C'était le comportement de l'implémentation C++/QT originale.
Les trois firing policies
| Policy | Condition | Opérateurs |
| ANY | Au moins 1 NEW, aucun EMPTY | Défaut — Processor, Fork, Sink, Accumulator, HumanInput |
| ALL | Toutes les entrées NEW | Barrier |
| COND | Sous-ensemble spécifique (délégué à l'opérateur) | Switch, Gate, Filter, Router, Guard, Merge |
Source : fire si toutes les sorties sont EMPTY. LiveSource : fire sur user_event() — transition d'environnement (ADR-035).
canFire() — Template Method sur l'opérateur
Le moteur appelle op.canFire(), point. La règle est portée par l'opérateur.
// Défaut ANY + garde EMPTY
const Operator = {
canFire(inputStates, outputStates) {
if (inputStates.some(s => s === "EMPTY")) return false;
return inputStates.some(s => s === "NEW");
}
};
// Overrides
Merge.canFire = (ins) => ins.some(s => s === "NEW"); // XOR-join — EMPTY ignored
Barrier.canFire = (ins) => ins.every(s => s === "NEW");
Switch.canFire = (ins) => ins.in_data === "NEW" && ins.in_index === "NEW";
Gate.canFire = (ins) => ins.in_data === "NEW" && ins.in_condition === "NEW";
Filter.canFire = (ins) => ins.in_data === "NEW";
Source.canFire = (ins, outs) => outs.every(s => s === "EMPTY");
// LiveSource : eop = user_event() — géré par le renderer (ADR-035)
Boucles
- Merge (ANY) est la pièce maîtresse — réinjecte un token dans la boucle
- Guard (COND, backlog) sort de la boucle quand une condition config est remplie
- Sécurité :
max_loop_iterations dans la config pour détecter les boucles infinies
Modes d'exécution
| Mode | Comportement |
| Séquentiel (actuel) | Un seul opérateur tire par step |
| Concurrent (backlog) | Tous les fireable tirent simultanément |
| Réactif | Déclenché par la présence de LiveSource — dérivé de la topologie, pas de config |
Signature type
// Structurel — pas de config, pas d'attribut
{
id: "fork",
family: "structural",
firingPolicy: "ANY",
inputs: [{ id: "in", type: "Any" }],
outputs: [{ id: "out_0", type: "Any" }, { id: "out_1", type: "Any" }],
run(inputs) { return { out_0: inputs.in, out_1: inputs.in }; }
}
// Comportemental avec config
{
id: "processor_example",
family: "behavioral",
attributes: ["config"],
inputs: [{ id: "in_a", type: "DataString" }],
outputs: [{ id: "out_a", type: "DataString" }],
run(inputs, { config }) {
return { out_a: transform(inputs.in_a, config) };
}
}
// Comportemental avec human
{
id: "human_input",
family: "behavioral",
attributes: ["human"],
control: "text_input_control",
inputs: [{ id: "in_context", type: "Any" }],
outputs: [{ id: "out_value", type: "DataString" }],
run(inputs, { signal }) {
return new Promise((resolve, reject) => {
signal?.addEventListener("abort", () => reject(new Error("cancelled")))
})
}
}
// Comportemental avec live — ouvre la composition
{
id: "live_source",
family: "behavioral",
attributes: ["live"],
control: "live_input_control",
inputs: [],
outputs: [{ id: "out_data", type: "Any" }],
run(inputs, { signal }) {
return new Promise((resolve, reject) => {
signal?.addEventListener("abort", () => reject(new Error("cancelled")))
})
}
}
Correspondance Workflow Patterns (van der Aalst et al.)
Source : Russell, N., ter Hofstede, A.H.M., van der Aalst, W.M.P., & Mulyar, N. (2006). Workflow Control-Flow Patterns: A Revised View. BPM Center Report BPM-06-22.
Légende
| Statut | Signification |
| ✅ Natif | Couvert directement par un opérateur ou une connexion |
| 🔶 Composable | Couvert par combinaison de primitives existantes |
| 🔴 Manquant | Non couvert — implémentation requise |
Groupe 1 — Basic Control Flow (WCP 1–5)
| WCP | Nom | Statut | Opérateur(s) | Note |
| WCP-1 | Sequence | ✅ Natif | Connexion | Toute connexion A→B est une séquence |
| WCP-2 | Parallel Split | ✅ Natif | Fork | 1→N, ANY |
| WCP-3 | Synchronization | ✅ Natif | Barrier | N→N, ALL |
| WCP-4 | Exclusive Choice | ✅ Natif | Gate / Switch | Gate = N=2, Switch = N>2 |
| WCP-5 | Simple Merge | ✅ Natif | Merge | N→1, ANY |
Groupe 2 — Advanced Branching and Synchronization (WCP 6–9)
| WCP | Nom | Statut | Opérateur(s) | Note |
| WCP-6 | Multi-Choice | ✅ Natif | Router | Plusieurs sorties activables simultanément |
| WCP-7 | Structured Synchronizing Merge | 🔶 Composable | Router + Barrier | Barrier attend les branches activées par Router — nécessite connaissance statique des branches actives |
| WCP-8 | Multi-Merge | ✅ Natif | Merge (ANY) | Chaque token traité dès réception |
| WCP-9 | Structured Discriminator | 🔴 Manquant | — | Policy FIRST — premier opérateur qui répond gagne. Priorité haute pour les race LLM |
Groupe 3 — Structural (WCP 10–11)
| WCP | Nom | Statut | Opérateur(s) | Note |
| WCP-10 | Arbitrary Cycles | 🔶 Composable | Merge + Guard | Guard en backlog |
| WCP-11 | Implicit Termination | ✅ Natif | — | Détection automatique Completed |
Groupe 4 — Multiple Instance (WCP 12–15)
| WCP | Nom | Statut | Opérateur(s) | Note |
| WCP-12 | Multiple Instances without Synchronization | 🔴 Manquant | — | Fork dynamique N instances sans attente |
| WCP-13 | Multiple Instances with a Priori Design-Time Knowledge | 🔴 Manquant | — | N connu à la conception |
| WCP-14 | Multiple Instances with a Priori Runtime Knowledge | 🔴 Manquant | — | N connu au runtime |
| WCP-15 | Multiple Instances without a Priori Runtime Knowledge | 🔴 Manquant | — | N inconnu — génération à la volée |
WCP 12–15 forment le cluster MapReduce. Priorité haute pour les workflows IA.
Groupe 5 — State-Based (WCP 16–18)
| WCP | Nom | Statut | Opérateur(s) | Note |
| WCP-16 | Deferred Choice | 🔶 Composable | LiveSource + Gate | L'environnement choisit la branche — composition ouverte requise |
| WCP-17 | Interleaved Parallel Routing | 🔴 Manquant | — | N activités en ordre quelconque, jamais simultanées — mutex implicite |
| WCP-18 | Milestone | 🔴 Manquant | — | Activité conditionnée à l'état d'une autre — nécessite observation d'état global |
Groupe 6 — Cancellation and Force Completion (WCP 19–24)
| WCP | Nom | Statut | Opérateur(s) | Note |
| WCP-19 | Cancel Activity | 🔴 Manquant | — | Signal CANCEL ciblé sur une activité — backlog |
| WCP-20 | Cancel Case | 🔶 Composable | Stop global | Stop annule la composition entière |
| WCP-21 | Structured Loop | 🔶 Composable | Merge + Guard | Guard en backlog |
| WCP-22 | Recursion | 🔴 Manquant | — | Composition récursive — hors modèle CVM actuel |
| WCP-23 | Transient Trigger | 🔶 Composable | LiveSource | Signal externe one-shot — last-write-wins approche ce comportement |
| WCP-24 | Persistent Trigger | ✅ Natif | LiveSource | Signal externe persistant — sémantique naturelle de LiveSource |
Groupe 7 — Advanced Cancellation (WCP 25–27)
| WCP | Nom | Statut | Opérateur(s) | Note |
| WCP-25 | Cancel Region | 🔴 Manquant | — | Annuler un sous-graphe délimité — nécessite notion de région |
| WCP-26 | Cancel Multiple Instance Activity | 🔴 Manquant | — | Dépend de WCP 12–15 |
| WCP-27 | Complete Multiple Instance Activity | 🔴 Manquant | — | Dépend de WCP 12–15 |
Groupe 8 — Advanced Synchronization (WCP 28–36)
| WCP | Nom | Statut | Opérateur(s) | Note |
| WCP-28 | Blocking Discriminator | 🔴 Manquant | — | Premier arrivé gagne, les autres bloqués jusqu'à reset |
| WCP-29 | Cancelling Discriminator | 🔴 Manquant | — | Premier arrivé gagne, les autres annulés |
| WCP-30 | Structured Partial Join | 🔴 Manquant | — | Attendre M parmi N branches (M statique) |
| WCP-31 | Blocking Partial Join | 🔴 Manquant | — | M parmi N, retardataires bloqués |
| WCP-32 | Cancelling Partial Join | 🔴 Manquant | — | M parmi N, retardataires annulés |
| WCP-33 | Generalised AND-Join | 🔶 Composable | Barrier (ALL) | Version standard couverte — tolérance aux branches mortes manquante |
| WCP-34 | Static Partial Join for Multiple Instances | 🔴 Manquant | — | Dépend de WCP 12–15 |
| WCP-35 | Cancelling Partial Join for Multiple Instances | 🔴 Manquant | — | Dépend de WCP 12–15 |
| WCP-36 | Dynamic Partial Join for Multiple Instances | 🔴 Manquant | — | Dépend de WCP 12–15 |
Groupe 9 — Structural (WCP 37–38)
| WCP | Nom | Statut | Opérateur(s) | Note |
| WCP-37 | Acyclic Synchronizing Merge | 🔶 Composable | Router + Barrier | Même contrainte que WCP-7 |
| WCP-38 | General Synchronizing Merge | 🔴 Manquant | — | Version acyclique et cyclique — nécessite observation d'état dynamique |
Groupe 10 — Instance Patterns (WCP 39–43)
| WCP | Nom | Statut | Opérateur(s) | Note |
| WCP-39 | Critical Section | 🔴 Manquant | — | Mutex entre sous-graphes — mode concurrent requis |
| WCP-40 | Interleaved Routing | 🔴 Manquant | — | Version non-structurée de WCP-17 |
| WCP-41 | Thread Merge | 🔴 Manquant | — | Fusionne des threads — mode concurrent requis |
| WCP-42 | Thread Split | 🔴 Manquant | — | Crée des threads — mode concurrent requis |
| WCP-43 | Explicit Termination | ✅ Natif | Sink | Fin explicite déclenchée par un Sink terminal |
Récapitulatif
| Statut | Nombre | WCP |
| ✅ Natif | 10 | 1, 2, 3, 4, 5, 6, 8, 11, 24, 43 |
| 🔶 Composable | 9 | 7, 10, 16, 20, 21, 23, 33, 37 |
| 🔴 Manquant | 24 | 9, 12, 13, 14, 15, 17, 18, 19, 22, 25, 26, 27, 28, 29, 30, 31, 32, 34, 35, 36, 38, 39, 40, 41, 42 |
Couverture (natif + composable) : 19/43 — 44 %
Clusters manquants
| Cluster | Patterns | Priorité |
| MapReduce | WCP 12–15, 26, 27, 34, 35, 36 | Haute — workflows IA multi-LLM |
| Discriminator / Partial Join | WCP 9, 28–32 | Haute — race LLM (WCP-9 seul suffit à court terme) |
| Cancel ciblé | WCP 19, 25 | Moyenne — un signal CANCEL sur région couvre les deux |
| Concurrent | WCP 17, 39, 40, 41, 42 | Bloqué par mode séquentiel actuel |
| Divers | WCP 18, 22, 38 | Basse — complexité élevée, cas rares |
Historique des décisions
| Date | Décision |
| 02-03/03/2026 | Termes validés par O. Cugnon de Sévricourt |
| 03/03/2026 | "structurel" remplace "generics" (ancien terme C++/QT) |
| 03/03/2026 | Merge, Barrier (ex-Sync) confirmés dans l'implémentation C++/QT originale |
| 03/03/2026 | Défaut ANY + garde EMPTY confirmé |
| 03/03/2026 | Correspondance WCP validée |
| 03/03/2026 | canFire() = méthode sur l'opérateur avec override |
| 05/03/2026 | HumanInput — attribut human (ADR-034) |
| 05/03/2026 | LiveSource — attribut live, transition d'environnement (ADR-035) |
| 05/03/2026 | Distinction compositions fermées / ouvertes (ADR-035) |
| 06/03/2026 | Gate = Switch(N=2) alias booléen. Switch avec addPlug() |
| 06/03/2026 | Filter et Router ajoutés — attribut config |
| 06/03/2026 | FileLoader ajouté — attribut human, fire once au démarrage |
| 07/03/2026 | Deux familles : Structurel / Comportemental — terminologie propre à Caméléon, volontairement distincte de Gateway/Task (BPMN) et routing construct/activity node (van der Aalst). Structurel = on structure le flux. Comportemental = il se passe quelque chose. Culture produit. |
| 07/03/2026 | Trois attributs orthogonaux : config / human / live |
| 07/03/2026 | Data-driven vs Config-driven retiré comme axe formel — conséquence naturelle de l'attribut config |
| 07/03/2026 | Correspondance complète 43 WCP intégrée |
| 07/03/2026 | Sync renommé Barrier — plus explicite : décrit ce que l'opérateur fait (barrière de synchronisation) |