Types d'actions
Les douze types d'actions de Process Flow — leur rôle, leur syntaxe, ce que voit l'assigné, et les champs de schéma qui les pilotent
Un modèle d'action est ce qu'une tâche demande à quelqu'un (ou au système) de faire. Une tâche peut porter une ou plusieurs actions ; elles s'affichent côte à côte dans le panneau de détail de la tâche et se complètent indépendamment. Le choix du bon actionType est la décision d'authoring la plus structurante dans un Process Flow — il détermine ce que voit l'assigné, les données que la plateforme enregistre, et quelles portes et extensions en aval se déclenchent. Quand une tâche ne porte qu'une seule action, le tableau kanban affiche directement la puce du type de cette action (icône + couleur) sur la carte de tâche — les tâches à action unique se lisent comme « la tâche est l'action » d'un seul coup d'œil.
Cette page approfondit le tableau récapitulatif de la vue d'ensemble des Process Flows — une section par type, avec un exemple prêt à copier-coller issu d'une extension réelle dans les exemples du SDK, et une note en une ligne sur ce que voit concrètement l'assigné dans agentic.
Les douze types d'actions sont définis sous forme d'enum Zod dans @scrydon/sdk-authoring/process-flows. Ce schéma est la source de vérité — les helpers d'authoring (defineAction) et le runtime le consomment tous deux. Les valeurs inconnues sont rejetées à l'étape extension validate, avant la construction de l'archive.
Anatomie d'une action
Toutes les actions partagent la même enveloppe, quel que soit le type :
import { defineAction } from '@scrydon/sdk-authoring/process-flows'
defineAction({
slug: 'rollout-sign-off', // optional — stable handle, unique per template
name: 'Approve rollout plan', // required, 1–200 chars
description: 'Sign off on the customer rollout schedule.',
actionType: 'approval', // required, see below
isRequired: true, // required — drives gate completion
executionMode: 'manual', // optional — see below
dueOffsetDays: 5, // optional — relative due date
// ou due: { anchor, offset, time } — ancré sur un attribut, voir Échéances
metadata: { persona: 'champion' }, // optional — per-type config bag
// workflowId / workflowSlug only used by actionType: 'workflow' — mutually exclusive
})Les champs qui varient selon le type se trouvent tous dans metadata. Chaque section ci-dessous indique les clés de métadonnées que le runtime lit pour ce type spécifique.
slug est optionnel. Il donne à l'action un identifiant stable que d'autres déclarations peuvent viser — aujourd'hui le targetActionSlug d'une surface de sortie approval — sans dépendre du name affiché, que vous pouvez reformuler librement. Un slug se compose de lettres minuscules, de chiffres et de tirets, et doit être unique parmi toutes les actions d'étape et de tâche du modèle.
executionMode
Un seul champ facultatif contrôle le moment où une action s'exécute :
| Mode | Signification |
|---|---|
manual (par défaut) | L'assigné clique sur « Marquer comme terminé » / « Approuver » / « Téléverser ». Utilisé pour toute action pilotée par un humain. |
automatic | Le runtime la déclenche dès que les dépendances en amont sont satisfaites. Sans dépendances déclarées, ce moment est l'entrée dans l'étape — l'action s'exécute dès que sa tâche devient active dans l'étape. Utilisé presque exclusivement avec actionType: "workflow" pour les étapes IA/agent. |
on_entry | Se déclenche à l'instant où l'étape est saisie, avant tout affichage de l'interface de tâche. Utilisé pour les étapes de workflow de préparation qui préparent les données pour les étapes humaines qui suivent. |
Choisissez manual lorsqu'un humain est le principal. Choisissez automatic lorsque la plateforme doit piloter l'étape dès qu'elle le peut. Choisissez on_entry uniquement lorsque l'étape doit s'exécuter avant que l'étape soit interactable.
L'exécution automatique s'applique uniquement aux actions workflow. Les autres types d'actions
(distribution, checklist, déclencheurs vocaux, …) conservent leurs propres chemins de dispatch
quel que soit executionMode. L'entrée dans l'étape compte toutes les façons dont une tâche
atteint une étape : création d'instance, transition automatique ou approuvée, déplacement manuel
et déverrouillages différés.
Seules les actions document et approval affichent le panneau Recommandations IA —
les deux types où l'assigné prend une décision que la base de connaissances
peut éclairer. Les autres types d'actions n'affichent pas de section Recommandations.
Réalisé dans un autre système — metadata.externalSystem
Certaines étapes se font en dehors de Scrydon : une checklist cochée dans un système de dossiers, un formulaire rempli dans un ERP. Déclarez ce système sur une action manuelle et l'assigné voit où le travail se fait avant de commencer :
defineAction({
name: "Contrôles de la demande",
actionType: "checklist",
isRequired: true,
metadata: {
checklistItems: ["Le numéro d'entreprise est actif", "L'assurance responsabilité est valide"],
externalSystem: { name: "PermitDesk", url: "https://permitdesk.example.com/cases" },
},
});| Champ | Règle |
|---|---|
name | Obligatoire. Le nom du système, de 1 à 80 caractères (espaces de début et de fin retirés). |
url | Facultatif. Un lien https:// sans nom d'utilisateur ni mot de passe. Tout autre schéma (http:, javascript:, data:, …) échoue à la validation. |
Ce que voit l'assigné, de la même façon dans le Wizard et dans le Kanban :
- « Dans PermitDesk » sous le titre de la tâche.
- Une note en haut de la tâche qui indique que le travail se fait dans PermitDesk et que Scrydon ne peut pas le voir, et ne le vérifie donc pas. Avec une
url, elle porte un bouton Ouvrir dans PermitDesk, qui ouvre le lien dans un nouvel onglet. - Le nom du système sous la tâche dans Cette étape.
Ce n'est qu'un libellé et un lien : Scrydon n'appelle pas le système, et l'assigné termine toujours l'étape dans Scrydon. Le champ s'applique aux actions manual (le mode par défaut) de tout type. extension build échoue lorsqu'une action automatic ou on_entry le déclare — Scrydon exécute ces étapes, et le système d'une étape de workflow est affiché à partir de la connexion qu'utilisent ses blocs, sans rien à déclarer. L'installation d'une archive déjà construite ne fait qu'avertir. Lorsque les données stockées ne sont pas valides, l'étape n'affiche aucun système plutôt qu'un lien cassé.
checklist
L'action la plus légère. L'assigné voit un seul bouton intitulé « Marquer comme terminé » et clique dessus quand c'est fait. Aucun artefact, aucune métadonnée capturée au-delà de l'événement de complétion lui-même.
Utiliser pour : confirmations, attestations, acquittements « J'ai fait X » où la piste d'audit a simplement besoin de l'acteur et du horodatage.
Champs de schéma utilisés : name, description, isRequired, executionMode, metadata.persona, metadata.checklistItems (facultatif).
Exemple (de l'extension de détection multi-sources NATO — un opérateur confirme une alerte capteur) :
defineAction({
name: 'Confirm UAV detection',
description: 'Sensor operator confirms UAV radar reading and flags the track for fusion.',
actionType: 'checklist',
isRequired: true,
executionMode: 'manual',
metadata: { persona: 'operator' },
})Ce que voit l'assigné : une carte intitulée « Confirm UAV detection » avec un seul bouton principal. La complétion écrit une entrée de journal d'activité action_completed taguée avec l'identifiant de l'acteur.
Éléments de checklist
Quand une étape regroupe plusieurs confirmations, listez-les dans metadata.checklistItems. L'assigné voit alors une case par élément au lieu d'une seule case « Prêt à terminer », et Marquer comme terminé reste désactivé tant que tous les éléments ne sont pas cochés.
defineAction({
name: 'Log the report and stamp T0 (UTC)',
actionType: 'checklist',
isRequired: true,
metadata: {
persona: 'security-lead',
checklistItems: [
'Report received and logged: source, reporter, report id and contents',
'T0 stamped in UTC: the moment exploitation was corroborated, not the report time',
'CRA 24h early-warning deadline recorded',
],
},
})- Entre 1 et 25 éléments, de 1 à 300 caractères chacun, sans doublons. Une coche est enregistrée par le texte de l'élément dans
content.checkedItemsde l'action ; deux éléments identiques seraient indiscernables. checklistItemsn'est valide que sur une actionchecklist. Des éléments sur tout autre type d'action, ou une liste qui enfreint la règle ci-dessus, font échouerextension build(checklist_items_not_on_checklist,checklist_items_invalid). Une archive qui en contient déjà s'installe quand même, avec le même code en avertissement, car des manifestes antérieurs pouvaient porter cette clé avant qu'elle ait un sens.- Le serveur applique la liste, pas seulement le bouton. Terminer l'action alors qu'un élément n'est pas coché renvoie HTTP
409aveccode: "checklist_items_incomplete"et lesuncheckedItemsrestants. - Une liste
checklistItemsmal formée (par exemple des doublons ou un tableau vide) ne revient jamais à la case unique : l'étape ne peut être ni cochée ni terminée, le panneau indique que les éléments sont mal définis, et la terminer renvoie HTTP409aveccode: "checklist_items_invalid". Corrigez le modèle. - Chaque coche est enregistrée seule et fusionnée avec les coches déjà stockées : deux personnes qui cochent des éléments différents d'une même étape en même temps conservent chacune leur coche.
- Les coches d'une étape terminée sont celles sur lesquelles sa complétion a été jugée : les modifier renvoie HTTP
409aveccode: "checklist_action_completed". Repassez d'abord l'étape en attente. - Cocher la case de l'étape dans la liste des tâches ouvre le panneau de l'étape, car c'est là que les éléments se cochent.
- Une instance en cours garde le modèle avec lequel elle a démarré. Les éléments ajoutés à un modèle s'appliquent aux instances démarrées après la modification.
document
Un artefact textuel long-forme que l'assigné doit produire. La plateforme ouvre un éditeur markdown à l'intérieur de la tâche ; la page résultante est stockée dans la base de connaissances de l'espace de travail de l'instance et indexée pour la récupération par les étapes IA en aval.
Utiliser pour : plans, résumés, comptes-rendus, ordres du jour, procès-verbaux — tout ce où le contenu est le livrable, pas un fichier.
Champs de schéma utilisés : name, description, isRequired, executionMode, metadata.persona, metadata.promoteToCorpus (facultatif — l'un de summary / minutes / decisions / action-items ; à la complétion de l'étape, le document est également publié dans le dossier instances/<instance>/canonical/ de la KB de l'instance sous ce slug, où les étapes de promotion KB en aval vont le chercher).
Exemple (du modèle AI Boardroom — le secrétaire rédige l'ordre du jour) :
defineAction({
name: 'Create meeting agenda',
actionType: 'document',
isRequired: true,
executionMode: 'manual',
metadata: { persona: 'secretary' },
})Ce que voit l'assigné : une carte intitulée « Create meeting agenda » avec un bouton « Ouvrir l'éditeur ». La complétion est implicite — l'action est considérée terminée une fois la page sauvegardée avec du contenu non vide.
Où le document aboutit : pendant l'édition, le contenu réside dans l'action elle-même. Quand l'action est marquée terminée, la plateforme le convertit en Markdown et l'écrit comme une vraie page dans la base de connaissances de l'instance du process flow, sous instances/<instance>/documents/<stage>/ avec le nom de l'action comme titre de page. Compléter l'action à nouveau après des modifications met à jour la même page sur place. À partir de là, elle est interrogeable via la KB de l'instance et peut ancrer les étapes actionType: "workflow" ultérieures via metadata.retrieval. Les process flows créés avant l'existence des bases de connaissances d'instance en reçoivent une provisionnée automatiquement la première fois qu'un document, une transcription ou une page canonique y est écrit.
Les actions document affichent un panneau Recommandations IA : la plateforme lit les bases de connaissances de l'instance et du modèle, les extractions de réunion et l’entité liée, puis génère des suggestions lisibles sur ce que le document devrait couvrir. Les bases de connaissances sont lues avec la même récupération agentique approfondie que la Recherche du bloc Knowledge — elle parcourt chaque document téléversé (en cherchant, en listant et en lisant le contenu complet), et non seulement les premiers résultats de recherche, de sorte qu'un document que vous avez téléversé est reflété dans les suggestions même si son sujet ne correspond pas au nom de l'étape. Le même ancrage approfondi alimente Générer avec l'IA sur le corps du document. Chaque suggestion reste courte : un titre, un résumé en une phrase, deux ou trois points clés et le raisonnement complet. Un expandeur Détails de raisonnement montre comment chaque recommandation a été ancrée ; Actualiser régénère en fonction des connaissances les plus récentes. Les recommandations nécessitent qu'une extension LLM soit configurée pour l'organisation.
Chaque recommandation affiche son titre et un résumé d'une ligne, et les plus pertinentes apparaissent en premier avec l'étiquette Le plus pertinent. Lire la suite ouvre ses points clés et ses sources, et Raisonnement complet le texte intégral ; une seule recommandation est ouverte à la fois. Tant que le panneau est étroit, les recommandations se trouvent sous le résultat de l'action. Élargissez le panneau (faites glisser son bord, ou cliquez sur Afficher à côté) et elles passent dans une colonne à côté. La largeur choisie est mémorisée.
Réduire la variance des recommandations — metadata.selfConsistency
Sur une étape à fort enjeu, un seul tirage d'un modèle peut se fixer sur une
seule lecture des preuves. metadata.selfConsistency.samples demande à la
plateforme de tirer autant de complétions indépendantes et formulées
différemment en parallèle, puis de les consolider en une seule réponse.
{
name: 'Sign off consolidated advisory',
actionType: 'approval',
isRequired: true,
metadata: {
persona: 'approver',
selfConsistency: { samples: 3 }, // 1–5; omit or 1 = a single completion
},
}- S'applique uniquement aux types éligibles aux recommandations —
documentetapproval. - Omis, ou
samples: 1, est la valeur par défaut et se comporte exactement comme avant. - Plafonné à 5. Chaque échantillon est un appel LLM facturé, plus un de plus pour
la consolidation :
samples: 3coûte donc environ quatre complétions au lieu d'une — activez-le sur les étapes qu'un humain signe de son nom, pas sur tout un modèle. - Un échantillon qui échoue est écarté au lieu de faire échouer l'exécution ; si la consolidation elle-même échoue, le premier échantillon réussi est affiché à la place.
- L'expandeur Détails de raisonnement indique « Consolidé à partir de N analyses » uniquement lorsque la consolidation a réellement produit la réponse. Une exécution qui a tiré N échantillons mais s'est rabattue sur un seul n'affiche aucune note — la ligne documente la provenance de ce que vous lisez, pas ce qui a été payé.
- La variation vient de l'invite, pas de
temperature— les modèles de raisonnement qui refusent un réglage de température obtiennent quand même une vraie diversité.
Le modèle d'exemple advisory-consolidation l'active sur son étape de validation.
Questions et raffinement dans le Wizard : la Conversation d'action partagée à droite permet à chaque participant autorisé de poser des questions sur le document actuel et ses sources gouvernées. Une question normale ne modifie jamais le document. Pour utiliser une réponse comme instruction, choisissez explicitement Appliquer comme raffinement ; la plateforme réautorise l'action et met à jour le document par le même chemin de raffinement. Hors du Wizard, Améliorer avec Cortex, à côté de Régénérer, ouvre la même conversation dans un panneau latéral.
Le drapeau gate: "validate-and-rerun" sur une action document la transforme en porte douce pour une étape IA qui suit : après que l'humain a modifié le document, le workflow en aval se réexécute sur le contenu modifié. Utile pour les boucles « l'IA ébauche → l'humain révise → l'IA ré-exécute ».
Un document édité comme une liste — metadata.editor: "list"
Certains documents sont une liste plutôt qu'un texte : l'ordre du jour d'une
réunion, les points à aborder avec un fournisseur, le déroulé d'une séance.
metadata.editor: "list" donne à la personne assignée une liste ordonnée au lieu
de l'éditeur Markdown. Chaque élément a un titre, obligatoire, et une
description facultative en texte brut. On peut ajouter, modifier, supprimer
et réordonner les éléments par glisser-déposer.
import { DOCUMENT_EDITOR } from '@scrydon/sdk-authoring/process-flows'
defineAction({
slug: 'agenda',
name: 'Agenda',
actionType: 'document',
isRequired: true,
metadata: { persona: 'secretary', editor: DOCUMENT_EDITOR.LIST },
})- Seule une action
documentaccepteeditor. Sur un autre type, le build échoue avecmetadata.editor is only supported on a \document` action`. - La liste reste un document pour tout ce qui en lit un. À la complétion, elle est écrite dans la base de connaissances de l'instance sous forme de liste Markdown numérotée (chaque description sous son titre) : l'ancrage, le résumé de clôture et le PDF d'une distribution la lisent comme un document en texte libre.
- La personne assignée la termine avec Mark complete, ou Save & complete s'il reste des modifications non enregistrées. Un élément sans titre ne peut pas être enregistré.
- Une liste contient au plus 100 éléments, avec des titres de 200 caractères et des descriptions de 2 000 caractères au maximum.
- Modifier une liste déjà terminée marque Plus à jour les étapes qui la lisent et propose de relancer celles qui sont des étapes IA déjà exécutées. Voir Gérer les exécutions.
Pour qu'une étape IA écrive un premier jet dans la liste, pointez sa
surface de sortie document vers le slug de la liste.
Rapports sur le sujet
Un rapport sur un objet — un navire, un fournisseur, un actif — doit dire ce
que l'ontologie sait de cet objet, sous la classification propre de cet objet.
metadata.subjectReport remplit un document en prose à partir d'un modèle
Markdown, avec des faits que la plateforme lit par le chemin gouverné de
l'ontologie, au nom de la personne pour qui l'étape rédige. Aucun modèle ne
génère quoi que ce soit : un emplacement est remplacé par la valeur gouvernée,
ou par une lacune visible quand aucune source ne la détient.
defineAction({
name: 'Heads-up report',
actionType: 'document',
isRequired: true,
executionMode: 'automatic', // ou manuel : une personne appuie sur Rédiger
metadata: {
subjectReport: {
// Le sujet est désigné par la détection qui a ouvert le flux — lue dans
// le reçu de dispatch de la plateforme, jamais dans le contexte modifiable du flux.
subject: {
objectType: 'Vessel',
from: { kind: 'detection', evidence: 'entityId', property: 'mmsi' },
},
related: [
// L'alerte sur laquelle la détection s'est déclenchée.
{ kind: 'detection', as: 'alert', objectType: 'SecurityAlert', evidence: 'observationId', property: 'alertId' },
// L'évaluation la plus récente qui référence ce navire.
{ kind: 'referencing', as: 'risk', objectType: 'RiskAssessment', property: 'vesselMmsi', subjectProperty: 'mmsi', latestBy: 'assessedAt' },
// Les objets à un lien déclaré de distance.
{ kind: 'linked', as: 'owner', linkType: 'VesselOwner' },
],
// Un filtre sur ce à partir de quoi ce rapport est construit, par le
// `bindingContext.sourceSystem` de chaque liaison.
sources: { allow: ['vessel-registry', 'ssa-gateway'] },
// Où l'alerte a été émise, écrit {{alert.position}} dans le format de
// coordonnées commun (WGS 84, degrés et minutes décimales).
positions: { alert: { latitude: 'latitude', longitude: 'longitude' } },
template: [
'| Vessel | {{subject.name}} |',
'| IMO | {{subject.imo}} |',
'| Position at alert | {{alert.position}} |',
'| Operator verdict | {{feedback.verify-alert.value}} |',
].join('\n'),
// Optionnel : le texte approuvé est enregistré comme objet de l'ontologie.
persistAs: { objectType: 'HeadsUpReport', onApprovalOf: 'go-no-go' },
},
},
})- Uniquement sur une action
documenten prose. Tout autre type, oueditor: "list", fait échouer la construction. Chaque partie de la déclaration est stricte : un bloc de faits collé est refusé. - Le sujet vient de la détection d'ouverture.
subject.fromnomme la valeur de preuve de la détection (entityId, la propriété d'entité de la surveillance, ouobservationId, sa propriété d'observation) et la propriété du sujet qui la contient. Un flux que le dispatch de la surveillance n'a pas ouvert n'a pas de sujet : la rédaction est refusée avec422 subject_unresolved. La détection elle-même est lue au nom du rédacteur ; une détection qu'il ne peut pas voir refuse la rédaction (detection_not_visible), de même qu'un sujet qu'il ne peut pas voir (subject_not_visible) ou que la preuve désigne deux fois (subject_ambiguous). related— jusqu'à 8 lectures nommées.detectionlit l'objet que désigne la preuve de la détection.referencinglit les objets dont lapropertyest égale à l'identifiant du sujet, ou à sasubjectProperty(latestBygarde le plus récent ;limit1–5, par défaut 1).linkedlit les objets à unlinkTypede distance (limit1–10, par défaut 3). Les objets que le rédacteur ne peut pas voir sont absents.positions— poursubjectou une lecture liée, les propriétés qui portent sa latitude et sa longitude WGS 84 en degrés décimaux. Chacune active{{<namespace>.position}}, que la plateforme écrit dans le format de coordonnées commun : degrés et minutes décimales à la précision d'un rapport, par exemple51°58.2420' N 003°12.4560' E. Un axe absent ou hors limites est une lacune.template— Markdown avec{{subject.<property>}},{{<as>.<property>}},{{detection.detectionId|watchSlug|firedAt}}et{{feedback.<slug de tâche>.value|notes}}(une décision cartographique qu'une tâche précédente a enregistrée). Un emplacement qui ne nomme rien de déclaré fait échouer la construction. La plateforme ajoute les sources utilisées par le rapport. Écrivez un fait que vos sources ne détiennent pas encore en texte simple — une lacune étiquetée — jamais comme une valeur inventée.- Les données de démonstration sont étiquetées d'après leur source. Une
valeur lue d'une liaison qui déclare
bindingContext.demos'affiche « valeur (Mocked — demo data) ». Remplacez la source par une source réelle et la mention disparaît ; le flux ne change pas. sources.allowest un filtre, pas une contrainte appliquée : un fait issu d'un système source absent de la liste — ou d'une liaison qui ne déclare pas desourceSystem, ou d'un objet détenu par le registre — est écarté de ce rapport et listé comme exclu. Un sujet issu d'une telle source refuse la rédaction (subject_source_not_allowed). Les autres lectures de cette source ne sont pas affectées.- Au nom de qui il rédige. Une personne qui appuie sur Rédiger rédige
en son propre nom. Une étape
automaticrédige au nom de la personne qui occupe sa tâche (l'assigné, ou le titulaire de son persona de rôle) : donnez donc à la tâche undefaultAssignedRoleSlug. Tant que personne ne l'occupe, l'étape attend — elle ne lit jamais au nom de la plateforme — et elle est remplie à la lecture suivante du flux une fois le persona attribué. Un refus qui tient au flux (pas de détection, un sujet invisible) fait échouer l'étape avec cette raison. - Classification. Le rapport est classifié au plus haut de la classification propre de l'étape, du marquage de la détection et du marquage de chaque fait utilisé. Un fait issu d'une liaison adossée à une source ne porte pas de marquage propre : il compte à l'habilitation du rédacteur — prudent, et cela peut surclassifier.
- Un rapport au-dessus de la classification du flux est confiné. Les participants en dessous voient l'étape comme retenue ; seuls les participants habilités peuvent la modifier, la rédiger à nouveau ou la terminer ; elle reste hors du résumé de clôture et de la promotion canonique ; et sa page de base de connaissances est écrite à sa propre classification, si bien qu'une distribution exclut chaque destinataire en dessous.
persistAs— à l'approbation, le texte du rapport est enregistré comme objet de ce type, au nom de l'approbateur, marqué à la classification du rapport. Un lecteur en dessous de ce marquage en obtient une lecture vide.onApprovalOfnomme la tâche dont l'approbation approuve le rapport (obligatoire pour une étapeautomatic) ; sans lui, terminer l'étape vaut approbation. Le type doit déclarertitle,body,subjectType,subjectId,processFlowInstanceId,approvedBy,approvedAt(chaînes) etsources(json). Quand l'objet ne peut pas être enregistré, l'approbation n'a pas lieu :409 subject_report_not_drafted,403 subject_report_above_clearance(l'approbateur n'est pas habilité),422 subject_report_refused(type ou propriété non déclarés),503 subject_report_unavailable(réessayez).
approval
Une porte de validation. Le persona assigné voit une paire de boutons Approuver / Rejeter. Approuver marque l'action comme terminée et débloque les tâches en aval ; rejeter arrête le flow — la tâche de la porte reste ouverte et aucune étape en aval ne se déverrouille tant que la porte n'est pas re-décidée.
Utiliser pour : validations binaires de porte d'étape, décisions go/no-go, revues de gouvernance où une personne ayant l'autorité nommée doit attester du résultat. Si l'étape a plus de deux issues (par ex. poursuivre / ré-enquêter / re-trier), utilisez plutôt une action map — voir Les actions map comme portes de décision à issues multiples ci-dessous.
Champs de schéma utilisés : name, description, isRequired, executionMode, metadata.persona.
Exemple (du modèle AI Boardroom — validation exécutive du résumé rédigé par l'IA) :
defineAction({
name: 'Approve meeting summary',
actionType: 'approval',
isRequired: true,
executionMode: 'manual',
metadata: { persona: 'executive' },
})Ce que voit l'assigné : une carte avec les boutons Approuver et Rejeter. L'approbation écrit une entrée d'activité approval_granted ; le rejet enregistre la décision, laisse la tâche ouverte et bloque strictement la progression — l'étape ne peut pas se vider et aucune étape en aval ne se déverrouille tant qu'une porte est rejetée, même si toutes les autres actions de la tâche sont facultatives et terminées. Le blocage se lève quand la porte est re-décidée à approuvée (ou ignorée par une dérogation explicite). Cela vaut que la porte soit marquée isRequired ou non.
Les actions d'approbation affichent un panneau Recommandations IA contenant un brief de décision consolidé — risques clés, preuves disponibles et ce que montrent les flows similaires antérieurs — ancré dans les mêmes sources de connaissances que les recommandations de document. Chaque risque porte une gravité, les preuves une solidité (solide, partielle ou faible) et le précédent un nombre de flows antérieurs ; ces indications décrivent les éléments du dossier, jamais la décision. Le panneau ne recommande jamais d'approuver ou de rejeter ; la décision reste de la responsabilité de l'assigné.
La justification d'une décision est un texte opérationnel — n'y mettez pas de données personnelles.
Lorsqu'une décision est arrêtée, la plateforme enregistre une entrée de justification (le commentaire ou le motif qu'un opérateur saisit avec son approbation ou son rejet) dans le registre d'assertions d'ontologie, en ajout seul. Les entrées du registre ne peuvent pas être supprimées individuellement. La justification est destinée au raisonnement opérationnel — contexte tactique, citations de preuves, notes utiles à la mission — et non aux identifiants personnels, noms ou coordonnées. Les organisations soumises à l'art. 17 du RGPD (droit à l'effacement) doivent consulter leur documentation de preuves RGPD pour la position de la plateforme sur l'effacement et le risque résiduel accepté sur ce champ.
workflow
Exécute un workflow Scrydon — soit un agent IA agentique, une séquence de blocs d’extension, ou une porte human-in-the-loop. Le workflow s'exécute dans le moteur de workflow de la plateforme ; l'action se termine quand le workflow retourne.
Utiliser pour : étapes d'agent IA, extensions automatisées, portes HITL nécessitant plus qu'un oui/non — tout ce qui nécessite une logique exécutable dans le cadre du flow.
Champs de schéma utilisés : name, description, isRequired, executionMode (souvent automatic), workflowId ou workflowSlug (mutuellement exclusifs), metadata.aiAgent, metadata.retrieval, metadata.companyContextBriefing, metadata.outputSurface.
Il existe deux façons de référencer le workflow, et choisir entre elles est la décision d'authoring clé pour ce type :
Par workflowId — référencer un workflow existant
Utilisez ceci quand le workflow existe déjà dans le tenant. Deux cas courants : un agent @system/* fourni par la plateforme, ou un workflow que l'espace de travail a construit indépendamment.
Exemple (de l'étape de détection NATO — appel du détecteur multi-sources fourni par le système) :
defineAction({
name: 'Run UAV radar detection',
actionType: 'workflow',
isRequired: true,
executionMode: 'automatic',
workflowId: '@system/agent-multi-source-detector',
metadata: { persona: 'ai-platform', aiAgent: 'multi-source-detector' },
})Le runtime traite les identifiants @system/* de manière spéciale — ils se résolvent toujours, quel que soit l'espace de travail dans lequel l'instance s'exécute.
Par workflowSlug — distribuer le workflow dans la même extension
Utilisez ceci quand le workflow est propre au process et que vous voulez qu'ils soient distribués comme une unité atomique. Créez le workflow aux côtés du process flow dans le même .scrydon-extension.tar.gz ; l'importateur matérialise d'abord le workflow, puis rebind chaque workflowSlug à l'identifiant matérialisé avant que le process flow atterrisse.
Exemple (de l'extension SAP Activate Brabant — une porte HITL distribuée avec le modèle) :
defineAction({
name: 'Realize → Deploy Gate (HITL)',
actionType: 'workflow',
isRequired: true,
executionMode: 'manual',
workflowSlug: 'hitl-realize-gate', // resolved at install time
})workflowId et workflowSlug sont mutuellement exclusifs — le raffinement Zod rejette une action qui définit les deux. Voir Workflows pour le contrat de rebind de slug.
Ce que voit l'assigné : si executionMode est automatic ou on_entry, généralement rien — l'étape s'exécute en arrière-plan et affiche son résultat inline une fois terminée. Si manual, un bouton « Exécuter » apparaît ; cliquer dessus démarre le workflow et diffuse la progression dans la carte.
Évaluer la réponse (👍/👎) : dès qu'une étape d'agent a produit une sortie, l'assigné voit sous la réponse une barre pouce levé / pouce baissé, avec une note facultative. C'est une fonctionnalité intégrée à l'exécution : vous ne la rédigez pas, et elle ne bloque ni ne fait avancer le flux. Chaque personne a sa propre évaluation : un nouveau vote remplace (sans jamais dupliquer) l'évaluation précédente de cette personne, et l'action porte le décompte des 👍 et 👎 de toutes les personnes qui l'ont évaluée. Un 👎 n'exige pas de note.
Les évaluations guident les exécutions suivantes : les évaluations et notes sur les réponses antérieures d'une étape alimentent les exécutions suivantes de cette même étape — dans les exécutions ultérieures du même flux de processus, dans le même environnement d'espace de travail. L'agent les reçoit sous le titre Retours des relecteurs sur les réponses antérieures : le décompte et les notes les plus récentes, présentés comme des préférences qui ne peuvent pas prévaloir sur la tâche ni sur votre invite. Seuls les retours portant sur une sortie que l'exécution est habilitée à voir sont inclus ; les retours sur une sortie dont la classification est inconnue ou compartimentée ne sont jamais rejoués. Le panneau Invite & Contexte les liste sous Retours des relecteurs. Pour que les réponses d'une étape restent indépendantes des retours antérieurs, ajoutez answerFeedback à son metadata.excludeContext.
Questions de suivi : dès que l'étape a une réponse, sa conversation Cortex affiche une carte au-dessus de la zone de saisie, Cortex peut vous poser quelques questions. Les questions ne sont posées que sur demande : Posez-moi des questions fait demander à Cortex ce qu'il lui faudrait savoir pour améliorer la réponse. Il pose jusqu'à trois questions dans la conversation, chacune avec un champ de réponse ; l'assigné répond à ce qu'il peut et choisit Enregistrer les réponses et refaire le projet. Ensuite, la carte indique Vos N réponses sont utilisées pour cette étape, avec Voir ou modifier pour les changer. Les réponses sont enregistrées sur l'action et transmises à chaque exécution ultérieure de cette action — c'est l'assigné qui oriente cette étape, comme un affinage, donc excludeContext ne les supprime pas. Demander à nouveau conserve les questions déjà répondues. Un workflow canvas ne reçoit les réponses que si son bloc agent lit <start.processFlowContext> ; les agents @system/* les reçoivent toujours. La génération des questions utilise le modèle de langage par défaut de l'espace de travail ; elle est donc indisponible lorsqu'aucun n'est configuré.
Pour les étapes d'agent IA @system/*, metadata.retrieval vous permet d'ancrer l'agent dans des documents de tâches antérieures ; metadata.outputSurface déclare où atterrit le résultat de l'agent. Couverture complète sur la page parente : Ancrer une étape IA dans les documents d'espace de travail.
Ancrage adapté au client avec le Contexte de l'entreprise
Conservez l'invite de tâche de l'extension stable et utilisez metadata.companyContextBriefing lorsque l'action requiert un ancrage propre à l'organisation. Sa question est une question de récupération de 1 à 2 000 caractères, et non un remplacement de l'invite système :
defineAction({
name: 'Prepare strategy recommendation',
actionType: 'workflow',
isRequired: true,
workflowSlug: 'strategy-agent',
metadata: {
companyContextBriefing: {
question:
'Which verified priorities, constraints, risk appetite, stakeholders, terminology, and policies are relevant to this strategy recommendation?',
},
},
})Avant l'exécution, la plateforme ajoute les détails de l'étape, de la tâche, de l'action et du sujet à cette question, interroge le Contexte de l'entreprise et injecte la réponse citée comme catégorie de contexte séparée. Le texte récupéré est traité comme une preuve non fiable, jamais comme une instruction, et l'invite du workflow reste inchangée. Un Contexte de l'entreprise absent, vide ou temporairement indisponible ne bloque pas l'exécution ; l'action utilise l'invite générique de l'extension et le panneau Invite & Contexte affiche l'état du briefing.
Les administrateurs de l'organisation configurent et gouvernent cette source depuis la page Contexte de l'entreprise ; les auteurs d’extensions peuvent uniquement la lire via ce contrat de briefing ou une autre surface de lecture autorisée.
Posez d'abord la question, raffinez explicitement : la Conversation
d'action partagée du Wizard est un fil durable commun à toutes les personnes
autorisées pour l'action. Les participants peuvent poser des questions de suivi
sans modifier la sortie. Sur une réponse Cortex éligible, Appliquer comme
raffinement démarre une nouvelle exécution autorisée sur la sortie actuelle ;
Cortex ne peut pas décider silencieusement de la raffiner. Pour les workflows
canvas, le raffinement atteint l'agent uniquement si le workflow consomme
<start.processFlowContext>.
Où atterrit la sortie IA
Quand une action workflow exécute un agent @system/, son résultat atterrit là
où son outputSurface l'indique — chaque surface est explicite, et une action peut
déclarer les deux :
- Surface
workflow— écrite à deux endroits :- L'action elle-même — verdict structuré (
verdict,confidence,rationale, titres des apprentissages cités) plus une copie Markdown rendue. - La base de connaissances de l'instance — une page sous
instances/<instance>/assessments/<stage>/, écrite automatiquement à la complétion. Cette page est la source de vérité : Saved to knowledge base sous la sortie de l'action l'ouvre. Le niveau de confidentialité de la page est plafonné au niveau le plus élevé des sources qui l'ont informée, de sorte que les lecteurs en dessous de ce niveau voient un refus au lieu du contenu.
- L'action elle-même — verdict structuré (
- Surface
approval— une bannière consultative sur une action de décision humaine (approvaloumap) de la tâche nommée partargetTaskSlug, qui n'est pas forcément la tâche de l'agent. Voir Bannière consultative sur les actions de décision pour la manière dont la cible est choisie. - Surface
document— un premier jet écrit dans une liste de la propre tâche de l'agent, nommée partargetActionSlug. Une personne modifie ensuite cette liste et la termine. Sans surfaceworkflowà côté, aucune page d'évaluation n'est écrite.
defineTask({
slug: 'agenda',
stageSlug: 'prepare',
name: 'Agenda',
actions: [
defineAction({
slug: 'agenda',
name: 'Agenda',
actionType: 'document',
isRequired: true,
metadata: { persona: 'secretary', editor: DOCUMENT_EDITOR.LIST },
}),
defineAction({
name: 'AI draft: agenda',
actionType: 'workflow',
workflowSlug: 'ai-agenda',
metadata: {
outputSurface: { kind: 'document', targetActionSlug: 'agenda' },
},
}),
],
})La réponse de l'agent doit être du JSON : {"items":[{"title":"…","description":"…"}]},
ou un tableau de ces éléments. Les blocs de code et le texte autour sont tolérés.
Demandez cette forme dans l'invite et donnez au bloc Agent un
format de réponse strict pour que le modèle s'y
tienne. La plateforme ne garde que title et description de chaque élément et
ignore les autres champs. Elle saute les éléments sans titre et tronque les textes
trop longs aux limites de la liste.
Un premier jet n'écrase jamais le travail d'une personne. Il n'est écrit que si la liste est vide, ou si elle contient encore, sans modification, le jet d'une exécution précédente. Relancer l'étape remplace donc son propre jet et rien d'autre. Si la réponse ne contient aucun élément exploitable, une liste vide affiche un avis indiquant que le jet de l'IA n'a pas pu être lu. Une liste qui a déjà des éléments les garde.
La liste est le seul endroit où le jet apparaît. L'étape d'agent n'est pas affichée comme un bloc à part à côté de la liste : c'est une section Brouillon IA repliée sous la liste, dans toutes les vues. La section montre le statut de l'étape, sa relance et un éventuel échec, et s'ouvre d'elle-même quand l'étape a échoué ou attend une personne. À l'intérieur, l'étape indique combien d'éléments elle a placés dans la liste, sans en afficher une seconde copie. Si la réponse ne contenait aucun élément exploitable, elle affiche la réponse telle que le modèle l'a écrite. Elle ne compte pas non plus comme une étape à part : la progression de la tâche sur la carte Kanban et dans le rail du Wizard, ainsi que la liste des étapes de la tâche dans le rail, la laissent de côté.
Le build échoue quand targetActionSlug manque, ne correspond à aucune action de
la tâche de l'agent, ou désigne une action qui n'est pas une liste. Une surface
document sur une action d'étape fait aussi échouer le build : une action
d'étape n'a pas de tâche où chercher. À l'import d'une extension déjà publiée, ces cas
sont signalés comme des avertissements.
Réexécuter l'action met à jour la même page sur place. Le panneau Détails d'exécution sur l'action affiche l'enveloppe d'exécution (identifiant du workflow, identifiant d'exécution, statut, horodatages) et les charges utiles stockées brutes pour le débogage.
Quand la sortie est ancrée dans le Contexte de l'entreprise ou dans des contenus de KB d'instance, une section Références relie chaque source que l'utilisateur actuel est autorisé à ouvrir. Le serveur résout ces liens au moment de l'affichage ; les contenus supprimés ou inaccessibles sont signalés comme indisponibles au lieu d'exposer un chemin obsolète. Utilisez Focus pour agrandir une sortie de workflow, de document ou d'évaluation en lecture pleine page, puis A− / A+ pour adapter la taille du texte sans modifier la sortie enregistrée.
Comment la sortie IA est rendue — laissez l'invite choisir
Partout où un résultat d'agent est affiché (la carte d'action, la page d'évaluation, la vue document), la plateforme choisit le rendu d'après la forme de la réponse — ce qui signifie que votre invite décide de la présentation :
-
Blocs de contenu — une réponse qui est un tableau JSON de blocs de contenu est rendue avec les mêmes composants riches que le chat : sections de texte markdown, carte interactive, fiche de plan étape par étape, et chaque type de bloc que le chat gagnera à l'avenir. Instruisez l'agent dans votre invite, par exemple :
Answer as a JSON array of content blocks: [{ "type": "text", "text": "<your assessment as markdown>" }, { "type": "map", "title": "Affected area", "layers": [ { "id": "impact", "label": "Impact zone", "color": "#ef4444", "markers": [{ "id": "site-1", "lat": 51.2, "lng": 4.4, "label": "Site 1" }] } ] }] -
JSON structuré — tout autre objet/tableau JSON est rendu sous forme de fiches étiquetées (noms de champs humanisés, valeurs courtes en pastilles, champs de prose en markdown). Idéal pour les schémas de type verdict.
-
Prose — tout le reste est rendu en markdown.
La détection est stricte : si un seul élément d'un tableau JSON n'est pas un bloc de contenu valide, toute la réponse retombe sur les fiches structurées — une réponse de données n'est jamais partiellement avalée. Les types de blocs interactifs (options, confirmations) restent réservés au chat aujourd'hui et ne rendent rien sur les surfaces d'étape.
Verrouiller la sortie d'une étape — metadata.editableOutput: false
Par défaut, une personne peut modifier la réponse d'un agent, et le texte modifié
est conservé (et marqué comme relu par un humain). Déclarez editableOutput: false sur
une action workflow lorsqu'une modification serait ignorée silencieusement : une
étape dont la réponse alimente une étape d'agent ultérieure est réexécutée chaque fois
que ses propres entrées changent, et cette réexécution ne reprend pas le texte précédent.
Sur une étape verrouillée :
- il n'y a pas de contrôle Modifier ; le libellé « Modifiable uniquement par réexécution » le remplace, et Régénérer réexécute l'étape ;
- la plateforme refuse côté serveur toute modification directe de la réponse (HTTP 409,
code
output_edit_not_editable), quelle que soit la route utilisée ; - un relecteur modifie la réponse en indiquant à Cortex ce qu'il faut corriger. Cette instruction est enregistrée sur l'étape comme pilotage permanent — affichée sous forme de puce amovible — et l'étape est réexécutée à neuf, sans la réponse précédente. L'instruction s'applique ensuite à chaque exécution ultérieure de l'étape, y compris celles déclenchées par un changement en amont, jusqu'à ce que quelqu'un la retire avec le ✕ de la puce. Le pilotage fait au plus 2 000 caractères.
defineAction({
name: 'AI-voorstel: Risicoadvies',
actionType: 'workflow',
executionMode: 'automatic',
workflowSlug: 'risk-advice',
metadata: {
editableOutput: false, // its answer changes only by re-running
includeInSummary: false, // the pre-read already consolidates it
},
})Seule la valeur littérale false verrouille une étape ; un champ absent, true ou toute
autre valeur la laisse modifiable. Le drapeau s'applique uniquement aux actions workflow.
Exclure une étape du résumé de clôture — metadata.includeInSummary: false
Lorsqu'un flow se clôture, la plateforme écrit un summary.md condensé dans la base de
connaissances de l'instance. Définissez includeInSummary: false sur une étape dont le
contenu est déjà repris par une étape ultérieure, afin que le résumé ne le répète pas.
L'étape conserve sa page, son panneau et ses pièces jointes ; seul le résumé de clôture l'ignore.
Nommer un groupe dans le rail du wizard — attribut de tâche railGroupLabel
Lorsque plusieurs tâches d'une même étape alimentent toutes une seule tâche ultérieure (par
exemple cinq avis alimentant une note préparatoire), le rail du wizard les affiche comme un
seul arrêt repliable avec une pastille par étape. Le regroupement est déduit des dépendances ;
l'extension peut seulement le nommer, via l'attribut de tâche réservé railGroupLabel dans les
defaultAttributes du modèle de tâche. Sans lui, le rail utilise un libellé générique.
Invite & Contexte
Toute action portant un agent — une action workflow ou une étape d'agent @system/ —
affiche un panneau Invite & Contexte dans sa vue d'action (aussi bien dans le
parcours guidé Wizard que dans la fiche de détail de la tâche). Il expose l'invite
système utilisée par l'agent, le message utilisateur assemblé, ainsi que le
contexte inclus (documents d’entrée, la transcription de réunion et tout briefing
du Contexte de l'entreprise configuré). Ce que vous voyez correspond exactement à ce
qui s'exécute. Le contexte classifié n'est révélé que lorsque votre habilitation le permet.
Les invites de l'extension restent stables et en lecture seule. Utilisez
companyContextBriefingpour un ancrage propre au client plutôt que de copier ou remplacer l'invite.
Contrôler l'exposition du contexte
Par défaut, chaque étape d'agent reçoit l'intégralité du contexte éligible : documents d’entrée issus des tâches précédentes et la transcription de
réunion la plus récente de l'instance, et un résumé de contexte de scène (nom de
l'instance/tâche). Vous pouvez supprimer n'importe laquelle de ces catégories par étape
en déclarant metadata.excludeContext.
Les catégories :
| Catégorie | Ce qu'elle couvre |
|---|---|
inputDocs | Documents des tâches précédentes déclarés dans retrieval.inputs[] |
companyContext | Le briefing Company Context configuré avec companyContextBriefing |
priorCycles | Extraits des exécutions terminées antérieures de ce flux de processus |
answerFeedback | Les 👍/👎 et notes des relecteurs sur les réponses antérieures de cette même étape. Ils guident les exécutions suivantes de l'étape (même flux, même environnement), uniquement pour les sorties que l'exécution est habilitée à voir. |
transcript | La transcription de réunion la plus récente de l'instance |
stageSummary | L'en-tête nom instance/tâche qui ouvre le message utilisateur assemblé |
processContext | Contexte saisi par des personnes dans d'autres étapes de ce flux (actions document et map). Inclus automatiquement ; à supprimer lorsqu'une étape ne doit pas voir les saisies humaines en amont. |
Exemple — un agent ne devant pas voir les transcriptions de réunion :
metadata:
retrieval:
inputs:
- fromTask: upload-bid
excludeContext:
- transcript # this agent must not see meeting transcripts- Absent ou vide,
excludeContextsignifie que toutes les catégories éligibles sont exposées — comportement par défaut. - La suppression est appliquée à l'exécution : la source exclue n'est pas récupérée,
pas seulement masquée de l'interface. Supprimer
inputDocssupprime la spécification de récupération avant toute lecture ; supprimertranscriptignore entièrement la recherche de transcription ; supprimeranswerFeedbackne lit jamais les évaluations antérieures. - Une instruction d'affinage et les réponses de l'assigné aux questions de suivi ne sont pas des catégories : elles sont toujours transmises. Les réponses aux questions de suivi sont enregistrées sur l'action et utilisées à chaque exécution ultérieure de celle-ci.
- Le panneau Invite & Contexte de l'action affiche chaque catégorie comme Exposée ou Supprimée par le modèle, de sorte qu'il est toujours clair à quel contexte l'agent avait accès.
entity_link
Réservé / pas encore couvert par une extension d'exemple dans l'arborescence. Le schéma accepte entity_link et le runtime affiche une carte de substitution, mais aucun modèle d'exemple dans @scrydon/sdk-authoring/src/process-flows/examples/ ne l'utilise actuellement. Préférez document, file_upload, ou une action workflow qui écrit une instance typée via l'API d'ontologie jusqu'à ce qu'un exemple fonctionnel soit distribué. Créez un ticket si vous avez un cas d'usage qui nécessite ce type aujourd'hui.
L'intention est d'attacher une tâche à une instance d'objet typé provenant de l'ontologie — de sorte que l'action devienne « remplir cet objet » plutôt que « produire un artefact libre ». Le type d'objet serait déclaré via task.ontologyContract.writes (ou le ontologyContract de l'action), et le runtime scaffolderait un éditeur pour les propriétés de ce type.
Champs de schéma utilisés : name, description, isRequired, executionMode, ontologyContract (déclaré au niveau de l'action ou de la tâche).
file_upload
L'assigné téléverse un ou plusieurs fichiers. Les octets transitent dans la base de connaissances de l'espace de travail de l'instance, sont hachés et stockés via @scrydon/storage, et sont immédiatement indexés — les rendant disponibles pour les étapes actionType: "workflow" en aval via metadata.retrieval.inputs[].fromTask.
Utiliser pour : importer des documents source (appels d'offres, contrats, extensions de conseil d'administration, preuves d'audit) sur lesquels les étapes IA en aval doivent raisonner.
Champs de schéma utilisés : name, description, isRequired, executionMode, metadata.persona, et les contraintes de zone de dépôt optionnelles metadata.acceptedExtensions, metadata.acceptedMimeTypes, metadata.maxSizeMb.
Contraindre ce qui peut être téléversé. Une étape file_upload accepte toujours plusieurs fichiers ; vous ne pouvez pas désactiver la multiplicité. Ce que vous pouvez déclarer, c'est quels fichiers et quelle taille :
Champ metadata | Type | Effet |
|---|---|---|
acceptedExtensions | string[] | Extensions de fichier autorisées (point initial, insensible à la casse — ex. [".pdf", ".docx"]). |
acceptedMimeTypes | string[] | Types MIME autorisés (ex. ["application/pdf"]). Élargit l'acceptation — un fichier passe si son extension ou son type MIME correspond. |
maxSizeMb | number | Limite de taille par fichier, en mégaoctets. |
Les trois sont optionnels. Ne déclarez rien et l'étape revient aux valeurs par défaut de la plateforme : extensions .pdf .docx .xlsx .csv .pptx .txt .md .json, taille maximale 50 Mo. Déclarer acceptedExtensions ou acceptedMimeTypes remplace l'ensemble d'extensions par défaut, vous pouvez donc filtrer uniquement par MIME si vous préférez.
Ces limites sont appliquées à la fois dans le sélecteur (le filtre accept de la zone de dépôt et la vérification de taille) et côté serveur lors du téléversement — une requête qui contourne l'interface est toujours rejetée avec un 400. Les instances en cours existantes conservent les valeurs par défaut ; les contraintes prennent effet pour les flows instanciés après leur déclaration dans le modèle.
Accepté ≠ indexé dans la base de connaissances. Ce que vous autorisez ici contrôle quels fichiers s'attachent à l'étape. Qu'un fichier joint devienne également une page de base de connaissances interrogeable est décidé séparément par le pipeline d'ingestion, qui peut actuellement extraire du texte de : PDF, DOCX, ODT, PPTX, XLSX, TXT, MD, CSV, JSON et audio (transcrit) — chacune des extensions par défaut de la plateforme est donc indexée. Les autres types qu'un auteur autorise — les anciens .doc / .xls / .ppt, les images et les archives — se téléversent et restent téléchargeables comme pièces jointes, mais ne sont pas indexés dans la base de connaissances de l'instance, de sorte que les étapes workflow en aval utilisant metadata.retrieval ne peuvent pas lire leur contenu. Le téléversement lui-même réussit toujours — les fichiers non indexables sont simplement marqués « Non indexé » dans le panneau de la tâche, et le reste du lot est indexé normalement. Limitez acceptedExtensions à l'ensemble indexable si une étape existe uniquement pour alimenter une étape IA.
Exemple (du modèle AI Boardroom — le secrétaire téléverse l'extension du conseil d'administration) :
defineAction({
name: 'Upload board documents',
actionType: 'file_upload',
isRequired: true,
executionMode: 'manual',
metadata: {
persona: 'secretary',
// Optional — omit for the platform defaults (.pdf/.docx/.xlsx/.csv/.pptx/.txt/.md/.json, 50 MB).
acceptedExtensions: ['.pdf', '.docx', '.pptx'],
acceptedMimeTypes: ['application/pdf'],
maxSizeMb: 25,
},
})Ce que voit l'assigné : une carte avec zone de dépôt qui accepte plusieurs fichiers — sélectionnez-en plusieurs à la fois ou continuez à ajouter des fichiers au fil du temps (chaque téléversement s'ajoute à la liste des pièces jointes de l'étape ; les fichiers existants ne sont jamais remplacés). Le sélecteur ne propose que les types acceptés et rejette les fichiers dépassant la limite de taille. La complétion est explicite : dès qu'au moins un fichier est joint, l'assigné marque l'étape comme terminée (le bouton Marquer l'étape comme terminée sur la carte, ou la case à cocher de l'action dans la liste de contrôle de la tâche). Chaque fichier devient une page KB que les tâches en aval peuvent référencer par le slug de cette tâche.
Une action workflow en aval qui veut raisonner sur les fichiers téléversés les déclare via metadata.retrieval.inputs: [{ fromTask: "upload-board-documents", mode: "full" }]. Voir la section retrieval de la page parente.
distribution
Envoie un message aux détenteurs d'un ou plusieurs personas — et, sous réserve d'une politique d'organisation, à des destinataires entièrement extérieurs à l'organisation — et suit l'acquittement. Utilisé pour diffuser un résultat, demander un acquittement avant un exercice sur table, boucler avec des parties prenantes qui ne pilotent pas activement le flow, ou notifier une contrepartie comme un chercheur ayant signalé une vulnérabilité ou un client concerné.
La livraison passe par email via le mailer de la plateforme, et par Cortex (channel: "cortex") sous forme de message Cortex. Aucun des deux ne demande de configuration d'extension. channel: "slack" et channel: "teams" sont acceptés dans le manifeste comme intention déclarée, mais la livraison pour ces canaux sera activée dans une version future ; en attendant, le panneau affiche le canal déclaré et désactive l'envoi pour ceux-ci.
Utiliser pour : notifications, briefings, résumés poussés vers des canaux externes.
Champs de schéma utilisés : name, description, isRequired, executionMode, plus un bloc metadata plus riche :
Clé metadata | Signification |
|---|---|
persona | Qui l'envoie (piste d'audit). |
channel | "email" · "cortex" (livrés aujourd'hui ; voir Publier dans Cortex) · "slack" · "teams" (intention déclarée ; livraison pas encore active). |
recipientPersonas | Tableau de slugs de personas dont les détenteurs reçoivent le message. |
externalRecipients | Destinataires qui ne sont pas membres de l'organisation — { addresses?, fromContext? }. E-mail uniquement, et soumis à une politique d'organisation désactivée par défaut. Voir Joindre une personne extérieure à l'organisation. |
cortexChannels | Pour channel: "cortex" : jusqu'à 20 canaux Cortex où publier, en plus des messages directs des détenteurs de persona. Chaque référence désigne un seul canal : { kind: "literal", value: "<id du canal>" } (un id de canal est un UUID : celui d'un canal d'espace de travail ou d'un canal d'organisation, Default compris), ou { kind: "attribute", key: "<attribut>" } désignant un attribut déclaré, à valeur unique, de type string ou uuid, qui contient un id de canal. Lu uniquement sur une action distribution dont le channel est cortex ; toute autre action qui le déclare est refusée à l'installation. Voir Publier dans Cortex. |
attachments | Jusqu'à dix sélecteurs { fromTask } uniques (plafond sur les SOURCES déclarées ; une seule tâche de téléversement peut fournir plusieurs fichiers) pour des artefacts document, workflow ou file_upload en amont. Email uniquement. |
completion | "fire-and-forget" (terminé dès l'envoi) · "all-acknowledged" (chaque destinataire doit acquitter) · "quorum" (N acquittements nécessaires). |
quorumCount | Requis quand completion: "quorum". |
Exemple — distribuer un rapport prêt pour l'audit et ses fichiers justificatifs par email :
defineAction({
name: 'Distribute report summary',
actionType: 'distribution',
isRequired: true,
metadata: {
persona: 'compliance-lead',
channel: 'email',
recipientPersonas: ['compliance-lead', 'ciso', 'dpo', 'eng-owner'],
attachments: [
{ fromTask: 'final-report' },
{ fromTask: 'supporting-files' },
],
completion: 'all-acknowledged',
},
})Ce que voit l'assigné : un panneau ciblé montrant le canal déclaré, le mode de complétion et la liste de destinataires résolue (les utilisateurs assignés à chaque persona destinataire par nom). Pour l'email, un formulaire de composition (sujet pré-rempli avec le nom de l'action, message modifiable) envoie à chaque destinataire un lien personnel suivi ; une fois envoyé, le panneau affiche le statut d'acquittement par destinataire et une ligne de progression « N sur M acquittés » pour les modes all-acknowledged / quorum. Le lien suivi ouvre une page de confirmation ; le destinataire acquitte avec son bouton Acknowledge receipt (ouvrir le lien seul n'enregistre rien, si bien qu'un scanner de messagerie qui suit les liens ne peut acquitter pour personne). Une distribution cortex n'a pas de formulaire de composition : la plateforme la publie quand l'étape s'exécute.
Les liens de reçu sont à usage unique
Le lien de chaque destinataire porte un code à usage unique que Scrydon conserve pour ce seul destinataire :
- Un lien acquitte une seule fois, et seulement pour la personne à qui il a été envoyé. Le rouvrir affiche le reçu enregistré ; il n'en enregistre jamais un second. Un lien transféré ne peut acquitter pour personne d'autre.
- Un lien peut être remplacé. Quand l'envoi d'une étape est relancé parce que certains destinataires n'ont pas encore pu être joints, chaque destinataire qui n'a pas encore acquitté peut recevoir un nouveau lien — le lien seul, avec le sujet propre à l'étape : jamais à nouveau le message, le contenu ou une pièce jointe, et jamais un sujet saisi pour la relance. Cela arrive au plus une fois par heure et par étape ; une relance sans personne à joindre ne renvoie rien. L'ancien lien ouvre alors une page indiquant « This link was replaced » et invite le destinataire à utiliser son message le plus récent.
- Une étape terminée n'est pas renvoyée. Ses destinataires sont réglés ; un envoi sur cette étape est refusé (« Cette étape est déjà terminée »). Deux envois simultanés d'une même étape ne s'exécutent pas deux fois : le second est informé que l'étape est déjà en cours d'envoi.
- Les liens envoyés avant l'introduction des liens à usage unique ont été remplacés de la même manière, une fois, lors de la mise à niveau de votre déploiement. Un destinataire qui ouvre l'un de ces anciens liens voit la même page « remplacé ».
Avant l'envoi d'un nouveau lien, l'accès du destinataire est vérifié à nouveau. Une personne qui a perdu l'accès au flux, ou dont le domaine n'est plus autorisé pour la distribution externe, ne reçoit pas de lien et apparaît comme ignorée sur l'étape.
Les sélecteurs de pièces jointes doivent viser une dépendance directe ou transitive de la tâche, ou une tâche d'une étape antérieure lorsque le flux d'étapes est séquentiel. La tâche sélectionnée doit produire un document, une sortie de workflow ou un fichier téléversé. Les sorties générées sont jointes en Markdown UTF-8 ; les téléversements conservent leurs octets et type MIME d'origine. L'ensemble complet est résolu avant toute notification, avec au maximum vingt fichiers, 8 Mio par fichier et 8 Mio au total. L'email reçoit de vraies pièces jointes MIME, tandis que la notification in-app expose des liens autorisés même si le destinataire a désactivé l'email ou si aucun fournisseur n'est configuré. Si la source est supprimée ou modifiée après l'envoi, l'ancien lien ne sert pas des octets différents.
Publier dans Cortex
Avec channel: "cortex", une distribution publie son message dans Cortex au lieu de l'envoyer par email. Chaque détenteur d'un persona de recipientPersonas le reçoit dans son propre message direct, même s'il n'en a pas encore dans Cortex : la plateforme en ouvre un. Chaque canal de cortexChannels le reçoit comme publication. body est le message et il est obligatoire. Le subject, s'il est présent, s'affiche en gras au-dessus.
defineAction({
name: 'Post the release note',
actionType: 'distribution',
executionMode: 'automatic',
isRequired: true,
metadata: {
channel: 'cortex',
recipientPersonas: ['release-manager'],
// 'releaseChannel' is a string or uuid attribute, declared in
// template.attributes, that holds a Cortex channel id.
cortexChannels: [{ kind: 'attribute', key: 'releaseChannel' }],
subject: 'Release 4.2',
body: 'Release 4.2 is out. The notes are in the release workspace.',
completion: 'fire-and-forget',
},
})Le manifeste, et extension validate, refusent une distribution Cortex qui :
- n'est pas en
executionMode: "automatic"ou"on_entry". C'est la plateforme qui l'envoie ; il n'y a rien à composer ; - attend un acquittement. Un message Cortex ne porte pas de lien d'accusé de réception, donc
completiondoit valoir"fire-and-forget"(la valeur par défaut) ; - n'a pas de
body; - ne nomme ni persona ni canal ;
- déclare des
attachmentsou desexternalRecipients. Les deux restent réservés à l'email ; - liste une référence
cortexChannelsqui ne peut désigner aucun canal : une valeur littérale qui n'est pas un UUID, ou un attribut qui n'est pas déclaré, n'est pas de typestringouuuid, ou contient une liste (cardinality: "many"). Une référence désigne un seul canal ; pour plusieurs canaux, listez une référence par canal, jusqu'à 20.
cortexChannels n'est lu que sur une action distribution dont le channel est cortex. Toute autre action qui le déclare est refusée à l'installation.
Un attribut qui désigne un canal est lu au moment où l'étape s'exécute. S'il n'a pas de valeur pour cette exécution, la référence ne désigne aucun canal : rien n'est publié pour elle et rien n'est enregistré, car le canal n'est peut-être pas encore choisi. Une valeur qui n'est pas un id de canal (pas un UUID), ou un attribut que le modèle ne déclare pas, est ignorée, et la clé de l'attribut est listée sous unresolvableCortexChannels dans l'entrée de journal d'activité distribution_dispatched de l'étape, écrite quand l'envoi réussit. Si cela ne laisse à l'étape personne à qui envoyer, elle échoue.
Au nom de qui il publie. Le lecteur propre à l'étape : la personne assignée à la tâche de l'étape, sinon celle qui tient le persona de rôle de la tâche ; si personne, le compte Run As du workflow auquel l'action renvoie ; à défaut, la personne qui a lancé le flow. Une distribution ordinaire ne renvoie à aucun workflow : elle passe donc du détenteur du persona directement à la personne qui a lancé le flow ; l'étape Run As ne s'applique qu'à une action qui en référence un. C'est le même ordre que celui qu'une étape suit pour lire ses entrées. Un flow créé par le système, ou lancé par un workflow d'extension, n'a pas de personne qui l'a lancé : sans Run As, il n'y a donc personne au nom de qui publier. Un compte Run As dont l'habilitation a été relevée au-dessus de celle pour laquelle il a été accrédité n'est pas utilisé, sans repli sur la personne qui a lancé le flow. Un Run As qui est un compte de service publie sous l'identité Cortex propre à ce compte, membre des canaux des espaces de travail auxquels il appartient, et d'un canal d'organisation seulement si un gestionnaire de ce canal (la personne qui l'a créé ou un administrateur de l'organisation) l'y a ajouté ; Default n'en contient jamais. Rien ne substitue une autre identité : une étape sans personne au nom de qui publier est refusée par la plateforme, et rien n'est publié.
Ce qui est enregistré. Chaque destinataire, personne ou canal, est une tentative de livraison, comme pour l'email. Une publication que Cortex a acceptée est enregistrée comme publiée, avec son id. Une publication refusée par la plateforme est enregistrée comme ignorée, avec la raison sous la forme collaboration_post_<raison> ; par exemple collaboration_post_not_a_channel_member quand le lecteur de l'étape n'a pas rejoint le canal. Les raisons sont celles de action.collaboration.post, qui encadre aussi chaque publication : ses limites, sa prévention des pertes de données et ses contrôles de classification s'appliquent, et Cortex doit être activé pour l'organisation (Disponibilité). L'étape se termine dès qu'une publication est arrivée ; une publication qui a échoué en chemin n'est plus retentée ensuite.
Quand rien n'est arrivé. Si aucune publication n'est arrivée et qu'au moins une a échoué d'une façon qu'un nouvel essai peut corriger (Cortex indisponible, limite de débit, panne réseau), l'étape échoue comme réessayable et repasse en attente. Elle est renvoyée dès que l'une de trois choses se produit : le flux avance, quelqu'un ouvre le flux, ou la relance horaire de la plateforme s'exécute. Un flux que personne ne touche est donc retenté dans l'heure. Une relance utilise les mêmes clés : une publication déjà passée n'est donc jamais publiée deux fois. Chaque publication est tentée cinq fois au plus par exécution. Un essai que Cortex a limité en débit ne compte pas parmi les cinq : la publication n'a pas été refusée, seulement retardée, elle est donc retentée jusqu'à ce que Cortex l'accepte. Après le cinquième échec compté, le destinataire est enregistré comme ignoré, avec collaboration_post_retries_exhausted. Si personne n'a été atteint, l'étape échoue alors définitivement, pour l'une de deux raisons, et aucune n'est retentée :
- Cortex n'a pas pu publier avec le compte de l'étape. Le compte au nom duquel l'étape publie n'a pas pu se connecter, par exemple parce qu'il a été désactivé ou révoqué.
- Cortex n'a pas réussi à accepter la publication, à aucun des essais.
Une publication que la plateforme a refusée définitivement n'est pas retentée ; elle ne compte donc jamais parmi les cinq. Si toutes les publications ont été refusées (par exemple collaboration_post_not_a_channel_member pour l'unique destinataire), personne n'a été atteint et l'étape échoue avec distribution_no_recipients_delivered.
Quand une publication est peut-être arrivée. Il arrive que Cortex accepte une publication sans pouvoir confirmer ensuite qu'elle a été envoyée. Cette publication est peut-être dans la conversation : elle n'est donc pas retentée, et le destinataire est enregistré comme ignoré, avec collaboration_post_unconfirmed. Si personne d'autre n'a été atteint, l'étape échoue définitivement avec distribution_cortex_post_unconfirmed, jamais avec distribution_no_recipients_delivered, et l'étape indique que le message a peut-être été publié. Vérifiez la conversation dans Cortex avant de rouvrir l'étape : une étape rouverte publie avec de nouvelles clés, donc une publication déjà passée apparaîtrait deux fois. Cette raison est donnée plutôt que celle d'une publication qui a épuisé ses essais ; une publication qu'un nouvel essai peut encore faire arriver passe avant elle, et l'étape reste réessayable.
Une fois la cause corrigée, rouvrez l'étape pour publier à nouveau. Quand les cinq essais sont épuisés, renvoyer la même exécution ne publie rien, car ses tentatives sont déjà réglées ; une réouverture démarre une nouvelle exécution, comme pour un stade rouvert, décrit plus bas.
Le message est envoyé par Cortex et porte la trace de l'exécution qui l'a publié. Il ne mentionne personne : @room, les ids d'utilisateur et les liens vers des personnes dans le texte ne notifient personne.
Joindre une personne extérieure à l'organisation
recipientPersonas se résout, via les affectations de persona de l'instance, en comptes utilisateurs de la plateforme : seule, une action de distribution ne peut donc atteindre que des personnes déjà membres de l'organisation. Or une grande partie des processus réels existent à cause d'une contrepartie — un chercheur qui signale une vulnérabilité dans le cadre d'un SLA de divulgation coordonnée, un client dont le propre délai légal ne démarre qu'une fois notifié, un candidat, un fournisseur, une personne concernée au sens de l'art. 34 du RGPD — et chacune d'elles est externe par définition.
metadata.externalRecipients les ajoute. C'est additif : le déclarer ne retire jamais un destinataire issu d'un persona.
Clé externalRecipients | Signification |
|---|---|
recipients | Recommandé. Une liste de références : { kind: "attribute", key } désigne un attribut d'instance déclaré, résolu à chaque exécution ; { kind: "literal", value } est une adresse fixée à la rédaction. Un attribut peut contenir une adresse ou, avec cardinality: "many", une liste. |
addresses | Adresses e-mail fixées au moment de la rédaction — boîte de réception d'un régulateur, alias de sécurité partagé. Équivaut à des destinataires literal, à ceci près qu'une adresse mal formée est ignorée silencieusement (comme auparavant) au lieu d'être signalée comme non résolue. |
fromContext | Alias hérité. Chemins séparés par des points dans les valeurs enregistrées de l'instance. Un chemin à un seul segment (reporterEmail) se résout exactement comme { kind: "attribute", key: "reporterEmail" }, déclaration et classification comprises ; un chemin imbriqué (reporter.email) reste lu pour les flux écrits avant les attributs. Les nouveaux flux déclarent un attribut et utilisent recipients. |
defineAction({
name: 'Acknowledge receipt to the reporter',
actionType: 'distribution',
isRequired: true,
metadata: {
persona: 'security-lead',
channel: 'email',
externalRecipients: {
recipients: [
// Déclaré dans template.attributes ; enregistré au démarrage du run.
{ kind: 'attribute', key: 'reporterEmail' },
// Et l'alias de divulgation partagé est toujours en copie.
{ kind: 'literal', value: 'security@example.com' },
],
},
completion: 'all-acknowledged',
},
})Déclarer un destinataire externe n'est pas l'autorisation de le joindre. Chaque adresse résolue est vérifiée à l'envoi face à la politique de distribution externe de l'organisation, désactivée par défaut, qui n'autorise rien tant qu'un administrateur ne l'a pas activée et n'a pas autorisé le domaine du destinataire dans Paramètres → Gouvernance → External Distribution. Un modèle dont l'envoi externe est interdit le signale à l'inspection du pack au moment de l'installation, puis de nouveau sur l'étape elle-même avant tout envoi — il ne se termine jamais comme s'il avait envoyé quelque chose. Chaque modification de cette politique est enregistrée dans le journal d'audit sous process_flow.external_distribution.updated, avec l'administrateur qui l'a faite et les paramètres avant et après.
Un destinataire attribut sans valeur sur ce run est ignoré. Un destinataire dont la valeur enregistrée n'est pas une adresse exploitable, ou que le modèle ne déclare pas, est signalé sur l'étape comme non résoluble au lieu d'être écarté en silence.
Ce qui change pour un destinataire externe :
- E-mail uniquement. Aucune notification dans l'application : il n'y a pas de compte pour l'afficher.
channeldoit valoir"email"; le validateur de manifeste refuse un destinataire externe sur tout autre canal. - Le même lien d'accusé de réception suivi. Il reçoit l'URL de reçu signée identique, si bien que les modes
all-acknowledgedetquorumle comptent exactement comme un membre. C'est toute la différence entre un flux qui enregistre qu'un message était dû et un flux qui enregistre qu'il a été envoyé et reçu. - Les pièces jointes sont gouvernées séparément. Elles sont refusées tant que l'organisation ne les a pas autorisées pour la distribution externe, et chacune doit se situer au niveau ou en dessous du plafond de classification configuré. Un artefact généré (sortie d'un
documentou d'unworkflow) porte une classification résoluble et peut être envoyé ; un fichier téléversé non — son marquage réside dans la couche de stockage, que le répartiteur ne peut pas lire sans principal — les téléversements ne sont donc jamais envoyés à un destinataire externe. L'étape le signale au lieu d'abandonner le fichier en silence. - Les pièces jointes arrivent en octets, jamais en liens. L'URL de pièce jointe dans l'application exige une session : un lien échouerait dans la messagerie d'une contrepartie.
- Le résultat de la livraison est enregistré dans les deux cas. Un rejet ou un fournisseur de messagerie non configuré est enregistré comme un échec sur l'action, distinguable d'un envoi qui a abouti.
Un opérateur peut également ajouter une adresse externe ponctuelle dans le formulaire de rédaction au moment de l'envoi, soumise à la même politique. Les destinataires déclarés par le pack et ceux ajoutés par l'opérateur sont tous affichés sur l'étape, chacun avec son propre verdict.
Lorsque personne ne détient un persona destinataire, le contenu n'est envoyé à personne à la place du persona. Les destinataires sont résolus à partir des assignations du persona, puis d'un rôle d'organisation portant le même slug. Si aucun des deux ne donne personne, les administrateurs de l'organisation reçoivent à la place un avis de pourvoi. Cet avis nomme le persona non assigné et renvoie au flow où quelqu'un peut lui être assigné. Il ne contient ni l'objet, ni le corps, ni les pièces jointes de la distribution, car le contenu était adressé au titulaire du persona et un administrateur n'est pas cette personne. L'étape échoue ensuite avec la raison Personne ne détient le persona destinataire de cette étape, affichée dans son panneau. Dès qu'une personne est assignée au persona, l'étape repasse en attente : une étape automatique est renvoyée aussitôt, et une étape manuelle peut être renvoyée. Les destinataires ajoutés à la main par un opérateur et les destinataires externes reçoivent quand même l'envoi, car ils ont été adressés directement et non via le persona. Il en va de même d'un canal Cortex de cortexChannels : il reçoit quand même la publication, les administrateurs reçoivent quand même l'avis de pourvoi, et l'étape n'échoue faute de titulaire que si l'envoi ne nomme personne d'autre. Un flow démarré par un déclencheur n'a aucune assignation de persona. La liste des destinataires de l'étape indique lorsqu'une personne a été atteinte via le rôle d'organisation du même nom.
Les approbations suivent la même règle. Seule une personne assignée au persona peut approuver. Un administrateur est prévenu pour qu'il pourvoie le persona, et ne peut pas approuver à sa place. Assignez quelqu'un au persona pour débloquer l'étape.
Si un envoi échoue pour une autre raison (aucun canal de livraison configuré, aucun destinataire joignable, pièces jointes indisponibles, ou échec de la recherche du persona lui-même, qui est relancée automatiquement), la raison est enregistrée sur l'action et affichée dans son panneau. Cette raison appartient à cette tentative : elle est effacée quand l'étape est envoyée avec succès, quand l'affectation du persona la remet en attente, et quand son étape de flux est rouverte, car une étape rouverte n'a pas encore été tentée.
Une étape dont le stade est rouvert est renvoyée comme un nouvel envoi. Quand une approbation refusée ramène le flux à un stade antérieur, une étape de diffusion déjà envoyée repasse en attente et son panneau propose à nouveau le formulaire de rédaction et Envoyer. Le nouvel envoi a ses propres destinataires, pièces jointes et liens d'accusé de réception. Les accusés de réception de l'envoi précédent ne comptent pas pour lui, et un lien d'accusé de réception de l'ancien e-mail ne fonctionne plus une fois l'étape renvoyée.
all-acknowledged et quorum ne comptent que les destinataires qui peuvent encore acquitter. Un destinataire qui perd l'accès au process flow après la livraison est exclu de l'exigence : l'étape se termine dès que tous ceux qui peuvent acquitter l'ont fait. S'il ne reste personne, l'étape échoue avec une raison explicite au lieu d'attendre indéfiniment. La plateforme revérifie les étapes en attente toutes les heures, même quand plus personne ne peut cliquer. À l'envoi, une étape quorum est refusée si le nombre de destinataires réellement joignables est inférieur à quorumCount.
Envoi automatique à l'entrée dans l'étape. Les actions de distribution rédigées avec executionMode: "on_entry" (ou "automatic" sans dépendance amont déclarée) se déclenchent automatiquement dès que leur étape devient active — aucune action de l'assigné n'est nécessaire pour lancer l'envoi. Utilisez-le pour les notifications de diffusion qui doivent partir dès l'ouverture d'une étape, par exemple pour prévenir les parties prenantes qu'une revue a commencé.
voice_trigger
Capture de l'audio contre un chemin défini sur le modèle. L'enregistrement est automatiquement transcrit (STT passe par la capacité STT). Quand l'action se termine, la transcription est écrite comme une page dans la KB de l'instance du process flow sous instances/<instance>/transcripts/<stage>/, et l'audio brut est mis en file d'attente pour ingestion sous instances/<instance>/audio/<stage>/ — les deux prêts à ancrer les étapes en aval.
Utiliser pour : enregistrements de réunions, dictée, enrichissement vocal d'un flow qui est majoritairement silencieux le reste du temps.
Champs de schéma utilisés : name, description, isRequired, executionMode, metadata.persona, metadata.triggerPath (correspond à un path sur une entrée template.voiceTriggers[] de niveau supérieur).
Exemple (du modèle AI Boardroom — enregistrement de la réunion elle-même, couplé à un déclencheur vocal de niveau supérieur qui configure le STT et l'enrichissement en aval) :
// On the task action:
defineAction({
name: 'Record meeting',
actionType: 'voice_trigger',
isRequired: true,
executionMode: 'manual',
metadata: { persona: 'secretary', triggerPath: 'board-meeting' },
})
// On the top-level template:
defineVoiceTrigger({
path: 'board-meeting',
sttProvider: 'openai',
language: 'en',
enrichments: [
{ workflowId: '@system/agent-meeting-summarizer' },
],
})Ce que voit l'assigné : une carte avec un bouton d'enregistrement. Pendant un enregistrement de Process Flow, la plateforme met à jour un brouillon de transcription unique dans la KB d'instance à une cadence limitée, et le Wizard conserve la vue des connaissances et la Conversation d'action partagée à côté de l'enregistreur. Les participants autorisés peuvent poser des questions semi-en direct sur le même brouillon persisté ; le navigateur n'envoie jamais directement à Cortex de parole privée non persistée. À l'arrêt, le reste de la transcription et l'audio sont vidés avant la complétion de l'action, et les nouvelles tentatives mettent à jour les mêmes artefacts sans créer de doublons. Les workflows d'enrichissement s'exécutent ensuite sur la transcription finalisée.
Le couplage est ce qui rend les déclencheurs vocaux puissants : l'action déclare « enregistrer ici », et le déclencheur vocal au niveau du modèle déclare « pour tout enregistrement au chemin X, exécuter ces enrichissements ». Une transcription, de nombreux agents en aval.
Vous ne déployez rien vous-même pour ce couplage. La plateforme matérialise le déclencheur vocal automatiquement la première fois qu'un assigné démarre un enregistrement : un workflow de déclencheur dédié (nommé Voice Trigger — <path>) est provisionné une fois par environnement d'espace de travail, portant le fournisseur STT, le modèle et la langue de l'entrée voiceTriggers[]. Les chemins sont mis en correspondance en ignorant un slash initial, donc /board-meeting et board-meeting se couplent bien.
map
Affiche une couche géo gouvernée en direct — un ou plusieurs types d'objets d'ontologie avec des colonnes de coordonnées liées — à l'intérieur de la tâche, et capture une classification de l'opérateur par entité. L'assigné voit la carte, touche une entité pour la sélectionner et choisit l'une des options déclarées par l'auteur (en ajoutant éventuellement des notes libres). La classification enregistrée est stockée sur l'action ; un writeTarget optionnel la réécrit dans la table gérée qui sous-tend le type d'objet.
Utiliser pour : étapes de situation tactique / image opérationnelle commune, classification en réponse à incident, toute étape où un opérateur doit voir des données spatiales et prendre une décision par entité.
Champs de schéma utilisés : name, description, isRequired, executionMode, et un bloc metadata avec deux sous-objets :
Clé metadata | Sous-clé | Signification |
|---|---|---|
geo | objectTypes | Tableau de slugs de types d'objets d'ontologie à afficher. Chaque type doit être lié à une table gérée avec au moins une colonne de coordonnées. |
geo | ontologySlug | Facultatif. Restreint les couches géo aux types d'objets d'une ontologie précise. Omis, la plateforme cherche les objectTypes listés dans toutes les ontologies lisibles. |
geo | focusAttribute | Facultatif — mais de fait requis lorsque feedback.writeTarget est déclaré : l'identifiant de la ligne cible de la classification soumise est dérivé de l'entité focalisée. Une clé de l'objet de contexte de l'instance (la valeur context passée à la création de l'instance) dont la valeur est l'identifiant de l'entité à centrer et mettre en évidence au chargement. |
geo | focusDetection | Booléen facultatif. À true, et si l'instance a été ouverte par une détection de surveillance, la carte affiche aussi les contacts contributeurs de cette détection dans une couche supplémentaire. La carte n'affiche une détection que pour une instance ouverte par le dispatch de la surveillance lui-même ; la plateforme prend la détection dans l'enregistrement propre du dispatch, jamais dans le contexte de l'instance, de sorte qu'une instance démarrée manuellement — ou dont le contexte désigne une détection — n'affiche rien. La détection est lue avec l'habilitation du lecteur : une détection que le lecteur ne peut pas lire n'affiche non plus aucun contact — et les deux cas sont indiscernables, de sorte que la carte ne révèle jamais l'existence d'une détection masquée. Absent ou false → la carte n'affiche que objectTypes. |
feedback | prompt | Courte invite affichée au-dessus des options de classification. |
feedback | options | Tableau d'objets { value, label } déclaré par l'auteur — les choix possibles pour l'opérateur. 2 à 8 éléments requis. |
feedback | allowNotes | Booléen. À true, un champ de notes libres apparaît sous les options. |
feedback | writeTarget | Facultatif. Déclare une réécriture : à la soumission, la value choisie est insérée ou mise à jour (upsert) dans la table gérée qui sous-tend le type d'objet nommé. Exige que le type d'objet ait une liaison silver à identité mono-colonne ; les cibles à identité composite sont rejetées par défaut (fail-closed). |
Contrat d'échec de la réécriture. Si writeTarget est déclaré et que l'upsert échoue (HTTP 502 write_through_failed), l'action reste ouverte au lieu de se terminer — les notes et la classification enregistrées sont conservées, et un bouton Réessayer apparaît. L'opérateur peut ainsi soumettre à nouveau sans ressaisir sa décision.
writeTarget a besoin d'une ligne cible dans laquelle écrire. Déclarez geo.focusAttribute et instanciez le flow avec un contexte qui porte l'identifiant de l'entité focalisée — une soumission sans focus résoluble est rejetée avec 400 target_id_required. Si votre étape n'a pas d'entité focalisée unique, omettez writeTarget et enregistrez la classification sur l'action uniquement.
Exemple (de l'extension drone-response C2 — un opérateur capteur classe une piste détectée sur la situation tactique) :
- name: Tactical picture
actionType: map
isRequired: true
metadata:
geo:
objectTypes: [track] # bound, geo-bearing ontology object types
focusAttribute: trackRef # key in instance context holding the focused feature id
feedback:
prompt: Classify this track
options:
- { value: friendly, label: Friendly }
- { value: hostile, label: Hostile }
- { value: unknown, label: Unknown }
allowNotes: true
writeTarget: # optional write-through to the bound table
objectType: track
field: classificationLe trackRef ci-dessus est résolu depuis l'objet de contexte de l'instance au moment du rendu — c'est une clé dont la valeur est l'identifiant d'entité (par ex. un UUID) avec lequel le flow a été instancié. L'objet de contexte est défini quand createProcessInstance est appelé avec un paramètre context ; les auteurs d'extensions doivent documenter les clés attendues par leur modèle.
Ce que voit l'assigné : un panneau de carte pleine largeur où les types d'objets déclarés sont affichés comme couches gouvernées (les données sont limitées à l'habilitation de l'opérateur). Une entité est présélectionnée si focusAttribute se résout ; l'opérateur peut aussi toucher n'importe quelle entité. Avec focusDetection, les contacts qui ont déclenché la détection apparaissent comme une couche à part dans la légende. Le panneau de retour — invite, pastilles d'options, champ de notes optionnel — glisse depuis le bas de la carte. Soumettre termine l'action (et déclenche la réécriture, si elle est déclarée), sauf si l'option choisie porte returnToStage — voir ci-dessous.
Les couches géo sont servies par la même projection gouvernée que la vue carte de l'ontologie — les coordonnées passent par les règles de masquage DLP de la table gérée, de sorte que les entités que l'opérateur ne peut pas voir à son niveau d'habilitation sont simplement absentes de la couche. La carte affiche le même ensemble d'entités que celui que l'opérateur pourrait interroger via l'API des instances d'ontologie.
writeTarget exige que la liaison silver du type d'objet utilise une identité mono-colonne (une colonne identifie une ligne de façon unique). Si votre table utilise une clé composite, omettez writeTarget et effectuez l'écriture dans une action workflow en aval.
Mises à jour en direct. Lorsque le flux d'image opérationnelle commune de la plateforme est actif, la couche de carte relit automatiquement les données gouvernées à l'arrivée d'un signal de changement pertinent — sans rafraîchir la page. La relecture passe par les mêmes points d'accès gouvernés que le rendu initial, de sorte que l'opérateur ne voit que les entités pour lesquelles il est habilité. Aucune donnée de position n'est poussée directement dans la carte depuis le flux temps réel.
Bras de retour : returnToStage
Toute option de feedback.options peut porter un champ facultatif returnToStage.
Lorsque l'opérateur choisit cette option :
- La classification et les notes sont enregistrées immédiatement (elles ne sont jamais perdues, même si une étape ultérieure échoue).
- Le flow rouvre l'étape nommée — l'étape cible repasse à
in_progresset l'étape courante est réinitialisée ànot_started. - L'action map n'est pas terminée — elle reste ouverte pour une nouvelle décision une fois l'étape retravaillée de nouveau vidée.
L'étape est identifiée par son slug (le suffixe du stageSnapshotId après le
dernier segment :stage:, ou l'identifiant de snapshot complet, ou le nom de
l'étape). Un slug inconnu renvoie une erreur 422 return_stage_not_found.
feedback:
prompt: What is your decision?
options:
- { value: recon, label: 'Proceed to recon' }
- { value: re-triage, label: 'Return to triage', returnToStage: triage }
allowNotes: trueDans l'exemple ci-dessus, choisir Proceed to recon termine l'action et fait
avancer le flow. Choisir Return to triage enregistre la classification comme
preuve et renvoie le flow à l'étape triage sans terminer l'action de décision —
l'opérateur reverra la même décision sur carte une fois l'étape de triage
retravaillée.
writeTarget est ignoré pour les soumissions du bras de retour ; la
classification est enregistrée sur l'action uniquement.
Les actions map comme portes de décision à issues multiples
Une action map fait aussi office de porte de décision à issues multiples — elle
ne se limite pas à la classification spatiale. Quand l'étape DECIDE du modèle a
besoin de plus de deux issues (Approuver / Rejeter), modélisez-la comme une action
map dont la liste d'options porte vos issues. Les workflows canvas en aval lisent
ensuite la décision via <start.processFlow.feedback.<taskSlug>.value> :
N'entourez PAS une référence de guillemets dans une expression de condition. Le bloc Condition évalue son expression comme du code : une chaîne résolue est donc substituée sous forme de littéral JavaScript entre guillemets — les guillemets sont ajoutés pour vous.
// ✅ Correct — the reference substitutes as "recon", so this is a string comparison
<start.processFlow.feedback.decide.value> === "recon"
// ❌ Wrong — becomes ""recon"" === "recon", which fails to parse
"<start.processFlow.feedback.decide.value>" === "recon"Cette page conseillait l'inverse jusqu'ici, et la consigne a été inversée par un
correctif plutôt que par conception : avant que le bloc Condition ne mette
lui-même les références entre guillemets, une référence nue devenait réellement
un identifiant nu. Depuis ce changement, la forme entourée de guillemets à la
main lève Unexpected trailing tokens — un bloc en erreur plutôt qu'une
branche silencieuse, ce qui est plus visible mais reste le mauvais résultat pour
quiconque suit les instructions.
# In the DECIDE task — a map action with multiple outcomes:
- name: DECIDE
actionType: map
metadata:
geo:
objectTypes: [track]
focusAttribute: trackRef
feedback:
prompt: What action is required?
options:
- { value: recon, label: 'Proceed to recon' }
- { value: re-triage, label: 'Return to triage', returnToStage: triage }
allowNotes: true
writeTarget:
objectType: track
field: decision
# In the RESPOND canvas workflow — a condition block branching on the decision:
# condition expression (correct — the reference is NOT quoted):
# <start.processFlow.feedback.decide.value> === "recon"L'enregistrement feedback de <start.processFlow.feedback> est indexé par
slug de modèle de tâche. Chaque entrée expose trois champs : value (la valeur
de l'option choisie), notes (le texte libre de l'opérateur, null lorsque le
champ de notes est omis ou laissé vide) et decidedAt (horodatage ISO-8601,
null s'il n'est pas disponible). La dernière soumission map terminée pour cette
tâche l'emporte ; une réouverture laisse l'entrée précédente intacte jusqu'à ce
qu'une nouvelle soumission soit validée.
Le retour map est aussi indexé dans la base de connaissances de l'instance
(dossier map-feedback/<stageSlug>/, slug de page = le name de l'action en
kebab-case — pas le champ slug optionnel de l'action). Les étapes d'agent
workflow en aval peuvent s'ancrer dans la décision via
metadata.retrieval.inputs[].fromTask, exactement comme pour les tâches document
et file-upload.
Bannière consultative sur les actions de décision
Une action workflow qui exécute un agent @system/ peut envoyer son verdict à
une action de décision — une porte approval ou une décision map — pour qu'il
soit examiné avant que l'humain ne tranche. Déclarez la surface de sortie
approval avec le slug de la tâche qui porte la décision :
defineAction({
name: 'Run AI assessment',
actionType: 'workflow',
workflowId: '@system/agent-multi-source-detector',
executionMode: 'automatic',
metadata: {
outputSurface: [
'workflow',
{ kind: 'approval', targetTaskSlug: 'decide-response' },
],
},
})La bannière consultative apparaît en haut de la carte de décision quand l'étape IA
se termine. Une réponse en forme de verdict (JSON { verdict, confidence, … })
affiche la recommandation et sa confiance ; toute autre réponse affiche un extrait
du texte de l'agent lui-même (jusqu'à 600 caractères) plutôt qu'un verdict
inventé. La décision de l'humain reste l'autorité ; la bannière est purement
consultative, et elle est fusionnée dans l'action sans écraser une décision déjà
enregistrée. advisorySource (par défaut ai-agent) indique la provenance de
l'avis.
Quelle action de décision le reçoit. Les candidates sont les actions de
décision propres à la tâche cible plus les actions de décision de chaque
étape — les actions d'étape sont apposées sur une tâche à son entrée dans chaque
étape, si bien qu'une tâche qui traverse un flow à étapes accumule une porte par
étape. Lorsque la tâche cible porte plus d'une candidate, donnez à la porte un
slug d'action et visez-la avec targetActionSlug :
defineStage({
name: 'Recover',
slug: 'recover',
transitionMode: 'approval',
actions: [
{
slug: 'recovery-sign-off', // unique across the whole template
name: 'Incident Commander signs off',
actionType: 'approval',
isRequired: true,
},
],
})
// …on the agent step:
metadata: {
outputSurface: [
'workflow',
{
kind: 'approval',
targetTaskSlug: 'incident',
targetActionSlug: 'recovery-sign-off',
},
],
}Les règles, vérifiées de la même façon à la construction ou à l'import de l'extension et au moment où l'avis est écrit :
| Configuration | Résultat |
|---|---|
Pas de targetActionSlug, la tâche porte une seule action de décision propre | Cette action — inchangé par rapport à avant l'existence des slugs d'action. |
Pas de targetActionSlug, la tâche n'en a aucune propre mais exactement une porte d'étape | Cette porte d'étape. |
Pas de targetActionSlug, la tâche porte plusieurs actions de décision propres | La première d'entre elles dans l'ordre des actions de la tâche (sortOrder, égalités départagées par un identifiant interne stable), comme avant l'existence des slugs d'action. La construction et l'import signalent un avertissement qui les nomme, tout comme le journal du runtime. Ajoutez un targetActionSlug pour choisir. |
Pas de targetActionSlug, la tâche n'en a aucune propre mais plusieurs portes d'étape | Rien n'est écrit. La construction et l'import signalent un avertissement, et le journal du runtime nomme chaque candidate. |
targetActionSlug correspond exactement au slug d'une candidate | Cette action. |
targetActionSlug ne correspond à aucune action de décision (une faute de frappe, ou le slug d'une action qui n'est pas de décision) | Erreur à la construction, qui nomme les candidates. Avertissement à l'import — voir ci-dessous. |
targetTaskSlug absent (y compris le raccourci 'approval'), ne désigne aucune tâche, ou désigne une tâche qui ne peut porter aucune action de décision | Erreur à la construction. Avertissement à l'import — voir ci-dessous. |
L'import ne refuse jamais une extension déjà construite à cause de ces
cibles. Avant ces vérifications, une cible invalide était acceptée et
n'écrivait simplement rien : une extension construite avant elles — peut-être
déjà installée ailleurs — peut en contenir une. Son import l'accepte :
/api/extensions/import/inspect renvoie chaque constat comme un
avertissement avec le même code, et /api/extensions/import installe
l'extension et consigne chacun dans le journal du serveur. L'avis n'est
toujours écrit nulle part. L'écran de téléversement des extensions n'affiche
pas encore ces avertissements. Reconstruire l'extension échoue tant que la
cible n'est pas corrigée.
Les slugs d'action suivent la grammaire habituelle des slugs (lettres minuscules,
chiffres et tirets) et doivent être uniques dans tout le modèle — actions de
chaque étape et de chaque tâche — car une porte d'étape est apposée sur chaque
tâche qui entre dans l'étape. Un slug mal formé ou en double fait échouer
extension build (action_slug_invalid, action_slug_duplicate). Une archive
qui en contient déjà un s'installe quand même, avec le même code en
avertissement : un targetActionSlug qui correspond à deux actions est ambigu
et n'écrit rien.
Quand une tâche entre de nouveau dans une étape (par exemple après un retour), la porte de l'étape est apposée à nouveau ; l'avis atterrit sur la copie la plus récente, et uniquement sur la copie propre à la tâche cible — une autre tâche qui traverse la même étape garde sa porte intacte. Un avis destiné à une porte dont la tâche n'a pas encore atteint l'étape est ignoré. Un avis sur la mauvaise porte est pire que pas d'avis du tout.
destruction
Détruit les copies, détenues par la plateforme, des données qu'une étape précédente a recueillies, et enregistre si cette destruction est prouvée. Ce type sert aux obligations de conservation et d'effacement, par exemple une demande au titre de l'article 17 du RGPD ou la clause de destruction d'un contrat, pour lesquelles le résultat de l'étape fait office de preuve. Il est donc volontairement difficile à falsifier : le résultat est établi par la plateforme, jamais saisi à la main.
À utiliser pour : purger des données personnelles téléversées dès que le processus n'en a plus besoin, et consigner la suppression d'une copie conservée dans un autre système.
Champs de schéma utilisés : name, description, executionMode (obligatoirement manual : toute autre valeur est rejetée par extension validate) et metadata.destruction, qui prend l'une des formes ci-dessous. Une étape de destruction est toujours obligatoire : déclarez isRequired: true, car isRequired: false est rejeté par extension validate. L'étape retient sa tâche, est marquée comme obligatoire, bloque la clôture du flux par Terminer et bloque l'avancement dans l'assistant tant qu'elle n'a pas été exécutée. Une destruction facultative permettrait de clore le flux avec les fichiers toujours stockés et sans aucun résultat enregistré. Les mêmes règles s'appliquent à une étape de destruction déclarée dans les actions d'une étape du processus, que reçoit chaque tâche qui y entre.
metadata.destruction | Signification |
|---|---|
{ kind: "process_files", fromTask: "<slug-de-tâche>" } | Les fichiers téléversés dans la ou les actions file_upload de cette tâche, dans la même exécution. La plateforme les supprime puis vérifie leur disparition. La validation rejette un slug qui ne désigne aucune tâche, ou une tâche sans action file_upload. |
{ kind: "external", system: "<nom>" } | Une copie dans un système que la plateforme ne peut pas atteindre, par exemple une plateforme de formulaires sans API de suppression, ou une boîte mail. L'opérateur la supprime sur place et consigne ce qu'il a fait. |
Chaque exécution enregistre exactement l'un de ces trois résultats :
| Résultat | Quand |
|---|---|
verified | Chaque copie a été supprimée définitivement et un contrôle ultérieur a confirmé sa disparition. C'est aussi le cas lorsque la plateforme ne détenait aucune copie. |
attested | Le résultat repose sur quelque chose que la plateforme ne peut pas observer : la déclaration d'une personne (toute cible external), ou une copie seulement masquée au lieu d'être détruite (suppression logique). Un résultat attesté n'est jamais présenté comme vérifié. |
failed | Une suppression a renvoyé une erreur, une copie était encore présente après coup, la vérification après coup n'a pas pu être faite (par exemple parce que le fournisseur de stockage l'a refusée), ou l'étape de téléversement désigne un fichier qui n'appartient pas à cette exécution. L'étape reste ouverte. Une nouvelle tentative ne porte que sur les copies restantes, et les copies déjà détruites restent consignées. |
Ce qu'une étape process_files supprime : pour chaque fichier téléversé dans la tâche désignée, l'original stocké et sa copie dans la base de connaissances de l'instance (l'enregistrement source, les pages qui en dérivent et ses fichiers stockés). Un fichier dont toutes les copies ont disparu est retiré de l'étape de téléversement, pour qu'aucun écran ne propose un téléchargement qui n'existe plus. Un fichier dont il reste une copie reste sur l'étape de téléversement.
Uniquement les copies de cette exécution. L'étape ne supprime un fichier que s'il a été téléversé dans cette exécution : un fichier stocké dans le dossier de téléversement propre à cette exécution, dans son espace de travail et son environnement, et une copie dans le dossier propre à cette exécution de la base de connaissances (lorsque les exécutions d'un même modèle partagent une base de connaissances, les dossiers des autres exécutions sont hors d'atteinte). Tout autre élément désigné par l'étape de téléversement est laissé intact et consigné comme refused: not a copy held by this run, et le résultat est failed, car l'étape désigne quelque chose que la plateforme n'a pas détruit.
Ce que « vérifié » ne couvre pas. Cela signifie que la suppression par la plateforme a réussi et qu'un contrôle de l'objet actuel ne l'a plus trouvé. Cela n'atteint pas :
- La rétention côté fournisseur de stockage, comme le versionnage d'un bucket, la rétention après suppression d'un compte de stockage ou les sauvegardes. La plateforme ne les voit pas : configurez-les chez le fournisseur selon votre obligation.
- Le contenu qu'une étape IA a déjà produit à partir des fichiers, comme un document généré, une page d'évaluation ou un message déjà envoyé par une étape
distribution. Placez l'étape de destruction avant ces étapes, ou traitez-les séparément. - Les copies dans d'autres systèmes, y compris les fichiers qu'une extension a transmis ailleurs. Déclarez-les comme cible
external.
Piste d'audit : chaque résultat, failed compris, écrit un événement d'audit process_flow.destruction.recorded, au nom de la personne qui a exécuté ou attesté l'étape. Il porte le résultat, le type de cible, le nombre de copies, l'existence d'une attestation et, pour une cible external, le system déclaré. Il ne porte jamais un nom de fichier ni une clé de stockage, car ceux-ci peuvent être les données personnelles à effacer. Une nouvelle tentative qui répète l'écriture d'audit pour le même résultat est dédupliquée : une destruction laisse un seul événement d'audit. Le journal d'activité de l'exécution enregistre une entrée destruction_recorded.
Exemple :
- name: Collecter l'export de paie
slug: collect-payroll
actions:
- name: Téléverser l'export de paie
actionType: file_upload
isRequired: true
- name: Purger l'export de paie
slug: purge-payroll
actions:
- name: Détruire les données de paie téléversées
actionType: destruction
isRequired: true
executionMode: manual
metadata:
destruction:
kind: process_files
fromTask: collect-payroll
- name: Confirmer la suppression du formulaire d'entrée
actionType: destruction
isRequired: true
metadata:
destruction:
kind: external
system: Microsoft FormsCe que voit l'assigné : pour process_files, un bouton Détruire les fichiers précédé d'une confirmation, puis le résultat et l'état de chaque copie. Pour external, un champ de note et un bouton Enregistrer l'attestation. L'étape ne peut pas être cochée depuis la checklist. La mise à jour générique d'une action refuse de terminer une étape de destruction, de l'ignorer (skipped), de la marquer en échec (failed) ou en cours (in_progress), ou d'en écrire le contenu, et renvoie 409 destruction_outcome_required. Le moteur refuse les mêmes changements par tous les autres chemins, de sorte que la clôture du flux par Terminer ne peut pas non plus ignorer l'étape. Si le stade contenant l'étape est rouvert, le résultat précédent est archivé et l'étape doit être exécutée à nouveau, de sorte que les fichiers téléversés entre-temps sont eux aussi détruits. Une seule personne peut exécuter l'étape à la fois : une seconde requête pendant l'exécution reçoit 409 destruction_in_progress. L'enregistrement désigne chaque copie par son type et son numéro, jamais par son nom de fichier.
form
Demande à une personne de remplir des champs déclarés par l'auteur et transmet les réponses sans les conserver. La plateforme valide l'envoi par rapport aux champs déclarés, remet les valeurs à la destination du formulaire, puis les abandonne. Seul le fait de l'envoi est enregistré sur l'action : quand, par qui, vers quelle destination, et le résultat.
À utiliser pour : recueillir des données sur lesquelles le flux doit agir mais qu'il ne doit pas garder — un contact ponctuel pour une passation, une confirmation accompagnée de quelques réponses typées, des informations dont un tiers a besoin par e-mail. Les numéros d'identification nationaux et les coordonnées bancaires ne doivent pas passer par la destination email ; voir Destinations ci-dessous. Si les réponses elles-mêmes sont le résultat que le flux doit conserver, utilisez plutôt une action document (ou un workflow qui écrit un objet d'ontologie).
Champs de schéma utilisés : name, description, isRequired, et metadata.form, obligatoire pour ce type (une action form sans ce bloc échoue à extension validate) :
Clé de metadata.form | Signification |
|---|---|
prompt | Optionnel. Une courte consigne affichée au-dessus des champs. |
fields | 1 à 50 déclarations de champ (ci-dessous). Les clés doivent être uniques dans le formulaire. |
destination | Optionnel. Où vont les réponses : { kind: "transient" } (par défaut) ou { kind: "email", to, subject }. Voir Destinations ci-dessous. |
Chaque champ :
| Clé du champ | Signification |
|---|---|
key | Identifiant, ^[a-zA-Z][a-zA-Z0-9_]{0,63}$. |
label | Affiché à la personne qui remplit le formulaire. |
description | Optionnel. Texte d'aide sous le libellé. |
type | text, textarea, email, number, date (ISO YYYY-MM-DD), boolean ou select. |
required | false par défaut. Un boolean obligatoire doit être coché. |
options | Uniquement pour select, et obligatoire dans ce cas : des éléments { value, label } aux valeurs uniques. |
maxLength | Types texte uniquement. Par défaut : text/email 1 000, textarea 10 000 ; au maximum 20 000. |
Exemple :
defineAction({
name: 'Payroll details',
actionType: 'form',
isRequired: true,
metadata: {
form: {
prompt: 'Provide the details payroll needs for the first salary run.',
fields: [
{ key: 'iban', label: 'IBAN', type: 'text', required: true, maxLength: 34 },
{ key: 'contractStart', label: 'Contract start', type: 'date', required: true },
{
key: 'contractType',
label: 'Contract type',
type: 'select',
options: [
{ value: 'employee', label: 'Employee' },
{ value: 'contractor', label: 'Contractor' },
],
},
{ key: 'confirmed', label: 'The details above are correct', type: 'boolean', required: true },
],
destination: { kind: 'transient' },
},
},
})Ce que voit l'assigné : les champs déclarés, une mention indiquant où vont les réponses, et un bouton Envoyer. Pour transient, la mention indique que les réponses ne sont pas conservées. Pour email, elle liste les adresses et précise que Scrydon ne conserve pas les réponses, mais que le fournisseur de messagerie et les boîtes de réception des destinataires en gardent une copie. Les erreurs sont affichées champ par champ avant tout envoi. L'envoi termine l'action. Ensuite, l'étape affiche Formulaire envoyé, l'heure, et la même indication sur qui détient une copie. Il n'y a aucune réponse à afficher, puisque Scrydon n'en a gardé aucune.
Ce que couvre « non conservé ». Les valeurs envoyées ne sont écrites ni sur l'action, ni dans la base de connaissances de l'instance, ni dans le journal d'activité, ni dans un événement, une ligne de log ou une trace, et un envoi refusé n'est pas gardé pour une nouvelle tentative — la personne renvoie le formulaire. Un test CI envoie un jeton unique via le formulaire et le recherche dans chaque table de la base de données, chaque appel de log, chaque span et chaque requête adressée au sidecar Dapr. Ce qui est enregistré est content.formSubmission : submittedAt, submittedBy, destination et outcome. Un échec de livraison email écrit aussi une entrée du journal d'activité qui nomme des destinataires, jamais des réponses (voir Destinations).
Seule la route du formulaire termine un formulaire. Une action form ne peut être ni terminée, ni voir son contenu écrit, ni — une fois envoyée — être remise dans un autre statut via la mise à jour générique d'action ; chacun de ces cas renvoie 409 avec code: "form_submission_required". Pour recueillir à nouveau les réponses, rouvrez l'étape. Deux envois simultanés du même formulaire ne le terminent qu'une fois ; le second renvoie 409 avec error: "already_completed". Le moteur refuse de terminer une action form sans enregistrement d'envoi, quel que soit le chemin qui le demande.
Destinations.
kind | Ce qui se passe à l'envoi |
|---|---|
transient | La validation constitue toute la livraison. Les valeurs n'atteignent aucun autre système. |
email | Les réponses sont envoyées par e-mail en texte brut à chaque adresse de to, avec une ligne Libellé : réponse par champ déclaré sous la consigne. Scrydon n'en garde aucune copie ; le fournisseur de messagerie de votre organisation et la boîte de réception de chaque destinataire en gardent une. |
Une destination email déclare :
| Clé | Signification |
|---|---|
to | 1 à 10 adresses externes. Les adresses sont mises en minuscules, et les doublons sont refusés. |
subject | 200 caractères au maximum, fixés à la rédaction. Il ne peut contenir ni espace réservé {{…}} ni caractère de contrôle. L'objet est la seule partie du message que les journaux de messagerie enregistrent, il ne peut donc jamais porter une réponse. |
destination: {
kind: 'email',
to: ['payroll@example.com'],
subject: 'New starter details',
},Chaque destinataire doit être autorisé par la politique Distribution externe de votre organisation. La politique est vérifiée à chaque envoi, avant tout envoi de message. Si un seul destinataire n'est pas autorisé, rien n'est envoyé à personne. extension install signale aussi les destinataires que la politique n'autorise pas.
La liste des domaines autorisés est le seul contrôle. N'utilisez pas la destination email pour des numéros d'identification nationaux ou des coordonnées bancaires. Dans cette version, un destinataire est vérifié par rapport à la liste d'autorisation de la Distribution externe, et rien d'autre : Scrydon n'applique pas de DLP au corps de l'e-mail et ne compare les réponses à aucune classification ni habilitation. Une fois envoyées, les réponses sont détenues par le fournisseur de messagerie de votre organisation et dans la boîte de réception de chaque destinataire, selon leur propre durée de conservation et hors du contrôle de Scrydon. Utilisez la destination email pour des informations dont vous acceptez une copie dans une boîte de réception.
Les destinataires reçoivent le message un par un, dans l'ordre de to, et le premier envoi en échec arrête les suivants. Un envoi en échec renvoie 502 et laisse l'étape ouverte :
delivery_failed: aucun destinataire n'a reçu les réponses.delivery_partial: les destinataires situés avant celui en échec ont reçu les réponses.
Dans les deux cas, le journal d'activité enregistre une entrée form_delivery_failed. Elle contient l'outcome (failed ou partial), deliveredTo, failedRecipient (l'adresse et un code de raison) et notAttempted. Elle ne contient jamais les réponses.
Un envoi ne peut pas être annulé, et l'e-mail part avant que l'étape soit terminée. Dans deux cas, un destinataire peut donc recevoir deux fois les mêmes réponses. Cette version l'accepte :
- Deux envois simultanés. Les deux peuvent transmettre leurs réponses. Celui qui ne termine pas l'étape renvoie tout de même
409avecerror: "already_completed", même si son e-mail est parti. - Un nouvel envoi après
delivery_partial. L'étape est encore ouverte, et un nouvel envoi écrit à tous les destinataires deto, y compris ceux qui ont déjà une copie.
La livraison vers une intégration, une écriture d'ontologie ou des membres internes, ainsi que l'enregistrement des réponses sur l'action, ne sont pas disponibles. Chacune garderait une copie des réponses quelque part dans Scrydon.
persona_assignment
Une étape dont l'achèvement est un choix de personnes : l'assigné désigne les membres de l'organisation qui tiendront l'un des personas du modèle, et valider ce choix termine l'étape. À utiliser quand la question de qui fera le travail suivant se tranche pendant l'exécution — le manager désigne le parrain d'un nouvel arrivant, le responsable d'incident nomme le rapporteur — plutôt qu'au démarrage de l'instance.
Champs de schéma utilisés : name, description, isRequired, et
metadata.assignsPersona — le slug d'un persona déclaré dans
template.personas. assignsPersona est obligatoire, executionMode doit être
manual (quelqu'un doit choisir), et le persona doit pouvoir être tenu par un
humain : extension validate rejette un persona non déclaré
(unknown_persona), un persona system (system_persona_not_assignable), et
un persona qui décide d'une approbation dans le même modèle — l'approverPersona
d'une étape, le persona d'une action d'approbation, ou le rôle de tâche
auquel une telle action se rabat (approval_persona_not_assignable). Une
exécution ne peut pas désigner le persona qui approuve son propre travail : ce
pas ne pourrait jamais aboutir.
personas: [
{ slug: 'hiring-manager', displayName: 'Manager recruteur', cardinality: 'one' },
{ slug: 'buddy', displayName: 'Parrain', cardinality: 'one' },
],
taskTemplates: [
{
slug: 'pick-buddy',
stageSlug: 'onboarding',
name: 'Choisir le parrain',
defaultAssignedRoleSlug: 'hiring-manager',
actions: [
defineAction({
name: 'Désigner le parrain',
actionType: 'persona_assignment',
isRequired: true,
metadata: { assignsPersona: 'buddy' },
}),
],
},
{
slug: 'first-day-lunch',
stageSlug: 'onboarding',
name: 'Organiser le déjeuner du premier jour',
defaultAssignedRoleSlug: 'buddy',
dependsOnTaskSlugs: ['pick-buddy'],
},
],Ce que voit l'assigné : un sélecteur de personnes parmi les membres de
l'organisation — un seul choix pour un persona one, plusieurs pour un persona
many — avec les titulaires actuels, et un bouton Attribuer et terminer.
Ce que fait l'achèvement. Le choix passe par le même chemin d'écriture que la
boîte de dialogue Gérer les personas d'un opérateur, enregistré avec la
provenance task (les identifiants de la tâche et de l'action figurent dans
l'entrée d'audit) :
cardinality du persona | Effet du choix |
|---|---|
one | Remplace le titulaire actuel. Choisir deux personnes est refusé. |
many | Ajoute les personnes choisies ; les titulaires existants restent. |
Une tâche qui dépend de l'étape — ici first-day-lunch — reste verrouillée
jusqu'à ce que le choix soit enregistré, et les nouveaux titulaires sont
notifiés des tâches ouvertes qui attendent leur persona. L'étape ne peut pas être
terminée autrement : la marquer comme terminée sans choix est refusé avec
persona_assignment_required.
Deux choix sont toujours refusés, et chaque refus est audité :
se désigner soi-même — la personne qui termine l'étape ne peut pas se
choisir (assign_persona_self_seating) ; et quelqu'un d'extérieur à
l'organisation — les non-membres ne sont jamais désignés
(assign_persona_no_assignable_users quand personne n'a pu l'être).
Une étape peut pourvoir un persona qui décide une approbation de
l'exécution — le responsable du recrutement qui désigne le signataire de
l'offre, par exemple. Les personnes choisies peuvent ensuite approuver les
actions que ce persona conditionne, et la désignation est inscrite au journal
d'activité de l'instance avec l'étape qui l'a faite. Seul un workflow se voit
refuser cela (assign_persona_approval_persona).
Choisir le bon type
Un bref arbre de décision pour les auteurs incertains :
| Si l'étape est… | Utiliser |
|---|---|
| Une confirmation, attestation, ou validation « je l'ai fait » | checklist |
| Du texte libre que l'assigné rédige à l'intérieur du flow | document |
| Une validation formelle qui bloque l'étape suivante | approval |
| De la logique exécutable — un agent IA, une séquence d'extension, ou une porte HITL | workflow |
| Des octets venant de l'extérieur du flow (PDFs, présentations, exports) | file_upload |
| Un message diffusé vers un canal externe | distribution |
| Capture audio | voice_trigger |
| Couche géo gouvernée + classification par l'opérateur (étape de connaissance de la situation spatiale) | map |
| Des réponses typées sur lesquelles le flux doit agir sans les conserver | form |
| Supprimer les données qu'une étape précédente a recueillies, avec la preuve de leur disparition | destruction |
| Choisir qui tient un persona pour la suite de l'exécution | persona_assignment |
Si l'étape consiste à « remplir un objet typé », il n'existe pas encore de type d'action dédié — modélisez-la comme une action workflow qui appelle un agent ou un bloc d’extension pour créer l'instance via l'API d'ontologie.
Pièges de validation courants
La commande extension validate exécute le schéma Zod et rejette les manifestes qui violent ces règles. Les plus fréquents :
| Erreur | Cause | Correction |
|---|---|---|
workflowId and workflowSlug are mutually exclusive on an action template | workflowId et workflowSlug définis tous les deux sur la même action. | Choisissez-en un. Utilisez workflowId pour @system/* et les workflows appartenant au tenant ; utilisez workflowSlug pour les workflows distribués dans la même extension. |
Invalid enum value. Expected … received "custom" (ou similaire) | Une valeur actionType mal orthographiée ou héritée. | Le runtime accepte exactement les onze types de cette page. Il n'y a pas de type notification ou custom au niveau du SDK. |
A form action must declare metadata.form (its fields and destination) | actionType: form sans bloc metadata.form. | Déclarez les champs sous metadata.form.fields. |
A select field must declare its options / Field keys must be unique within a form | Un champ select sans options, ou deux champs avec la même key. | Donnez ses options à chaque select et une clé unique à chaque champ. |
400 invalid_submission à l'envoi d'un form | Un champ obligatoire est vide, une valeur a le mauvais type ou est trop longue, une valeur de select n'est pas une option déclarée, ou une clé n'est pas déclarée. | La réponse liste chaque champ en échec par clé et code d'erreur — jamais la valeur. Corrigez la réponse et renvoyez. |
422 destination_not_permitted à l'envoi d'un form | La politique Distribution externe de l'organisation n'autorise pas l'un des destinataires email. recipientErrors nomme chaque adresse refusée. Rien n'a été envoyé. | Demandez à un administrateur d'autoriser le domaine, ou modifiez les destinataires et republiez. |
503 destination_unavailable / 502 delivery_failed / 502 delivery_partial à l'envoi d'un form | La politique n'a pas pu être lue, ou un envoi d'e-mail a échoué : delivery_failed signifie qu'aucun destinataire n'a reçu les réponses, delivery_partial que les destinataires situés avant celui en échec les ont reçues. L'étape reste ouverte, et rien des réponses n'est enregistré. Un envoi en échec écrit une entrée form_delivery_failed dans le journal d'activité, qui nomme qui a reçu une copie. | Renvoyez le formulaire. Après delivery_partial, les destinataires qui ont déjà les réponses en reçoivent une seconde copie. |
409 form_submission_required sur une mise à jour d'action | Un client a tenté de terminer une action form, d'écrire son contenu ou de changer le statut d'un formulaire envoyé via la mise à jour générique d'action. | Envoyez via le formulaire ; pour modifier un formulaire envoyé, rouvrez son étape. |
Destruction action "…" must be manual | Une action destruction déclare executionMode: automatic ou on_entry. | Retirez executionMode ou mettez-le à manual. Le résultat est enregistré au nom de la personne qui effectue ou atteste la suppression. |
Destruction action "…" must be required | Une action destruction déclare isRequired: false. | Mettez isRequired: true. Une étape de destruction qui ne doit pas forcément s'exécuter n'a pas sa place dans le modèle. |
Destruction action "…" declares no destruction target | actionType: destruction sans metadata.destruction. | Déclarez { kind: process_files, fromTask } ou { kind: external, system }. |
Destruction target "…" holds no file_upload action / references no task in this template | fromTask désigne une tâche sans action file_upload, ou aucune tâche. | Faites pointer fromTask vers le slug de la tâche qui recueille les fichiers. |
409 destruction_outcome_required au runtime | Quelque chose a tenté de terminer, d'ignorer, de marquer en échec ou de démarrer une étape destruction, ou d'en écrire le contenu, par la mise à jour générique d'une action. | Exécutez l'étape par son propre point d'accès (POST …/actions/<id>/destruction), celui qu'utilise le panneau de tâche. Une étape de destruction qui ne doit pas s'exécuter n'a pas sa place dans le modèle. |
409 destruction_in_progress au runtime | Une autre requête exécute déjà la même étape de destruction. | Attendez la fin de son exécution et rechargez l'étape. Une réservation laissée par une requête interrompue expire après 15 minutes. |
Une copie consignée comme refused: not a copy held by this run | L'étape de téléversement désigne un fichier qui n'a pas été téléversé dans cette exécution, par exemple après une modification de sa liste de fichiers. | La plateforme ne le supprimera pas depuis cette étape. Retirez l'entrée de l'étape de téléversement et détruisez le fichier depuis l'exécution qui le détient. |
Une copie consignée comme probe failed: … | La suppression a été envoyée, mais la vérification après coup n'a pas pu être faite. Sur S3, des identifiants sans s3:ListBucket se voient refuser cette vérification pour un fichier déjà supprimé. | Corrigez la permission de stockage, puis exécutez l'étape à nouveau. |
Required sur quorumCount | metadata.completion: "quorum" sans quorumCount. | Ajoutez quorumCount: <n>. |
Slug must be lowercase alphanumeric with hyphens sur workflowSlug | Underscores ou majuscules dans le slug. | Utilisez le kebab-case. |
Le triggerPath de l'action ne se résout pas au runtime | metadata.triggerPath sur une action voice_trigger ne correspond à aucune entrée dans template.voiceTriggers[].path. | Le schéma ne détecte pas cela ; « Préparer l'enregistrement » échoue avec « Déclencheur vocal introuvable » (404). La correspondance ignore un slash initial mais est par ailleurs exacte — corrigez le chemin dans l'un ou l'autre endroit pour les faire correspondre, puis republier l'extension. |
400 à la soumission d'une action map au runtime | La valeur de classification soumise ne fait pas partie des valeurs déclarées dans metadata.feedback.options. | Le runtime rejette toute valeur absente de la liste d'options déclarée par l'auteur — corrigez la valeur soumise ou mettez à jour la liste d'options dans l'extension et republiez-la. |
Où aller ensuite
La page parente — anatomie d'un modèle, exemple complet, étapes de build/upload, limites de sécurité.
Créez les workflows que actionType: "workflow" référence. Couvre le contrat de rebind workflowSlug.
Téléchargez les archives complètes des extensions dont les exemples de cette page sont tirés.
Les types d'objets typés référencés par entity_link et par ontologyContract sur les tâches et actions.