# sim.pflow.xyz > Turn an informal description (a decision, a process, the mechanism a > paper states, or source code via `sim_code_to_flow` where the deployment > has an LLM configured) into an executable > Petri-net model, then check it: against an event log with > `sim_conformance` (fitness, precision, per-trace activities that could not > fire), knob by knob with `sim_diagnose`, and run by run with `sim_receipt` > (a signed result anyone can replay at `/api/receipts/verify`). > > Fidelity to a source document is a method here, not yet an engine output. > The labels agree, agree-but-unblinded and split come from a daily > arXiv model-match study: an LLM builds a model of a paper's stated > mechanism with these tools, records its prediction before comparing, and > labels each paper `agree`, `disagree`, `split` (at least one claim agrees > and at least one disagrees) or `indeterminate`, with a blinding status. > agree-but-unblinded means the model agrees but the paper's numbers were > seen before the prediction was written, so the agreement may have been > built in. No `sim_*` tool computes or returns these labels; they are the > LLM's judgement, and a split is where a person has to look. > > Worked examples: cost-benefit analysis for anything you can describe as a > flow. Should I hire another barista, add a van, or build this feature? Describe the > decision as a Petri net — staffing, queues, equipment, patience, or a > backlog gated by cost-benefit review — store it with one POST, and every > model gets the same instruments: seeded scenario comparison, disruption > injection, calibration against your own event log, synthetic event-log > datasets, model diagnostics that rank every knob by measured influence on > the objective, and one-click publishing into your own Google Sheets with > live charts. Models are content-addressed and immutable; scenarios are > pure, seeded reads (same body on the same surface, same answer, byte for > byte — HTTP and MCP fill unset hours/realizations/seed differently, so set > them to compare across surfaces). The diagnostics > pass has found capacity that does nothing (a café's pantry stock, a > hospital's ED bays) and even capacity that *hurts* the objective (a > software team's headcount) — so "add more" gets checked instead of assumed. Models are flat JSON: `{"name", "places": [{"id","initial",…}], "transitions": [{"id","rate",…}], "arcs": [{"from","to","weight",…}]}` plus optional declared metamodel blocks — `simulation.objective` (a numeric marking expression the diagnostics rank on), `parameters` (arc weights or capacities exposed as named knobs), `stages` on a transition (Erlang-k service time), and `schedule` on a transition (a piecewise day shape carried by the model). The discrete engine enforces read arcs, inhibitors, capacities, and non-accelerating queues, and says plainly what it could not enforce. ## Docs - [Full guide](https://sim.pflow.xyz/llms-full.txt): model format, the HTTP API per model, the MCP tools, the metamodel, Sheets publishing, showcases - [Model JSON-LD context](https://sim.pflow.xyz/context/model/v1): every model-schema field as a linked-data term - [Diagnosis/classification JSON-LD context](https://sim.pflow.xyz/ns/v1/context): the `@context` every diagnosis, classification and lineage document carries - [Vocabulary](https://sim.pflow.xyz/ns/v1): glossary of load-bearing terms (isKinetic, capacity, ratePerHour, viewRole, rateOverrides, …) - [Model kinds](https://sim.pflow.xyz/ns/models/v1): the seven `@type` names sim_propose_types proposes and sim_link accepts - [Predicates](https://sim.pflow.xyz/ns/predicates/v1): every relation predicate, with its axis, subject and object kinds, inverse, and writers ## API - [Catalog](https://sim.pflow.xyz/api/catalog): curated consoles plus every model dedicated to the commons (CC0-1.0, CC-BY-4.0 or CC-BY-SA-4.0), JSON, CORS-open; its `model` field is each entry's current content id. `?type=` filters by `@type` relation, `?feeds={id}` lists what a model's bindings feed, `/api/catalog/types` tallies the types - [Models](https://sim.pflow.xyz/api/models): `POST` a model to store it (owned if signed in; anonymous uploads are stored unowned for good — reachable by id, listed for nobody, and nobody can delete, license or supersede them, even after storing the identical model signed in; to own a model, sign in before creating it, and a changed model is a new id; a key the schema does not define is a 400 naming it; the 201 carries `warnings` when there are any); `GET` lists the models visible to you, `?scope=curated` the worked examples (`commons`, or `mine` when signed in) and `?q=` those matching every term; `GET /api/models/{id}` returns one model (`Accept: application/ld+json` wraps it), and a 404 for a name rather than an id says to search with `?q=` (a model created without sign-in is listed for nobody: keep the id its create returned) - Per-model reads (all seeded, all pure): `GET {id}/rates` (declared knobs), `GET {id}/ports`, `GET {id}/canonical`, `POST {id}/scenario`, `POST {id}/scenario/compare`, `GET {id}/dataset` (`?seed=&cases=&format=csv|jsonl`), `GET {id}/diagnose`, `{id}/verify`, `{id}/invariants`, `{id}/crosscheck`, `{id}/evaluate`, `POST {id}/reach`, `GET {id}/bindable?port=&direction=&among=` (what can bind to that port; 1 token). `GET /api/systems/{id}` reads a System, `GET /api/systems?naming={id}` lists the systems naming a model or binding (1 token each). Caps, refused not clamped: realizations ≤ 200, hours ≤ 10000, samples ≤ 5000, ≤ 20 scenarios per compare; diagnose/crosscheck hours ≤ 168. Anonymous compute is rate-limited per client IP (IPv6: per /64), or per account when a bearer is sent (429 with Retry-After); a compare costs one token per scenario, and storing a model (create, compose, calibrate) is charged too - Per-model derivations, open to anonymous callers (signed in, the new model is yours; anonymous, it is stored unowned for good): `POST {id}/calibrate` (event log → new calibrated model), `POST {id}/compose`; and `POST {id}/receipt` (Ed25519 signed run receipt, no sign-in) - Signed in only: `POST {id}/publish` and `POST {id}/publish/compare` (→ your Google Sheets), `POST {id}/license` and `DELETE {id}` (owner) - [Components](https://sim.pflow.xyz/api/components): subnet templates (arrivals, service, hazard, inventory, decision, mailbox, datastore) - [Receipt key](https://sim.pflow.xyz/api/receipts/key): public key for `POST /api/receipts/verify` ## MCP - [MCP server](https://sim.pflow.xyz/mcp): Streamable HTTP, OAuth 2.1 with dynamic client registration (`/oauth/register`, `/oauth/authorize`, `/oauth/token`), asked for only by private tools and by sim_list_models with scope "mine". Public tools (no sign-in; the ones that run the engine are rate-limited per client IP, or per account when a bearer is sent): sim_list_models (scope "mine" needs sign-in), sim_get_model, sim_components, sim_scenario, sim_compare, sim_dataset, sim_diagnose, sim_classify, sim_propose_types, sim_verify, sim_invariants, sim_crosscheck, sim_evaluate, sim_run_pipeline, sim_check_witness, sim_reach, sim_system, sim_get_artifact. Every call is checked against the tool's declared arguments before it runs or is charged: an undeclared argument is refused naming the one it likely meant (`q` → `query`), a value of the wrong type is refused, never read as the default, and a string argument that carries JSON (a scenario, a model, properties) takes the object or array itself too. sim_create_model takes anonymous calls too (1 token, from the client IP's bucket anonymous and the account's signed in): without sign-in the model is stored unowned for good, as anonymous `POST /api/models` stores it (reachable by id, listed for nobody; nobody can delete, license or supersede it, and storing the identical model signed in later does not change that); signed in, it is yours, so sign in before creating a model you mean to own. It refuses a key the schema does not define, naming its path and the likely key, and returns `warnings` for a model that stores but probably misstates itself. Models (needs sign-in, as does every group below unless marked public; OAuth opens the first time): sim_delete_model, sim_license_model, sim_supersede_model, sim_migration_offers, sim_accept_migration, sim_compose, sim_calibrate, sim_refine, sim_extend, sim_diff, sim_canonical. Structural edges: sim_prove (isomorphicTo, subnetOf, preservesInvariant or refines between two stored models: proved, recorded with a content-addressed witness — a map; for preservesInvariant (every P-invariant of the object pulls back along an induced embedding) the map and a cover of rational coefficients; for refines (every firing sequence of the subject, transitions the object lacks by id silent, is one of the object; untimed, guarded nets unknown) a finite simulation relation, which makes the claim unbounded; neither of those two is transitive, so sim_query never closes over them; refuted, a preservesInvariant or refines refutation carrying a counterexample (a trace, or an invariant per embedding image) that sim_check_witness(refutation=…) or `POST /api/refutation/check` re-checks for 1 token; or unknown when a search budget runs out, which records nothing and is not a no; with via — a sim_query derivation, or a stored result and row — it promotes a derived fact instead of searching: the derivation is re-checked from the stored witnesses and its composed map stored as one direct witness of the same edge, carrying promotedFrom; never an unknown or a derivation that fails re-check; 2 compute tokens), and sim_check_witness (public, no sign-in, free: re-check a witness, or every witness of a structural edge, against both stored models; no search; or, with derivation, re-check a sim_query derivation — every step's witness, the chain, the composed map's digest and the composed map against both end models — for 1 compute token; anonymous over HTTP as `POST /api/derivation/check` with {"derivation": …}, 1 token per client; or, with reach, re-check a sim_reach answer, its via re-checked whole, 1 token, `POST /api/reach/check`; or, with reachResult, re-check a stored sim_reach answer end to end — record, via and witness, an unknown staying unknown, with its currency — 1 token, `POST /api/reach/check` with {"result": …} or `GET /api/lineage/{id}`; or, with refutation, re-check a refuted preservesInvariant or refines answer's counterexample, 1 token, `POST /api/refutation/check`). Reachability (public, 2 compute tokens: a search plus a store; `POST /api/models/{id}/reach`): sim_reach(system, from, to) asks whether marking to (only the places it names, each exactly) is reachable from from (a sparse override on the declared marking) in ONE stored net, untimed: no with a P-invariant that separates them (or after an exhaustive search), yes with a firing trace anyone can replay (shortest among the firings the search makes), or unknown (unknownBecause lists each cause: the 100,000-marking and 20,000,000-unit budgets, a firing past 1,000,000 tokens on a place, a guard that fails to evaluate, a transition left out for a parameter or unparseable guard or a lost precondition, the invariant algebra's budget, truncation or coefficient size, or a trace too costly to re-check) — unknown is not a no; a sim_bind binding or collection answers unknown (not-one-net); a system id answers on its one net (with via) or unknown (not-one-net) when it holds bindings; on a one-net system a from/to key may name a member's place as : (the member's id, or its name when no other member shares it), translated into the net place its embedding maps it to and listed in via.translation, which sim_check_witness(reach=…) re-checks against the stored witnesses; a missing member or place, a data place, a key that reads two ways, a place two valid witnesses map differently, or two keys on one net place with different counts is refused, and more than 16 other witnesses to compare answers unknown (translation-budget). Every answer, unknown included, is stored as a content-addressed question (the id, from and to as written, definition, budgets) and result (the answer and what it rests on: the net, models, edges and witnesses); the reply names both ids, a repeated question is served from a per-instance cache with the same bytes, and an answer over 262,144 bytes is answered unstored (storedNote). Queries (sign-in, read scope; 2 compute tokens per call, cached or not; anonymous over HTTP as `POST /api/query`, 2 tokens per client): sim_query answers bounded paths (mode path, maxHops 1-8), closure over the transitive predicates (broader, narrower, supersededBy, migratedFrom, isomorphicTo, subnetOf) and conjunctive patterns of up to 4 triples joined on ?variables. A structural edge counts only with a witness that re-checks valid now; a derived structural fact carries a derivation and is never stored. complete means complete over eligible edges; truncated puts rows only in partialRows; an ask answers yes, no (its caveat says how far the search looked: only closure rules out a derived edge; not a refutation) or unknown (a bound stopped it, or a stored row could not be decoded). Pattern numbers in a result are 0-based positions in query.normalized.where; refusals number patterns as sent, from 1. Each answer is a content-addressed Query and Result (GET /api/lineage/{id}; a Result's currency is computed on read, 1 token, and is not current once the graph, the registry or the evaluator moved), cached only while the graph it read is unchanged. Runs and fitting: sim_receipt, sim_conformance, sim_conformance_stream, sim_optimize, sim_param_heatmap, sim_distill. Publishing: sim_publish, sim_publish_compare, sim_publish_app, sim_my_sheets. Pipelines: sim_bind (checked by binds-port-v1, the rule sim_run_pipeline applies: output a token place tagged port.output, input a rated transition tagged port.input, kinds equal, port.unit equal where both declare one — a unit missing on either end is unchecked, never agreed; a numeric transform at most 1000000; a self-binding is refused, with or without a delay; a refusal stores nothing; delay in hours, (0, 10000], checked by binds-port-v3 — the target receives the flow that many hours later and runs on its declared rate before then; differing units bind only with a declared conversion — convertFrom, convertTo, convertFactor, all three, 1 convertFrom = convertFactor convertTo, exactly the two ports' declared units and never inferred from their names, factor in [1e-6, 1000000], transform times factor at most 1000000, checked by binds-port-v2 and applied by sim_run_pipeline after the transform), sim_get_binding and GET /api/lineage/{id} (the check computed now; a binding recorded before the check is flagged legacy, and sim_run_pipeline refuses it by the same text), sim_list_bindings, sim_run_pipeline (1 token per model run, charged up front; a loop of at most 4 models runs only through a delayed binding, solved by sweeps — at most 24, and 48 model runs — to the exact fixed point over the horizon at the seed, pipeline-loop-v1, with its limits: mean-field, a declared delay, the seed history, nothing past the horizon; a loop whose delay cannot outrun the hours/60 resampling is refused, naming the delay or hours that run). Systems (ROADMAP.md Phase 11f): sim_create_system (sign-in, write scope, 2 tokens) stores a content-addressed System — a collection's model and binding members snapshotted now, or named model and binding ids; every binding must check now and any loop pass through a delayed binding (the view lists it with maxHours); at most 64 models and 64 bindings; the id hashes the lists, the name and the snapshotted collection, so a snapshot and an explicit list are two systems. sim_system (public, 1 token) takes {id} (the view, computed now: checks, isomorphicTo/subnetOf edges among the models with up to 128 witnesses re-checked, and the shape one-net / pipeline / separate / unknown; one-net counts a member embedded by a direct witnessed edge or a chain of one predicate, named as a derivation sim_check_witness(derivation=...) re-checks) or {model, port, direction?, among?} (what can bind to that port, by the same rule, over every model in the universe through a port index read once per model id: at most 256 not-yet-indexed models read per call (truncated, bound fetches, past it; ask again), 100000 candidate ports checked, 50 rows, ranked units-agree first; with among a system, bindable-v3: a binding that would take a bound port or close an undelayed loop is never offered, and one closing a loop that already holds a delayed binding is listed with closesLoop) or {naming} (the stored systems naming a model or binding, sorted by id: at most 100 rows and 256 unread systems fetched per call, status truncated or incomplete past either, rows then in partialSystems; a superseded model's sim_migration_offers lists them too). Systems are linkable and collectable: hasMember, cites, about, broader and related take a system (systems do not nest: a collection snapshot skips one). refines and preservesInvariant are proved pair by pair with sim_prove (not transitive, no closure); "what embeds/contains this pattern" is sim_query {"select":["?m"],"where":[{"subject":"

