sim.pflow.xyz is where the models on this blog run. Each one is a Petri net stored as a small JSON document — places, transitions, arcs, rates — and every model in the catalog gets the same instruments: seeded scenarios, side-by-side comparison, diagnostics, structural checks and calibration against an event log. Nothing is written per model. The instruments read the document.
The question it is built around: what if the model was the app? The Model Is the App makes that argument at length. This post is the tour.
The catalog has well over a hundred entries now. Three of them make a sensible path through it, each adding one idea to the last:
| Model | What it adds | Open it |
|---|---|---|
| Tic-tac-toe | Places, transitions, turn tokens; a game the engine can solve | console · app |
| Café | Weighted arcs, capped stock, a shared staff pool, customers who give up | console |
| Vet clinic | Many contending pools, read-arc gates, disruptions | console |
Every console is the same page, /whatif/?id={model}, pointed at a different id. The knobs it shows are the ones the model declares.
Nine cells, two players, a turn token that passes back and forth, and win detectors that end the game. The stored model is 34 places and 35 transitions, including a move counter and a draw place so every game ends in an outcome you can count.
Because the model declares its players and an objective, the engine can play it. Ask sim_evaluate for exact search and you get minimax over the declared game — it blocks, takes wins, and shows O can always avoid losing. Ask for the incidence method and you get the closed-form strategic values: centre 2.0, corner 1.5, edge 1.0, the count of win lines through each cell. Same net, two readings. Tic-Tac-Toe Model has the full analysis, including where the plain ODE ranking falls short of search.
Three drinks, a pantry, two baristas. An espresso draws 20g of beans and a cup; a latte 15g of beans and 50ml of milk; a cappuccino 15g and 30ml. Stocks are capped and restock; customers who wait too long walk out.
The café is built from three separately written nets — counter, pantry, staff — fused so that starting a drink is one firing across all three: the order leaves the queue, the stock is drawn down and a barista is taken off the floor in the same step. The barista and the beans are marked kinetic: false: they permit a drink to start without making it start faster. Leave that out and a bigger pantry brews quicker, which no café does.
That structure is what makes "should I put a third barista on?" a question with an answer. Headcount is the initial marking on one place; change it and compare. Coffee Shop Model works through a larger five-drink net with the same pattern of recipes as arc weights.
38 places and 42 transitions: vets, techs, a receptionist, exam rooms, surgery and dental suites, radiology, recovery kennels, and walk-ins who leave if they wait too long. Every service seizes its staff and its room when it starts and releases them when it finishes, so how busy the vets are is a marking you can read.
Here the console's disruptions earn their keep — an emergency wave, a surgery day — and so does sim_diagnose, which ranks every knob by its measured effect on the objective. What-If Analysis and The Capacity Paradox are what came out of pointing it at this clinic and at models like it.
Every model in the catalog uses the same vocabulary:
The first three are structure (what can happen). Tokens are state (what is the case now). Rates make it a simulation (what tends to happen). Declarations say what you are asking.
The instruments are the same for every model, from the catalog or one you wrote five minutes ago:
sim_scenario and sim_compare run seeded stochastic simulations; the same body on the same surface gives the same answer, byte for byte, so the difference between two scenarios is the change you made, not the dice.sim_diagnose ranks every knob by measured influence. It has found capacity that does nothing and capacity that makes the objective worse.sim_verify answers declared properties — deadlock-free, bounded, mutual exclusion, an invariant expression — as proved, refuted or unknown, and unknown is never a pass. sim_invariants hands back the conservation laws in the structure; sim_crosscheck runs the model discretely and continuously and reports where the two disagree.sim_conformance replays an event log against the model and reports fitness and precision, with the activities that couldn't fire; sim_calibrate learns rates from the log and returns a new model.sim_receipt returns a signed receipt over the model, the scenario and the result that anyone can replay at /api/receipts/verify.Models are content-addressed and immutable: a change is a new model with its own id, so "which version did you run?" has a definite answer.
Petri nets are visual and dynamic, and reading about tokens doesn't build the intuition that moving them does. Make a move in the tic-tac-toe app and watch a token leave one place, the transition fire, and a token land in another place. Add a barista to the café and watch the walkouts fall — or not. The concepts click in a way a diagram alone doesn't manage, and the console is honest about the second case: if a knob does nothing, the comparison shows it doing nothing.
Traditional development starts with code and describes it afterwards. Here the model comes first and stays the source of truth:
The bet is that a good enough model makes everything downstream of it — the analysis, the console, the app — a reading of the document rather than a separate program that can drift from it.
claude mcp add --transport http sim https://sim.pflow.xyz/mcp
The public tools work without signing in; storing models you own asks you to sign in the first time. The help page and llms.txt cover the rest.
For the underlying library: go-pflow on GitHub
For the theory behind ODE analysis: Declarative Differential Models
For what-if scenario planning: Skip the Spreadsheet: What-If Analysis
See also Chapter 17: The Visual Editor in Petri Nets as a Universal Abstraction.