33800 Docs

← Retour

Proposition : Systeme de Routines (Automatisation par Nodes)

Status : IMPLEMENTEE

Vision

Un systeme ou l'utilisateur (ou l'IA) peut construire des routines = enchainements d'actions avec du control flow (if/else, for, while), ou chaque node recoit les outputs des nodes precedents.

┌──────────┐    ┌──────────────┐    ┌─────────────┐    ┌──────────┐
│ Trigger  │───►│ Gmail: list  │───►│ For each    │───►│ Output   │
│ (cron/   │    │ messages     │    │ message     │    │ resultat │
│  manual/ │    │              │    │  ┌────────┐ │    │          │
│  webhook)│    │ out: items[] │    │  │Get msg │ │    │          │
└──────────┘    └──────────────┘    │  │details │ │    └──────────┘
                                    │  └───┬────┘ │
                                    │      │      │
                                    │  ┌───▼────┐ │
                                    │  │If from │ │
                                    │  │contains│ │
                                    │  │"amazon"│ │
                                    │  └───┬────┘ │
                                    │   Y  │  N   │
                                    │  ┌───▼────┐ │
                                    │  │Notify  │ │
                                    │  │push    │ │
                                    │  └────────┘ │
                                    └─────────────┘

3 modes de creation :

  1. L'IA construit : "previens-moi quand je recois un mail d'Amazon" → l'IA genere la routine JSON complete
  2. L'utilisateur construit via l'editeur visuel node-by-node, avec l'IA en copilote pour aider a configurer les input/output de chaque node
  3. Hybride : l'utilisateur demarre, l'IA complete les parties complexes (expressions, mappings)

Architecture

Modele de donnees

Table routines

CREATE TABLE connectors.routines (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id uuid NOT NULL,
  name varchar(200) NOT NULL,
  description text,
  -- Le graph complet des nodes (voir schema ci-dessous)
  nodes jsonb NOT NULL DEFAULT '[]',
  -- Connections entre nodes
  edges jsonb NOT NULL DEFAULT '[]',
  -- Variables globales de la routine (scope partage)
  variables jsonb DEFAULT '{}',
  -- Trigger config
  trigger_type varchar(20) NOT NULL DEFAULT 'manual',  -- manual, cron, webhook, event
  trigger_config jsonb DEFAULT '{}',
  -- Etat
  is_active boolean DEFAULT false,
  last_run_at timestamp,
  last_status varchar(20),  -- success, error, running
  -- Meta
  tags text[],
  created_at timestamp DEFAULT now(),
  updated_at timestamp DEFAULT now()
);

Table routine_executions

CREATE TABLE connectors.routine_executions (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  routine_id uuid NOT NULL REFERENCES connectors.routines(id),
  user_id uuid NOT NULL,
  status varchar(20) NOT NULL DEFAULT 'running',  -- running, success, error, cancelled
  trigger_type varchar(20),
  -- Log d'execution de chaque node (ordered)
  node_results jsonb DEFAULT '[]',
  -- Variables finales apres execution
  variables jsonb DEFAULT '{}',
  error_message text,
  started_at timestamp DEFAULT now(),
  completed_at timestamp,
  duration_ms integer
);

Schema des Nodes

Chaque node est un objet JSON dans le tableau nodes :

interface RoutineNode {
  id: string;              // ex: "node_1", "node_2"
  type: NodeType;
  label: string;           // Nom affiche dans l'editeur
  config: NodeConfig;      // Configuration specifique au type
  position?: { x: number; y: number };  // Position dans l'editeur visuel
}

type NodeType =
  | 'trigger'       // Point d'entree (cron, webhook, manual, event)
  | 'action'        // Execute une action existante
  | 'fetch'         // Appel API direct (sans action pre-definie)
  | 'transform'     // Extrait/transforme des donnees
  | 'condition'     // If/else (branchement)
  | 'loop'          // For each / while
  | 'delay'         // Attendre N secondes
  | 'output'        // Resultat final / notification
  | 'variable'      // Set/get variable

Schema des Edges

interface RoutineEdge {
  id: string;
  from: string;        // node_id source
  to: string;          // node_id destination
  label?: string;      // Pour les conditions: "true" / "false"
  port?: string;       // Pour les loops: "body" / "done"
}

Configs par type de node

action — Execute une action existante

