AI agent code skill to generate valid BPMN XML files.
SaferSkills independently audited bpmn-xml-generator (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Produces BPMN 2.0 XML that is valid against the OMG specification and executable on the engine the user targets.
| Layer | Files | Responsibility |
|---|---|---|
Core (this file + references/) | elements.md, patterns.md, examples.md, validation-errors.md | OMG BPMN 2.0 only: elements, structure, child order, modeling rules. Never emits engine extensions. |
Profile (profiles/) | stateway.md, camunda8.md, flowable.md | One engine: namespace, expression language, extension elements, support matrix, deployment. |
Tooling (scripts/) | validate.mjs, layout.mjs | Executable checks and diagram layout. |
Workflow: gather requirements → model in core BPMN → apply the profile → validate → lay out → deliver.
Read only what you need: this file always; profiles/<engine>.md once the engine is known; references/* when a construct needs detail.
Never generate a BPMN file from an incomplete description. Ask every missing item in one message.
If the user only wants a diagram for documentation, use the core profile (no extensions).
order-approval).Add the questions listed in section 6 of the selected profiles/<engine>.md (e.g. URL and sync/async mode for Stateway HTTP tasks, job types for Camunda 8, delegate kind for Flowable).
"Approve or reject" → confirm: exclusive choice? what ends each path? what is the condition and the default? "Notify A and/or B" → inclusive vs parallel. "Wait for X or timeout" → event-based gateway or boundary timer (check the profile's support matrix — e.g. Stateway does not execute event-based gateways).
Only if the user explicitly says to proceed with defaults: generate, and list every assumption (names, conditions, assignees, endpoints) right after the file so they can be corrected.
Full attributes and child order: references/elements.md. Advanced patterns: references/patterns.md. Every construct must also be allowed by the profile's support matrix (section 4 of the profile); if it is unsupported there, use the listed alternative; if parse-only/unverified, warn the user.
| Category | Elements |
|---|---|
| Events | startEvent, endEvent, intermediateCatchEvent, intermediateThrowEvent, boundaryEvent |
| Event definitions | timer, message, signal, error, escalation, conditional, link, compensate, cancel, terminate |
| Activities | task, userTask, manualTask, serviceTask, sendTask, receiveTask, scriptTask, businessRuleTask, callActivity |
| Sub-processes | subProcess (embedded, event sub-process triggeredByEvent), transaction, adHocSubProcess |
| Loops | multiInstanceLoopCharacteristics (parallel/sequential), standardLoopCharacteristics |
| Gateways | exclusiveGateway, inclusiveGateway, parallelGateway, eventBasedGateway, complexGateway (discouraged) |
| Flows | sequenceFlow (+ conditionExpression, gateway/activity default) |
| Data | dataObject, dataObjectReference, dataStoreReference, property, ioSpecification, data associations, itemDefinition |
| Artifacts | documentation, textAnnotation, association, group (+ category) |
| Root elements | process, message, signal, error, escalation, itemDefinition, dataStore, category |
Out of scope (for now): collaborations, pools (participant), lanes, message flows, choreographies, conversations, CMMN. If asked for pools/lanes, say so and model the single executable process.
Start from the profile's Definitions header (section 2 of the profile). The core skeleton is:
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
id="Definitions_process-key"
targetNamespace="http://bpmn.io/schema/bpmn"
exporter="bpmn-xml-generator"
exporterVersion="2.0">
<!-- root elements used by the process: message, signal, error, escalation … -->
<process id="process-key" name="Process Name" isExecutable="true">
<!-- ═══ START ═══ -->
<startEvent id="start" name="Start">
<outgoing>flow_start_to_first_task</outgoing>
</startEvent>
<!-- ═══ ACTIVITIES ═══ -->
<task id="first_task" name="First Task">
<incoming>flow_start_to_first_task</incoming>
<outgoing>flow_first_task_to_end</outgoing>
</task>
<!-- ═══ GATEWAYS ═══ -->
<!-- ═══ END ═══ -->
<endEvent id="end" name="End">
<incoming>flow_first_task_to_end</incoming>
</endEvent>
<!-- ═══ SEQUENCE FLOWS ═══ -->
<sequenceFlow id="flow_start_to_first_task" sourceRef="start" targetRef="first_task"/>
<sequenceFlow id="flow_first_task_to_end" sourceRef="first_task" targetRef="end"/>
</process>
</definitions>targetNamespace is required by the XSD; any URI works (keep the existing one when editing).<definitions> — and no other engine's.documentation → extensionElements → incoming → outgoing → type-specific children:
ioSpecification → property → dataInputAssociation → dataOutputAssociation → loopCharacteristics (last);dataInputAssociation → event definition (last);ioSpecification: dataInput* → dataOutput* → inputSet → outputSet (both sets mandatory).id on every element; ids are XML NCNames (start with a letter/underscore, no spaces).<incoming>/<outgoing> children must mirror the sourceRef/targetRef of the sequence flows.message, signal, error, escalation are siblings of <process>, never inside it.<sequenceFlow> elements at the bottom of their container, after the nodes.name.conditionExpression except the one in default.synchronize parallel paths with a parallel gateway (never several flows straight into a task).
interrupting boundary) must end in a terminate end event or be restructured (ERR-022).
<outgoing> but never <incoming>.{{…}} Stateway, =FEEL Camunda 8, ${…} Flowable.& → &, < → < (so && → &&); " → " inside attributes.PT1H, 2026-12-31T09:00:00Z, R3/PT1H); cron/expressions only if the profile allows them.variable-mapping mechanism (Stateway/Zeebe ioMapping, Flowable variables) for execution data.
Process kebab-case; tasks verb_noun; gateways gw_{purpose}; flows flow_{source}_to_{target}; boundary events {type}_boundary_{activity}; messages/signals/errors/escalations msg_/sig_/err_/esc_. Full table: references/validation-errors.md §5.
Write the XML to a .bpmn file, then run the validator from this skill's directory (the folder that contains this SKILL.md):
# once per machine (Node.js ≥ 20; needs network access the first time)
npm ci --prefix <skill-dir>/scripts
node <skill-dir>/scripts/validate.mjs <file.bpmn> --profile <stateway|camunda8|flowable|core> [--engine-version 8.10]It checks: allowed namespaces → bpmn-moddle parse (unknown elements, unresolved references) → OMG XSD via xmllint (child order, mandatory children) → bpmnlint (recommended + the profile's support matrix, expression syntax and configuration; camunda-compat for Camunda 8).
references/validation-errors.mdand the profile's section 8.
unverified on the engine).If xmllint is missing the validator skips the XSD layer and prints a notice — apply §4.1 carefully.
Engines run files without a diagram, but bpmn.io, the Camunda/Flowable modelers and monitoring tools need the bpmndi:BPMNDiagram section. Unless the user declines:
node <skill-dir>/scripts/layout.mjs <file.bpmn> # in place; replaces any existing DI
node <skill-dir>/scripts/validate.mjs <file.bpmn> --profile <p> # re-validate (DI is now checked too)camunda:/zeebe:/flowable:/stateway: from another enginetargetNamespace set; isExecutable="true" on executable processesioSpecification has inputSet and outputSetsourceRef/targetRef exists and matches <incoming>/<outgoing>message, signal, error, escalation) declared and referencedsupported in the profile's matrix (or the user was warned){process-key}.bpmn; never truncate with <!-- … -->).parse-only/unverified.For files over ~150 lines, offer a table of elements and flows.
POST /v1/definitions for Stateway).stateway:, zeebe:, flowable:,camunda: = Camunda 7, out of scope — ask which supported profile to migrate to).
targetNamespace, extension data you do not touch, and the order of existing elements.<incoming>/<outgoing> in sync with every flow you add, remove or reconnect.layout.mjs (it regenerates the whole DI —tell the user manual positions are lost) or ask whether to keep the old DI and place new shapes manually.
(section 5), rewrite expressions, then validate with the target profile.
| File | Read when |
|---|---|
profiles/stateway.md / camunda8.md / flowable.md | As soon as the engine is known (header, expressions, support matrix, extensions, examples) |
profiles/_contract.md | Adding or changing a profile |
references/elements.md | Attributes, child order and semantics of any core element |
references/patterns.md | Multi-instance, loops, event sub-processes, errors/escalations, transactions, compensation, links, conditional events, ad-hoc, data and artifacts |
references/examples.md | Complete engine-agnostic examples to adapt |
references/validation-errors.md | Interpreting validator output; ID naming conventions |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.