Server definition
- Hash
- sha256:825123b6bf5caa66a59f74a03ea8d761619087e0264bd28713307e30792d64e9
- What it is
- What a remote MCP server returned when asked what it offers: 54 tools
The blob, as servednamed by its sha256
{
"instructions": null,
"tools": [
{
"description": "Carry offered taxonomic edges of a superseded model forward to its current successor. edges lists 1 to 25 edge ids that sim_migration_offers(model) offered; pass successor (the current it returned) so a different current successor — a re-point or a chain that grew since — is refused rather than followed. Every edge is checked before anything is written (it must touch model, be offered, pass sim_link's check, you must own model or have authored the edge, and the edge it becomes must pass its predicate's write rule for you — hasMember only for the collection's owner); one failure refuses the call and writes nothing. Each accepted edge is written on the successor attributed to you, with no timestamp, plus a migratedFrom edge from it to the old edge (provenance; sim_edges(<new edge>) shows it, and sim_query closure over migratedFrom traces it back across several supersessions). The old edge stays. Re-running converges on the same ids; an edge you already linked the same way is reused (existing: true, keeping its own timestamp). A call stopped after some edges were written says which were and which were not. Needs sign-in with write scope (opens OAuth the first time); MCP only. Costs 1 compute token per call.",
"inputSchema": {
"properties": {
"edges": {
"description": "1 to 25 edge ids sim_migration_offers(model) offered",
"items": {
"type": "string"
},
"type": "array"
},
"model": {
"description": "content id of the superseded model",
"type": "string"
},
"successor": {
"description": "the current successor sim_migration_offers returned; a different current successor now is refused",
"type": "string"
}
},
"required": [
"model",
"edges"
],
"type": "object"
},
"name": "sim_accept_migration",
"outputSchema": null
},
{
"description": "Record a checked connection between two stored models' declared ports, fromModel's fromPort (a place tagged port.output) feeding toModel's toPort (a transition tagged port.input), as a content-addressed Binding (ROADMAP.md Phase 9/11f). Checked now, by the same binds-port-v1 rule sim_run_pipeline applies: both ports exist with the right direction and element kind, the output is a token place, the input is not a delayed transition, the declared kinds are equal, port.unit agrees exactly where both ends declare one (a unit missing on either end is reported unchecked, never agreed), and a numeric transform is at most 1000000. Differing units bind only with a declared conversion, never inferred from the unit names (\"order\" and \"orders\" are two units): pass convertFrom, convertTo and convertFactor together (1 convertFrom = convertFactor convertTo), checked by binds-port-v2 — both ports declare port.unit, the two differ, convertFrom and convertTo are exactly those units, the factor is between 1e-06 and 1000000, and the transform times the factor is at most 1000000; sim_run_pipeline multiplies the resampled rate by the transform and then by the factor. A binding without a conversion is checked by binds-port-v1 exactly as before and keeps its id. A delay (hours, in (0, 10000]) is checked by binds-port-v3 (v2 plus check 12: a finite number in range; 0 is no delay and keeps the binding's id): the target receives the source's flow that many hours later and runs on its own declared rate before then, and a loop between models runs only through a binding that declares one. A self-binding is refused, with or without a delay: a model driving its own input is a loop inside one net, built with sim_extend. Set-level checks (a loop with no delayed binding, two bindings into one input, the schedule-segment cap, a loop over 4 models) belong to sim_run_pipeline and sim_create_system; a binding that closes a loop with bindings already stored is not refused here, since a loop exists only in a set someone runs or defines. A refusal stores nothing. It records binding→fromModel \"from\", binding→toModel \"to\" and fromModel→toModel \"feeds\"; storing the same binding twice returns the same id. A recorded binding passes every run's per-binding check; a run still applies the set-level checks to the set it is given and can stop on what only the run decides (a resampled rate that overflows at a very short horizon, the step budget, the deadline). Costs 1 compute token (it stores a Binding).",
"inputSchema": {
"properties": {
"convertFactor": {
"description": "how many convertTo one convertFrom is (1 convertFrom = convertFactor convertTo), between 1e-06 and 1000000; sim_run_pipeline multiplies the resampled rate by it, after any numeric transform",
"type": "number"
},
"convertFrom": {
"description": "a declared unit conversion's source unit: exactly fromPort's port.unit. Pass convertFrom, convertTo and convertFactor together or none of them",
"type": "string"
},
"convertTo": {
"description": "a declared unit conversion's target unit: exactly toPort's port.unit, which must differ from fromPort's",
"type": "string"
},
"delay": {
"description": "hours after the source's flow that toPort receives it, in (0, 10000]; default unset, no delay. Before the delay ends toPort runs on its own declared rate. A loop between models runs only through a binding with a delay (binds-port-v3)",
"type": "number"
},
"fromModel": {
"description": "id of the model supplying the connection's output",
"type": "string"
},
"fromPort": {
"description": "element id of the declared output port on fromModel: a token place tagged port.output (see GET /api/models/{id}/ports)",
"type": "string"
},
"toModel": {
"description": "id of the model receiving the connection",
"type": "string"
},
"toPort": {
"description": "element id of the declared input port on toModel: a rated transition tagged port.input",
"type": "string"
},
"transform": {
"description": "how fromPort's value becomes toPort's. A plain non-negative number is applied by sim_run_pipeline as a scale factor on the resampled rate; it scales, never converts a unit. Any other text (a unit note, a resample window, an aggregation) is recorded, reported by sim_run_pipeline as an assumption, and treated as 1:1",
"type": "string"
}
},
"required": [
"fromModel",
"fromPort",
"toModel",
"toPort"
],
"type": "object"
},
"name": "sim_bind",
"outputSchema": null
},
{
"description": "Calibrate a model against YOUR event log — the reading that meets reality. Upload CSV (case_id, activity, timestamp; the shape sim_dataset emits, activities = transition ids), and rates are learned from the observed timings: sources from inter-arrival times, services from the gap before their completions, all per hour. Instant-pickup transitions (declared rate >= 100) keep their declared rate — their observed gap is the queue wait, and learning it would destroy the calibration discipline. A transition declaring a delay (a fixed-duration timer, not a rate) is fit differently and returned in learnedDelays instead of learnedRates: the MEDIAN observed gap, in hours, written onto the transition itself since a delay has no solver-map slot — a gapCV in rateEvidence far from 0 means the log looks exponential, not fixed, and the calibration says so in a caveat rather than trusting the median anyway. Returns a NEW content-addressed model (learned rates in the solver map, learned delays on the transitions, declared values otherwise untouched, lineage recorded) plus a conformance report: fittingPercent (full replays) is the headline, worst traces named with the activities that could not fire. tokenFitness is a second, harsher reading of the same replay (raw tokens present vs. required at every step, not full-trace success) that under-reads any net with a resource pool — read fittingPercent, not tokenFitness, unless you specifically want the raw-token number. Every learned rate or delay has an entry in rateEvidence: n (gaps it rests on), gapCV (sample std dev over mean of those gaps; ~1 for exponential timings, near 0 for a true timer) and insufficient when n < 2 — n=0 yields nothing, n=1 a value with no spread. Learned values on a structure that cannot replay the traces would be numerology — read fittingPercent before trusting them. Costs 3 tokens from your account's compute bucket (a replay of the log, 2, and the stored model, 1).",
"inputSchema": {
"properties": {
"id": {
"description": "model id to calibrate",
"type": "string"
},
"log": {
"description": "the event log, as CSV text",
"type": "string"
}
},
"required": [
"id",
"log"
],
"type": "object"
},
"name": "sim_calibrate",
"outputSchema": null
},
{
"description": "Tell whether two differently-labelled models are actually the same net: an isomorphism-invariant id computed from the model's EXACT automorphism orbits (orbits.go), not the colour-refinement (WL) kind sim_classify falls back to when the exact search can't decide. Two models differing only by renaming places or transitions share the same canonicalId even though their content-addressed ids (from sim_get_model) differ — this is the id to compare, not the model id, when checking whether a catalog already holds this net. Also returns the non-trivial automorphism orbits and generator count the id was computed from: zero generators means the net is rigid (no symmetry at all), which is itself a fact about the net's structure. Refuses (as a tool error) when the exact search would spend more than its 1,000,000-step budget (refinement steps, about half a second) — too large or too symmetric for this implementation, per orbits.go — rather than silently falling back to a weaker answer; sim_classify's own fallback covers that case for classification specifically. Costs 1 compute token (one search), as GET /api/models/{id}/canonical does. Equal canonical ids mean the same net under the comparison sim_prove uses for isomorphicTo; to get the place/transition bijection and record it as a witnessed edge, call sim_prove(subject, \"isomorphicTo\", object).",
"inputSchema": {
"properties": {
"id": {
"description": "model id",
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
"name": "sim_canonical",
"outputSchema": null
},
{
"description": "Re-check a stored structural witness against both stored models with the independent checker (pkg/morphism), which does no search. Given a structural edge's id, checks every witness of that edge; an edge with none answers unwitnessed (unproven, not disproved). Verdicts: valid, invalid (the map is not a morphism of the stored models), or uncheckable (a model was deleted, a net is malformed, or the predicate is now checked under a newer definition). Public: no sign-in. A witness or edge id is free (no compute tokens). With derivation (from sim_query): re-checks every step's witness, the chain, the composed map's digest and the composed map against both end models, and answers {verdict, reason, hops} (the body POST /api/derivation/check returns); a derivation naming a definition other than the predicate's current one is uncheckable; costs 1 compute token. With reach (a sim_reach answer): replays a trace through metamodel.Enabled/Fire and the guards, or re-checks an invariant witness against the stored model, answering {verdict, reason}; from and to are held to the scenario caps (1,000,000 per place, 4,000,000 in total), and a replay is priced before its first firing and is uncheckable over 200,000,000 work units; an exhaustive no is uncheckable (re-run sim_reach); an unknown answer is refused; costs 1 compute token (POST /api/reach/check). With reach on a system's answer, via is re-checked whole with it: the system is stored and holds the net, every other model has exactly one embedding, each embedding's witness or derivation re-checks valid now, and any translation of component places follows them. With reachResult (the result id sim_reach returns): re-checks a stored answer end to end — the record (its bytes hash to its id, its stored question asks what the answer answers under the budgets it records, and over names what it rests on), via as above, and the witness — answering {verdict, reason, parts, answer, currency}; an unknown answer stays unknown (its witness part is uncheckable, never a no); currency says whether this server would compute the same result (definition, budgets, registry, view, every model still stored) and lists superseded models without flipping current; costs 1 compute token (POST /api/reach/check with {\"result\": …}, or GET /api/lineage/<result>). With refutation (a refuted sim_prove answer's refutation, ROADMAP.md Phase 12i): re-checks its counterexample against both stored models with pkg/morphism — a refines trace replayed through the shared firing rule on both nets, or for preservesInvariant each embedding re-checked as an induced embedding, its invariant re-checked as a P-invariant of the object (y·C = 0 for every transition) and its pullback shown to break at the subject transition named (that the list holds every embedding is the search's claim and is not re-checked) — answering {verdict, reason}; costs 1 compute token (POST /api/refutation/check). A preservesInvariant or refines witness id re-checks its cover or simulation relation the same way (free, bounded: at most 4,096 cover terms, or 4,096 pairs and 20,000,000 firing-rule units). Give exactly one of id, derivation, reach, reachResult or refutation. Writes nothing.",
"inputSchema": {
"properties": {
"derivation": {
"description": "a sim_query derivation as a JSON string: {\"predicate\", \"definition\", \"subject\", \"object\", \"steps\": [{\"edge\", \"witness\", \"inverse\"}], \"composed\"}",
"type": "string"
},
"id": {
"description": "a witness id, or a structural edge's relation id",
"type": "string"
},
"reach": {
"description": "a sim_reach answer as a JSON string: the object exactly as returned, serialized",
"type": "string"
},
"reachResult": {
"description": "the content id of a stored sim_reach answer (sim_reach's result)",
"type": "string"
},
"refutation": {
"description": "a refuted sim_prove answer's refutation as a JSON string: the object exactly as returned, serialized ({\"predicate\", \"subject\", \"object\", \"counterexample\"})",
"type": "string"
}
},
"type": "object"
},
"name": "sim_check_witness",
"outputSchema": null
},
{
"description": "Discover the parameter classes of a stored model and return them as JSON-LD with empty annotation slots for you to fill in (label, comment, unit, domain, substitutes — nothing else; membership/kind/evidence are derived and settled by measurement, not yours to edit). With verify=true a shared colour is checked by exact automorphism proof where the search can decide it (settling interchangeability outright, the stronger claim), falling back to the sampled permutation experiment only where it can't — the exact search refuses past its 1,000,000 refinement-step budget on nets too large or too symmetric for it. Read the sim://docs/classification resource once for the colour-refinement caveat and the annotation contract in full.",
"inputSchema": {
"properties": {
"id": {
"description": "model id",
"type": "string"
},
"inline_context": {
"description": "embed the full JSON-LD @context map in the result instead of the URL it is served from (https://sim.pflow.xyz/ns/v1/context). Default false: the URL resolves to the identical map, so only set this for an offline consumer that cannot fetch it.",
"type": "boolean"
},
"verify": {
"description": "run the permutation experiment (costs simulation; default false, and classes then say they are candidates)",
"type": "boolean"
}
},
"required": [
"id"
],
"type": "object"
},
"name": "sim_classify",
"outputSchema": null
},
{
"description": "Derive a Petri-net model from source code with the configured LLM (control flow, state machine, resources or concurrency focus), validate it, and store it as a NEW model you own. Runs generator.CodeToFlow directly — this tool is its only surface on sim.pflow.xyz, which mounts no code-to-flow HTTP endpoint; refused when this deployment has no LLM provider configured. Returns the new id when the answer validates, otherwise the raw model JSON and the validation errors so you can fix and sim_create_model it by hand.",
"inputSchema": {
"properties": {
"code": {
"description": "source code to analyse",
"type": "string"
},
"focus": {
"description": "control-flow (default), state-machine, resources or concurrency",
"type": "string"
},
"language": {
"description": "source language hint, e.g. go, python, javascript",
"type": "string"
},
"name": {
"description": "name for the derived model",
"type": "string"
}
},
"required": [
"code"
],
"type": "object"
},
"name": "sim_code_to_flow",
"outputSchema": null
},
{
"description": "Run several scenarios against one model on one shared seed and return them side by side — the seed sharing is server-enforced, so differences are the scenarios, not the dice. Returns a summary by default (finals, throughput/mean/P95 metrics, contention, depletion — no time series); pass full=true for the complete trajectories, which run to hundreds of KB. A scenario carrying \"summary\": true stays summarized even under full=true, so one comparison can chart some scenarios and only read the rest. Unset hours default to 8, samples to 60 (the trajectory grid, which only matters under full=true — metrics are time-weighted and do not depend on it) and realizations to 16 per scenario. Each scenario can set its own \"engine\" (see sim_scenario / docs/engine-selection.md); comparing an \"ode\" run against an \"ssa\" one is legitimate but the shared seed only removes dice from scenarios using the same engine. Costs 1 token per scenario from the compute bucket (per address, or per account when signed in).",
"inputSchema": {
"properties": {
"full": {
"description": "include the sample-grid time series in every result (large; default false); a scenario with its own \"summary\": true is left summarized regardless",
"type": "boolean"
},
"id": {
"description": "model id",
"type": "string"
},
"scenarios": {
"description": "JSON array of at most 20 scenarios, each with a name, e.g. [{\"name\":\"today\",\"hours\":8},{\"name\":\"one more\",\"hours\":8,\"marking\":{\"staff/available\":3}},{\"name\":\"bigger batches\",\"hours\":8,\"params\":{\"batch_size\":6}}]; each is held to sim_scenario's caps",
"type": "string"
}
},
"required": [
"id",
"scenarios"
],
"type": "object"
},
"name": "sim_compare",
"outputSchema": null
},
{
"description": "List the component registry: pre-baked subnet templates (arrivals, service, hazard, inventory, decision, mailbox, datastore) with the calibration discipline baked into the arcs and rates. Each entry names its ports (places you can attach onto existing places), its params with recommended defaults, and the discipline notes explaining WHY the template is shaped the way it is. Compose them with sim_compose.",
"inputSchema": {
"type": "object"
},
"name": "sim_components",
"outputSchema": null
},
{
"description": "Instantiate a registry component into a model and store the result as a NEW content-addressed model you own (lineage recorded when composing onto an existing id). Omit id to start a model from the component alone; pass attach to fuse a component port onto one of the model's existing places (e.g. attach {\"queue\": \"tickets_queue\"} wires a hazard onto the service's queue). Prefix namespaces the created elements (defaults to the component name). Three calls build a working helpdesk: arrivals, then service attached to its queue, then hazard attached to the same queue — the result passes diagnose because the discipline is in the template.",
"inputSchema": {
"properties": {
"attach": {
"description": "JSON object mapping port name -> existing place id",
"type": "string"
},
"component": {
"description": "registry component name (see sim_components)",
"type": "string"
},
"id": {
"description": "model to compose onto; omit to start fresh",
"type": "string"
},
"name": {
"description": "model name for the stored result (kept from the base when composing onto an id)",
"type": "string"
},
"params": {
"description": "JSON object overriding param defaults, e.g. {\"staff\": 3}",
"type": "string"
},
"prefix": {
"description": "instance prefix for created elements (default: component name)",
"type": "string"
}
},
"required": [
"component"
],
"type": "object"
},
"name": "sim_compose",
"outputSchema": null
},
{
"description": "Check how well a stored model matches an observed event log WITHOUT rewriting its rates — the read sim_calibrate bundles into calibration, offered on its own and in full: fitness (can the model replay each case?), precision (does it allow behaviour never observed?), generalization and simplicity, with per-trace diagnostics naming the activities that could not fire. Log is CSV (case_id, activity, timestamp; the shape sim_dataset emits, activities = transition ids). The log is replayed one case at a time from the model's initial marking, so the model should be the per-case workflow; a resource net whose places are shared across cases will not fit. Caveats name what the analysable net encoded lossily. Costs 2 tokens from your account's compute bucket.",
"inputSchema": {
"properties": {
"id": {
"description": "model id",
"type": "string"
},
"log": {
"description": "the event log, as CSV text",
"type": "string"
}
},
"required": [
"id",
"log"
],
"type": "object"
},
"name": "sim_conformance",
"outputSchema": null
},
{
"description": "Mint a named Collection you own and return its content id. A collection carries no member list of its own — a mutable list would change the collection's own id every time something joined it, the same reason Lineage lives beside a model rather than inside it. Add members with sim_link(collectionID, \"hasMember\", memberID) — members must be stored entities — and read them back with sim_neighbors(collectionID, \"hasMember\") or sim_edges(collectionID). Only you, the owner, may add members: hasMember's write rule is subject-owner (https://sim.pflow.xyz/ns/predicates/v1), so sim_link refuses anyone else, naming the rule. The name and your account together are the content id: creating a collection with a name you already used returns your existing id, and another account's collection with the same name is a different collection. A collection created before collections had owners has none, so its membership is frozen as it stands (still readable); create your own to add to. Members cannot be removed: an edge, once written, is immutable. Needs sign-in with write scope (opens OAuth the first time); costs 1 compute token.",
"inputSchema": {
"properties": {
"name": {
"description": "collection name",
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"name": "sim_create_collection",
"outputSchema": null
},
{
"description": "Store a Petri-net model (JSON with name/places/transitions/arcs) and return its content id. Models are immutable; a changed model is a new id. Structural validation rejects malformed nets with every reason at once.\n\nThe model is yours: it appears only in your own listing until you dedicate it to the commons with sim_license_model, and you can remove it with sim_delete_model. Anyone you give the id to can use it either way.\n\nNeeds sign-in (opens OAuth the first time); anonymous callers can POST /api/models over HTTP instead, stored unowned.",
"inputSchema": {
"properties": {
"model": {
"description": "the model JSON",
"type": "string"
}
},
"required": [
"model"
],
"type": "object"
},
"name": "sim_create_model",
"outputSchema": null
},
{
"description": "Define a System (ROADMAP.md Phase 11f): a content-addressed, stored set of models and the bindings between them, so the catalog can answer questions about them together. Pass collection (snapshot its model and binding members now; other members are listed under skipped, and a grown collection is a new system, the old id still readable) or models and bindings (JSON arrays of ids), not both. Every binding's endpoints join the models (listed under implied); every binding must pass the check sim_bind applies now (binds-port-v1, or binds-port-v2 for one that declares a unit conversion, or binds-port-v3 for one that declares a delay, and the system then records bindingCheck binds-port-v2 or binds-port-v3, v3 first; a legacy binding sim_bind would refuse today is refused here) and the set must close no loop without a delayed binding, take each input port once, fit the schedule-segment cap and hold no loop over the model limit — the checks sim_run_pipeline applies before it knows the hours. At most 64 models and 64 bindings, and a snapshotted collection of at most 256 members. The id is the hash of the sorted lists, the name and the snapshotted collection (if any), so the same call gives the same id; a snapshot and an explicit list of the same ids are different systems. Returns the id and the view sim_system computes (checks, the structural edges among the models with up to 128 witnesses re-checked now, and the shape: one-net, pipeline, separate or unknown). This is not refines: a system's edges are isomorphicTo and subnetOf (embeds by wiring / is the same coloured net). refines (trace inclusion with transitions matched by id) and preservesInvariant (the object's P-invariants pull back along an embedding) are proved one pair at a time by sim_prove and are never walked by closure, since neither is declared transitive. For \"what contains this pattern?\" ask sim_query with closure: {\"select\":[\"?m\"],\"where\":[{\"subject\":\"<pattern id>\",\"predicate\":\"subnetOf\",\"object\":\"?m\",\"mode\":\"closure\"}]} (isomorphicTo for copies; add {\"subject\":\"<collection>\",\"predicate\":\"hasMember\",\"object\":\"?m\"} to scope it), each row carrying a derivation sim_check_witness re-checks; a subnetOf row claims no behaviour inclusion. For \"what does this model refine?\" ask sim_query for one hop: {\"select\":[\"?m\"],\"where\":[{\"subject\":\"<model id>\",\"predicate\":\"refines\",\"object\":\"?m\"}]}, each row an edge sim_prove recorded. A loop between models runs only through a binding that declares a delay (sim_bind delay; ROADMAP.md Phase 12j): a system may hold one, of at most 4 models, and its view lists it under pipeline.loops with pipeline.maxHours, the longest horizon sim_run_pipeline runs it at (0 when no horizon of 0.01 h or more does; pipeline.runnable covers only the checks that do not depend on the hours), where it is solved as an exact fixed point over the horizon (pipeline-loop-v1). A loop with no delayed binding is refused when a system is defined and by sim_run_pipeline, and sim_bind refuses a self-binding. A port search with among a system (bindable-v3) lists a candidate that closes a loop already holding a delayed binding, with that loop's models under closesLoop, and counts one that would close a loop of undelayed bindings under excluded as closes-loop (sim_bind with a delay can close it); a catalog or collection search does not consult stored bindings, so sim_create_system or sim_run_pipeline is where such a loop is refused. Needs sign-in (opens OAuth the first time); costs 2 compute tokens; writes one System.",
"inputSchema": {
"properties": {
"bindings": {
"description": "JSON array of binding ids sim_bind returned; their models are included automatically",
"type": "string"
},
"collection": {
"description": "a collection id whose model and binding members to snapshot (instead of models/bindings); at most 256 members; part of the id",
"type": "string"
},
"models": {
"description": "JSON array of model ids, e.g. [\"id1\",\"id2\"]",
"type": "string"
},
"name": {
"description": "a name for the system (at most 256 bytes); part of its id",
"type": "string"
}
},
"type": "object"
},
"name": "sim_create_system",
"outputSchema": null
},
{
"description": "Run every applicable READING of a model against the others and report agreement or divergence with the reason: discrete SSA means vs the continuous mean-field solve, algebraically derived conservation laws vs simulated means, and (for game-schema models) the closed-form incidence ranking vs rollouts vs exact search. Divergence is a finding, not an error — small-count mean-field gaps and the prior's threat-blindness are named as such. Trust is agreement between independent readings of one structure. Gated nets (read arc, inhibitor, reached capacity, guard) have no ODE reading to compare against at all — see docs/engine-selection.md for the four-rule decision behind which readings even apply.",
"inputSchema": {
"properties": {
"hours": {
"description": "horizon (default 8, max 168)",
"type": "number"
},
"id": {
"description": "model id",
"type": "string"
},
"realizations": {
"description": "SSA runs averaged, max 200 (default 24)",
"type": "number"
}
},
"required": [
"id"
],
"type": "object"
},
"name": "sim_crosscheck",
"outputSchema": null
},
{
"description": "Generate a synthetic event log from a stored model (seeded SSA playout; case-per-arrival). Returns CSV. Deterministic: same id, same seed, same bytes.",
"inputSchema": {
"properties": {
"cases": {
"description": "cases to generate (default 200, max 2000 over MCP)",
"type": "number"
},
"id": {
"description": "model id",
"type": "string"
},
"seed": {
"description": "PRNG seed (default 1)",
"type": "number"
}
},
"required": [
"id"
],
"type": "object"
},
"name": "sim_dataset",
"outputSchema": null
},
{
"description": "Delete a model you created. Refused for the curated catalog, for models you do not own, and for models already dedicated to the commons (a dedication is irrevocable).",
"inputSchema": {
"properties": {
"id": {
"description": "model id to delete",
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
"name": "sim_delete_model",
"outputSchema": null
},
{
"description": "Returns what one more of each resource (and a change in each rate) is worth to the outcome, each ranked against a measured noise floor — no fitness test to write. Also reports generic gates (mass balance, dormant sources, staffing knee, whether any knob binds), the parameter classes among the controls, and four structural readings needing no run (T-invariants, siphons/traps with deadlock witnesses, CTMC lumpability, constrained lumping). Pure read. Loss/success inference and objective framing can be corrected by tagging places or declaring simulation.objective — read the sim://docs/classification resource once for how to read influence and noise, the structural readings, and the corrections.",
"inputSchema": {
"properties": {
"hours": {
"description": "horizon per run (default 8, max 168)",
"type": "number"
},
"id": {
"description": "model id",
"type": "string"
},
"inline_context": {
"description": "embed the full JSON-LD @context map in the result instead of the URL it is served from (https://sim.pflow.xyz/ns/v1/context). Default false: the URL resolves to the identical map, so only set this for an offline consumer that cannot fetch it.",
"type": "boolean"
},
"maxRealizations": {
"description": "bounds how far the adaptive default may escalate (default 200, the same ceiling an explicit realizations refuses above). Ignored once realizations is set. For a caller with its own latency budget, not for narrowing a report.",
"type": "number"
},
"realizations": {
"description": "runs averaged per measurement, max 200. Leave unset and the default ADAPTS: a 24-realization pilot that doubles while the baseline outcome sits inside its own noise floor, up to 200 (or maxRealizations, if set); the report's realizations field and sample-size finding record where it settled and why. Set it and that exact count is used, never more. If the report still says underpowered after adapting, raise hours or set a count explicitly.",
"type": "number"
},
"seed": {
"description": "seed shared by every run, so differences measure the knob and not the dice (default 7)",
"type": "number"
}
},
"required": [
"id"
],
"type": "object"
},
"name": "sim_diagnose",
"outputSchema": null
},
{
"description": "Structural difference between two stored models: places, transitions and arcs added or removed, and surviving elements whose numbers changed (initial, capacity, rate, stages, arc weight or kind). The readout for what a builder turn, a sim_extend or a sim_refine actually changed between two ids in a lineage.",
"inputSchema": {
"properties": {
"a": {
"description": "model id (before)",
"type": "string"
},
"b": {
"description": "model id (after)",
"type": "string"
}
},
"required": [
"a",
"b"
],
"type": "object"
},
"name": "sim_diff",
"outputSchema": null
},
{
"description": "Distill exact search into the play scorer: fit rate multipliers for named transition groups so play's rankings agree with exact minimax, on positions sampled by random self-play and labeled by search. This is TACTICAL calibration — the counterpart of sim_calibrate, which learns rates from an event log. The division of labor is deliberate (petri-pilot experiments/ode-minimax): structure carries the tactic, and no fitting of an unmodified net's rates can express what its final state cannot separate — declare the structural prior as transitions in the model (e.g. forced-reply copies of the plays, catalyzed by the opponent's pattern) and distill the magnitudes it introduced. Zero agreement improvement is a finding about the structure, not a failed fit. Read agreementBefore/agreementAfter, not the loss: the hinge loss can overstate failure while every argmax is right. At most 16 groups, each non-empty, and no transition in two groups; the estimated work (loss evaluations × candidate moves × realizations × horizon) must stay under a fixed cap, and an over-cap request is refused with the estimate. Costs 5 tokens from your account's compute bucket at the defaults, scaled by that work estimate (rounded up).",
"inputSchema": {
"properties": {
"groups": {
"description": "JSON object: group name -> transition ids sharing one fitted multiplier, e.g. {\"detectors\":[\"x_win_0\",\"o_win_0\"],\"draw\":[\"call_draw\"]}",
"type": "string"
},
"id": {
"description": "model id (needs simulation.objective, players with turnPlace)",
"type": "string"
},
"options": {
"description": "JSON: {\"games\":20,\"positions\":40,\"iters\":40,\"horizon\":3,\"realizations\":40,\"seed\":11,\"engine\":\"\"} (the defaults shown; 0 or unset takes the default). Caps: games at most 100, positions at most 100, iters at most 100, realizations at most 200, horizon at most 10000; over a cap, a negative value or an unknown key is refused",
"type": "string"
}
},
"required": [
"id",
"groups"
],
"type": "object"
},
"name": "sim_distill",
"outputSchema": null
},
{
"description": "List every relation touching an entity, as either subject or object. Answered from an index by subject and object; each call re-lists the store so edges written by other instances are seen. Legacy edges whose predicate predates the registry are included with axis \"unregistered\". Structural edges (isomorphicTo, subnetOf, preservesInvariant, refines) list the witnesses that prove them; one listing none is marked unwitnessed and proves nothing. An isomorphicTo edge is the same net up to renaming, not the same behaviour: its definition (coloured-net-v1) does not compare guards, stages, schedules, constraints, objectives or non-refine tags such as outcome — read the witness (GET /api/lineage/{witnessId}, whose notCompared lists what that pair carries; sim_check_witness re-checks it) before treating two models as behaving alike.",
"inputSchema": {
"properties": {
"id": {
"description": "entity id to look up",
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
"name": "sim_edges",
"outputSchema": null
},
{
"description": "Score a player's legal next moves with the PLAY method (the blog post's move picker): apply each candidate hypothetically and score the expected objective of the position it leads to, by seeded SSA rollouts (seed 11, shared across candidates so a difference measures the move, not the dice) — the best move scores highest. Needs the game schema (simulation.objective + simulation.players). The response names the engine. This tool always uses play on SSA: next-move elimination (rate-zero ablation, which reads an ungated net through the continuous ODE relaxation), the closed-form incidence reduction and exact search are the HTTP evaluate endpoint's method option (eliminate | incidence | search), not selectable here.",
"inputSchema": {
"properties": {
"horizon": {
"description": "model time to explore ahead (default 3, max 10000)",
"type": "number"
},
"id": {
"description": "model id",
"type": "string"
},
"marking": {
"description": "JSON object, sparse marking override (the position to evaluate from); default = the initial marking",
"type": "string"
},
"player": {
"description": "player name from simulation.players",
"type": "string"
},
"realizations": {
"description": "SSA rollouts per candidate move (default 40, max 200)",
"type": "number"
}
},
"required": [
"id",
"player"
],
"type": "object"
},
"name": "sim_evaluate",
"outputSchema": null
},
{
"description": "Apply structural edits to a stored model and store the result as a NEW model you own, with lineage back to the original — the same vocabulary the guided builder uses behind its interview, now callable directly. Operations (JSON array, each with \"op\"): add_place {id, initial}, add_transition {id, guard, event}, add_arc {from, to, weight, kinetic, type}, remove_place, remove_transition, remove_arc {from, to}, set_rate {id, rate}, set_initial {id, initial}, set_capacity {id, capacity}. The edited model is validated before it is stored; a set of operations that leaves the net malformed is refused with every reason, and nothing is written. Returns the new id, the operations applied, and the structural diff.",
"inputSchema": {
"properties": {
"id": {
"description": "model id to edit",
"type": "string"
},
"name": {
"description": "optional name for the edited model",
"type": "string"
},
"operations": {
"description": "JSON array of operations",
"type": "string"
}
},
"required": [
"id",
"operations"
],
"type": "object"
},
"name": "sim_extend",
"outputSchema": null
},
{
"description": "Fetch a stored Binding by id, with its check computed now (binds-port-v1; binds-port-v2 for a binding that declares a unit conversion, which is returned as convert; binds-port-v3 for one that declares a delay, returned as delay in hours): ok (with the unit verdict: agree, converted or unchecked), refused (a binding recorded before the check that sim_bind and sim_run_pipeline would refuse today, flagged legacy) or uncheckable (a model it names was deleted).",
"inputSchema": {
"properties": {
"id": {
"description": "binding id",
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
"name": "sim_get_binding",
"outputSchema": null
},
{
"description": "Fetch a stored model's full Petri-net JSON by id.",
"inputSchema": {
"properties": {
"id": {
"description": "model id (content hash)",
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
"name": "sim_get_model",
"outputSchema": null
},
{
"description": "Derive a model's full algebraic invariant structure: conservation laws (Farkas P-invariants — weighted place sums every run preserves, the arithmetic a trust panel should show), firing cycles (T-invariants, named per-cycle with a readable detail sentence, each tagged StructuralProof), and the siphon/trap report (every minimal siphon and trap found from the arc structure, plus deadlock witnesses — minimal siphons holding no tokens at this model's own initial marking, which proves every transition needing one permanently disabled). This is the same computation sim_diagnose's structural fields read from, not a lesser copy of it. Pure structure, no simulation; every claim holds for every trajectory from this initial marking.",
"inputSchema": {
"properties": {
"id": {
"description": "model id",
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
"name": "sim_invariants",
"outputSchema": null
},
{
"description": "Dedicate a model you created to the commons under CC0-1.0, CC-BY-4.0, CC-BY-SA-4.0. It then appears in every user's listing with the license shown, and the dedication is IRREVOCABLE — it cannot be changed or deleted afterwards, which is what makes it safe for others to build on. CC0-1.0 is the cleanest choice for a model: attribution terms are hard to honor for a net someone folds into a larger one.",
"inputSchema": {
"properties": {
"id": {
"description": "model id to dedicate",
"type": "string"
},
"license": {
"description": "one of CC0-1.0, CC-BY-4.0, CC-BY-SA-4.0",
"type": "string"
}
},
"required": [
"id",
"license"
],
"type": "object"
},
"name": "sim_license_model",
"outputSchema": null
},
{
"description": "Record a typed edge between two stored entities (models, prompts, artifacts, maps, collections, bindings, relations — not witnesses), using a registered predicate. sim_link writes these (subject → object): @type (model → a https://sim.pflow.xyz/ns/models/v1 type name); about (artifact/prompt/map/collection → model/collection/binding/system); broader (model/collection/system → model/collection/system); cites (any entity → any entity); closeMatch (model → model); hasMember (collection → any entity); related (model/collection/system → model/collection/system). from, to and feeds are recorded only by sim_bind, produced only by sim_prompt/sim_reroll, supersededBy only by sim_supersede_model, migratedFrom only by sim_accept_migration; narrower is broader read backwards and is never stored; structural predicates (isomorphicTo, subnetOf, preservesInvariant, refines) are recorded only by sim_prove, together with the witness that proves them. Each is refused here, naming the tool that records it (or the reversed call, for narrower). Subject and object must be stored entities of the kinds the predicate relates, passed by content id (for @type, the object is a /ns/models/v1 type name). Full definitions at https://sim.pflow.xyz/ns/predicates/v1. Distinct from the Lineage a model/prompt/artifact already carries, which is specifically derivation (parent -> prompt -> child). Who may write an edge is its predicate's write rule (writeRule in the registry): hasMember is subject-owner — only the collection's owner (who created it with sim_create_collection) adds members, and a collection created before collections had owners takes no new ones; every other predicate sim_link writes is any-signed-in, an assertion attributed to you. A refusal names the rule. Edges are immutable and are never retracted. Linking the same subject/predicate/object again as the same user — either direction for a symmetric predicate — is idempotent: it returns the existing relation's id (existing: true) rather than recording a duplicate edge. Needs sign-in (opens OAuth the first time); the edge is attributed to you; costs 1 compute token. Writes one Relation, or nothing when it already exists.",
"inputSchema": {
"properties": {
"object": {
"description": "content id of the stored entity the edge points to; for @type, a type name from https://sim.pflow.xyz/ns/models/v1",
"type": "string"
},
"predicate": {
"description": "one of @type, about, broader, cites, closeMatch, hasMember, related",
"type": "string"
},
"subject": {
"description": "content id of the stored entity the edge starts from",
"type": "string"
}
},
"required": [
"subject",
"predicate",
"object"
],
"type": "object"
},
"name": "sim_link",
"outputSchema": null
},
{
"description": "List the content id of every stored Binding.",
"inputSchema": {
"type": "object"
},
"name": "sim_list_bindings",
"outputSchema": null
},
{
"description": "List the models visible to you: the curated catalog, models dedicated to the commons (their entry carries the license), and your own (marked mine). Other users' undedicated models are not listed, but any model id works with every sim_* tool — an id someone shares with you is the model.",
"inputSchema": {
"type": "object"
},
"name": "sim_list_models",
"outputSchema": null
},
{
"description": "Fetch a stored Map's key->value data by id.",
"inputSchema": {
"properties": {
"id": {
"description": "map id",
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
"name": "sim_map_get",
"outputSchema": null
},
{
"description": "List the content id of every stored Map.",
"inputSchema": {
"type": "object"
},
"name": "sim_map_list",
"outputSchema": null
},
{
"description": "Store a key->value lookup table as its own content-addressed entity — a generated parameter sweep, a rate table, a component registry, anything shaped as key->value rather than free text (an artifact) or a Petri net (a model). Returns its content id; the same data, even with keys inserted in a different order, returns the same id.",
"inputSchema": {
"properties": {
"data": {
"description": "the table as a JSON object",
"type": "string"
}
},
"required": [
"data"
],
"type": "object"
},
"name": "sim_map_put",
"outputSchema": null
},
{
"description": "List what happens to the edges of a superseded model. Taxonomic edges (hasMember, @type, broader, related, closeMatch, cites, about) are OFFERED: each row gives the edge as it would read on the model's current successor (followed through its supersession chain), whether sim_link's checks pass for it now (passes, refused, or unchecked when the store could not answer), any earlier migrations of it, and the sim_accept_migration call that accepts it. Nothing moves automatically, the old edge stays, and this read writes nothing: offers are computed on read and never stored. Tool-written edges (from/to/feeds, produced, supersededBy), structural edges (isomorphicTo, subnetOf, preservesInvariant, refines) and legacy unregistered predicates STAY with the old model, each with its reason; a structural row names its witnesses and the sim_prove call that would make a new edge for the successor. Stored systems naming the model are listed under systems (sim_system {naming}'s answer, at most 100 rows): a System names exact ids, so supersession changes none of them, and systemsNote names the sim_create_system call with the successor. A chain that cycles, runs past 64 hops, ends at a deleted model or reaches a model now owned by someone else is reported as such, with no target. Accepting needs you to own the superseded model or to have authored the edge, and the edge it becomes must pass its predicate's write rule for you, as sim_link would (hasMember: you own the collection); youMayAccept says whether you may, per edge, and writeRule says why not when only the write rule refuses. 100 edges per page (pass next as after); predicate narrows to one stored predicate (narrower is refused: pass broader). Needs sign-in with read scope (opens OAuth the first time); anonymous over HTTP as GET /api/models/{id}/migrations, with no youMayAccept. Costs 1 compute token per call.",
"inputSchema": {
"properties": {
"after": {
"description": "the next value a previous call returned, for the following page",
"type": "string"
},
"model": {
"description": "content id of the superseded model",
"type": "string"
},
"predicate": {
"description": "read only this predicate's edges",
"type": "string"
}
},
"required": [
"model"
],
"type": "object"
},
"name": "sim_migration_offers",
"outputSchema": null
},
{
"description": "List the sheets this user has published, with their URLs.",
"inputSchema": {
"type": "object"
},
"name": "sim_my_sheets",
"outputSchema": null
},
{
"description": "One-hop traversal from an entity, read through the predicate registry. With no predicate: the object of every relation where the entity is the subject. With a predicate: the entities one hop along it — for an inverse name that is never stored (narrower) the stored predicate is read backwards (the subjects of the broader edges pointing at the entity), and for a symmetric one (related, closeMatch) both directions count, since an edge recorded from the other end says the same thing. A legacy free-form predicate still answers from its stored edges; a name that is neither registered nor used by any stored edge is refused. Pass a collection's id with predicate hasMember to list its members. A structural neighbour (isomorphicTo, subnetOf, preservesInvariant, refines) is a bare id here: sim_edges lists the edge's witnesses, and isomorphicTo does not compare guards, objectives or outcome tags. Ordered by timestamp, then relation id: tool-recorded edges (from/to/feeds, supersededBy, and edges carried forward by sim_accept_migration, unless it reused one you had already linked) carry no timestamp, come first and are in no particular order among themselves, so the last supersededBy neighbor is not necessarily the current successor. Not de-duplicated.",
"inputSchema": {
"properties": {
"id": {
"description": "subject id to traverse from",
"type": "string"
},
"predicate": {
"description": "restrict to this predicate; omit for every outgoing relation",
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
"name": "sim_neighbors",
"outputSchema": null
},
{
"description": "Multi-objective optimisation over transition rates for a stored model: Monte Carlo samples the rate ranges, runs each combination to the horizon with the continuous engine, and returns every sample with a Pareto flag — the non-dominated set is the trade-off frontier ('which staffing is non-dominated on served vs walked out'). Continuous reading: a model with a schedule or a gate is refused with the reason (use sim_compare with explicit scenarios for those). Costs 1 token per 50 samples (rounded up) from your account's compute bucket; rate ranges must be non-negative.",
"inputSchema": {
"properties": {
"hours": {
"description": "horizon per run (default 8, above 0 and at most 10000)",
"type": "number"
},
"id": {
"description": "model id",
"type": "string"
},
"objectives": {
"description": "JSON array of {\"place\": id, \"direction\": \"max\"|\"min\"}",
"type": "string"
},
"parameters": {
"description": "JSON object transition_id → [min, max] rate range, e.g. {\"finish_brew\": [10, 40]}",
"type": "string"
},
"samples": {
"description": "Monte Carlo samples (default 100, 1..1000; outside that is refused)",
"type": "number"
},
"seed": {
"description": "sampling seed, a whole number (default 42)",
"type": "number"
}
},
"required": [
"id",
"parameters",
"objectives"
],
"type": "object"
},
"name": "sim_optimize",
"outputSchema": null
},
{
"description": "Two-rate grid for a stored model: vary two transition rates over ranges, run each combination to the horizon with the continuous engine, and return the observable's final value as a grid — 'which regime of arrivals × restock keeps the queue empty'. Continuous reading: a model with a schedule or a gate is refused with the reason. Costs 1 token per 50 grid cells (rounded up; a 40x40 grid empties the bucket) from your account's compute bucket.",
"inputSchema": {
"properties": {
"hours": {
"description": "horizon per run (default 8, above 0 and at most 10000)",
"type": "number"
},
"id": {
"description": "model id",
"type": "string"
},
"log_scale": {
"description": "space the grid in log10 (default false)",
"type": "boolean"
},
"observable": {
"description": "place id whose final value fills the grid",
"type": "string"
},
"param_x": {
"description": "first transition id",
"type": "string"
},
"param_y": {
"description": "second transition id",
"type": "string"
},
"range_x": {
"description": "JSON [start, stop, n] for param_x: non-negative rates, n a whole number 2..40",
"type": "string"
},
"range_y": {
"description": "JSON [start, stop, n] for param_y: non-negative rates, n a whole number 2..40",
"type": "string"
}
},
"required": [
"id",
"param_x",
"param_y",
"observable",
"range_x",
"range_y"
],
"type": "object"
},
"name": "sim_param_heatmap",
"outputSchema": null
},
{
"description": "Ask an LLM to derive something from a stored entity: a variant model, a report, a piece of generated code — whatever the prompt asks for. The parent's JSON rides along as context, the same way the guided builder gives its interviewer the draft. The parent is looked up as a model first, then a prompt, then an artifact, then a map — whichever resolves — and the context block is labelled by what kind it found (\"## Parent model\", \"## Parent prompt\", ...), so the LLM is never told a report is a Petri net. The prompt is stored first and content-addressed like a model, so it has an id of its own before the LLM ever answers; both the prompt and whatever came back are placed in lineage under the parent (sim_prompt as the activity), so Ancestry walks parent -> prompt -> result. A Relation{prompt, \"produced\", result} is recorded alongside — sim_reroll's forward index, and queryable directly via sim_edges/sim_neighbors. If the response parses and validates as a Petri-net model it is stored as a NEW model you own; otherwise the raw text is stored as an artifact. Refused if this deployment has no LLM provider configured.",
"inputSchema": {
"properties": {
"parent": {
"description": "id to run the prompt against — a model, prompt, artifact, or map",
"type": "string"
},
"system": {
"description": "optional system-level instructions, in addition to the parent context this tool always supplies",
"type": "string"
},
"text": {
"description": "the natural-language instruction",
"type": "string"
}
},
"required": [
"parent",
"text"
],
"type": "object"
},
"name": "sim_prompt",
"outputSchema": null
},
{
"description": "Propose candidate @type values for one or more stored models, e.g. \"QueueingSystem\" or \"ResourcePool\", from a small Diagnose run this tool performs on each model (nothing is cached or reused between calls). Every rule is a hand-written assumption about what a shape of knobs/loss/siphons/classes tends to mean, not a structural proof or a measurement, so results are ASSUMPTION-grade until a human reviews one and applies it — apply with sim_link(id, \"@type\", \"<Type>\"), there is no separate apply tool. Pure read; nothing here is written to any model. Defaults to scanning the visible catalog (up to limit) when ids is omitted. Costs one Diagnose run per model — 5 tokens each from the compute bucket (per address, or per account when signed in), charged as each model is reached — so limit and realizations are both capped.",
"inputSchema": {
"properties": {
"ids": {
"description": "JSON array of model ids to consider, e.g. [\"id1\",\"id2\"]. Omit to scan every model ListFor(\"\") would list (the public catalog), truncated to limit.",
"type": "string"
},
"limit": {
"description": "maximum number of models to diagnose (default 10, 1..25; over 25 is refused, and so are explicit ids naming more models than limit) — a cost control, since this runs a simulation per model",
"type": "number"
},
"realizations": {
"description": "realizations per model's Diagnose run (default 8, 1..16; outside that is refused) — deliberately small, this only needs to name a shape, not measure precise influence",
"type": "number"
}
},
"type": "object"
},
"name": "sim_propose_types",
"outputSchema": null
},
{
"description": "Prove a structural relation between two stored models and record it with its witness. isomorphicTo: an exact canonical-labelling search (the one behind sim_canonical, seeded with exactly what the check compares) finds a place/transition bijection preserving arcs (direction, type, weight, kinetic), place sort, initial marking, capacity, rate or delay and refine.* tags. It does not compare guards, stages, schedules, constraints, non-refine tags (outcome, port.*), data-place types and initial values, objectives, players or asserted classes; those the pair carries are listed as notCompared, so proved is not 'behaves the same'. subnetOf: a bounded embedding search finds an injective, sort-preserving map (token places to token places, data places to data places, transitions to transitions) of the subject's places and transitions into the object's whose arcs among the image are exactly the subject's (initial markings, capacities, rates, delays and tags are not compared). preservesInvariant (invariant-pullback-v1): an induced embedding as subnetOf's along which every P-invariant of the object pulls back to a P-invariant of the subject (the incidence the shared firing rule implies: consuming and producing arcs on token places). Proved carries the map and a cover — per subject transition, rational coefficients of object transitions with the same incidence — which proves it for every invariant at once, plus invariantDimension (0 means vacuous: the object conserves nothing). Embeddings are tried one image at a time (embeddings onto one image differ by a symmetry of the subject, which keeps or breaks the pullback with them), at most 16 images. refines (id-simulation-v1): every firing sequence of the subject from its initial marking, transitions the object has no transition of (by id) dropped as silent, is one of the object from its own; untimed, guards and fixed delays refused (unknown: a delay is a timer whose in-flight markings an atomic step skips), rates not compared, at least one shared transition id required. Proved carries a simulation relation (pairs of markings closed under the subject's firings, at most 4,096; relationPairs counts them, GET /api/lineage/<witness> lists them), which makes the claim unbounded. Neither is declared transitive, so sim_query never closes over them. The answer is proved (with the map, in the caller's direction, or the relation), refuted (exhaustive, with the reason; a refuted refines, or a refuted preservesInvariant where the subject embeds at all, carries refutation, a counterexample sim_check_witness(refutation=…) re-checks: a trace for refines, an invariant per embedding for preservesInvariant, whose list of embeddings is the search's claim; a preservesInvariant refuted because no embedding exists carries none, as a refuted subnetOf carries none) or unknown (a search budget ran out; nothing is recorded, retrying gives the same answer, and unknown is not a no). Needs sign-in (opens OAuth the first time), including with record=false. Costs 3 compute tokens from your account's bucket, or 2 with record=false. Budgets: 1,000,000 refinement steps for both nets' canonical labelling together (isomorphicTo), 2,000,000 embedding steps (subnetOf), 4,000,000 units for elimination, embedding and tests together (preservesInvariant), 20,000,000 firing-rule units and 4,096 pairs (refines); the whole call is also bounded by the 55 s request limit. Writes, only when proved and record is true (the default): one witness, then one relation; isomorphicTo is stored with the smaller id as subject, and storedAs says so when that turned the call round. Otherwise it writes nothing. sim_check_witness re-checks either at any time. With via, sim_prove promotes instead of searching: it takes a structural fact sim_query derived (two to eight stored witnesses composed), re-derives it here from the stored witnesses — every step's witness re-checked against both stored models now, the chain, the composed map's digest, and the composed map checked against the two end models by pkg/morphism — and records the composed map as one direct witness through the same write gate as a search's, so the edge has the same id (one per subject, predicate and object) and is trusted exactly as far. The witness records the derivation as promotedFrom; re-promoting the same derivation writes nothing new (existing: true). A derivation that re-checks invalid or uncheckable is refused and nothing is written; an unknown is never promoted. Costs 2 compute tokens (a derivation re-check plus a store) instead of 3; record=false is refused with via.",
"inputSchema": {
"properties": {
"object": {
"description": "content id of the stored model the edge points to",
"type": "string"
},
"predicate": {
"description": "isomorphicTo, subnetOf, preservesInvariant or refines",
"type": "string"
},
"record": {
"description": "when proved, store the witness and then the edge; false answers without writing (default true)",
"type": "boolean"
},
"subject": {
"description": "content id of the stored model the edge starts from",
"type": "string"
},
"via": {
"description": "promote a fact sim_query derived instead of searching (JSON, as a string or an object): {\"derivation\": <a row's support[].derivation>} or {\"result\": \"<sim_query result id>\", \"row\": n, \"support\": k} (support only when the row carries more than one derivation; 0-based). The derivation must name exactly this subject, predicate and object (either order for isomorphicTo)",
"type": "string"
}
},
"required": [
"subject",
"predicate",
"object"
],
"type": "object"
},
"name": "sim_prove",
"outputSchema": null
},
{
"description": "Publish a stored model into the signed-in user's Google Sheets: the model workbook (live formulas when honest, a refusal tab when not), a server-run scenario as data tabs, and trajectory + contention charts. Returns the sheet URL. Counts against the daily quota, and costs 1 token from your account's compute bucket for the run.",
"inputSchema": {
"properties": {
"id": {
"description": "model id",
"type": "string"
},
"scenario": {
"description": "optional scenario JSON to run for the data tabs; sim_scenario's defaults (hours 8, realizations 16, seed 20260809) fill whatever is omitted",
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
"name": "sim_publish",
"outputSchema": null
},
{
"description": "Publish the generated application for a model you own — the single-file HTML a generator produced from the model's `view` prompt. Served at /app/<id> in a sandboxed opaque origin (no cookies, no session; only the CORS-open public API is reachable). START FROM THE RUNTIME, not from scratch: /lib/app-template.html is a working console that imports /lib/sim-console.js and composes <sim-controls>, <sim-disruptions>, <sim-net>, <sim-timeline>, <sim-trajectory> and <sim-results> — the same components the generic console at /whatif/ runs. Composing them is how an app inherits role derivation, the fungible-set collapse, the influence ranking that never filters, the contention ledger and the verbatim caveats, none of which the checks below can verify you reimplemented correctly. Root-relative /lib/ imports are allowed. Off-origin loading is stopped in two places: the upload checks refuse an off-origin <script src> or <link href>, and the app is served under a Content-Security-Policy that confines scripts, styles, fetches, images and fonts to this origin — which is what stops the forms the checks do not parse (an inline module's import \"https://…\", a dynamic import(), a CSS @import). Checks refuse an app that is empty, oversized, never references its model id, or carries an off-origin <script src>/<link href>; behavioral correctness (does the app actually do what the view says) is on the generator and any browser gate you run.",
"inputSchema": {
"properties": {
"html": {
"description": "the complete self-contained HTML document",
"type": "string"
},
"id": {
"description": "model id the app presents",
"type": "string"
}
},
"required": [
"id",
"html"
],
"type": "object"
},
"name": "sim_publish_app",
"outputSchema": null
},
{
"description": "Publish a multi-scenario comparison into the signed-in user's Google Sheets — sim_compare's export, the counterpart of sim_publish for a single scenario. Runs every scenario on one shared seed (the same server-enforced sharing sim_compare uses, so differences are the scenarios and not the dice) and writes a comparison table plus a trajectory chart, rather than one scenario's own data tabs. Returns the sheet URL. Counts against the same daily publish quota as sim_publish, and costs 1 token per scenario from your account's compute bucket.",
"inputSchema": {
"properties": {
"id": {
"description": "model id",
"type": "string"
},
"scenarios": {
"description": "JSON array of scenarios, each with a name, e.g. [{\"name\":\"today\",\"hours\":8},{\"name\":\"one more\",\"hours\":8,\"marking\":{\"staff/available\":3}}]; sim_compare's defaults (hours 8 and realizations 16 per scenario, seed 20260809) fill whatever is omitted",
"type": "string"
}
},
"required": [
"id",
"scenarios"
],
"type": "object"
},
"name": "sim_publish_compare",
"outputSchema": null
},
{
"description": "Answer a question over the edge graph between stored models: bounded paths, closure over transitive predicates, and conjunctive patterns of up to 4 triples joined on shared ?variables. The query argument is JSON: {\"select\": [\"?m\"], \"where\": [{\"subject\": \"?m\", \"predicate\": \"subnetOf\", \"object\": \"<content id>\", \"mode\": \"closure\"}, {\"subject\": \"?m\", \"predicate\": \"@type\", \"object\": \"QueueingSystem\"}]}; a term starting with ? is a variable, anything else a constant (a 24-hex content id, or for @type's object a model kind). Modes: edge (default; one stored edge, read through the registry: narrower is read as broader turned round, symmetric predicates match either way round), path (1..maxHops hops, maxHops 1 to 8 required; a row says reached by a path unless the predicate is transitive), closure (no maxHops; allowed only on the transitive predicates broader, isomorphicTo, migratedFrom, narrower, subnetOf, supersededBy). Put the bound term in object to walk backwards. Unregistered (legacy) predicates are one stored edge in their stored direction only, never walked or closed over. A structural edge (isomorphicTo, subnetOf, preservesInvariant, refines) counts only when one of its stored witnesses re-checks valid now; a derived structural fact carries a derivation (the chain of witnesses and the composed map's digest) that sim_check_witness(derivation=…) re-checks, and is never stored; excluded lists the edges not walked and why. Closure runs per predicate (isomorphicTo hops never feed a subnetOf closure). status complete means complete over the eligible edges (and, for path, within maxHops: hopLimitReached names patterns with edges beyond); truncated means a bound was reached, rows is absent and the rows found so far are in partialRows (true, but not all of them). A query with no variables answers yes, no or unknown (a bound stopped it, or a stored row could not be decoded); a no's caveat says how far the search looked (only closure rules out a derived edge, and a path cut at maxHops says so), and no is not a refutation. Pattern numbers in a result (support[].pattern, hopLimitReached, unmatched, truncated.pattern) are 0-based positions in query.normalized.where; refusals number your patterns from 1. Limits: 4 patterns, 8 hops, 200 rows, 10,000 intermediate rows, 1,000,000 work units, 64 models probed, a 256 KiB result, a 16 KiB query; the whole call is bounded by the 55s request limit. Over a limit before running is a refusal naming it; reached while running is truncated, and retrying gives the same answer. Needs sign-in with read scope (opens OAuth the first time). Costs 2 compute tokens per call, cache hits and refusals included. Writes one Query and one Result entity, content-addressed (resolve either at GET /api/lineage/{id}; stored is false, with storedNote saying why, if the write failed or a composed map was refused); a repeated question is served from the cache only while the edges and witnesses it read, and the models it checked, are unchanged. An answer naming models that have since been superseded carries superseded ([{model, successor, current, chainStatus}]); the answer is still right about those versions.",
"inputSchema": {
"properties": {
"query": {
"description": "the query as JSON: {\"select\": [variables], \"where\": [{\"subject\", \"predicate\", \"object\", \"mode\", \"maxHops\"}]}",
"type": "string"
}
},
"required": [
"query"
],
"type": "object"
},
"name": "sim_query",
"outputSchema": null
},
{
"description": "Returns whether the marking to is reachable from the marking from in ONE stored net (a model, or a system that is one net): yes with a firing trace, no with a re-checkable P-invariant (or after an exhaustive search), or unknown with every reason. A sim_compose result is one fused net, so it qualifies; a sim_bind binding or a collection links rates, not tokens, and answers unknown (not-one-net). A system id (sim_create_system) is answered on its one net when one member has every other embedded in it by a witnessed subnetOf or isomorphicTo edge or a chain of edges of one predicate (the answer carries via: an edge and witness per member, or the derivation sim_check_witness(derivation=…) re-checks); otherwise it answers unknown: not-one-net when it holds bindings (they link rates, not tokens) or no member qualifies, system-truncated when its witness bound was reached or the chain search stopped at a bound. On a one-net system, a key in from or to may name a member's place as <model>:<place> (the member's id, or its name when no other member shares it): it is translated into the net place that member's embedding maps it to, the answer's from and to are the translated question, and via.translation lists each key's member, place and net place, which sim_check_witness(reach=…) re-checks against the stored witnesses. Refused, naming the key: a member or place the system does not have, a data place, a key that reads more than one way, a place two valid witnesses of the member's edge map differently, and two keys landing on one net place with different counts; more than 16 other witnesses to compare answers unknown (translation-budget). from is a sparse override on the model's declared initial marking (unset = declared). to constrains only the places it names, each to exactly that count; naming every token place asks for an exact marking. Unknown or data places, negative or fractional counts and counts over the scenario caps (1,000,000 per place, 4,000,000 in total) are refused. Reachability here is untimed: each firing is one atomic step under the shared firing rule (read and inhibitor arcs, capacities, guards decidable from the marking); rates, schedules, delays and stages play no part. Answers: no with a P-invariant y whose weighted sum differs between from and to (when to leaves a place of y free, to alone must already exceed it), valid whatever read arcs, inhibitors, capacities or guards allow, since those only remove firings; no (exhaustive) when every marking reachable from from was searched; yes with a firing trace (shortest among the firings the search makes; reads says when it skipped some); unknown (unknownBecause lists each cause) when neither is found within 100,000 markings or 20,000,000 work units, when a firing would pass 1,000,000 tokens on a place, when a guard fails to evaluate at a reached marking, when a transition whose guard reads action parameters (or does not parse, or whose precondition was lost) was enabled and so left out, when the invariant algebra runs past its 20,000,000-operation budget, is cut short or needs coefficients over 2147483647, or when a found trace would cost the independent checker more than 200,000,000 work units to replay. Unknown is not a no. Every answer (unknown included) is stored: question is the content id of the question as asked (the id, from and to as written, the definition and the budgets), result the id of the answer with what it was computed over (the net, every model, edge and witness via rests on, the predicate registry and system view for a system); a repeated question is served from a per-instance cache with the same bytes, and storedNote says why an answer too large to store (over 262,144 bytes) was not. Public: no sign-in; costs 2 compute tokens (a search plus a store). Re-check a yes or an invariant no with sim_check_witness(reach=<this answer>), which also re-checks via whole; re-check a stored answer end to end with sim_check_witness(reachResult=<result>) or GET /api/lineage/<result>.",
"inputSchema": {
"properties": {
"from": {
"description": "JSON object of place -> whole token count overriding the declared initial marking, e.g. {\"queue\": 3}; unset = the declared marking. On a system, a key may be <model>:<place> for a member's place",
"type": "string"
},
"system": {
"description": "content id of one stored model, or of a system (sim_create_system)",
"type": "string"
},
"to": {
"description": "JSON object of the places to constrain, each to exactly that count, e.g. {\"done\": 1}. On a system, a key may be <model>:<place> for a member's place, translated into the net's",
"type": "string"
}
},
"required": [
"system",
"to"
],
"type": "object"
},
"name": "sim_reach",
"outputSchema": null
},
{
"description": "Run a seeded scenario and get back the result PLUS a signed run receipt: an Ed25519 certificate over (model id, scenario, result hash, service revision). Anyone can check it two ways — verify the signature offline against the embedded public key (proves this service reported this result), and POST it to /api/receipts/verify (no auth) to replay the run and confirm the result hash reproduces (proves the run is reproducible, not invented). The current signing key is at GET /api/receipts/key. Reproducibility is the bottom rung of the trust ladder receipts build: play the model, check the anchors, re-run the seed, verify the certificate. Costs 1 token from your account's compute bucket.",
"inputSchema": {
"properties": {
"id": {
"description": "model id to run",
"type": "string"
},
"scenario": {
"description": "scenario JSON (hours, samples, seed, marking, rates, schedule, summary — the same shape sim_scenario takes); sim_scenario's defaults (hours 8, realizations 16, seed 20260809) fill whatever is omitted, and the receipt's scenario records the filled-in values so its replay reproduces this run. The result hash covers the result as returned, so a summary: true scenario certifies the summarized form and replays to it",
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
"name": "sim_receipt",
"outputSchema": null
},
{
"description": "Refine a model's parameter classes by editing what the model SAYS (tags on a place or transition, or assertedClasses), then re-derive. Returns a NEW model id (ids are content addresses, so the original stays reachable) plus a before/after class diff. tags can only split classes; assertedClasses declares a merge and gets re-verified and costed, never trusted blind. Read the sim://docs/classification resource once for why the two levers are not symmetric. Costs 2 tokens from your account's compute bucket (the stored model, 1, and the verified re-classification, 1).",
"inputSchema": {
"properties": {
"assertedClasses": {
"description": "JSON array, e.g. [{\"id\":\"items\",\"members\":[\"item0\",\"item1\"],\"note\":\"one stocking decision\"}]",
"type": "string"
},
"id": {
"description": "model id to refine",
"type": "string"
},
"signature": {
"description": "optional hex signature over the CID of the signed claim; see modelstore.SignedClaim for the exact bytes. An unverifiable signature is refused, not stored with a flag.",
"type": "string"
},
"signer": {
"description": "optional {\"type\":\"eth\"|\"ed25519\",\"address\":\"...\"} — signs the lineage claim so it is the refiner's word rather than the server's account of a session",
"type": "string"
},
"tags": {
"description": "JSON object of place OR transition id -> {key: value}, e.g. {\"nurse_avail\":{\"refine.shift\":\"night\"}}. Keys not prefixed refine. are stored as metadata and refine nothing. classify.go's colour refinement seeds from both places' and transitions' tags, so either kind of id works here.",
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
"name": "sim_refine",
"outputSchema": null
},
{
"description": "Re-run a stored sim_prompt against the SAME parent it originally ran against — a sibling attempt, never a chain: it never derives from the previous attempt's output, only from the original parent, so rerolling ten times leaves ten independent siblings in lineage rather than a chain of ten. Reuses the original prompt's text and system unless you override them here. The original prompt and its result are left untouched; this stores a new prompt and a new result (model or artifact, same rule as sim_prompt) under Activity sim_reroll. When called with neither override and the deployment's LLM provider and model are unchanged since the original ran, the response carries a reproducibility field checked against every prior result this exact prompt has ever produced (via the same forward \"produced\" relation sim_edges/sim_neighbors can query directly): \"verified\" if this result content-matches one of them, \"diverged\" if it doesn't, \"not verified\" if there's no prior result on record yet.",
"inputSchema": {
"properties": {
"prompt": {
"description": "id of the sim_prompt (or earlier sim_reroll) to re-run",
"type": "string"
},
"system": {
"description": "override the original prompt's system text; default reuses it verbatim",
"type": "string"
},
"text": {
"description": "override the original prompt's text; default reuses it verbatim",
"type": "string"
}
},
"required": [
"prompt"
],
"type": "object"
},
"name": "sim_reroll",
"outputSchema": null
},
{
"description": "Returns one scenario result per model that a set of stored Bindings (see sim_bind) touches, run as a composed pipeline, with the assumptions the composition makes, inTransit (per delayed binding, the source's flow its delay carried past the horizon) and, when the set holds a loop, the loop's reading (loop, pipeline-loop-v1: verdict, claim, components with sweepsNeeded and sweepsRun, sweepsRun, residual). Costs 1 compute token per model run, charged up front, before any model is read, for the most runs the set can take — one per model outside a loop, and a loop's models times the most sweeps it can take — and not refunded when a loop settles in fewer. Defaults: hours 8, seed 1, realizations 0 (one SSA trajectory per model; set it, e.g. 16 as sim_scenario does, since bindings and loops couple means). Caps: hours up to 10000, realizations up to 200, as sim_scenario. Each model runs through its own ordinary scenario in topological order, an output port's trajectory resampled into the target's input-transition schedule, one seed and horizon shared by every model (the discipline sim_compare enforces within one model). A declared conversion is applied exactly as declared: the resampled rate is multiplied by any numeric transform, then by the convert factor. A binding with a delay drives its target that many hours later; before then the target runs on its own declared rate. A loop between models runs only when some binding on it declares a delay. It is solved by sweeps, each running every member once with one delayed binding (cut) driven by the previous sweep, until a sweep reproduces the previous one's schedules bit for bit: verdict fixed-point, whose claim (also assumptions' first line) is that this is the unique fixed point of the coupling over 0 to hours at the seed, followed by its four limits — each model was driven by the other's mean across realizations (mean-field: for joint dynamics build one net with sim_extend), each delay is declared, not measured, before its delay a target ran on its declared rate, and nothing is claimed past the horizon (a fixed point can be a loop that grows; read the series). Verdict stopped means a run hit the step budget or token cap (from sweep 2 on, typically a loop whose gain is above 1, which grows every sweep; in sweep 1, before any feedback has gone round the loop, the model itself at these hours and realizations) and not-settled is a defect to report; neither carries steps, and models after the loop are not run. Refused before anything is charged or read, each refusal naming the value and the bound: more than 64 binding ids; realizations or hours out of range (the text sim_scenario gives); and the set-level refusals, whose tool error starts with its code — a loop with no delayed binding (closes-loop), two bindings into one input (port-taken), more than one binding into a model (segment-cap: each becomes a schedule of up to 61 segments, and a scenario holds at most 100), a loop of more than 4 models (loop-too-large), a loop whose total delay does not exceed one resample interval (hours/60) per binding (loop-lag), one needing more than 24 sweeps (loop-horizon), loops needing more than 48 model runs together (run-cap); loop-lag and loop-horizon name the delay that would run at these hours and the most hours that run at this delay, and sim_system's view of a system holding the loop reports that most as maxHours. Refused after the charge (which pays for reading the models) and before anything runs: any binding that fails the check sim_bind applies (binds-port-v1; v2 for a unit conversion; v3 for a delay; a binding recorded before a check may be refused here by the same text), and a delayed binding whose target declares so many schedule segments before the delay ends that its schedule would pass the cap at these hours (segment-cap, code first as above). Every result names its seams: no model's own fitness gates cover the join, one unit line per binding (units agree; converted, naming the factor; or unchecked where a port declares no port.unit), and that a binding couples means: a driven model ran on its source's mean, so its std_dev is its own spread given that mean, not the joint system's. A driven model's resampled schedule keeps one random stream per realization across its segments, so splitting a run into segments adds nothing where no rate changes; the resampling itself is a piecewise-constant approximation of the source's mean, which smooths anything shorter than one resample window (hours/60). Public: no sign-in; writes nothing.",
"inputSchema": {
"properties": {
"bindingIds": {
"description": "JSON array of binding ids to run together, e.g. [\"id1\",\"id2\"] — every model these bindings touch is included automatically.",
"type": "string"
},
"hours": {
"description": "horizon in hours, shared across every model in the pipeline (default 8)",
"type": "number"
},
"realizations": {
"description": "realizations per model (default 0, which the engine runs as a single realization — one SSA trajectory per model, so set it, e.g. 16 as sim_scenario defaults to, when the resampled series should be a mean rather than one draw; max 200)",
"type": "number"
},
"seed": {
"description": "shared seed across every model's run (default 1)",
"type": "number"
}
},
"required": [
"bindingIds"
],
"type": "object"
},
"name": "sim_run_pipeline",
"outputSchema": null
},
{
"description": "Run a seeded what-if scenario against a stored model: marking overrides, rate overrides, piecewise rate schedules, and params assignments to the model's declared structural parameters (arc weights, capacities — batch sizes and shelf sizes). Pure read — asking cannot change the model. Returns trajectory, final marking, metrics, contention, caveats and assumptions. \"samples\" (default 60) is the trajectory's resolution: the number of evenly spaced points from 0 to hours inclusive at which times and every place's series (mean and std_dev per point) are reported — it sizes the answer, not the run, since metrics (throughput, mean, p95, utilization, inFlight) are time-weighted over every firing and do not change with the grid. \"summary\": true omits the times and series arrays entirely (the keys are absent, not null) and returns just final, metrics, depleted, contended, caveats and assumptions — the verdict without the chart data, and the right form when nothing will be plotted. Transitions declaring stages (phase-type durations) run with the declared lower spread — the engine expands them structurally and reports in the model's own vocabulary. A model-declared schedule (the day shape on a transition) is honored by every run; the scenario's own schedule or rate override still wins for that transition. \"engine\" picks the reading: \"ssa\" (default, discrete Gillespie — the right choice whenever counts are small enough that variance is the answer, or a schedule is in play) or \"ode\" (continuous mass-action; refuses a schedule, and refuses outright rather than silently misread a model carrying a read arc, inhibitor, reached capacity, guard or non-kinetic arc — Forecast's caveats name which). See docs/engine-selection.md for the full decision rule, including why an arc weight above 1 gets a genuinely different rate law from each engine, and sim_crosscheck to run both readings side by side.",
"inputSchema": {
"properties": {
"id": {
"description": "model id",
"type": "string"
},
"scenario": {
"description": "scenario JSON, e.g. {\"hours\":8,\"samples\":60,\"realizations\":16,\"seed\":7,\"marking\":{\"staff/available\":3},\"params\":{\"batch_size\":6},\"schedule\":{\"arrive\":[{\"until\":2,\"value\":12},{\"until\":8,\"value\":4}]},\"engine\":\"ssa\",\"summary\":false}; hours defaults to 8, samples to 60, realizations to 16, seed to 20260809, summary to false (full trajectory); hours above 10000, realizations above 200 or samples above 5000 are refused",
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
"name": "sim_scenario",
"outputSchema": null
},
{
"description": "Mark an old version of your model as replaced by a newer one. The old id keeps working and its commons dedication (if any) stands — only the listing moves on to the successor. Both models must be yours. It also records a supersededBy edge (old → new), so sim_edges(old) and sim_neighbors(old, \"supersededBy\") show that the supersession happened. Re-pointing an old model to a different successor keeps the earlier edge too, and these edges carry no timestamp, so their order says nothing about which came last: the current successor is the `current` this tool returns (the model's own supersededBy record), not the edge list. Taxonomic edges on the old model are offered for migration to the current successor (sim_migration_offers, accepted with sim_accept_migration), never moved; structural and tool-written edges stay with it.",
"inputSchema": {
"properties": {
"new": {
"description": "model id of the successor",
"type": "string"
},
"old": {
"description": "model id being replaced",
"type": "string"
}
},
"required": [
"old",
"new"
],
"type": "object"
},
"name": "sim_supersede_model",
"outputSchema": null
},
{
"description": "Returns a stored System's view ({id}), the models whose ports can bind to one declared port ({model, port, direction?, among?}), or the stored systems that name a model or a binding ({naming}); one form per call. {id}: the system's view, computed now and never stored (system-view-v2): each model (present, superseded), each binding with its binds-port check (v1, or v2 with its convert when it declares a unit conversion, or v3 with its delay when it declares one) and whether the set runs as a pipeline (with its loops and maxHours when it holds one), the isomorphicTo/subnetOf edges among the models with up to 128 witnesses re-checked (past that the view is truncated and the rest read unchecked), and the shape — one-net when one member has every other embedded in it by a direct witnessed edge or a chain of edges of one predicate through any stored model, every hop's witness and the composed map re-checked (net, embeddings: a direct one names edge and witness, a chained one its derivation, which sim_check_witness(derivation=…) re-checks; closure says what the chain search cost; sim_reach then answers on that net), pipeline when it holds bindings (they link rates, not tokens: sim_reach answers unknown), separate otherwise (notOneNet says why; a chain mixing subnetOf and isomorphicTo hops does not count), unknown when the witness bound was reached before any member qualified, or the chain search stopped at its hop, model or work bound (closure-incomplete: not a no). {model, port, direction?, among?}: what can bind to that declared port — every model with a port sim_bind would accept against it without a unit conversion (opposite direction, equal kind, units agreeing where both declare one), ranked units-agree first, then model id and port; each row carries the sim_bind arguments. A port whose declared unit differs is counted under excluded as unit-differs, not listed: it binds only through sim_bind with convertFrom, convertTo and convertFactor (binds-port-v2), and an empty answer means none binds without one. The universe is the visible catalog, or among (a collection's model members, at most 256, or a system's models, where a candidate that would take a bound port or close a loop of undelayed bindings is counted under excluded, never listed, and one that closes a loop already holding a delayed binding is listed with closesLoop: bindable-v3). Every model in the universe is searched (bindable-v2; bindable-v3 among a system), through a port index that keeps each model's ports by direction and kind, read once per content id and synced from the listing on every call (a model another instance stored is searched on the next call). At most 256 models the index does not hold yet are read per call (past it status is truncated with bound fetches and unscanned says how many were left; ask again and it continues), 100000 candidate ports checked and 50 rows listed: past any bound status is truncated, and if a member model cannot be read status is incomplete (listed under unreadable); in both cases the rows are in partialRows. candidates, scanned, unscanned, fromIndex, read and portChecks say exactly how far it searched; the index holds at most 16777216 bytes of ports (of which at most 2097152 for models no catalog-wide search has listed, such as an among search's or the asked model's) and at most 65536 bytes for one model, and a model past any of these is searched but not kept (notKept). The call's token covers its first 64 reads; each further 64 cost one more token, charged before they are read, and when the bucket cannot cover them status is truncated with bound rate. {naming}: every stored system whose models or bindings include that id (as: model or binding), sorted by system id, from an index synced from the store's listing on every call (a system another instance stored is seen on the next read). At most 100 systems listed (past it status is truncated, total says how many) and at most 256 not-yet-read systems fetched per call (past it status is incomplete and unindexed says how many are unread; ask again and it continues); the index holds at most 33554432 bytes of systems, and once full nothing more is indexed on that instance (status incomplete, unindexed says how many, and asking again does not change it); a stored system that cannot be decoded also keeps status incomplete (listed under unreadable), and asking again does not clear it; in every case the rows are in partialSystems. A system cannot be named (systems do not nest) and a collection is refused, naming the remedy; an id no entity holds any more (a deleted model) is answered when a system still names it. Systems are linkable and collectable: sim_link puts one in a collection (hasMember) and relates it with cites, about, broader and related. This is not refines: a system's edges are isomorphicTo and subnetOf (embeds by wiring / is the same coloured net). refines (trace inclusion with transitions matched by id) and preservesInvariant (the object's P-invariants pull back along an embedding) are proved one pair at a time by sim_prove and are never walked by closure, since neither is declared transitive. For \"what contains this pattern?\" ask sim_query with closure: {\"select\":[\"?m\"],\"where\":[{\"subject\":\"<pattern id>\",\"predicate\":\"subnetOf\",\"object\":\"?m\",\"mode\":\"closure\"}]} (isomorphicTo for copies; add {\"subject\":\"<collection>\",\"predicate\":\"hasMember\",\"object\":\"?m\"} to scope it), each row carrying a derivation sim_check_witness re-checks; a subnetOf row claims no behaviour inclusion. For \"what does this model refine?\" ask sim_query for one hop: {\"select\":[\"?m\"],\"where\":[{\"subject\":\"<model id>\",\"predicate\":\"refines\",\"object\":\"?m\"}]}, each row an edge sim_prove recorded. A loop between models runs only through a binding that declares a delay (sim_bind delay; ROADMAP.md Phase 12j): a system may hold one, of at most 4 models, and its view lists it under pipeline.loops with pipeline.maxHours, the longest horizon sim_run_pipeline runs it at (0 when no horizon of 0.01 h or more does; pipeline.runnable covers only the checks that do not depend on the hours), where it is solved as an exact fixed point over the horizon (pipeline-loop-v1). A loop with no delayed binding is refused when a system is defined and by sim_run_pipeline, and sim_bind refuses a self-binding. A port search with among a system (bindable-v3) lists a candidate that closes a loop already holding a delayed binding, with that loop's models under closesLoop, and counts one that would close a loop of undelayed bindings under excluded as closes-loop (sim_bind with a delay can close it); a catalog or collection search does not consult stored bindings, so sim_create_system or sim_run_pipeline is where such a loop is refused. Public: no sign-in; costs 1 compute token (a port search, 1 more per 64 models read past its first 64); writes nothing.",
"inputSchema": {
"properties": {
"among": {
"description": "a collection (at most 256 members) or system id to search instead of the visible catalog",
"type": "string"
},
"direction": {
"description": "\"output\" or \"input\", needed only when the element declares both",
"type": "string"
},
"id": {
"description": "a system id from sim_create_system: describe it",
"type": "string"
},
"model": {
"description": "a model id: with port, what can bind to that port",
"type": "string"
},
"naming": {
"description": "a model or binding id: list the stored systems that name it",
"type": "string"
},
"port": {
"description": "the element id of model's declared port",
"type": "string"
}
},
"type": "object"
},
"name": "sim_system",
"outputSchema": null
},
{
"description": "Verify declared properties of a stored model: deadlock-free, bounded, mutual-exclusion, invariant expressions, reachable/unreachable targets. Verdicts are proved/refuted/unknown — unknown is never a pass — and each carries a method: structural means it holds for ANY initial marking (linear algebra on the incidence matrix, the strongest claim available), exhaustive means this marking's full state space, partial means truncated (only refutations sound). Caveats name anything the analysis net could not express.",
"inputSchema": {
"properties": {
"id": {
"description": "model id",
"type": "string"
},
"properties": {
"description": "JSON array of properties, e.g. [{\"kind\":\"deadlock-free\"},{\"kind\":\"mutual-exclusion\",\"places\":[\"win_x\",\"win_o\"]},{\"kind\":\"invariant\",\"expr\":\"a + 2*b == 10\"}]. Default: bounded + deadlock-free.",
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
"name": "sim_verify",
"outputSchema": null
}
]
}Verify it yourself
curl -s https://api.teppi.xyz/v1/evidence/sha256:825123b6bf5caa66a59f74a03ea8d761619087e0264bd28713307e30792d64e9 | sha256sum