- Gate’s doors
/v1,/mcp/<name>,/a2a/<name>- a Gate rule reads
- the caller at every hop, and the call at the hop it names
- Flow’s agent step
agent:runon a BPMN service task or a CMMN task- the records
- Gate’s signed trail per call, and Flow’s case record, checked by
kirto-flow-replay
An agent is a client that calls a model, calls tools and calls other agents, inside some larger piece of work. Two questions follow, and each product answers one. Gate answers whether this call may leave, with what in it, and as whom. Flow answers which step of the work the agent is in, what that step lets it do, and who decides before the work moves on.
Two halves
Gate checks each call the agent makes. Flow decides which step the agent is in, and records the run so it replays.
Gate sees every call and no process. Flow sees the process and none of the bytes on the wire. An agent that runs inside Flow and calls out through Gate leaves both records.
Gate: every call the agent makes
An MCP server declared once in Gate answers at /mcp/<name>, and a peer agent at /a2a/<name>, beside the models at /v1. A client sends its requests there with a Gate virtual key or a token from your identity provider. Gate strips that credential, sends the one configured for the upstream, and forwards the request byte for byte. A tool call and a message to a peer agent are read by the same rules, drawn against the same budgets and written to the same record as a model request, so there is no second policy to keep in step with the first.
Access rules run deployment-wide first and then in the caller's profile, and the profile's list can only narrow the first. A rule that names no hop runs at every one and reads only who is calling. A rule that names a hop also reads that hop's own variables.
| hop | at | the call's variables |
|---|---|---|
| model request | llm | model, served_model, provider, route |
| MCP tool call | mcp | server, method, name, args |
| A2A message | a2a | peer, method, role, task_id, context_id, parts |
At the MCP hop args is the tool's arguments, so a rule refuses a write before the call leaves. Guardrails read the content: the arguments and the result of a tool call, and each part of an A2A message. A refusal at either door is a JSON-RPC error whose data.code is the reason, and the same reason goes on the record.
Every rule also reads three caller variables: key, agent and user. They are always present, so a rule can refuse a write that has no signed-in person behind the agent. A token_exchange credential presents the caller's token to the issuer that minted it and asks for one minted for the upstream, so an MCP server sees the person and applies its own policy for them. Gate serves the approved MCP servers as a private registry on the MCP registry v0.1 API with every connect URL pointing at its own door, and serves each peer agent's card rewritten to its own address and signed with ES256. Front a peer agent and serve a private MCP registry walk through both.
Flow: the step the agent is in
In Flow an agent is a task in a BPMN process or a CMMN case. The task names an agent definition, and the definition names the provider, the model, the instruction, the tools it may call and the bounds of its loop. The definition is stored by version and pinned when the case starts, so publishing a new version cannot change a loop already running.
The task below is the agent's step in a refund process. The steps before it decide whether it runs, and the steps after it decide what its answer is allowed to cause.
<serviceTask id="assess">
<extensionElements>
<kirto:taskDefinition type="agent:run">
<kirto:input name="agentRef" value="refund-assessor"/>
<kirto:input name="prompt" value="${claim}"/>
<kirto:output name="assessment" source="text"/>
</kirto:taskDefinition>
</extensionElements>
</serviceTask>An agent's tools are Flow connectors, the same handlers a service task calls: an HTTP or OpenAPI call, an MCP tool, an email, a script. A connector written once is both a process step and an agent tool. allowedToolNamespaces is the agent's allowlist, and a call outside it comes back to the model as TOOL_NOT_ALLOWED. Every loop needs a maxIterations cap, and can carry a wall-time deadline and a token budget. Each ends the step with its own error code, which a boundary event in the process can catch.
A CMMN case can go further and decide the agent's tools turn by turn. In case mode the agent's tool list is re-read each turn from the case's discretionary items that apply at that moment, and it can only narrow the allowlist. A tool call can be planned into the case as a plan item instead of being dispatched, so it appears in the case's own record like an item a person planned. With a manual activation rule on that item, the model proposes the call and a person starts or rejects it.
A person's decision elsewhere in the process is a user task, and the run waits on it until someone completes it. On the agent itself, an approval namespace gates the loop after a gated tool has run: the next turn waits for a person to approve, and a rejection ends the step with AGENT_REJECTED. A call that must wait before it runs belongs in a user task ahead of the agent step, or in a case-mode plan item with a manual activation rule.
The record, and the replay
Flow writes one append-only record per case. It opens with the pinned model and version, the build and the starting principal. It records each event the case took in, each command it dispatched, each condition and each DMN decision with its inputs, outputs and matched rules, each human gate and each state change. Each turn of an agent loop is recorded with the model's raw reply and the OpenTelemetry GenAI attributes: the system, the model, the token usage and the tools.
Replay reads the record back and re-feeds its inputs through a fresh engine on the pinned model, then compares the chain it derives with the stored one. The agent step's recorded result is one of those inputs, so the model is not called again. kirto-flow-replay exits 0 when the chain matches and 1 when it diverges, and it also exits 1 when this build's major or minor version differs from the one that recorded the case, because a reducer change can then explain the difference. The same replay runs in the modeler and behind Flow's API.
Gate's record is the other half. Each model call and tool call the agent made is a signed, chained record naming the key, the rule that decided and the controls it cited, and kirto-gate audit verify checks it offline.
Both on one agent
An agent definition with a baseURL sends its model calls there, so pointing it at Gate puts each turn through Gate's model door. The secret it names holds a Gate virtual key, so Gate knows which agent is calling.
{
"name": "refund-assessor",
"provider": "openai",
"model": "gpt-4.1",
"baseURL": "https://gate.internal/v1",
"secretRef": "gate-key-refunds",
"instruction": "Assess the refund claim for order ${orderId}.",
"allowedToolNamespaces": ["mcp"],
"maxIterations": 8,
"deadlineSeconds": 300
}Its tools go the same way. An MCP tool in Flow is a registration naming the server's endpoint, its credential and the tools models may call, pinned per version. Registering Gate's /mcp/orders door as the endpoint, with the same key as the credential, puts every tool call under Gate's rules. The rule below lets that key read orders and refuses anything else at the MCP hop.
access_rules:
- name: refunds-agent-reads-orders-only
at: [mcp]
condition: >-
key.name == "refunds" && server == "orders" && !name.startsWith("get")
action: deny
message: "the refunds agent may read orders, not change them"The refund case then leaves two records. Flow's says the claim reached the assessment step, what the agent answered in each turn, which refund limit the DMN decision applied, and who approved the payment. Gate's says what each of the agent's calls carried, which identifiers were rewritten before the provider saw them, and which tool call was refused under which rule.
- Three call kinds, one door. A model at
/v1, an MCP server at/mcp/<name>and a peer agent at/a2a/<name>, under the same keys, rules, budgets, guardrails and record. - The agent is a step.
agent:runsits on a BPMN service task or a CMMN task, with its definition pinned by version when the case starts. - Tools are connectors. The handler an agent calls is the one a service task calls, inside an allowlist the agent cannot widen.
- A person decides where the model says it. A user task, an approval gate, or a manual activation rule on a case item.
- The process replays.
kirto-flow-replayre-derives the case's chain from its recorded inputs and the pinned model, and exits 1 on a divergence. - Each call is signed. Gate's record names the key, the rule and the controls, and checks offline.
Where each one ends
- Gate sees only what crosses it. An agent that reaches a model or a tool directly is ungoverned, and Gate does not discover it. A Flow agent definition without a
baseURLcalls its provider directly, and Flow does not force it through Gate. - Two dialects at the model door. Gate's model door speaks the OpenAI and Anthropic dialects. A Flow agent set to the
geminiprovider speaks Gemini's own API and cannot point at it. A Gemini model reached through Gate uses theopenaiprovider and Gemini's OpenAI-compatible surface. - The agent loop is history, not replay. Replay covers BPMN and CMMN. An agent's turns are recorded in full and read back, and the process replays around the step's recorded result.
- Case mode runs on the DBOS host. The broker, Flow's main host, runs agents with a static tool list and refuses case mode today. Case-mode grants and planned tool calls run on the DBOS host.
- Sub-agents stay inside Flow. A Flow agent calls another as a tool inside the same loop. That call does not cross Gate's A2A door, though each sub-agent's model calls can.
- Two records, no shared id. Flow does not read Gate's trail, and no identifier joins a Gate record to the Flow case step that caused it.
Further reading
- The tool result is the channel: why Gate reads tool results as well as the user's turn.
- Orchestration and choreography in one model: how a CMMN case and a BPMN process divide the work.
- Authenticate agents with your identity provider, and Flow's documentation.