Skip to content

FE-1887: Add a compiler from Flow Definitions to Petri nets - #10097

Open
CiaranMn wants to merge 8 commits into
mainfrom
cm/fe-1896-compile-flow-definitions-to-petri-nets
Open

CiaranMn wants to merge 8 commits into
mainfrom
cm/fe-1896-compile-flow-definitions-to-petri-nets

Conversation

@CiaranMn

Copy link
Copy Markdown
Member

🌟 Purpose

We want to use Petri net (PN) representations for Flows in two places:

  1. In the frontend, for editing purposes (although we will also be introducing a visual abstraction on top of Petri nets to make editing easier, while also offering the ability to see the underlying Petri net – FE-1912)
  2. In the executor itself, which will use the PN representation and PN semantics to decide when to execute steps, route outputs/inputs, etc (FE-1904)

The PN representation will not be stored in the database. We will keep using the more minimal Flow Definition format. This gives us the freedom to change how we compile to a PN, for example to change how we compose sub-nets.

This PR adds the compiler: a new package, @local/flow-petri-net, that turns a FlowDefinition into a Petrinaut-format net. The alternative to using a new package would be the existing @local/hash-isomorphic-utils, but making that dependent on @hashintel/petrinaut-core (as the compiler requires) would cause a change to petrinaut-core to invalidate the build of many other packages. We can always revisit this.

Nothing in the app calls the compiler yet. FE-1897 (next) will use it to show a flow as its net in the flow and run pages, and FE-1904 to run them.

This is an initial version – we'll definitely need to change it when we update how subnet composition works in Petrinaut (moving to place fusion), and there are probably lots of improvements that can be made beyond those noted in known issues.

🔍 Changes

Where What it does
libs/@local/flow-petri-net/README.md What a compiled net looks like, with diagrams, and its limitations.
src/compile-flow-definition.ts compileFlowDefinition, the package's one export: indexes the steps, adds the root net's places and runStart, compiles each step, and generates the subnets the steps use.
src/compile-flow-definition/generate-action-subnet.ts The subnet each action's steps are instances of: a port for each input and output, and the lifecycle of running the action, with retries.
src/compile-flow-definition/compile-action-step.ts, compile-for-each-step.ts Each kind of step: an action step's instance and its route and fail transitions, and a for-each step's iterator and accumulator.
src/compile-flow-definition/shared/value-places.ts Where each flow input and step output is held, and the copy transitions where one token has to become several.
src/compile-flow-definition/shared/ (the rest) The step index, the net being built, ids, token colours, the generated kernels, layout, and helpers for making places, transitions and arcs.

What the executor and a run's view need from the net

  • Roles. Each transition has a metadata.role. The executor (FE-1904) fires succeedAttempt and failAttempt when an action's activity completes, and every other transition as soon as it's enabled. Each role is named for what its transition does, verb first, so that no two kinds of transition share one.
  • Steps. Every transition added for a step names it in metadata.stepId, so a run's view (FE-1932) can map between steps and the net.
  • Ids. The compiler builds the net from readable keys, such as p.input.prompt or t.route.<step>. Since FE-1882, Petrinaut converts any id that isn't a UUID to a UUIDv5 when it loads a net. Each id in a compiled net is already that UUID, made from its key with toPetrinautId.

⚠️ Known issues

  • A compiled net doesn't simulate meaningfully. The generated kernels only carry run_id and item_path forward, and there are no guards: nothing correlates tokens of one run, or stops the iterator and accumulator. The executor interprets transitions by their role instead (FE-1904).
  • Skipping isn't represented yet. An optional input with nothing connected, and no default, gets a token marked absent (present: false). But a step whose value on a "skip when missing" connection is absent isn't skipped in the net, and an optional output the action didn't produce isn't marked absent: both need the executor's absent tokens (FE-1904).
  • A for-each step's item tokens stay after their item finishes. t.startItem puts one token per item in the step's item place, and the steps inside the for-each read it, so each of them can see the item. Nothing consumes it. Consuming it needs a transition that knows every step of that item has finished, which needs the item correlation that FE-1914 adds.
  • Petrinaut's Actual Mode can't show a run on the net yet. It only reads the places of the root net, and a step's outputs usually stay in its instance's ports. FE-1932 shows a run on its net, and covers places inside instances.
  • An action with no outputs leaves its done token in its port. No action has no outputs today, so this case isn't tested.

@vercel

vercel Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

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

4 Skipped Deployments
Project Deployment Actions Updated
hash Ignored Ignored Preview Oct 20, 2026 12:58pm UTC
hashdotdesign-tokens Ignored Ignored Preview Oct 20, 2026 12:58pm UTC
petrinaut Skipped Skipped Oct 20, 2026 12:58pm UTC
petrinaut-docs Skipped Skipped Oct 20, 2026 12:58pm UTC

