Provably Safe
AI Agents.

AI agents decide which tools to call: shell commands, APIs, databases. PetriFlow intercepts every call and enforces rules they cannot break.

npm install @petriflow/rules
require lint before test
require test before deploy
require human-approval before deploy
limit deploy to 2 per session
# that's it. each rule is mathematically verified.

4 rules, 1 domain. Every reachable state checked. Zero bypasses possible.

AI agents call tools.
Nothing controls the order.

An AI agent works by calling tools: functions that send messages, run shell commands, query databases, deploy code. The model picks which tools to call and in what order. There is no runtime layer between the model’s decision and the tool executing.

"Most failures are not fancy exploits. Someone messaged the bot, and the bot did what they asked." - OpenClaw safety documentation

Prompts don’t enforce

You can write “always back up before deleting” in your system prompt. The model reads it. The model might follow it. Instructions are suggestions, not constraints.

Deny-lists are never complete

You block rm -rf but miss find -delete. You block DROP TABLE but miss TRUNCATE. The agent finds the gap you didn’t think of.

IAM controls who, not when

Identity management controls which users can call a tool. It doesn’t control the order. “This agent can run bash” doesn’t prevent it deleting without backing up first.

More tools, more risk

Every tool you hand an agent is a tool it can misuse. You’re scaling capability and risk together, with nothing constraining the interaction between tools.

Your instructions are prompts.
Make them enforceable.

Tools make your agent powerful. Rules make it safe.

Skill alone
# --- # name: cleanup # description: Delete unused files # --- ## Important Always back up files before deleting. Never delete without a backup. ## Usage 1. Back up the target directory 2. Delete unused files 3. Verify cleanup

The model reads this. The model might follow it.

Nothing makes it.

Skill + PetriFlow rule
# allow only these bash commands: map bash.command cp as backup map bash.command rm as delete # block everything else: block bash # enforce ordering: require backup before delete

Only mapped commands can reach the shell. unlink, shred, anything unmapped is blocked.

Allowlist, not deny-list.

A WhatsApp bot that
can't message the wrong person.

A real agent skill for sending WhatsApp messages via CLI. The skill tells the model how to send messages. Three rules make it safe.

SKILL.md
# WhatsApp CLI --- name: whatsapp description: Send WhatsApp messages --- ## Safety Require explicit recipient + message text. Confirm recipient + message before sending. If anything is ambiguous, ask first. ## Send wacli send text --to "+1415..." --message "..." wacli send file --to "+1415..." --file /path # the model reads these instructions. # nothing enforces them.
whatsapp.rules
# look up the recipient first. # no guessing phone numbers from memory. require whatsapp.lookup before whatsapp.send # human confirms recipient + message. # manual gate. no auto-bypass possible. require human-approval before whatsapp.send # one send per lookup cycle. # no batch-firing after a single lookup. limit whatsapp.send to 1 per whatsapp.lookup
// what happens when the agent tries to send a message
Agent calls whatsapp { action: "send", to: "+1415...", message: "Hey!" }
BLOCKED no recipient lookup yet
Agent calls whatsapp { action: "lookup", query: "Sarah" }
OK resolves to +14155551212
Agent tries whatsapp { action: "send" } again
BLOCKED human hasn't approved yet
PetriFlow prompts: "Send 'Hey!' to Sarah (+14155551212)?"
Human approves → manual gate opens
Agent calls whatsapp { action: "send", to: "+14155551212", message: "Hey!" }
ALLOWED lookup done, human approved, token consumed
Agent tries to send another message
BLOCKED budget consumed, must look up again

Policy checks vs safety rules.

Four scenarios. Same requirements. Two fundamentally different approaches.

