Most codebases have state machines hiding in plain sight — buried in switch statements, handler chains, and nested conditionals. Code-to-flow — the sim_code_to_flow tool on sim.pflow.xyz's MCP server — extracts them as Petri nets.
Hand it source code — Go, Python, JavaScript, Rust, Solidity, or anything else; the language is only a hint — and Claude reads it, picks out states and actions, and returns a Petri net as JSON: places, transitions and arcs that can be fired, simulated, checked for deadlocks and replayed against a log of the running program, which is what a flowchart can't do.
The extraction is a single call to Claude at temperature 0.2 under a fixed system prompt (pkg/generator/codetoflow.go in sim's source). The prompt asks for the structural rules — every place on at least one arc, every transition with an input and an output arc, the model kept minimal, essence over implementation detail — but a prompt only asks. What checks the answer is the validator (pkg/validator/validator.go): it rejects a model with no places or no transitions, an unconnected place or transition, or an arc naming something undefined, then walks the reachability graph (capped at 10,000 states) and attaches a warning for each deadlock it finds. A deadlock is a warning rather than a failure, because a workflow that has finished is a deadlock too. The validator also computes boundedness and liveness, but the code-to-flow reply carries none of this — a model that validates is stored and its id returned — so for deadlocks and boundedness, run sim_verify on the stored model (bounded and deadlock-free are its defaults).
The same code looks different through different lenses:
Leave the focus unset and the tool asks for control flow; set another and we see the same code from a different angle. We call these lenses loosely. A focus mode is one line in a prompt and has no put. The lens in the categorical sense — a get/put pair between a state space and a view of it — is the analysis net in The Incidence Reduction, and whether extractions of the same code under two focus modes are related by anything like that is untested.
A simple order handler:
func processOrder(order Order) error {
if err := validate(order); err != nil {
return err
}
charge(order.Payment)
ship(order.Address)
notify(order.Customer)
return nil
}
We have no saved run of this example — no golden output, no test — so what follows is the shape of the answer, not a recorded result, and two runs need not agree. The answer this post first reported had six places (pending, validated, charged, shipped, completed, failed) and four transitions (validate, charge, ship, notify). As stated, that can't be quite right: with four transitions, failed either has no arc, which the validator rejects as an unconnected place, or validate writes to validated and failed at once. The early return needs a transition of its own (a reject from pending to failed, say). The code says nothing at all about a failed charge — its result is never checked — so a faithful extraction has no branch there either; the net shows that gap as an absence, and no deadlock check will flag it. Store the result and a scenario run shows flow through the pipeline.
pflow.xyz is for drawing nets from scratch. Code-to-flow solves the opposite problem: there's already a codebase and we want to see what state machine it encodes.
The LLM handles the pattern recognition — identifying states, transition triggers, resource consumption — and produces a model that makes the implicit structure explicit. It also works as a comprehension tool. Paste unfamiliar code, get back a Petri net that shows the flow. The visual model is often easier to reason about than reading the source.
MCP tool — sim_code_to_flow on sim.pflow.xyz's MCP server (https://sim.pflow.xyz/mcp), from Claude Desktop, Claude Code, Cursor or any other MCP client. It takes code, an optional language hint, a focus and a name. When the answer validates, it is stored as a new model you own and the tool returns its id; when it doesn't, the tool returns the raw JSON and the validation errors, so you can fix the model and store it by hand with sim_create_model. Storing a model needs sign-in.
The tool is the only surface: sim.pflow.xyz mounts no code-to-flow page or HTTP endpoint, and a deployment with no LLM provider configured refuses the tool.
Previously the pflow pipeline ran one direction:
design model → generate code → deploy
Code-to-flow reverses it:
existing code → extract model → analyze → improve → regenerate
Take a legacy system, extract the state machine as a Petri net, check its properties (bounded? live? deadlock-free?) by exhaustive reachability — a proof only when the graph finishes under the 10,000-state cap — then generate a clean implementation from the checked model. From there the model is the source of truth, and Claude is the bridge between informal code and formal net. We have not yet run that loop end to end on a real legacy codebase.
Try it: connect an MCP client to https://sim.pflow.xyz/mcp and call sim_code_to_flow.
The step nothing checks yet is the first one. The validator says the net is well formed; it can't say the net is this code's net. Claude's reading of the source is the only link, and we have not compared an extraction against what the program actually does. sim already has the instrument: sim_conformance replays an event log against a stored model and reports fitness and precision, naming the activities that could not fire. So the test is buildable — log the running code, replay the log against the extracted net, count what fails to replay. Until it runs, an extracted model is a well-formed reading of the code, and "source of truth" starts only at the point where we decide to treat it as one.
For readers of the March version (revised 2026-10-16, against the source; nothing re-run):
sim_verify does).failed needs a transition of its own. It is now presented as the shape of an answer, not a result, and the "what if charge fails" check is gone: the source has no failure branch for charge, so there is nothing for a deadlock check to find.sim_code_to_flow MCP tool on sim.pflow.xyz. The web page and HTTP endpoint this post described are gone; the tool stores a model that validates instead of returning it, and its focus defaults to control flow rather than auto-detect.Related: Introducing sim.pflow.xyz · Declarative Differential Models for the ODE theory · Small Models > LLMs, where code-to-flow is one stage of the pipeline · The Model Is the App