A generic workflow execution engine for .NET, with an operator dashboard.
Workflow steps are written in C#. The engine handles everything around that code: sequencing it, giving it somewhere to keep state, surviving restarts, retrying what is worth retrying, and showing an operator what happened.
Status: early, but working end to end. M1–M4 complete — 293 backend tests and 53 frontend tests. A workflow can be defined in C#, executed, persisted, resumed after a restart, driven over HTTP and watched in an Angular dashboard.
There is no authentication. Anything that can reach the API can start, inspect and cancel workflows. No retry, no compensation, single node only. See known limitations.
public sealed class GreetWorkflow : IWorkflowDefinition
{
public string Id => "greet";
public int Version => 1;
public void Build(IWorkflowBuilder builder) =>
builder.AddStep("say-hello", () => new SayHello());
}
var registry = new WorkflowRegistry();
registry.Register(new GreetWorkflow());
var engine = new WorkflowEngine(registry);
var instance = await engine.StartAsync("greet", version: 1);
// instance.Status == InstanceStatus.CompletedFull walkthrough: Defining a workflow.
| Requirements | Scope, functional and non-functional requirements |
| Architecture | Components, execution model, limitations |
| Frontend architecture | Dashboard shape and decisions |
| Implementation plan | Roadmap and status |
| Decision records | Why things are the way they are |
| Prior art | What is borrowed from other engines, and what differs |
| Defining a workflow | Usage guide |
| Writing a persistence provider | Implementing IWorkflowStore |
| HTTP API | Endpoints, and why they are shaped that way |
| API error contract | What every error response means |
| Deployment | CI/CD and homelab setup |
| Security | Reporting vulnerabilities, enabled protections |
Requires the .NET 10 SDK (pinned in global.json).
dotnet build
dotnet test
dotnet format --verify-no-changes --severity warnFrontend (Angular 22, requires Node 24):
cd src/frontend
npm ci
npm test -- --watch=false
npm run lint
npm run buildTwo processes. The API first:
dotnet run --project src/backend/FlowDeck.ApiThen the dashboard, in another shell:
cd src/frontend
npm startThe dashboard is on http://localhost:4200 and proxies /api to the API on
:5299 (proxy.conf.json, the development-server counterpart of the
container's nginx.conf). The API also serves its OpenAPI document at
/openapi/v1.json and a Prometheus scrape at /metrics.
The http launch profile sets FlowDeck__Samples, which registers the
definitions in samples/ and seeds a few instances — a straight line, a fork, a
conditional branch, a retry, a rollback and a suspend, so every badge and every
operator action has an example. They are business fiction and a deployed
FlowDeck never registers them: the flag is set in launchSettings.json and
nowhere else, because WebApplicationFactory also hosts in Development and an
environment check alone would seed them into every API test's fixture.
Storage defaults to in-memory, so a restart starts clean.
Every change goes through a pull request. main blocks force-pushes,
deletions, non-linear history and unsigned commits, with no bypass.
After cloning, enable the secret-scanning hook:
git config core.hooksPath .githooks
scoop install gitleaks # or https://github.com/gitleaks/gitleaks#installingThe hook refuses to run if gitleaks is missing rather than letting unscanned
commits through.
Work is tracked as GitHub issues grouped into milestones. Stories carry Given/When/Then acceptance criteria; tests are written before implementation. Non-obvious decisions get an ADR in the same pull request.
Design patterns are borrowed deliberately from WorkflowCore (step model), Hangfire (durable state, distributed locking), Elsa v3 (definition versioning, suspension) and Octopus Deploy (dashboard UX).
No source code from any of them is copied. Some API vocabulary does overlap with WorkflowCore, which is recorded openly in ADR-0011. Prior art sets out what differs, what does not, and what was deliberately left behind.