What this skill is for
A Camunda 7 .bpmn is a program the engine runs. The main job here is to
assemble a business process by wiring together activities that already exist —
reusable service tasks implemented as Java delegates or external-task topics — and
add the control flow (gateways, user tasks, DMN, timers, error handling, async)
that makes it deploy and run on Camunda Platform 7 (and the API-compatible CIB
seven fork).
This skill composes processes; it does not write activity implementations. Writing a new activity (a delegate bean, a worker) is a separate task. When the process needs a step that has no existing activity, you draw a deployable stub (its name and input/output contract) and record it in a follow-up spec for that separate task — you do not invent the Java/worker code here.
The four jobs people bring:
- Assemble a process (from text or a sketch) out of existing activities.
- Make a plain BPMN executable by wiring its tasks to existing activities (and stubbing the rest).
- Edit / extend a Camunda process — add steps, branches, timers, error handling — wiring existing activities and stubbing new ones.
- Validate & fix — find why it won't deploy or misbehaves, and repair it.
Division of labour:
- You decide the process shape and map each step to an existing activity from the catalog, or — if none fits — to a stub plus a spec entry.
- The bundled
camunda-tool.mjsparses without losingcamunda:data, regenerates layout, validates structure, and lints execution readiness.
Inputs & outputs
What you give the skill:
- The task — a process description (free text) or a path to an existing
.bpmnto make executable / extend / fix. - The project catalog — the reusable inventory + declarations to assemble
from. The canonical form is one JSON document (schema + template in
references/wiring-and-stubs.mdandassets/catalog.template.json), but the skill is flexible: it also accepts the catalog inline in the prompt, as YAML, or as a Markdown table/list, and normalizes it to that schema. All sections are optional. If no catalog is given, say so and infer one from the repo — scan forJavaDelegate/@Componentbean names,camunda:topicstrings,.dmndecision ids,.formfiles, existing<bpmn:message>/<bpmn:error>declarations, and group names — then confirm the inferred catalog with the user before building. - Conventions — a
conventionsblock in the catalog, or inferred from the project, or asked (see "Establish project conventions first").
What the skill returns:
- the executable
.bpmn(semantics +camunda:+ regenerated DI), passingvalidateandlint; - a
<process>-activities-spec.mdwhenever anything was stubbed (activities / forms / DMN decisions); .form/.dmnfiles only if you explicitly ask the skill to build them (normally those are referenced from the catalog or stubbed).
Whatever form the catalog arrives in, treat the JSON schema as the internal model: map every entry onto it before wiring.
Reconciling the prompt with the catalog
The prompt is itself a catalog source — the user often names activities, variables, messages, errors, roles or conventions inline. Merge those with the catalog file, with two rules:
- Extra context in the prompt (something the prompt describes that the catalog doesn't list): treat it as part of the catalog and use it — wire the activity the user says exists (don't stub what they've already specified), declare the message/error, use the variable names. Note in the follow-up that it came from the prompt and suggest adding it to the catalog so it's reusable.
- Conflict (the prompt and the catalog describe the same entry differently — different implementation type/ref/topic, variable type, message or error code): do not silently pick one and move on, and never emit both. Use the catalog as the source of truth for reusable definitions (it reflects what actually exists in the project), keep the model internally consistent, and surface the conflict explicitly in the follow-up — state both values, say which you used and why, and ask the user to confirm (interactive) or flag it for them to correct (non-interactive). The exception is when the user is clearly overriding on purpose ("our X is actually a delegate now") — then prefer the prompt, but still call out that it diverges from the catalog.
Three layers, and why generic tooling breaks Camunda files
A generic .bpmn has semantics (bpmn:process) and DI (bpmndi: — the
picture). A Camunda 7 file adds a third, the point of it all:
- Execution — the
camunda:attributes that bind each node to how it runs:camunda:delegateExpression/camunda:type="external"+camunda:topic,camunda:inputOutput,camunda:assignee,camunda:decisionRef,camunda:asyncBefore,camunda:historyTimeToLive.
Consequences:
- Never hand-write DI. Edit semantics + execution; let
layoutredraw it. - Never round-trip a Camunda file through generic BPMN tooling. Plain
bpmn-moddle/bpmn-js/bpmn-auto-layoutdon't knowcamunda:and silently delete every extension on save, turning an executable process back into a picture. The bundled tool registerscamunda-bpmn-moddleand grafts fresh DI onto the extension-preserving XML, so execution data is never touched.
Setup (once per machine)
npm install --prefix "<SKILL_DIR>"
<SKILL_DIR> is the folder containing this SKILL.md. "Cannot find package" later
means the install was skipped — run it and retry.
Establish project conventions first
Before authoring, pin down the conventions that shape every node — the defaults the whole process must follow. Determine them in this order, preferring inference over interrogation:
- The catalog's
conventionsblock, if present (form mechanism, service-task style, expression language, async/HTTL/retry defaults, naming). - Project config & docs —
CLAUDE.md/README for house rules; Spring Bootapplication.yaml(camunda.bpm.*) for the engine's HTTL default and history level. - Existing
.bpmnin the repo — runsummarizeon one or two and copy the house style:camunda:formRefor generatedformData? delegates or external tasks? what id/topic/bean naming?
If a convention that materially changes the output still can't be inferred, ask — briefly and batched. The two that matter most:
- Form mechanism: Camunda Forms (
.formviaformRef, recommended on 7.15+) / embedded HTML (formKey="embedded:…") / generatedformData/ external form key. - Service-task style for new/stub steps: external task / delegate / expression
/ connector.
(Also confirm, if unclear: expression conventions, async policy, the
historyTimeToLiveandfailedJobRetryTimeCycledefaults, naming.)
If you see a better approach than what's implied — e.g. the repo hand-writes
formData but Camunda Forms .form is cleaner on a 7.15+ engine, or a delegate
is simpler than an external task for in-process logic — say so in one line and
recommend it; let the user decide. In a non-interactive run, state the
conventions you assumed instead of blocking. State the conventions up front, then
apply them consistently to every node (and to stubs).
The reliable loop
For every job: establish conventions → get the catalog → map steps to catalog entries (or stubs) → write semantics + execution → layout → validate → lint → spec → fix.
-
Understand the inputs. For an existing file, run
summarize(it shows each node's implementation, async flags, forms, decisionRef, historyTTL, and which nodes are stubs):node "<SKILL_DIR>/scripts/camunda-tool.mjs" summarize path/to/file.bpmnGet the project catalog — the reusable inventory and declarations to build from:
activities(delegates/topics),forms,variables(the data dictionary),messages/signals/errors/escalations(event declarations),decisions(DMN),roles(candidate groups/users), andconventions. The user provides it (prompt or file); if none is given, ask for it, and only as a last resort infer it from the repo. Reuse the catalog's exact names everywhere (variable names, error codes, message names, group names) so the model is consistent. The full catalog schema is inreferences/wiring-and-stubs.md. -
Map each process step to an activity, then write semantics +
camunda:. For each service step: if the catalog has a matching activity, wire it exactly (its delegate bean name or external topic, plus acamunda:inputOutputmapping for the data it consumes/produces). If nothing fits, make it a stub (next section). Hand-author a semantics-only document (nobpmndi:) with the extensions inline. Always setisExecutable="true"andcamunda:historyTimeToLive. Get exact XML from the references — don't guess. -
Regenerate layout (preserves
camunda:extensions; non-destructive — it re-syncs existing DI and only adds/reroutes what changed). Add--rebuildto regenerate the whole diagram from scratch after big structural edits:node "<SKILL_DIR>/scripts/camunda-tool.mjs" layout in.bpmn [out.bpmn] [--rebuild] -
Validate structure:
node "<SKILL_DIR>/scripts/camunda-tool.mjs" validate out.bpmn -
Lint execution + control flow:
node "<SKILL_DIR>/scripts/camunda-tool.mjs" lint out.bpmnERROR = won't deploy / will throw; WARN = deploys but misbehaves; INFO = advisory (it also lists any stub activities so you remember the spec).
-
Write the follow-up spec for every stub (next section but one).
Don't claim done until validate and lint both pass (exit 0), and every stub
is in the spec.
Assemble from existing activities; stub the rest
This is the core behaviour, and the thing to get right:
-
Prefer existing activities. Each service/business-rule step should reuse a catalog activity. Wire it with the catalog's exact reference —
camunda:delegateExpression="${beanName}"for a delegate, orcamunda:type="external" camunda:topic="the-topic"for an external task — and add acamunda:inputOutputmapping for the variables it reads and writes, taken from the catalog entry. Match the catalog's implementation style; don't convert a delegate activity into an external one or vice-versa. -
Do NOT write activity implementations. Building a delegate bean or a worker is a separate task. While assembling/fixing a process you produce the
.bpmn(+ DMN/forms if asked) and, for new activities, a spec — never the.java/ worker code. If the user explicitly asks you to also implement an activity, treat that as the separate task and only then use the implementation patterns inreferences/wiring-and-stubs.md. -
Stub a missing activity instead of inventing it. A stub is a real, deployable node so the whole process can be laid out, validated and even deployed as a skeleton — but it is clearly marked as "to build". Represent it as a service task that is:
- named for the business step,
- bound as an external task with a clear
camunda:topic(kebab-case of the name) — the most decoupled "awaiting a worker" placeholder; or, if the project's catalog is delegate-based,camunda:delegateExpression="${proposedBeanName}", - given a
camunda:inputOutputcontract (the inputs it will consume and the outputs it will produce — this is the interface the spec is written against), - marked as a stub so tooling and humans can see it:
<bpmn:serviceTask id="SendRejection" name="Send rejection notice" camunda:type="external" camunda:topic="send-rejection-notice"> <bpmn:documentation>STUB — to implement (see activities spec). In: applicantId, reason. Out: notificationId.</bpmn:documentation> <bpmn:extensionElements> <camunda:properties><camunda:property name="stub" value="true" /></camunda:properties> <camunda:inputOutput> <camunda:inputParameter name="applicantId">${applicantId}</camunda:inputParameter> <camunda:inputParameter name="reason">${rejectionReason}</camunda:inputParameter> <camunda:outputParameter name="notificationId">${notificationId}</camunda:outputParameter> </camunda:inputOutput> </bpmn:extensionElements> </bpmn:serviceTask>
summarizethen tags it{STUB}andlintlists it under INFO. Tell the user, in plain language, which steps you wired from the catalog and which you stubbed. Be honest that a stub is a deployable skeleton, not a runnable process: the engine will wait forever at an external-task stub (no worker subscribed), and a stubformRefshows "form not found" in Tasklist. It deploys and validates; it won't run end-to-end until the stub is built. Say this in the follow-up. -
Every user task gets assignment AND a form. Forms work exactly like activities: a user task is wired to a form from the catalog's
forms(setcamunda:formRef="<formId>" camunda:formRefBinding="latest", or acamunda:formKey). If no catalog form fits, form-stub it — pointcamunda:formRefat a proposed form id, mark it<camunda:property name="formStub" value="true">with a<bpmn:documentation>FORM STUB — fields: …</bpmn:documentation>, and add the form (its id + fields, derived from the task's decision/data) to the follow-up spec.lintwarns about any user task with no form and lists form-stubs under INFO. Don't leave a user task formless. -
Reuse the catalog's declarations and variable names. Wire user-task assignment to catalog
roles(candidate groups/users); business rule tasks to catalogdecisions(camunda:decisionRef); and message/signal/error/escalation events to catalog declarations — creating the matching root<bpmn:message>/<bpmn:signal>/<bpmn:error errorCode>/<bpmn:escalation>from the catalog entry (these are declarations you write directly, not implementations to stub). Name process variables and everycamunda:inputOutputparameter from the catalog'svariablesdictionary, not invented synonyms — consistent names are what make the steps actually wire together. A referenced message/error/role not in the catalog: add it and flag it. A missing DMN decision is stubbed like an activity (business rule task pointing at a proposeddecisionRef+ a spec entry).
The follow-up spec for new activities & forms
For every stub activity and every form-stub, write (or append to) a spec — a
Markdown file beside the .bpmn, e.g. <process>-activities-spec.md. It is the
handoff for the separate implementation task. Use the template
assets/activity-spec.template.md. Per activity entry: name, the BPMN node
id/topic, business purpose, inputs (name / type / source variable), outputs
(name / type), error outcomes (BPMN error codes), and the suggested
implementation type (delegate vs external worker) consistent with the catalog.
Per form entry: the form id (the one you put in camunda:formRef), the user
task it belongs to, and its fields (key / label / type, required?). End with a
checklist. Keep it precise enough to build each activity/form from the spec alone
without re-reading the diagram.
Report back: the follow-up
End every run with a short, skimmable follow-up — a handoff, not a wall of text. Three sections; omit any that are empty (except Done); keep each bullet to one line; the detail lives in the spec file, not here. Match the user's language.
Done — what you assembled/changed and that it's verified: the result file +
validate/lint pass; which steps you wired from the catalog; what you added
(gateways, error handling, timers, forms); the conventions applied.
Needs you — concrete asks, highest-impact first: stub activities to implement
and form-stubs to create (→ <process>-activities-spec.md); data gaps (a
variable a step needs that nothing upstream produces yet); conventions/assumptions
to confirm; deploy steps (e.g. deploy the .form/.dmn alongside the .bpmn). If
there's nothing, say "Deployable as is."
Watch out — narrow spots / risks that could bite at runtime: runtime deps (e.g. a Groovy script engine), interrupting vs non-interrupting choices, async/transaction trade-offs, anything you assumed rather than knew. Omit if none.
Aim for a handful of bullets total. If you stubbed things, the single most useful line is the pointer to the spec.
Reference files — read the one you need, don't guess XML
| When you're… | Read |
|---|---|
| Wiring existing activities and forms, representing stubs/form-stubs, the catalog format, or the spec | references/wiring-and-stubs.md |
| Writing the exact XML for service/user/rule tasks, listeners, I/O, async, events, call activity, process attrs | references/camunda-extensions.md |
| Diagnosing a deploy/runtime failure, or want the anti-pattern checklist + C7-vs-C8 cheat-sheet | references/execution-checklist.md |
| Form mechanics (formRef/.form, formKey, formData) or DMN decision tables | references/forms-and-dmn.md |
| Deploying (REST / Spring Boot / Modeler) or writing JUnit 5 + camunda-bpm-assert tests | references/deployment-and-testing.md |
assets/ has templates: an executable process skeleton (a wired activity + a
stub), a DMN table, a Camunda Form, the catalog format, the activities spec, and —
for the separate implementation task only — JavaDelegate / external-worker
skeletons.
Modeling for the engine, not just the picture
- Always
isExecutable="true"+camunda:historyTimeToLive(e.g.P30D) on a runnable process — since 7.20 the engine rejects deployment without TTL. DMN<decision>s need it too. - Expressions are JUEL (
${...}) in BPMN attributes; FEEL only in DMN. - Pair and guard gateways. Give a diverging exclusive/inclusive gateway a
defaultflow; never put conditions on a parallel gateway's outgoing flows. - Transaction boundaries where failure must be isolated:
camunda:asyncBefore="true"on external/long activities, loops, before waits; addcamunda:failedJobRetryTimeCycle. Keepcamunda:exclusive="true". - Declare what you reference (
message/signal/errorroots; errors need anerrorCode). Correlation is by name, not id. - Camunda 7, never 8. Use
camunda:(URIhttp://camunda.org/schema/1.0/bpmn), neverzeebe:, neverexecutionPlatform="Camunda Cloud". If the user actually wants Zeebe, say so and stop.
Layout: non-destructive by default
layout preserves camunda: extensions on every path. It is also
non-destructive: if the file already has DI it re-syncs — keeps your
existing geometry, drops shapes for deleted elements, auto-places new ones, and
reroutes only stale edges (so hand-tuned diagrams survive an edit). Pass
--rebuild to discard all DI and regenerate from scratch (use after big
structural changes, or when a diagram is messy). It lays out single processes,
collaborations (stacked pools + message flows), lanes (swimlane bands),
and data objects / text annotations / associations; sub-processes are kept as
drill-down planes. Only groups are not auto-placed. If a request leans on
groups, place them in a modeler or say so.
Bundled tools
| Command | Purpose |
|---|---|
summarize <file> [--json] |
Outline + per-node implementation, stub markers, async, forms, decisionRef, historyTTL |
layout <in> [out] [--rebuild] |
Safe layout: re-sync existing DI (or generate); always preserves camunda: extensions |
validate <file> |
Parse, flag missing shapes / overlaps / parse warnings (plane-aware) |
lint <file> |
Control-flow + structural + Camunda execution problems (ERROR/WARN/INFO); lists stubs |
diff <a> <b> |
Semantic + implementation diff (added/removed/renamed/retyped/rewired/impl-changed) |
find <file> <term> |
Find flow elements by name/type |
All live in scripts/camunda-tool.mjs (engine in scripts/lib.mjs; tests:
npm test).