Comparison of policy checks vs PetriFlow safety rules across four scenarios
Scenario Policy checks PetriFlow rules
Tool approval if (!approved) deny()
Bypassable check can be skipped, token can expire between check and use
require human-approval before shell
Proven safe no code path reaches shell without approval token
Message gating if (!allowlist.has(channel)) deny()
Bypassable allowlist stale, new channel added without check
require read-channel before send
Proven safe send is structurally unreachable without prior read
Privilege escalation if (BLOCKED.includes(tool)) deny()
Bypassable list incomplete, agent finds unlisted equivalent
block privilege-escalation
Proven safe transition provably dead in all reachable states
Sandbox escape if (!inSandbox()) deny()
Bypassable misconfigured mount, container escape, race condition
require human-approval before host-access
Proven safe only a manual gate can unlock host access

Simple by default.
Powerful when you need it.

Three layers. Most users never leave the first.

1
Pick a preset
Zero configuration. Built-in safety patterns for the most common agent risks. Pick a preset, pick your framework adaptor, done. Each preset is already mathematically verified.
import { backupBeforeDelete,
  createGateManager } from "@petriflow/rules"

const manager = createGateManager(
  [backupBeforeDelete()],
  { mode: "enforce" }
)
2
Write declarative rules
Custom constraints, simple syntax. Define your own safety rules with a small declarative language. PetriFlow compiles them into verifiable structures and checks every reachable state before your agent runs.
require read-channel before send-message
require human-approval before deploy-prod

limit  discord.sendMessage to 5 per session
block  discord.timeout # provably dead
3
Full control
Custom state machines with formal verification. Define places, transitions, tool mappings, and semantic validators. Design any safety topology you need. The same exhaustive verification runs on your custom definitions.
defineSkillNet({
  places: ["ready", "backedUp"],
  transitions: [
    { name: "backup",
      inputs: ["ready"],
      outputs: ["backedUp"],
      tools: ["backup"],
      deferred: true },
    { name: "destroy",
      inputs: ["backedUp"],
      outputs: ["ready"],
      tools: ["destructive"] },
  ]
})

One safety layer.
Any agent framework.

PetriFlow is a standalone gate that sits between your agent and its tools. In-memory state machine checks, no network calls, sub-millisecond overhead. Thin adaptors connect it to whatever framework you already use.

Claude Code

Hook into Claude Code's tool pipeline. Gate bash, file operations, and MCP tools.

import { configure } from "@petriflow/claude-code"

const config = configure(".")

// merge into .claude/settings.json
// hooks: SessionStart, PreToolUse,
//   PostToolUse, PostToolUseFailure

Vercel AI SDK

Wrap tool definitions with gate logic. Works with generateText, streamText, and any model provider.

import { createPetriflowGate } from "@petriflow/vercel-ai"

const gate = createPetriflowGate([safetyNet])

await generateText({
  tools: gate.wrapTools({ bash, write }),
  system: gate.systemPrompt(),
})

OpenClaw

Replace policy checks with structural rules. Drop-in safety upgrade for OpenClaw agents.

import { createPetriGatePlugin }
  from "@petriflow/openclaw"

// drop-in plugin for OpenClaw agents
const plugin = createPetriGatePlugin(
  [observeBeforeSend()],
  { mode: "enforce" }
)

pi-mono

Native extension for pi-mono. Full access to skill nets and tool mapping.

import { composeGates }
  from "@petriflow/pi-extension"

// native extension with full net access
const gates = composeGates(
  [testBeforeDeploy()],
  { mode: "enforce" }
)

A type system
for agent tools.

TypeScript catches undefined is not a function before your code runs. PetriFlow catches "agent deleted without backing up" before your agent runs. Same idea: compile-time proof, not runtime hope.

Every rule compiles to a state machine. Every reachable state is enumerated. If the verifier says "safe," it means safe in every possible execution, not just the ones you tested.

196
Reachable states verified
4/4
Terminal states valid
0
Bypassable checks
$ petriflow verify
Verifying backup-before-delete
  states: 196 · terminal: 4 · deadlocks: 0

  invariant "no unguarded delete"
    PASS holds in all reachable states

  invariant "privilege escalation impossible"
    PASS transition provably dead

  invariant "human gate required for prod"
    PASS no auto path exists

All invariants verified.
0 violations across 196 states.