{
  action_id: string;                    // Reference vers actions table
  input_mapping: Record<string, string>;// Map: param_name → expression
}
// Exemple:
{
  action_id: "uuid-gmail-list",
  input_mapping: {
    maxResults: "3",
    q: "{{trigger.params.search_query}}"  // Vient du trigger
  }
}

fetch — Appel API direct

{
  connector: string;
  instance: string;
  method: string;
  path: string;        // Supporte {{expressions}}
  body?: any;
}
// Exemple:
{
  connector: "gmail",
  instance: "Gmail",
  method: "GET",
  path: "/gmail/v1/users/me/messages/{{nodes.loop.item.id}}?format=metadata"
}

condition — If/else

{
  // Expression evaluee comme truthy/falsy
  expression: string;
  // Operateur + valeur pour comparaison simple
  operator?: 'equals' | 'contains' | 'gt' | 'lt' | 'exists' | 'matches';
  left: string;      // Expression: "{{nodes.get_msg.output.from}}"
  right?: string;    // Valeur: "amazon" ou "{{variables.threshold}}"
}

2 sorties : edge avec label: "true" et edge avec label: "false"

loop — For each / While

{
  loop_type: 'for_each' | 'while';

  // For each: itere sur un tableau
  items_expression?: string;  // "{{nodes.list_msgs.output.messages}}"

  // While: condition de continuation
  while_expression?: string;  // "{{variables.counter}} < 10"

  // Securite
  max_iterations: number;     // Default: 50, hard limit: 200
}

Le corps du loop = les nodes connectes via port: "body". L'item courant est accessible via {{nodes.LOOP_ID.item}} et l'index via {{nodes.LOOP_ID.index}}.

transform — Extraire/transformer des donnees

{
  // Expression d'entree
  input: string;      // "{{nodes.get_msg.output}}"
  // Operations chainables
  operations: TransformOp[];
}

type TransformOp =
  | { type: 'pick'; fields: string[] }           // Garder certains champs
  | { type: 'get'; path: string }                // Extraire un sous-champ (dot notation)
  | { type: 'map'; expression: string }           // Transformer chaque element
  | { type: 'filter'; expression: string }        // Filtrer un tableau
  | { type: 'join'; separator: string }            // Joindre un tableau en string
  | { type: 'template'; template: string }         // Template string
  | { type: 'regex'; pattern: string; group?: number }

output — Resultat final / notification

{
  output_type: 'result' | 'notify' | 'variable';
  // result: stocke dans routine_executions.node_results
  // notify: envoie une notification (push/mail via notify_send)
  // variable: set une variable globale de la routine

  value: string;         // Expression template
  notify_channel?: string; // Pour notify: 'push', 'email'
  variable_name?: string;  // Pour variable: nom de la variable
}

trigger — Point d'entree

{
  trigger_type: 'manual' | 'cron' | 'webhook' | 'event';
  cron_expression?: string;    // "0 8 * * *" (tous les jours a 8h)
  webhook_path?: string;       // "/hooks/routine-xyz"
  event_source?: string;       // Futur: "gmail.new_message", "github.push"
  params_schema?: object;      // JSON Schema des params d'entree (manual/webhook)
}

Expressions (le coeur du systeme)

Syntaxe template {{...}} pour referencer les donnees :

Expression Description
{{trigger.params.x}} Parametre du trigger
{{nodes.NODE_ID.output}} Output complet d'un node
{{nodes.NODE_ID.output.field}} Champ specifique
{{nodes.NODE_ID.output.items[0].name}} Acces tableau + champ
{{nodes.LOOP_ID.item}} Item courant dans un loop
{{nodes.LOOP_ID.index}} Index courant (0-based)
{{variables.NAME}} Variable globale de la routine
{{env.DATE}} Date courante ISO
{{env.TIMESTAMP}} Timestamp unix

Evaluation : simple template engine maison, pas d'eval(). Parse les {{}}, resolve les paths avec dot-notation, retourne la valeur.


Moteur d'execution (routineExecutor)

executeRoutine(routineId, userId, triggerParams?) → RoutineExecution

Algorithme

  1. Charger la routine (nodes + edges)
  2. Creer routine_execution (status: running)
  3. Trouver le node trigger (racine du graph)
  4. Execution par traversee du graph :
    • Maintenir un context = { nodes: {}, variables: {}, trigger: {} }
    • Pour chaque node, dans l'ordre topologique : a. Resoudre les expressions d'input avec le context b. Executer le node selon son type c. Stocker le resultat dans context.nodes[node.id].output d. Suivre les edges sortantes vers les nodes suivants
    • Pour les condition : suivre uniquement l'edge "true" ou "false"
    • Pour les loop : executer le sous-graph du body pour chaque item
  5. Mettre a jour routine_execution (status: success/error)