Request Review

@github-actions github-actions Bot added area/deps Relates to third-party dependencies (area) 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/legal Owned by the @legal team labels Oct 19, 2026
@CiaranMn
CiaranMn requested a balanced review from Copilot October 19, 2026 16:59
@CiaranMn
CiaranMn added this pull request to stack #10074 October 19, 2026 17:01

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.

🟡 Changes recommended

Valid definitions can produce identifier collisions, rejected kernels, blocked item consumers, and ambiguous input routing.

4 open issues
What changed in this PR

Adds @local/flow-petri-net to compile Flow Definitions into Petrinaut nets for future visualization and execution, while keeping definitions as the source of truth.

Changes:

  • Compiles action lifecycles, for-each steps, and value routing.
  • Adds deterministic IDs, token colours, metadata, kernels, and layout.
  • Includes compatibility tests, documentation, and package setup.
File Description
yarn.lock Registers the workspace package.
libs/​@local/​flow-petri-net/​turbo.json Configures build and test dependencies.
libs/​@local/​flow-petri-net/​tsconfig.json Configures TypeScript checking.
libs/​@local/​flow-petri-net/​tsconfig.build.json Configures build output.
libs/​@local/​flow-petri-net/​src/​compile-flow-definition/​shared/​value-places.ts Allocates value storage and consumer copies.
libs/​@local/​flow-petri-net/​src/​compile-flow-definition/​shared/​step-places.ts Defines lifecycle place IDs.
libs/​@local/​flow-petri-net/​src/​compile-flow-definition/​shared/​net-elements.ts Defines transition roles and element helpers.
libs/​@local/​flow-petri-net/​src/​compile-flow-definition/​shared/​net-builder.ts Accumulates root-net elements.
libs/​@local/​flow-petri-net/​src/​compile-flow-definition/​shared/​lay-out.ts Positions net nodes.
libs/​@local/​flow-petri-net/​src/​compile-flow-definition/​shared/​kernels.ts Generates placeholder token kernels.
libs/​@local/​flow-petri-net/​src/​compile-flow-definition/​shared/​flow-steps.ts Indexes steps and resolves payload shapes.
libs/​@local/​flow-petri-net/​src/​compile-flow-definition/​shared/​colors.ts Defines token schemas and colours.
libs/​@local/​flow-petri-net/​src/​compile-flow-definition/​shared/​action-subnet-ids.ts Defines subnet and port IDs.
libs/​@local/​flow-petri-net/​src/​compile-flow-definition/​generate-action-subnet.ts Generates action lifecycle subnets.
libs/​@local/​flow-petri-net/​src/​compile-flow-definition/​generate-action-subnet.test.ts Tests subnet ports and lifecycle structure.
libs/​@local/​flow-petri-net/​src/​compile-flow-definition/​compile-for-each-step.ts Compiles iteration and collection.
libs/​@local/​flow-petri-net/​src/​compile-flow-definition/​compile-action-step.ts Compiles action instances and connections.
libs/​@local/​flow-petri-net/​src/​compile-flow-definition.ts Coordinates validation and net compilation.
libs/​@local/​flow-petri-net/​src/​compile-flow-definition.test.ts Tests compatibility, routing, and determinism.
libs/​@local/​flow-petri-net/​README.md Documents architecture and limitations.
libs/​@local/​flow-petri-net/​package.json Declares exports, scripts, and dependencies.
libs/​@local/​flow-petri-net/​LICENSE.md Declares the package licence.
libs/​@local/​flow-petri-net/​eslint.config.js Configures package linting.
libs/​@local/​flow-petri-net/​docs/​task-dependencies.json Records task dependency relationships.

Review effort: Balanced

Comment thread libs/@local/flow-petri-net/src/compile-flow-definition/compile-action-step.ts Outdated
Comment thread libs/@local/flow-petri-net/src/compile-flow-definition/compile-action-step.ts Outdated
Comment thread libs/@local/flow-petri-net/src/compile-flow-definition/shared/value-places.ts Outdated
Comment thread libs/@local/flow-petri-net/src/compile-flow-definition/compile-action-step.ts Outdated
…om its consumer with ~, so no step id can collide with another key
…ther than its label, so kernels can address it
…tion's inputs takes, which its arcs alone don't say
… own, as other values do, so two consumers don't race for one token
@CiaranMn
CiaranMn force-pushed the cm/fe-1896-compile-flow-definitions-to-petri-nets branch from 757ad7f to 415425e Compare October 20, 2026 07:48
@vercel
vercel Bot temporarily deployed to Preview – petrinaut October 20, 2026 07:48 Inactive
@vercel
vercel Bot temporarily deployed to Preview – petrinaut-docs October 20, 2026 07:48 Inactive
…copy's value from its consumer with the step run id separator rather than a literal
@vercel
vercel Bot temporarily deployed to Preview – petrinaut-docs October 20, 2026 08:20 Inactive
@vercel
vercel Bot temporarily deployed to Preview – petrinaut October 20, 2026 08:20 Inactive
@CiaranMn
CiaranMn marked this pull request as ready for review October 20, 2026 09:10
@CiaranMn
CiaranMn requested review from a team as code owners October 20, 2026 09:10
@cursor

