An agent-native, adaptive case & workflow engine — CMMN 1.1 implemented on Microsoft Orleans, event-sourced, multi-tenant.
Most workflow engines make you draw the path in advance: a flowchart of steps, branches, and gates that the engine then marches through. That works for predictable, repeatable processes. It breaks down for knowledge-intensive work — case management, investigations, approvals, anything where the right next step depends on facts that only emerge as the work happens.
CMMN (Case Management Model and Notation) is the OMG standard for the other kind of process: discretionary, adaptive work where the path is discovered at runtime, not fixed at design time. A case defines the possible activities, the milestones that mark progress, and the sentries (event/condition rules) that govern when things become available — not a rigid sequence. Wayfinder is a distributed CMMN 1.1 runtime built on that model.
Each case instance, plan item, sentry, and definition is an independent Orleans grain (virtual actor), giving horizontal scale across thousands of concurrent cases with event-sourced state — every state transition is captured by construction, so a case's full history is always reconstructable, not just its current snapshot.
Implemented today:
- Case plan model, stage, and task lifecycles (state machines per CMMN §8.4)
- Model-driven case instantiation —
ICaseGrain.Create()builds the plan-item tree from the CasePlanModel (discretionary items excluded, per §8.7 planning semantics) - Human tasks, milestones, and event listeners (timer, user)
- Sentry/criterion evaluation — entry and exit criteria, onPart AND-semantics, ifPart conditions over case data, standalone-ifPart sentries, per-onPart re-arm across repetitions
- Case file items (§8.3) — full lifecycle, JSON content, and case-file events that drive sentries and timer start-triggers
- Planning tables, discretionary items, and applicability rules (risk-based tailoring)
- Manual activation, repetition, and required rules
- Expression evaluation with case-file context — conditions like
value.amount > 100evaluate over the referenced case file item (sandboxed Jint) - Multi-tenant case isolation via Orleans compound grain keys
- Event sourcing via Orleans JournaledGrain — a full audit trail by construction
.cmmnXML import/export (CmmnXmlSerializer.Import/.Export), gated by a capability lint (CmmnCapabilityLint) that flags unsupported constructs at import time rather than failing silently at runtime- Deployed multi-silo Orleans clustering — Azure Table cluster membership and durable Azure Table reminders, container-aware endpoints, a
Dockerfile(src/Flow.Silo/Dockerfile), and a 3-silo evaluation stack (devops/eval/docker-compose.yml)
Known gaps (tracked as work items; see the roadmap):
- ProcessTask / CaseTask / DecisionTask exist in the model but have no runtime behaviors
- HTTP ingress (
src/Flow.Api, OData) is in progress — cases and definitions have initial endpoints, but coverage of the full engine surface is not complete yet - No runtime planning-apply surface yet — a discretionary item can be queried (
PlanningTableGrain.GetPlannableItems) but not yet selected into a live case - Orleans streams and the pub/sub store are still in-memory in every environment, including the deployed clustering path (
AddMemoryStreams,AddMemoryGrainStorage("PubSubStore")) — a real durability gap: a deactivated grain's sentries can miss events delivered while it wasn't listening - CMMN timers still run on Quartz's volatile in-memory
RAMJobStore, not a durable job store, even where Orleans clustering itself is durable - No MCP ingress yet — agents drive the engine through
Flow.Application's command/query handlers or the OData API today, not a dedicated agent protocol
Wayfinder does not claim OMG CMMN conformance against the OMG spec's own certification process. What it does have: an internal conformance suite (src/Flow.Grains.Tests.Integration/Conformance) that drives real .cmmn sample files through the public grain surface and checks them against the spec's own lifecycle tables — currently 35 scenarios, 35 green, 0 skipped (see COVERAGE.md for the row-by-row spec mapping, including the gaps it still tracks honestly as KnownGap/NotApplicable).
The end goal is an engine that AI agents drive directly, not just humans through a UI — cases, tasks, and case-file data exposed through a protocol agents already speak (MCP), sitting on top of an API that's transport-agnostic by design (Flow.Application holds the CQRS command/query handlers; Flow.Api is one ingress onto them today, with MCP planned as a fast-follow rather than a rewrite).
This project was recently renamed Case.Flow → Wayfinder. The rename is in flight: repository, issues, and docs now say Wayfinder, but the solution, projects, and namespaces have not been renamed yet — that's tracked as a separate follow-up PR. Until then, the actual paths and commands are still src/CaseFlow.sln and Flow.*, as documented below. Don't be surprised to see Flow.Grains, Flow.Silo, etc. — that's expected until the rename PR lands.
| Path | Purpose |
|---|---|
src/Flow.Grains.Interfaces |
Domain model (XSD-generated CMMN metamodel + partials), grain interfaces, snapshots |
src/Flow.Grains |
Core engine — grains, behaviors, state machines, stores |
src/Flow.Silo |
ASP.NET Core Orleans silo host |
src/Flow.Api |
HTTP ingress (OData) |
src/Flow.Application |
Application layer — CQRS-style command/query handlers |
src/Flow.Contracts |
Shared request/response contracts |
src/Flow.Grains.Tests |
Unit tests (xUnit, Moq, AutoFixture) |
src/Flow.Grains.Tests.Integration |
Integration tests (Orleans TestCluster, in-memory) |
src/Flow.Grains.Tests.Utils |
Shared test utilities and mocks |
src/Flow.Api.Tests |
API layer tests |
devops/ |
CI pipeline, deploy, and infrastructure (Docker Compose for local dependencies) |
docs/ |
Design docs — CMMN overview and architecture evaluations |
- .NET 10 (LTS) —
global.jsonpins the SDK - Microsoft Orleans 10.2.1 — JournaledGrain event sourcing, memory streams, TestCluster
- System.Text.Json throughout (polymorphic CMMN model via a fail-closed
[XmlInclude]-derived resolver) - Stateless (plan-item lifecycle state machines)
- Quartz (timer scheduling; durable/clustered scheduling planned)
- Jint 4.x (JavaScript expression evaluation, sandboxed: timeout/statement/memory budgets)
- xUnit / Moq / AutoFixture / FluentAssertions
Dependency security is enforced at restore time: NuGet Audit fails any build with a moderate-or-higher advisory (direct or transitive), with nuget.org pinned as the sole package source.
- COVERAGE.md — what is built and verified: the internal CMMN conformance suite's row-by-row spec mapping
- GitHub Milestones — what is planned: roadmap milestones and expression-language strategy
- GitHub Issues — bug reports, feature requests, and open questions
- docs/01-cmmn-overview.md — CMMN standard overview and concept mapping
- docs/02-codebase-evaluation.md — architectural assessment (historical snapshot, pre-modernization)
- docs/08-orleans-provider-evaluation.md — Orleans storage/streaming provider evaluation
dotnet build src/CaseFlow.sln
dotnet test src/CaseFlow.slnRequires the .NET 10 SDK. Most integration tests need no external dependencies — Orleans TestCluster with in-memory storage and streams. The build fails on vulnerable packages by design.
Journaled-grain (case-file) state persists to Azure Blob Storage, backed locally by Azurite:
docker compose -f devops/infrastructure/docker-compose.yml up -dThen run the silo as usual — Flow.Silo's Development configuration already points at
Azurite's well-known dev connection string.
The Azurite-dependent test suite (Flow.Grains.Tests.Integration's Storage tests) is
unrelated to the compose stack above: it self-provisions its own throwaway Azurite container
per run via Testcontainers, so it only needs Docker
running — no docker compose up and no fixed port to collide with a developer's own Azurite.
It skips itself automatically when Docker isn't reachable, so dotnet test works with or
without Docker running.
Wayfinder follows GitFlow:
developis the default, integration branch — all feature work merges here first.mainholds releases only, updated fromdevelopvia release branches.- Feature branches:
feature/<work-item>_<short-description>, offdevelop. - Hotfix branches:
hotfix/<short-description>, offmain.
Versioning is computed by GitVersion — see GitVersion.yml
for the exact branch-to-version rules (develop → -develop.N prereleases, release/* → -rc.N,
tagged main → the clean release version).