Limites de securite

Limite Valeur Raison
Max nodes par routine 50 Eviter les routines trop complexes
Max iterations par loop 200 Eviter les boucles infinies
Max execution time 5 min Timeout global
Max API calls par execution 30 Eviter le spam API
Max routines actives (cron) 20 par user Limiter la charge

Integration IA (le vrai pouvoir)

Nouveau tool_call pour le chat : type=routine

[TOOL_CALL: type=routine | action=create | params={"name":"...", "nodes":[...], "edges":[...]}]
[TOOL_CALL: type=routine | action=execute | id=ROUTINE_ID | params={"search":"amazon"}]
[TOOL_CALL: type=routine | action=list]

Prompt systeme enrichi

Ajouter dans buildUserTools() :

Format Routine: [TOOL_CALL: type=routine | action=create | params={...}]

Tu peux creer des routines pour automatiser des taches. Une routine est un enchainement
d'actions avec du control flow. Exemples :
- "previens-moi quand je recois un mail d'Amazon" → routine cron qui check gmail + filtre + notify
- "sauvegarde mes repos github tous les jours" → routine cron qui list repos + for each + ssh backup

L'IA genere le JSON

L'utilisateur dit : "previens-moi quand je recois un mail de la banque"

L'IA genere :

{
  "name": "Alerte mail banque",
  "trigger_type": "cron",
  "trigger_config": { "cron_expression": "*/15 * * * *" },
  "nodes": [
    { "id": "trigger", "type": "trigger", "label": "Toutes les 15 min", "config": { "trigger_type": "cron" }},
    { "id": "list", "type": "fetch", "label": "Lister mails recents", "config": {
      "connector": "gmail", "instance": "Gmail", "method": "GET",
      "path": "/gmail/v1/users/me/messages?maxResults=5&q=newer_than:15m"
    }},
    { "id": "loop", "type": "loop", "label": "Pour chaque mail", "config": {
      "loop_type": "for_each", "items_expression": "{{nodes.list.output.messages}}", "max_iterations": 10
    }},
    { "id": "get_detail", "type": "fetch", "label": "Details du mail", "config": {
      "connector": "gmail", "instance": "Gmail", "method": "GET",
      "path": "/gmail/v1/users/me/messages/{{nodes.loop.item.id}}?format=metadata&metadataHeaders=Subject&metadataHeaders=From"
    }},
    { "id": "check_bank", "type": "condition", "label": "Mail de la banque ?", "config": {
      "operator": "contains",
      "left": "{{nodes.get_detail.output.payload.headers[0].value}}",
      "right": "banque"
    }},
    { "id": "notify", "type": "output", "label": "Notification", "config": {
      "output_type": "notify", "notify_channel": "push",
      "value": "Mail de la banque : {{nodes.get_detail.output.snippet}}"
    }}
  ],
  "edges": [
    { "id": "e1", "from": "trigger", "to": "list" },
    { "id": "e2", "from": "list", "to": "loop" },
    { "id": "e3", "from": "loop", "to": "get_detail", "port": "body" },
    { "id": "e4", "from": "get_detail", "to": "check_bank" },
    { "id": "e5", "from": "check_bank", "to": "notify", "label": "true" }
  ]
}

Logs et Audit (meme niveau que les actions)

Une routine produit des logs a 3 niveaux :

Niveau 1 : Execution globale (routine_executions)

Comme action_executions mais pour la routine entiere :

routine_executions
├── id, routine_id, user_id
├── status: running | success | error | cancelled
├── trigger_type: manual | cron | webhook | ai_chat
├── trigger_params: {...}          -- Params d'entree
├── node_results: [...]            -- Resultat de chaque node (voir niveau 2)
├── variables: {...}               -- Variables finales
├── error_message, error_node_id   -- Quel node a plante
├── started_at, completed_at, duration_ms
└── total_api_calls: number        -- Compteur API calls consommes

Niveau 2 : Execution par node (node_results dans routine_executions)

Chaque node loggue son execution dans le tableau node_results :

