|
| 1 | +# AGENTS.md |
| 2 | + |
| 3 | +Instructions for AI coding agents (Claude Code, Codex, Cursor, Aider, etc.) working in this repository. Follows the [agents.md](https://agents.md) convention. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +ColouredFlow is a 100% Elixir workflow engine built on [Coloured Petri Nets (CPN)](https://github.com/lmkr/cpnbook). It ships: |
| 8 | + |
| 9 | +- A CPN ML expression language and evaluator. |
| 10 | +- A DSL for defining nets in Elixir. |
| 11 | +- An event-sourced runtime executing enactments (workflow instances) as GenServers. |
| 12 | +- Pluggable storage — in-memory for tests, Ecto/Postgres for production. |
| 13 | + |
| 14 | +## Commands |
| 15 | + |
| 16 | +- `mix precommit` — **run before every commit.** Mirrors CI: deps check, format check, compile warnings-as-errors, credo, dialyzer, test. |
| 17 | +- `mix test` — full suite. Needs Postgres at `postgres:postgres@localhost`; `test_helper.exs` auto-creates DB `coloured_flow_test`. Set `RESET_DB=1` to drop & recreate. |
| 18 | +- `mix test path/to/file_test.exs:LINE` — single test. |
| 19 | +- `mix format` · `mix credo --strict` · `mix dialyzer` — individual checks. Dialyzer PLTs cached at `priv/plts/`. |
| 20 | + |
| 21 | +CI: `.github/workflows/elixir.yml` runs the same checks in parallel against Postgres 15. |
| 22 | + |
| 23 | +## Architecture |
| 24 | + |
| 25 | +The code splits into **static CPN structure**, **CPN semantics**, and the **enactment runner**. |
| 26 | + |
| 27 | +### Module map (`lib/coloured_flow/`) |
| 28 | + |
| 29 | +| Path | Role | |
| 30 | +| --- | --- | |
| 31 | +| `definition/` | Pure data model. `ColouredPetriNet` holds `colour_sets`, `places`, `transitions`, `arcs`, `variables`, `constants`, `functions`, optional `termination_criteria`. | |
| 32 | +| `enactment/` | Runtime values: `Marking` (tokens on a place), `BindingElement` (transition bound to input tokens), `Occurrence` (fired binding — the event-source event). | |
| 33 | +| `multi_set.ex` | Multiset token bag. Sigil: `~MS[{1, "a"}, {2, "b"}]`. | |
| 34 | +| `notation/` | Elixir DSL: `colset/1`, `val/1`, `var/1`, `arc` macro. | |
| 35 | +| `builder/` | Converts DSL input into a validated `ColouredPetriNet`. | |
| 36 | +| `validators/` | Static definition checks (colour sets, arc typechecks) + enactment-time invariants. | |
| 37 | +| `expression/` | CPN ML compiler/evaluator; builds guards and bindings for arc inscriptions. | |
| 38 | +| `enabled_binding_elements/` | Given marking + transition, enumerates every firing binding. Core Petri-net semantics. | |
| 39 | +| `runner/` | GenServer-based execution engine — see below. | |
| 40 | + |
| 41 | +### Runner (`lib/coloured_flow/runner/`) |
| 42 | + |
| 43 | +Turns a stored CPN + initial markings into a supervised, persistent process tree. |
| 44 | + |
| 45 | +- **Supervision tree.** `Runner.Supervisor` → `Enactment.Registry` (via-tuple naming) + `Enactment.Supervisor` (`DynamicSupervisor`) → one `Runner.Enactment` GenServer per workflow instance. |
| 46 | +- **Enactment state.** `markings`, live `workitems`, monotonically-increasing `version`. Keyed by `enactment_id`, `restart: :transient`. Boot: load latest `Snapshot` (or initial markings) → replay `Occurrence`s via `CatchingUp` → recompute enabled bindings via `WorkitemCalibration` → take new snapshot. |
| 47 | +- **Workitem lifecycle.** `enabled → started → completed`, plus `reoffer` (exception recovery) and `withdraw` (system). State machine in `Runner.Enactment.Workitem`. Public API: `WorkitemTransition.{start,complete}_workitem`. Recomputation: `WorkitemCalibration`, `WorkitemCompletion`, `WorkitemConsumption`. |
| 48 | +- **Enactment lifecycle.** `running → {terminated | exception}`; `exception` can recover to `running`. Logic in `Runner.Enactment.EnactmentTermination`, driven by `Runner.Termination`. Termination kinds: |
| 49 | + |
| 50 | + | Kind | Trigger | |
| 51 | + | --- | --- | |
| 52 | + | `:implicit` | no more firable workitems reachable | |
| 53 | + | `:explicit` | definition's `termination_criteria` met | |
| 54 | + | `:force` | user call | |
| 55 | + |
| 56 | +- **Event sourcing.** Firing a binding element produces an `Occurrence`; snapshots are periodic roll-ups. Recovery = load snapshot + replay occurrences after it. |
| 57 | +- **Storage** (`runner/storage/`). `Runner.Storage` behaviour; implementation picked via `Application.get_env(:coloured_flow, ColouredFlow.Runner.Storage)[:storage]`. |
| 58 | + |
| 59 | + | Implementation | Use | Location | |
| 60 | + | --- | --- | --- | |
| 61 | + | `Storage.Default` | production — Ecto/Postgres | `storage/schemas/`; migrations `storage/migrations/{V0,V1}` | |
| 62 | + | `Storage.InMemory` | tests | `storage/in_memory.ex` | |
| 63 | + |
| 64 | +- **Worklist** (`runner/worklist/`). `WorkitemStream.live_query` + `list_live` stream live workitems via cursor (see `TrafficLight` example's `WorkitemPubSub`). |
| 65 | +- **Telemetry** (`runner/telemetry/`). Events emitted throughout the enactment lifecycle. |
| 66 | + |
| 67 | +## Conventions |
| 68 | + |
| 69 | +- Prefer `typed_structor` over plain `defstruct` + `@type t` for new data types — used pervasively. |
| 70 | +- DSL macros `colset`, `val`, `var`, `return` are registered as `locals_without_parens` — don't add parens when the formatter complains. |
0 commit comments