Instructions for AI coding agents (Claude Code, Codex, Cursor, Aider, etc.) working in this repository. Follows the agents.md convention.
ColouredFlow is a 100% Elixir workflow engine built on Coloured Petri Nets (CPN). It ships:
- A CPN ML expression language and evaluator.
- A DSL for defining nets in Elixir.
- An event-sourced runtime executing enactments (workflow instances) as GenServers.
- Pluggable storage — in-memory for tests, Ecto/Postgres for production.
mix precommit— run before every commit. Mirrors CI: deps check, format check, compile warnings-as-errors, credo, dialyzer, test.mix test— full suite. Needs Postgres atpostgres:postgres@localhost;test_helper.exsauto-creates DBcoloured_flow_test. SetRESET_DB=1to drop & recreate.mix test path/to/file_test.exs:LINE— single test.mix format·mix credo --strict·mix dialyzer— individual checks. Dialyzer PLTs cached atpriv/plts/.
CI: .github/workflows/elixir.yml runs the same checks in parallel against
Postgres 15.
The code splits into static CPN structure, CPN semantics, and the enactment runner.
| Path | Role |
|---|---|
definition/ |
Pure data model. ColouredPetriNet holds colour_sets, places, transitions, arcs, variables, constants, functions, optional termination_criteria. |
enactment/ |
Runtime values: Marking (tokens on a place), BindingElement (transition bound to input tokens), Occurrence (fired binding — the event-source event). |
multi_set.ex |
Multiset token bag. Sigil: ~MS[{1, "a"}, {2, "b"}]. |
notation/ |
Low-level building blocks reused by dsl/: colset/1, val/1, var/1, plus arc/expression helpers. |
dsl/ |
High-level declarative workflow DSL (use ColouredFlow.DSL). Composes notation/ into a validated ColouredPetriNet at compile time. See dsl/spec.md. |
builder/ |
Converts DSL input into a validated ColouredPetriNet. |
validators/ |
Static definition checks (colour sets, arc typechecks) + enactment-time invariants. |
expression/ |
CPN ML compiler/evaluator; builds guards and bindings for arc inscriptions. |
enabled_binding_elements/ |
Given marking + transition, enumerates every firing binding. Core Petri-net semantics. |
runner/ |
GenServer-based execution engine — see below. |
Turns a stored CPN + initial markings into a supervised, persistent process tree.
-
Supervision tree.
Runner.Supervisor→Enactment.Registry(via-tuple naming) +Enactment.Supervisor(DynamicSupervisor) → oneRunner.EnactmentGenServer per workflow instance. -
Enactment state.
markings, liveworkitems, monotonically-increasingversion. Keyed byenactment_id,restart: :transient. Boot: load latestSnapshot(or initial markings) → replayOccurrences viaCatchingUp→ recompute enabled bindings viaWorkitemCalibration→ take new snapshot. -
Workitem lifecycle.
enabled → started → completed, plusreoffer(exception recovery) andwithdraw(system). State machine inRunner.Enactment.Workitem. Public API:WorkitemTransition.{start,complete}_workitem. Recomputation:WorkitemCalibration,WorkitemCompletion,WorkitemConsumption. -
Enactment lifecycle.
running → {terminated | exception};exceptioncan recover torunning. Logic inRunner.Enactment.EnactmentTermination, driven byRunner.Termination. Termination kinds:Kind Trigger :implicitno more firable workitems reachable :explicitdefinition's termination_criteriamet:forceuser call -
Event sourcing. Firing a binding element produces an
Occurrence; snapshots are periodic roll-ups. Recovery = load snapshot + replay occurrences after it. -
Storage (
runner/storage/).Runner.Storagebehaviour; implementation picked viaApplication.get_env(:coloured_flow, ColouredFlow.Runner.Storage)[:storage].Implementation Use Location Storage.Defaultproduction — Ecto/Postgres storage/schemas/; migrationsstorage/migrations/{V0..V3}Storage.InMemorytests storage/in_memory.ex -
Worklist (
runner/worklist/).WorkitemStream.live_query+list_livestream live workitems via cursor (seeTrafficLightexample'sWorkitemPubSub). -
Telemetry (
runner/telemetry/). Events emitted throughout the enactment lifecycle.
- Prefer
typed_structorover plaindefstruct+@type tfor new data types — used pervasively. - DSL macros are registered in
.formatter.exslocals_without_parens— don't add parens when the formatter complains. Current set:colset/1,var/1,val/1,return/1(notation);name/1,version/1,place/2,initial_marking/2,function/{1,2},transition/2,guard/1,action/1,input/{2,3},output/{2,3},termination/1,on_markings/1(high-level DSL). - Modules using
ColouredFlow.DSLexposecpnet/0(the materialised%ColouredPetriNet{}) and__cpn__/1reflection (:name,:version,:initial_markings), modeled afterEcto.Schema.__schema__/1.