Elsa
Foundation.
The modular architecture brain behind the next generation of Elsa Workflows.
Elsa Foundation decomposes the workflow engine into explicit domains, stable contracts, replaceable implementations, and sanctioned extension patterns — so Elsa can evolve without turning every package into a dependency magnet.
- 12+
- Domains
- .Core
- Contracts
- 1
- Artifact seam
What the refactor prevents
Seven shapes of accidental complexity that the Foundation refuses to allow.
God packages
One assembly knows everything.
Framework leakage
ASP.NET types in domain code.
Forced heavy dependencies
Pull EF Core to read a contract.
Infrastructure in contracts
.Core depends on a provider.
Inverted dependency direction
.Core references implementations.
Silent DI conflicts
Last-registered-wins surprises.
Names that hide meaning
Generic 'Service' / 'Manager' soup.
The Foundation does not eliminate complexity — it forces it into named, bounded places.
Before
Tangled package graphAfter
Domains bounded by .CoreEvery domain owns its contract
The same three-layer shape repeats for every feature in Elsa.
Domain / Feature Core
Contracts, models, value objects, events, exceptions, thin utilities.
e.g. Elsa.Workflows.Design.Core
No heavy dependencies.
Helper Libraries
Optional reusable implementations with focused dependencies.
e.g. Elsa.Tasks.Schedules
Never referenced by .Core.
Feature Implementation
DI registration and concrete services for a chosen technology.
e.g. Elsa.Serialization.Newtonsoft · Elsa.Locking.FileSystem
Implementations do not casually reference each other.
The Elsa domain tree
Domains grouped by role. Hover a card to see its boundary.
Workflow Core
Elsa.Workflows.Design
Authored workflow definitions: drafts, versions, validation, design-time persistence.
Design.Core · Design.Validation · Design.Persistence
Elsa.Workflows.Runtime
Executes workflow artifacts, instances, bookmarks, logs, runtime state.
Runtime.Core · Runtime.Bookmarks · Runtime.Logs
Platform Services
Elsa.Persistence
Storage commands and read-side abstractions.
Persistence.Core · Persistence.EFCore
Elsa.Locking
Distributed locks behind a single contract.
Locking.Core · Locking.FileSystem · Locking.Redis
Elsa.Serialization
Pluggable serialization surface.
Serialization.Core · Serialization.Newtonsoft · Serialization.SystemText
Elsa.Expressions
Expression languages and evaluation contracts.
Expressions.Core · Expressions.Liquid · Expressions.JavaScript
Elsa.Messaging
Outbound and inbound message integrations.
Messaging.Core · Messaging.MassTransit
Elsa.Scheduling
Time-based triggers and scheduled work.
Scheduling.Core · Scheduling.Quartz
Elsa.Tasks
Background tasks and schedule helpers.
Tasks.Core · Tasks.Schedules
Composition & Surface
Elsa.Modularity
Feature composition: registration, options, ordering.
Modularity.Core · Modularity.Features
Elsa.Api
HTTP-shaped surface for design and runtime endpoints.
Api.Core · Api.Endpoints
Elsa.Http
HTTP-related activities and runtime integrations.
Http.Core · Http.Activities
Elsa.Notifications
In-process pub/sub plus delivery strategies.
Notifications.Core
Design and Runtime are separate bounded contexts
Design owns authoring. Runtime owns execution. Runtime must not depend directly on Design.
Elsa.Workflows.Design
Drafts, versions, input/output definitions, activity trees, expression bindings, validation, layouts, design-time persistence.
Elsa.Workflows.Runtime
Executable artifacts, workflow instances, runtime state, bookmarks, logs, runtime integrations.
Design produces. Runtime executes. The artifact is the seam.
Runtime contract first
Two invariants describe everything the runtime is allowed to know about.
Executable-always-runs
If an artifact is published as runnable, the runtime must be able to load and execute it. Missing runtime types, registry drift, or module misconfiguration are bugs — not business gates.
Artifact-only runtime
Runtime depends on the runnable artifact and configured runtime features. Never on drafts, designer metadata, authoring history, or design persistence.
Runtime as an actor system
Elsa's runtime is moving from a broad in-memory execution object model toward isolated, addressable execution actors — each with its own identity, mailbox, state boundary, and checkpoint behavior. This is the emerging Elsa 4 execution model, not a ratified constitution rule.
“One execution, one durable identity.”
“Messages cross boundaries; state does not leak.”
“Runtime resumes from checkpoints, not from design documents.”
“Concurrency is explicit instead of accidental.”
Conceptual actors
Workflow execution actor
Owns one workflow execution: start, run, suspend, resume, complete, fault, cancel. Pinned to one executable artifact snapshot for its entire lifetime.
Activity execution actor
One concrete execution of one executable node, with a durable identity distinct from the authored activity id. Survives loops, parallel branches, retries, and nested workflows.
Scheduler actor
Owns pending work, delayed work, branch and iteration scheduling, and resumable continuations.
Bookmark / stimulus actor
Resolves external stimuli into durable resume targets. Bookmarks point to stable resume target ids inside the pinned artifact — never to C# callback method names.
Durable value boundary
Variables, workflow inputs and outputs, captured activity outputs, and external references are persisted on purpose. History is not runtime continuation state.
Incident & recovery actors
Faults, retries, leases, heartbeats, drain markers, and interrupted executions become explicit operational concepts rather than hidden side effects.
What it unlocks
Safer parallel execution
Cleaner suspension & resume
Better horizontal scaling
Inspectable runtime state
Deterministic snapshot pinning
Recovery without replaying authoring models
Extend without coupling
Two axes describe every legitimate extension in Elsa.
One implementation wins.
Replace a default implementation of a .Core contract — bring your own distributed lock provider or persistence commands.
Many contributions, one aggregator.
Add implementations alongside built-ins. A single owner-owned aggregator runs all contributions — validators, sources, contributors, processors, handlers.
One event concept, explicit delivery strategy
Contribution flows use a single aggregating handler — not scattered anonymous dispatch.
Catalogs replace live guessing
If an activity appears in the picker, it has a persisted catalog entry.
Visibility is structural: catalog presence first, context filtering later.
Providers are siblings, not leaks
The .Core package exposes the contract. Provider packages opt into concrete technology.
Compatibility through import
Import-only. No dual-run, no round-tripping, no Elsa-3-shaped runtime surface.
Why this matters
The Foundation is judged by what it makes possible for three audiences.
For users
- Smaller dependency surfaces
- Clearer deployment shapes
- Design-only, runtime-only, and combined hosts
- Replaceable infrastructure
- More predictable upgrades
For contributors
- Obvious ownership boundaries
- Cataloged extension points
- Less accidental coupling
- Safer refactoring
For Elsa itself
- Modular evolution
- Provider ecosystem growth
- Runtime hot-loading via Nuplane where appropriate
- Architecture verifiable with maps and tests
A workflow engine made of explicit parts.
Elsa Foundation turns the workflow engine from a single gravitational center into a composed system of domains, contracts, providers, catalogs, and bridges. The result is an architecture that can grow without hiding its dependencies.
Elsa Foundation · transitional architecture brain for Elsa Workflows