cursor Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Introduces the canonical flow→net mapping and stable place/transition ids that future run marking will depend on, but it is not wired into production paths yet and execution semantics remain in follow-up work.

Overview
Adds @local/flow-petri-net, a new library (separate from @local/hash-isomorphic-utils so Petrinaut isn’t pulled into the whole graph) whose only public API is compileFlowDefinition: it validates a FlowDefinition, builds an SDCPN, runs ELK layout, and returns a Petrinaut-ready net. Nothing in the app calls it yet; follow-ups will visualize flows and run them from the net.

The compiler wires action steps as component instances of per-action subnets (ports, attempt/retry/give-up lifecycle, max_retries from step config) with root startStep / recordStepFailure transitions. placeValues decides where inputs, outputs, and for-each items live and adds copyOutput only when one producer must fan out to multiple readers and/or consumers (read vs consume arcs). For-each steps get iterator, item fan-out, pending tokens for nested steps, and collect/finish transitions.

Root metadata documents run bootstrap (flowInputs, pendingAtStart, flowOutputs), and transitions carry metadata.role and stepId (plus inputSources on startStep) for a future executor. IDs are stable UUIDv5s from readable keys via canonicalizePetrinautIds. Kernels are placeholders (no guards / real iterator–accumulator logic), so meaningful simulation is deferred to role-based execution.

Coverage includes compiling all in-repo hardcoded flows, routing cases, determinism, and Petrinaut import/model build checks; README documents net shape and known limitations.

Reviewed by Cursor Bugbot for commit ca0fef0. 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.

🟡 Changes recommended

Ambiguous output keys merge valid values and discard collected array results.

1 open issue
4 resolved since last review

Review effort: Balanced

Comment thread libs/@local/flow-petri-net/src/compile-flow-definition/shared/value-places.ts Outdated

@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.

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 a79133c. Configure here.

Comment thread libs/@local/flow-petri-net/src/compile-flow-definition/shared/value-places.ts Outdated
…tor, and name each value's place by its key, so no two outputs share a place or a name
…sts, rather than through a RunRequests place and startRun transition
@vercel
vercel Bot temporarily deployed to Preview – petrinaut-docs October 20, 2026 12:56 Inactive
@vercel
vercel Bot temporarily deployed to Preview – petrinaut October 20, 2026 12:56 Inactive

if (key) {
getValue(key).consumers.push({
key: `${step.stepId}.${inputName}`,

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.

These keys are rebuilt in compile-action-step.ts L90 and compile-for-each-step.ts L66/L115. Could placeValues own them?

getValue(key).consumers.push({
key: `${step.stepId}.${inputName}`,
label: `${step.stepId}.${inputName}`,
consumes: consumes(source),

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.

A step inside a for-each that consumes an outer value only gets one token, so items after the first get stuck.

* collected output.
* - A step that gives up puts a token in `FailedStepRuns`.
*
* Transitions carry their role in `metadata.role`, for the executor to interpret: the net has no guards or

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.

there are placeholder kernels now.

Comment on lines +8 to +13
"exports": {
"./compile-flow-definition": {
"types": "./src/compile-flow-definition.ts",
"default": "./dist/compile-flow-definition.js"
}
},

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.

Only compileFlowDefinition is exported, but the executor and run view will need TransitionRole, the metadata shapes, and the key helpers. The README says ids are worked out from keys. Export those too?

} from "@local/hash-isomorphic-utils/flows/types";

describe("generateActionSubnet", () => {
it("gives an action a port per input and output, and one it gives up through", async () => {

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.

some actions never get compiled in any test. Could we loop over every action definition here?

This branch was successfully deployed

2 active (outdated) and 2 inactive deployments
Preview – petrinaut — ca0fef06 Deployed Oct 20, 2026 by vercel[bot]
Preview – petrinaut-docs — ca0fef06 Deployed Oct 20, 2026 by vercel[bot]
Preview – hash — 415425ec Deployed Oct 20, 2026 by vercel[bot]
Preview – hashdotdesign-tokens — c967ba37 Deployed Oct 20, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/deps Relates to third-party dependencies (area) 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/legal Owned by the @legal team

Development

Successfully merging this pull request may close these issues.

3 participants