Run agents inside a process you can replay.
Adaptive process orchestration: BPMN, CMMN and DMN run as written, an AI agent is one step inside them, and every step is recorded, so any run replays against the process definition that authorised it.
- 1case.startedrefund.bpmn v3 · build pinnedpinned
- 2condition.evaluatedamount <= 500 · trueamount <= 500 · falsere-deriveddiverged
- 3command.dispatchedAssess claim · agent:runre-derived
- 4agent.turngpt-4.1 · 2 tool calls · response keptfed in
- 5command.resultAssess claim · recommend: approvefed in
- 6human.gateApprove refund · claims-leadfed in
- 7state.transitionApprove refund · completedre-derived
- 8case.completedPay outre-derived
$ kirto-flow-replay -model refund.bpmn -history case-4821.json
{"chainLen": 4, "match": true, "versionSkew": false, …}exit 0
{"chainLen": 4, "divergences": [{"index": 0, "field": "attrs", …}], "match": false, …}exit 1
What a replay checks
A case's record holds two kinds of row: what the world handed the case, and what the engine derived from it. Replay feeds the first back in and derives the second again.
- The inputs are fed back in. Signals, messages, timer fires and task results, in the order they arrived. A model's answer and a tool's result cannot be recomputed, so the recorded ones are used.
- The rest is derived again. A fresh engine on the pinned model turns the inputs into conditions evaluated, decisions made, commands dispatched, transitions and completion. It carries no state from the original run and does no I/O.
- The two are compared by index. Each derived row is checked against the stored row at the same position, on type, element id and attributes. The sequence number, timestamp and pins are written by the host and are not compared. Every mismatch is reported with both values.
A clean replay shows the engine reached the stored chain from the inputs alone. The same replay runs in the browser modeller, on the gateway's replay endpoint, and in CI, where the exit code is the verdict.
Replay a case$ kirto-flow-replay -model refund.bpmn -history case-4821.json
{
"chainLen": 5,
"divergences": [
{
"field": "attrs",
"index": 0,
"recorded": "{\"expression\":\"amount \u003c= 50\",\"result\":true}",
"replayed": "{\"expression\":\"amount \u003c= 500\",\"result\":true}"
},
{
"field": "elementID",
"index": 1,
"recorded": "refuse",
"replayed": "pay"
}
],
"match": false,
"recordedWith": "0.1.0-dev",
"replayedWith": "0.1.0-dev",
"versionSkew": false
}
$ echo $?
1- exit 0
- Every derived row matches the stored one.
- exit 1
- A row diverged, or the replaying build differs from the recording build at MAJOR.MINOR.
- exit 2
- The replay could not run: a missing flag, an unreadable file, a history with no case.started.
Output of kirto-flow-replay on a two-task refund case recorded by Flow's engine, after two stored rows were edited: the gateway's condition and the task it dispatched.
The process governs the agent
Three OMG standards run as written, and an AI agent is one step inside them.
The path
BPMN 2.0
Processes whose order is known: gateways, events, subprocesses, compensation, multi-instance. An agent is a service task.
The judgement calls
CMMN 1.1
Cases whose next step is not known in advance. Discretionary items, planning tables and sentries decide what may happen next, including what an agent may call.
The rules
DMN
Decision tables and FEEL that a domain expert reads and checks. A business rule task evaluates them inside the engine, and the case records the rules that matched.
- Process
- BPMN 2.0
- Case
- CMMN 1.1
- Decisions
- DMN, with FEEL compiled to CEL
- DMN TCK
- 3,494 of 3,494 cases pass, across 147 models
- Model providers
anthropic,openai,gemini- Hosts
- The broker (
kirto-flow), and DBOS on Postgres
A reason/act loop that can be replayed
agent:run is a capability like any other. A BPMN service task or a CMMN task names an agent spec, and the spec names the provider, the model, the prompt, the tools and the limits. A case pins the spec version it started with.
Each model call and each tool call is recorded before the next one starts. After a crash the loop resumes from the record, and a call that completed is not made again. A call in flight when the worker died is dispatched again, with the same idempotency key.
An agent's tools are the process's own connectors, resolved through the same registry and described by the same schema. A connector written once is a service task and a tool.
The static agent- Every turn is a record. An
agent.turnrow keeps the model's response and the OpenTelemetry GenAI attributes. A replay feeds it back in. - The tools are an allowlist. A namespace or one exact tool. A call outside it is refused before the connector runs, and the model is told.
- A person can gate a call. Calls in an approval namespace wait for a decision in a durable suspension, which survives a restart.
- The limits are hard. A turn cap is required. A wall-time deadline and a token budget are optional, and each ends the loop with a code an error boundary catches.
- The case can decide the tools. In case mode the tool list is what the case grants now, CMMN discretionary items or BPMN ad-hoc activities, re-read every turn.
Pure engines, a host underneath
The standards are the model
A model is BPMN, CMMN or DMN XML. A case resolves a concrete model version and agent spec version when it starts, so publishing a new version does not move a running case.
The engines do no I/O
Each engine is a pure reducer: Advance(state, event) returns the commands to carry out and the next state. FEEL and DMN evaluate inside it. Every task, timer, message and model call leaves it as a command. The same state and event give the same commands, a determinism test holds the engines to it, and replay rests on it.
The host is replaceable
The broker runs the engines over a Raft log, and kirto-flow puts broker, engines, connectors and gateway in one binary that needs only a data directory. The DBOS host runs cases on Postgres. The model and the connector code are the same on both.
Model in the browser. Deploy to production.
The engine in the browser
The modeller loads Flow's engines compiled to WebAssembly. Start a process or a case, step it event by event, and see which elements fire, which conditions evaluate and what state results, before anything is deployed. FEEL, DMN, validation and replay run there too.
Panels from the connectors
Each connector describes its operations with a JSON Schema derived from its input struct. The modeller renders a task's properties panel from it, so the form and the code read one definition.
Publish, then activate
A model is published as a new version through the gateway's API, then activated. A new case that asks for the latest version gets the active one. Running cases keep the one they started on, and their records replay against it.
What Flow does not do
- Replay checks derivation, not inputs. It shows the stored chain follows from the stored inputs over the pinned model. An input edited together with every row derived from it replays clean. The record is not signed.
- Replay holds within a minor version. A build that differs from the recording build at MAJOR.MINOR exits 1, because a reducer change can change what the engine derives.
- Case mode needs the DBOS host. The broker's agent engine has no per-turn grant event yet and refuses a case-mode agent spec.
- Control acts between turns. A revoked grant, and on DBOS the deadline, take effect at the next turn. A model call in flight finishes.
- Budgets are turns, time and tokens. There is no spend cap in currency.
- Three model providers. Anthropic, OpenAI and Gemini, each through its own SDK. A spec's base URL points an adapter at another endpoint.