Install
$ agentstack add skill-camunda-skills-camunda-ai-agents ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ● Shell / process execution Used
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Camunda AI Agent
Build agentic AI processes in Camunda 8.8+: an LLM driver (the AI Agent connector, Sub-process variant) applied to an ad-hoc subprocess with tools modeled as BPMN activities. Covers shape, prompts, tool modeling with fromAi(), sub-flow tools, and multi-turn agent context.
The older Task variant (AI Agent connector on a service task paired with an external multi-instance ad-hoc subprocess and explicit feedback loop) is documented in [references/ai-agent-task.md](references/ai-agent-task.md) for the niche cases where you need to audit or intercept every tool call. The Sub-process variant is the recommended choice for everything else, and is what the rest of this skill teaches.
Prerequisites
- Camunda 8.8+ cluster (the AI Agent connector ships in 8.8+)
- c8ctl CLI installed and a profile configured — see camunda-c8ctl
- An API key for the model provider you'll use (Anthropic, Amazon Bedrock, Azure OpenAI, Google Vertex AI, OpenAI, or any OpenAI-compatible provider). Store it as a Camunda cluster secret, never in the BPMN file. For local c8run, see camunda-c8ctl for the secrets bootstrap flow.
Cross-References
- camunda-bpmn: BPMN basics, ad-hoc subprocess element, namespaces, the mandatory lint loop.
- camunda-connectors: The underlying
c8ctl element-templateworkflow used to apply the AI Agent template (and the REST connector commonly used as a tool). - camunda-feel: FEEL syntax for prompts,
fromAi()calls, result expressions, type coercion. - camunda-process-mgmt: Deploying the process, starting an instance, inspecting incidents from agent invocations.
Authoritative References
- AI Agent connector overview
- AI Agent Sub-process (recommended variant)
- AI Agent Task
- Tool Definitions — how tool name, description, and
inputSchemaare derived fromAi()FEEL function
Applying the AI Agent Connector
Example — apply the template via c8ctl rather than hand-writing the many provider/prompt/memory fields:
# 0. One-time per environment: sync the OOTB catalog. search,
# get-properties, and OOTB-ID apply need this to have run at
# least once. Skip on subsequent runs.
c8ctl element-template sync
# 1. Find the current template ID and version — they evolve
c8ctl element-template search "ai agent"
# 2. Inspect the properties you care about
c8ctl element-template get-properties
c8ctl element-template get-properties --detailed data.systemPrompt.prompt
# 3. Apply to your ad-hoc subprocess element
c8ctl element-template apply -i AgentTools process.bpmn \
--set provider.type=anthropic \
--set provider.anthropic.authentication.apiKey='{{secrets.ANTHROPIC_API_KEY}}' \
--set provider.anthropic.model.model=claude-sonnet-4-5 \
--set data.systemPrompt.prompt='="You are a customer support agent. Use the available tools to look up customers and orders, and escalate to a human only when needed."' \
--set data.userPrompt.prompt='="Customer " + customerId + " reports: " + issue' \
--set data.limits.maxModelCalls='=10'
The template handles zeebe:taskDefinition, the zeebe:adHoc collection bindings, default input mappings, and the model-provider-specific fields — they change across template versions, don't hand-code them.
Supported providers: anthropic, bedrock, azure-openai, vertex-ai, openai, plus OpenAI-compatible (custom endpoint).
The BPMN Shape
The host is bpmn:adHocSubProcess — not a service task and not a regular sub-process. Tools live inside it.
bpmn:adHocSubProcess (template applied here)
└──
bpmn:serviceTask — e.g., REST connector call
bpmn:scriptTask — FEEL computation
bpmn:userTask — human-in-the-loop
bpmn:subProcess — multi-step or async tool
Hard rules that the lint loop does NOT catch — verify by reading the BPMN:
- The element type must be
bpmn:adHocSubProcess. A regularbpmn:subProcessor a service task will not host the connector. - A tool's root node is the entry activity that the LLM picks. A root node has no incoming sequence flow and is not a boundary event. An incoming flow turns the node into a regular flow step and the agent never sees it.
- The ad-hoc subprocess must contain at least one activity — BPMN semantics; an empty agent is rejected.
- Somewhere in the tool's execution flow, the variable
toolCallResultmust be set — for a single-activity tool that's the activity itself; for a sub-flow tool, it can be any activity inside the sub-flow.
Defining Tools
Three things determine whether the LLM picks a tool correctly:
- Tool name — the activity ID in the ad-hoc subprocess (e.g.,
LookupCustomer,GetCurrentWeather). The connector uses the BPMNid, not the human-facingnameattribute, as the tool name passed to the LLM. Pick descriptive IDs.
- Tool description — the value of `
on the activity. If documentation is missing, the connector falls back to the activity'sname` attribute, but always set documentation explicitly. Strong descriptions say what the tool does, when to use it, when not to, and what it returns:
> "Look up a customer by ID. Returns the customer's name, tier, and account status. Call this when the user mentions a customer ID or name. Do not call for anonymous queries."
The LLM sees the description verbatim — keep it free of vendor names, internal URLs, or anything you wouldn't want quoted back. c8ctl element-template apply does not write ``; hand-edit it in after applying a tool connector template.
- Input schema — derived automatically from
fromAi()calls inside the activity's input mappings. NofromAi()calls → empty schema → the LLM can't pass parameters.
A tool can be a single activity (service task, script task, user task) or a sub-flow rooted at a bpmn:subProcess containing further activities. In both cases the LLM only sees the root node — descriptions, inputs, and schema are read from there. The internal sub-flow steps are invisible to the LLM; they execute in sequence per normal BPMN semantics and propagate variables up when the sub-process completes.
Worked XML for each of the four shapes (REST, script, user task, sub-flow) is in [references/tool-modeling.md](references/tool-modeling.md).
fromAi() — Declaring AI-Generated Parameters
fromAi() tags a value as "the LLM will provide this at runtime". The first argument must be a reference to toolCall. — the last segment becomes the LLM-visible parameter name. The function takes an optional description, type ("string" default, plus "number", "boolean", "array", "object"), a JSON-Schema fragment, and an options context for things like {required: false}.
Full signature, all 6 calling variants (positional, named, enum schemas, optional params, multi-call JSON bodies) in [references/fromai.md](references/fromai.md).
toolCallResult — Returning Output to the Agent
When a tool completes, the connector reads the variable named toolCallResult from the tool's scope and forwards it to the LLM as the tool-call response. The rule is about scope, not which activity sets it:
- Single-activity tool — the activity itself sets
toolCallResult(via a result expression / result variable / output mapping / script result variable). - Sub-flow tool — any activity inside the sub-flow can set
toolCallResult; BPMN variable scoping propagates the value to the sub-process scope when it completes.
Ways to set it depending on the activity type:
- Connector with result expression:
value="={toolCallResult: response.body}". - Connector with result variable: name the result variable
toolCallResult. - Output mapping: ``.
- Script task: ``.
The value can be primitive (string, number) or a complex FEEL context — it'll be serialized to JSON before being sent to the LLM. If toolCallResult is missing or empty when the tool completes, the connector sends a generic "tool succeeded without returning a result" message to the LLM and the next turn degrades. Always set it meaningfully.
Prompts
Both data.systemPrompt.prompt and data.userPrompt.prompt are FEEL strings — they start with =.
- Static prompt:
="You are ...". The=is mandatory even for plain text. - Variable interpolation:
="Customer " + customerId + " reports: " + issue.+coerces scalars to string. - Feedback-loop prompt:
=if (is defined(followUpInput)) then followUpInput else initialUserInput. Used when looping back into the agent with user follow-up — see "Response Interaction" below.
For long, structured prompts, build the string in a script task upstream and pass it in via a variable.
Tool-Call Feedback Loop
The tool feedback loop is internal: the agent job worker repeatedly calls the LLM, activates the tools it chose, collects results, and re-prompts until the LLM produces a final response or data.limits.maxModelCalls is reached. You don't model the loop — only the tools.
Reading the Agent Response
The agent writes its output to a single context variable named by the connector's Result Variable field — default agent. With multiple agents in the same process, give each a unique result variable name (e.g. mySecondAgent) and re-align the agent-context input field accordingly (mySecondAgent.context) to avoid interference between agents.
Which fields the result context contains depends on the response settings (examples below use the default agent):
agent.responseText— whendata.response.format.type=text(the default).agent.responseJson— whenformat=json, or whenformat=textwithdata.response.parseJson=true.agent.context— whendata.response.includeAgentContext=true. Needed for the Response Interaction pattern below.
Provider × format=json compat. Only OpenAI and Google Vertex AI support data.response.format.type=json natively. For Anthropic, Amazon Bedrock, Azure OpenAI, and OpenAI-compatible providers, use format=text + parseJson=true and read agent.responseJson.
Schema is a FEEL context literal. When format=json, data.response.format.schema is feel: required — write it as ={ type: "object", properties: { ... } }, not a quoted JSON string.
When JSON parsing fails. Behavior differs between the two paths: parseJson=true on format=text omits responseJson and still populates responseText with the raw (unparseable) text — no incident. Native format=json raises an incident with error code FAILED_TO_PARSE_RESPONSE_CONTENT; handle it with an error boundary event if you need graceful fallback.
Response Interaction (User Feedback Loop)
After the agent produces its final response, you may want a user (or another agent acting as a judge) to review or amend it and bounce it back in. The pattern is to route from the ad-hoc subprocess to a user task that collects followUpInput, then back to the same agent ad-hoc subprocess. The user-prompt FEEL switches between the initial and the follow-up input:
data.userPrompt.prompt = =if (is defined(followUpInput)) then followUpInput else initialUserInput
The agent preserves conversation context across re-entries; see the Sub-process docs for the current context-handling field names, since these have evolved across template versions.
Limits and Memory
data.limits.maxModelCalls— caps the number of LLM calls per agent execution. Always set it (5–20 is a typical starting range); without a sensible cap a misbehaving prompt can rack up cost. There is no time-based limit.data.memory.contextWindowSize— caps how many prior messages the agent replays to the LLM (default 20). Smaller saves tokens, larger preserves more context.data.memory.storage.type—in-process(default),camunda-document(offload to the Camunda Document store when context grows past variable size limits), orcustom. Use the hyphenated form.
Troubleshooting
Non-obvious failure modes the lint loop will not catch.
- Tool has an incoming sequence flow — it stops being a tool and becomes a regular flow step. The tool's ROOT node must have no incoming flow. Internal activities inside a sub-flow tool can (and do) have incoming flows — that's how the sub-flow works.
- Tool name confusion — the LLM-visible tool name is the BPMN
id, not thenameattribute. Use descriptive PascalCase IDs. - Bare-string prompts — both system and user prompts are FEEL. Even literals must be
="...". - Number-in-string FEEL — concatenating a number into a URL or message requires
string(x);+between a string and an un-coerced number fails. Cross-ref camunda-feel § type coercion. - Hyphenated memory storage type —
in-process,camunda-document,custom. Not camelCase.
Closing Step
Run the BPMN lint loop (see camunda-bpmn) before declaring the agent process done:
c8ctl bpmn lint process.bpmn
Lint catches structural BPMN problems but does not validate connector-template inputs. After lint is clean, verify by reading the BPMN:
- Host element is
bpmn:adHocSubProcesswith the AI Agent template applied. - Every tool's root node has no incoming sequence flow and has a `
element (apply` doesn't write it — set it via a direct edit). - Every tool's flow ends with
toolCallResultset in scope. - Both prompts start with
=. data.limits.maxModelCallsis set.- API keys are pulled from
{{secrets.*}}, not literal values.
References
For detailed reference material, read from references/:
- [tool-modeling.md](references/tool-modeling.md) — worked XML for the four tool shapes (REST connector, script task, user task, sub-flow), including async-callback sub-flows
- [fromai.md](references/fromai.md) — full
fromAi()signature, all calling variants (positional, named, enum schemas, optional params, multi-call JSON bodies) - [ai-agent-task.md](references/ai-agent-task.md) — the older Task variant (audit/intercept every tool call) — niche use only
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: camunda
- Source: camunda/skills
- License: Apache-2.0
- Homepage: https://camunda.com
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.