interface NodeExecutionResult {
  node_id: string;
  node_type: string;
  node_label: string;
  status: 'success' | 'error' | 'skipped';  // skipped = condition false
  input_resolved: any;     // Inputs apres resolution des expressions
  output: any;             // Resultat du node
  error?: string;
  started_at: string;
  duration_ms: number;
  // Pour les loops : resultats de chaque iteration
  iterations?: NodeExecutionResult[][];
}

Niveau 3 : Execution des actions sous-jacentes (action_executions)

Quand un node action ou fetch s'execute, il cree un enregistrement dans action_executions avec :

Ca veut dire que les logs des routines sont compatibles avec les logs des actions existantes. Un meme ecran d'historique peut afficher les deux.

Vue frontend : historique des routines

/routines/:id/history  →  Liste des executions
/routines/:id/history/:exec_id  →  Timeline detaillee node par node

┌─ Execution #42 — 31/01/2026 17:45 — ✓ Success (1.2s) ──────────────┐
│                                                                       │
│  [trigger] ✓ 0ms                                                      │
│  [list_mails] ✓ 120ms → 3 messages                                   │
│  [loop] 3 iterations                                                  │
│    ├─ #0 [get_detail] ✓ 80ms → [check_bank] ✓ true → [notify] ✓     │
│    ├─ #1 [get_detail] ✓ 75ms → [check_bank] ✗ false (skipped)       │
│    └─ #2 [get_detail] ✓ 90ms → [check_bank] ✗ false (skipped)       │
│                                                                       │
│  Total: 6 API calls, 1 notification envoyee                          │
└───────────────────────────────────────────────────────────────────────┘

Audit trail

Comme les actions, chaque operation sur une routine passe par audit.log() :


Copilote IA pour la configuration des nodes

Quand l'utilisateur construit manuellement une routine dans l'editeur visuel, l'IA l'assiste en temps reel :

Aide a la configuration des input/output

L'utilisateur ajoute un node fetch Gmail → le panel de config affiche :

  1. Suggestions de path basees sur les endpoints de CONNECTOR_TYPES
  2. Auto-completion des expressions : en tapant {{nodes. → liste des nodes precedents + leurs outputs connus
  3. Preview du mapping : montre un exemple de ce que l'expression va produire

Bouton "Suggerer" sur chaque node

Comme le bouton AI suggest qui existe deja pour les actions SSH, mais pour les nodes :

POST /api/routines/suggest-node-config
Body: {
  routine_context: { nodes, edges },  // Les nodes deja places
  node_id: "node_3",                  // Le node a configurer
  node_type: "condition",
  user_intent: "filtrer les mails de la banque"  // Optionnel
}
Response: {
  suggested_config: {
    operator: "contains",
    left: "{{nodes.get_detail.output.payload.headers[0].value}}",
    right: "banque"
  },
  explanation: "Verifie si le champ From du mail contient 'banque'"
}

Validation en temps reel

A chaque modification d'un node, le front envoie le graph au backend pour validation :


Frontend : editeur visuel de nodes

Librairie recommandee

@xyflow/react (ex React Flow) — MIT, mature, parfait pour les node editors. Deja utilise par n8n, Langflow, etc.

Vue editeur

Routes frontend

/routines              → Liste des routines
/routines/new          → Editeur visuel (nouveau)
/routines/:id          → Editeur visuel (existant)
/routines/:id/history  → Historique d'executions

Fichiers a creer

Backend (connectors-api)

Fichier Contenu
src/services/routines.ts CRUD routines + list executions
src/services/routineExecutor.ts Moteur d'execution (traversee graph, context, expressions)
src/services/routineExpressions.ts Parser/evaluateur d'expressions {{...}}
src/services/routineScheduler.ts Gestionnaire cron (node-cron ou setInterval)
src/data/routine-templates.ts Templates de routines pre-definies
Migration SQL Tables routines + routine_executions

Frontend (connectors-front)

Fichier Contenu
src/routes/routines/ Pages liste + editeur
src/components/RoutineEditor.tsx Editeur node (@xyflow/react)
src/components/nodes/*.tsx Composants visuels par type de node
src/components/NodeConfigPanel.tsx Panel config lateral

Plan d'implementation (par phases)

Phase 1 — Fondations

Phase 2 — Control flow

Phase 3 — Triggers automatiques

Phase 4 — Frontend editeur

Phase 5 — IA copilote + builder