","predicate":"subnetOf","object":"?m","mode":"closure"}]} (isomorphicTo for copies; add a hasMember pattern to scope to a collection), each row with a re-checkable derivation. Loops between models run only through a delayed binding (Phase 12j): an undelayed loop is refused, and sim_reach never answers yes or no for a system with bindings. Relations and maps: sim_link (typed edge from a registered predicate vocabulary, https://sim.pflow.xyz/ns/predicates/v1 (registry version eedea398298e1e96bc9b5bab since Phase 12i): hasMember, @type, broader, related, closeMatch, cites, about; refuses unknown, tool-recorded (from/to/feeds/produced/supersededBy/migratedFrom) and structural predicates (sim_prove writes those), and ids that are not stored; each predicate's writeRule says who may write it: hasMember is subject-owner (only the collection's owner adds members; a collection created before owners existed is frozen), the rest any-signed-in, attributed to you; edges are never retracted), sim_edges (structural rows list their witnesses), sim_neighbors, sim_create_collection (owned by you: name plus account is the id), sim_map_put, sim_map_get, sim_map_list. sim_supersede_model also records a supersededBy edge (its result's `current` names the current successor; edge order does not). Supersession moves nothing by itself: sim_migration_offers(model) (read scope, 1 token; `GET /api/models/{id}/migrations`, anonymous) lists the old model's taxonomic edges as offers for its current successor, each with sim_link's verdict now, and the structural and tool-written edges that stay; sim_accept_migration (write scope, 1 token; the model's owner or the edge's author) writes the offered edge on the successor plus a migratedFrom edge back to the old one; a sim_query answer or Result currency naming a superseded model lists it under superseded (current is unchanged). sim_bind refuses a model that is not stored, and any binding binds-port-v1 refuses. LLM derivation (needs the deployment's LLM provider): sim_prompt, sim_reroll, sim_code_to_flow. A model the LLM writes is held to sim_create_model's decode and, stored, carries its warnings. A sim_prompt or sim_reroll reply that is not stored as a model comes back inline as content (cut at 16384 bytes, truncated: true), with modelErrors listing every reason when it was model JSON that failed; sim_code_to_flow returns stored: false with the model JSON and an error or errors instead. sim_get_artifact (public, free; GET /api/artifacts/{id}) reads the stored prompt or artifact whole, with its lineage, for anyone with its id or the id of a result derived from it; a prompt with no requestTemplate (stored before sim_prompt said prompts are readable) comes back with its text and system withheld, and an artifact it produced with its content withheld. Only their author's sim_prompt or sim_reroll gets them as context; anyone else's gets the withheld document. Resources: `sim://docs/authoring` — how to write a model (a worked example, the fields a simulation reads, rates per hour and the rate law); `sim://docs/classification` — how to read sim_diagnose, sim_classify and sim_refine output. MCP defaults for unset fields: hours 8, realizations 16, seed 20260809 (HTTP scenario/compare/receipt: 1 hour, 1 realization, seed 0, compare seed 1; HTTP publish fills the MCP defaults. See llms-full.txt, "Defaults per surface") ## Consoles - [Landing + catalog](https://sim.pflow.xyz/): every model, with Open console / Open app / Dataset / Publish to Sheets - [What-if console](https://sim.pflow.xyz/vet-clinic/): generic console for any stored model id (`/whatif/?id={id}`) — knobs, disruptions, seeded comparison; `/vet-clinic/` redirects to it for the reference model - [Build a model](https://sim.pflow.xyz/build/): guided builder (sign-in) - Published apps: `https://sim.pflow.xyz/app/{id}` — single-file HTML an owner published from the model's `view` prompt, served in a sandboxed origin ## Showcases (CC0, in the commons) - [feature-lab](https://sim.pflow.xyz/whatif/?id=47db64e54d24ca0600360c8c): cost-benefit for software decisions — a backlog gated by cost-benefit evaluation, capacity-constrained engineering, a telemetry validate/kill measurement window, and an evolutionary feedback loop (validated features seed follow-on ideas). diagnose found more engineering capacity *hurting* the objective past a point — the same shape of finding this tool makes across any domain, not just software. - [delivery-fleet](https://sim.pflow.xyz/whatif/?id=e38fc49777b731cfbc2764b9): declared objective — how many vans, scored on parcels minus van-hours - [bakery-oven](https://sim.pflow.xyz/whatif/?id=830937feca631188b3c421ed): declared parameter — batch size as a structural knob - [car-wash](https://sim.pflow.xyz/whatif/?id=735dd8109ce265a6de2c37a3): stages — predictable wash cycle as phase-type (Erlang-k) service - [food-truck](https://sim.pflow.xyz/whatif/?id=5159060ebfc150ea61b8e7a6): schedule — the lunch rush as a model-carried day shape - Curated (content-addressed like everything else; `/api/catalog` has the current ids): [vet-clinic](https://sim.pflow.xyz/vet-clinic/), [cafe](https://sim.pflow.xyz/cafe/), [second-car](https://sim.pflow.xyz/second-car/) (one car or two vs ride-share over a week, in dollars; run with `hours: 168`), [predator-prey](https://sim.pflow.xyz/api/models/0b76c369b203dce8a78f6f0a) ## Related - [pflow.xyz](https://pflow.xyz): visual Petri net editor and browser ODE / simulation modules (`https://pflow.xyz/llms.txt`) - [pilot.pflow.xyz](https://pilot.pflow.xyz): analysis and code-generation MCP server (petri_ode, petri_rate_scan, petri_codegen, …) - [book.pflow.xyz](https://book.pflow.xyz): the book on modeling with Petri nets ## Optional - Run-receipt verification is public and needs no account: `POST /api/receipts/verify` replays the scenario and checks the signature - `/healthz` is reserved at the Cloud Run edge; probe `/api/health` (or `/api/catalog`) instead