Skip to content

The static agent

The static agent is agent:run in its default mode. Its tool allowlist is fixed when the loop starts and does not change for the life of the loop. It fits most agent tasks: research this and answer, triage this ticket and close or escalate it, draft a reply and ask a person before sending it. Everything here applies to an agent in a BPMN process and to one in a CMMN case.

A model element names an agent by reference and carries nothing else about it. The provider, model, prompt and tools live in an agent spec, stored once in the model store and resolved by that reference, the same relation a business rule task has to a DMN decision. A case pins the spec version it started with, so publishing a new version does not change a loop already running.

A spec is authored as a SKILL.md file: YAML frontmatter for the fields and a markdown body for the system prompt. The frontmatter keys are the spec’s JSON field names:

---
name: researcher
provider: anthropic
model: claude-sonnet-5
allowedToolNamespaces: [http, kb]
approvalNamespaces: [http]
maxIterations: 8
deadlineSeconds: 120
outputKey: answer
---
You are a research assistant. Prefer the internal knowledge base over the
web, and cite the source when you use the web.

The fields a static agent uses:

Field What it controls
provider, model The backend (anthropic, openai or gemini) and the model id. Both are explicit; the provider is never guessed from the model id.
instruction The system prompt: the markdown body. ${expr} spans render against the task’s inputs when the loop starts.
allowedToolNamespaces The tools the model may call. A namespace (http) grants every operation in it; an exact ns:op entry grants one tool. Empty makes a completion-only agent.
approvalNamespaces Tool namespaces whose calls need a person’s approval before the loop continues.
approvers The groups an approval task is addressed to on the broker.
approvalTimeoutSeconds How long an approval may wait. 0 waits until the deadline.
maxIterations Required. The cap on reason/act turns.
deadlineSeconds The whole loop’s wall-time budget. 0 turns it off unless the host sets a default.
maxTokens The output cap for each turn, passed to the provider.
maxTotalTokens The budget for input and output tokens summed across the loop, checked between turns. 0 is uncapped.
outputKey The variable the final answer is bound to. Defaults to text.
subAgents The agent specs this agent may call as tools, one tool each, named agent:<ref>.
maxDepth The deepest nesting level this spec may run at as another agent’s tool. 0 means 1.
propagateToolErrors Tool namespaces whose coded errors fail the task instead of going back to the model.
secretRef, baseURL The secret holding the provider key, and a non-default provider endpoint.

A model element carries the agent reference, the prompt and an output mapping, and nothing that depends on the engine. This BPMN service task runs the spec above:

<serviceTask id="ask">
<extensionElements>
<kirto:taskDefinition type="agent:run">
<kirto:input name="agentRef" value="researcher"/>
<kirto:input name="prompt" value="What changed in v2.3?"/>
<kirto:output name="answer" source="text"/>
</kirto:taskDefinition>
</extensionElements>
</serviceTask>

The kirto prefix is bound to https://kirto.dev/spec/flow/bpmn in a BPMN file. A CMMN task runs the same spec with the same extension, bound to https://kirto.dev/spec/flow/cmmn:

<task id="t-ask" name="Ask the researcher" isBlocking="true">
<extensionElements>
<kirto:taskDefinition type="agent:run">
<kirto:input name="agentRef" value="researcher"/>
<kirto:input name="prompt" value="What changed in v2.3?"/>
<kirto:output name="answer" source="text"/>
</kirto:taskDefinition>
</extensionElements>
</task>

Both reach the same agent:run capability, resolve the same spec and dispatch tool calls through the same registry. Moving an agent from a process to a case changes the element it sits on and nothing in the spec.

allowedToolNamespaces is a short list on purpose, so a reviewer can read it. A call outside it is refused before the connector runs. The model receives the refusal, TOOL_NOT_ALLOWED, as a tool error and can react: pick another tool, or say what it cannot do. The loop carries on.

approvalNamespaces adds a person on top of that boundary. A call in a gated namespace runs on the turn the model makes it. The loop then waits at the top of the next turn until someone approves or rejects the call. The wait is a suspension the host resumes, so it costs nothing while it lasts. On the broker the gate is a task addressed to the spec’s approvers. When one turn makes several gated calls, only the last is put to a person.

Each turn runs in this order:

  1. The loop checks the turn cap, any pending approval, the deadline and the token budget.
  2. It asks the model for the next step, offering the transcript and the tools the allowlist grants.
  3. The model answers with text, with tool calls, or with both.
  4. Each tool call is checked against the allowlist and dispatched through the registry.
  5. The tool results are appended to the transcript, and the next turn starts.
  6. An answer with no tool calls ends the loop, bound to outputKey.

Each model call and each tool call is recorded before the next one starts. After a crash the loop resumes from the record: it does not call a tool again whose result was recorded, and it does not ask the model a question it already answered.

A transient fault, such as a rate limit or a flaky connector, is retried a bounded number of times. Past that, the host’s own retry takes over, and an operator can cancel it. A classified terminal fault (bad credentials, a context too long, a tool’s own coded error) is not retried.

The deadline is enforced differently on the two hosts. On DBOS it is checked at the top of each turn, so a model call in flight always finishes. On the broker it is a timer armed when the loop starts, and the loop fails with AGENT_TIMEOUT when it fires, whatever the loop is waiting for.

Some codes go back to the model as a tool error, and the loop carries on. The rest fail the task with a coded error that a BPMN or CMMN error boundary can catch and branch on:

Code When Goes to
AGENT_SPEC_ERROR The agent reference does not resolve, or the spec is invalid: maxIterations not positive, a case-mode flag on a static spec. The task
SECRET_ERROR The provider key could not be resolved. The task
LLM_ERROR The model call failed with a fault that is not retryable. The task
TOOL_UNRESOLVED The model asked for a tool with no registered handler. The model
TOOL_NOT_ALLOWED The tool is outside allowedToolNamespaces or subAgents. The model
AGENT_DEPTH_EXCEEDED The spec was called as a tool deeper than its maxDepth. The calling model
AGENT_APPROVAL_TIMEOUT Nobody resolved a gated call within approvalTimeoutSeconds. The task
AGENT_REJECTED A person rejected a gated call. The task
AGENT_TIMEOUT deadlineSeconds ran out. The task
AGENT_TOKEN_BUDGET maxTotalTokens was reached before the next model call. The task
AGENT_MAX_ITERATIONS The turn cap was reached with no final answer. The task

A tool’s own coded error goes back to the model by default. It fails the task only when its namespace is in propagateToolErrors. An agent that runs out of turns is an outcome the model can branch on, the same as any other task failure.

The mode where the case decides the tools on every turn is covered in Agents governed by the case.