Skip to content

FE-1237: Add optional per-place token capacity - #9177

Merged
kube merged 6 commits into
mainfrom
cf/fe-1237-place-token-capacity
Aug 30, 2026
Merged

FE-1237: Add optional per-place token capacity#9177
kube merged 6 commits into
mainfrom
cf/fe-1237-place-token-capacity

Conversation

@kube

@kube kube commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

🌟 What is the purpose of this PR?

Adds an optional per-place token capacity, set from the place properties panel.
It also converts frames from growable to fixed-size, which the GPU backend (#9179) requires.

🔍 What does this change?

A transition cannot fire if firing would take an output place above its capacity. This is the standard Petri-net capacity constraint.

{ id: "p_buffer", name: "Buffer", capacity: 20 }
  • Output tokens apply at the end of a frame, and the check counts what earlier transitions in the same frame already committed, so several transitions feeding one capped place cannot collectively overflow it.
  • Deadlock detection uses the same check, so a net blocked only by full output places reports as deadlocked instead of stepping to maxTime.
  • buildSimulation rejects an initial marking that exceeds a place's capacity, with a message naming the place.
  • A net that declares no capacities gets empty constraint tables, and the hot path skips them.

Review fixes

  • The interactive stepping path (executeTransitions) now threads a pending-output tally into the capacity check, so several transitions feeding one capped place in a single step can no longer collectively overflow it. This matches the Monte Carlo path and the behaviour docs/simulation.md already promised. A regression test in compute-next-frame.test.ts pins it: two producers feed one capacity-1 place, and exactly one fires.
  • The initial-marking check in buildSimulation now normalizes the capacity the same way the runtime does, so a malformed capacity that runs unbounded cannot reject a marking.
  • The capacity NumberInput gained a max of 4294967294, one below the engine's unbounded sentinel.
  • The capacity checkbox's comment claimed the value defaults to the current token count. It now states the real behaviour: the capacity starts at 1, and a marking above the cap fails the build with a message naming the place.

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • modifies an npm-publishable library and I have added a changeset file(s)

📜 Does this require a change to the docs?

The changes in this PR:

  • require changes to docs which are made as part of this PR

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • do not affect the execution graph

🐾 Next steps

Build the fixed-size frame layout (SoA over WASM linear memory). The runtime still uses growable frames.

🛡 What tests cover this?

  • engine/capacity.test.ts covers the constraint tables and the headroom check.
  • monte-carlo/capacity.test.ts covers end-of-frame accumulation across several transitions feeding one place, and deadlock reporting.
  • engine/compute-next-frame.test.ts covers the interactive stepping path holding a capacity when two transitions feed one place in a single step.

❓ How to test this?

  1. Select a place, switch on Token capacity, and set a small value.
  2. Run the simulation. Transitions feeding that place stop firing at the cap, and the run reports as deadlocked once nothing else can fire.

@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hash Ready Ready Preview Aug 27, 2026 6:33pm
petrinaut Ready Ready Preview Aug 27, 2026 6:33pm
petrinaut-docs Ready Ready Preview Aug 27, 2026 6:33pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hashdotdesign-tokens Ignored Ignored Preview Aug 27, 2026 6:33pm

Request Review

@github-actions github-actions Bot added area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team labels Aug 8, 2026
@kube
kube force-pushed the cf/fe-1237-place-token-capacity branch from 6a7dd72 to 367c694 Compare August 13, 2026 08:20
@kube
kube force-pushed the cf/fe-1237-place-token-capacity branch from 367c694 to f78b501 Compare August 14, 2026 23:53
@kube
kube force-pushed the cf/fe-1237-place-token-capacity branch from f78b501 to 03a799f Compare August 15, 2026 00:51
@kube
kube force-pushed the cf/fe-1237-place-token-capacity branch from 03a799f to 9985050 Compare August 18, 2026 00:05
@kube
kube force-pushed the cf/fe-1237-place-token-capacity branch from 9985050 to 6e5226a Compare August 18, 2026 00:33
@kube
kube force-pushed the cf/fe-1237-place-token-capacity branch from 6e5226a to 3c8e832 Compare August 18, 2026 00:47
@cursor

cursor Bot commented Aug 21, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches core transition enablement and frame stepping in both interactive and Monte Carlo engines, but the feature is opt-in and heavily covered by new unit and integration tests.

Overview
Places can now declare an optional token capacity (schema, types, and a Token capacity control in the place properties panel). A transition is structurally disabled if firing would push any bounded output place over its limit—same idea as missing input tokens—using net change per firing (self-loops at a full place still work; read/inhibitor arcs do not free room).

The engine precomputes per-transition capacity constraints at build time, stores dense placeCapacities on EngineFrameLayout, and skips checks when no place is capped. buildSimulation rejects initial markings above a place’s normalized capacity. Both stepping paths (executeTransitions / computePossibleTransition and Monte Carlo advanceRun) thread pendingOutputCounts so end-of-frame output application cannot let multiple producers overflow one capped place in a single step. Deadlock / structural enablement treats full outputs like missing inputs.

Docs and arch notes are updated; nets without capacities keep empty constraint tables on the hot path.

Reviewed by Cursor Bugbot for commit 5c8e305. Bugbot is set up for automated code reviews on this repo. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment thread libs/@hashintel/petrinaut-core/src/simulation/engine/capacity.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e16c503. Configure here.

Comment thread libs/@hashintel/petrinaut-core/src/simulation/engine/capacity.ts
kube added 6 commits August 27, 2026 18:40
An experiment ran every run in a single worker, using one core however
many the machine had. Runs are independent, so they now split across
one worker per logical core minus one, capped at the run count —
measured at ~4x on 8 shards.

Sharding cannot change results: seeds derive from the run's global
index rather than its position in a shard, and per-frame statistics
recombine through the metric accumulator monoids, so output is
byte-identical at every shard count. Scalar frames carry their
pre-reduction accumulator state, since a mean of means is not a mean.
Replaces em dashes with commas, colons or full stops in
worker-sharding.mdx and the changeset. No content change.
A place can declare a maximum token count, which participates in
transition enablement the standard way: a transition cannot fire if it
would take an output place above capacity. Output tokens apply at the
end of a frame, so the check folds in what earlier transitions this
frame already committed.

Deadlock detection uses the same check, so a net blocked only by full
output places is reported as deadlocked rather than stepping to
maxTime doing nothing. Nets without capacities keep empty constraint
tables and skip the check.
Replaces em dashes with full stops. No content change.
@hash-release hash-release Bot mentioned this pull request Aug 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > backend Owned by the @backend team type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

3 participants