Provably safe AI agents. Rules your agent cannot break. Any framework. One safety layer.
PetriFlow compiles declarative safety rules into Petri nets that gate every tool call. Each rule is verified exhaustively before your agent starts. If the verifier says "safe," it means safe in every possible execution, not just the ones you tested.
# safety.rules
require backup before delete
require human-approval before deploy
limit discord.sendMessage to 5 per session
block rm
import { loadRules } from "@petriflow/rules";
import { createPetriflowGate } from "@petriflow/vercel-ai";
const { nets } = await loadRules("./safety.rules");
const gate = createPetriflowGate(nets);
await generateText({
tools: gate.wrapTools({ bash, delete, backup }),
system: gate.systemPrompt(),
});bun install
bun testRun an example agent (requires an API key for the model provider):
bun run examples/01-file-management/agent.tsThe core product: a framework-agnostic tool gating system with adapter packages for specific agent runtimes.
| Package | Description |
|---|---|
@petriflow/gate |
Framework-agnostic tool gating. Skill nets, deferred transitions, tool mapping, multi-net composition |
@petriflow/rules |
Declarative rules DSL. Compiles one-liner safety policies into verified skill nets |
@petriflow/vercel-ai |
Vercel AI SDK adapter. Wraps tool execute methods with gating |
@petriflow/pi-extension |
pi-mono adapter. Intercepts tool calls and enforces net structure |
@petriflow/agent-sdk |
Claude Agent SDK adapter. In-process callback hooks for programmatic agents |
@petriflow/claude-code |
Claude Code hook. Gates bash, file, and MCP tools via the hook system |
@petriflow/openclaw |
OpenClaw adapter. Maps gate concepts to OpenClaw hooks |
@petriflow/pi-assistant |
Four reusable skill nets: safe messaging, staged deploys, research-before-share, backup-before-delete |
Each rule compiles to a small, independent Petri net. At runtime every net is checked on every tool call. A tool can only fire if all nets allow it.
Key mechanisms:
- Deferred transitions allow the tool call immediately, but only advance the net when the tool succeeds (e.g. backup must actually work before delete is unlocked)
- Tool mapping splits one tool (e.g.
bash) into virtual tools based on command content (map bash.command rm as delete) - Multi-net composition means rules compose by intersection.
require lint before test+require test before deploy=lint → test → deploy, without the nets knowing about each other - Exhaustive verification enumerates every reachable state at compile time. Small nets, big guarantees.
require A before B # A must succeed before B is allowed
require human-approval before B # manual gate every time
block A # permanently blocked
limit A to N per session # N uses total
limit A to N per action # N uses, refills when action fires
map tool.field pattern as name # pattern-match tool inputs into virtual names
Full docs: petriflow.joshtuddenham.dev
Four runnable Vercel AI SDK agents in examples/:
| Example | What it demonstrates | |
|---|---|---|
01-file-management |
Deferred transitions (backup must succeed), permanent blocks (rm), sequence gates |
Tutorial |
02-deployment |
Human approval gates, multi-rule composition (lint → test → deploy) |
Tutorial |
03-discord-bot |
Dot-notation action dispatch, session rate limiting | Tutorial |
04-devops-assistant |
Cross-domain composition: 13 tools, 5 domains, 10 independently-verified rules | Tutorial |
Separate from the safety layer, PetriFlow also includes a general-purpose workflow execution engine. This extends petri-ts with guards, side-effect execution, timeouts, persistence, scheduling, and analysis.
| Package | Description |
|---|---|
@petriflow/engine |
Core types, firing engine, scheduler, pluggable persistence (SQLite included), analysis |
@petriflow/viewer |
Interactive React app. Click to fire transitions, live analysis, visual editor |
@petriflow/cli |
petriflow analyse <workflow.ts>. Prove properties from the command line |
Six example workflows in workflows/:
| Workflow | What it proves |
|---|---|
coffee |
Concurrency. heatWater and grindBeans fire independently, pourOver joins them |
github-lookup |
Real HTTP calls as transitions |
contract-approval |
Parallel approval gates. Finance and legal review independently |
order-checkout |
Resource contention. Cannot oversell inventory |
simple-agent |
Iteration loop with budget. Agent forced to respond when exhausted |
agent-benchmark |
Everything together: guards, executors, timeouts, human approval, deferred transitions |
# Viewer + API server
bun dev
# Run a workflow directly
bun run workflows/coffee/index.ts
# Analyse a workflow
bun packages/cli/src/cli.ts analyse workflows/agent-benchmark/index.ts
# Strict mode (exits 1 on issues, use in CI)
bun packages/cli/src/cli.ts analyse workflows/order-checkout/index.ts --strictimport { defineWorkflow, createExecutor, analyse, Scheduler, sqliteAdapter } from "@petriflow/engine";
import { Database } from "bun:sqlite";
const definition = defineWorkflow({
name: "my-workflow",
places: ["start", "end"],
transitions: [
{
name: "go",
type: "script",
inputs: ["start"],
outputs: ["end"],
guard: "ready",
execute: async (ctx) => ({ done: true }),
},
],
initialMarking: { start: 1, end: 0 },
initialContext: { ready: true, done: false },
terminalPlaces: ["end"],
});
// Prove properties before running
const result = analyse(definition);
// Run it
const db = new Database(":memory:");
const executor = createExecutor(definition);
const scheduler = new Scheduler(executor, { adapter: sqliteAdapter(db, definition.name) });
await scheduler.createInstance("instance-1");
await scheduler.tick();The scheduler takes a pluggable WorkflowPersistence adapter. SQLite is included; implement the interface for Postgres, Redis, or in-memory.
- petri-ts Petri net engine and analysis
- Bun runtime, test runner, package manager
- Turborepo monorepo orchestration
- Hono HTTP router for the server
- React Flow graph rendering for the viewer