Repository navigation
Behavioral Groups
A State Machine can coordinate other Behaviors by associating them with its states. The embedding APIs create a relationship between the State Machine and the Activity or Action it starts. Stateflows records the immediate parent and the group owner on the embedded Behavior, making it possible to communicate with related Behaviors and share state through owned Entities.
AddDoActivity and AddDoAction attach a do-Behavior to a State. It is initialized when that State is entered and finalized when the State is exited. While the State is active, the do-Behavior remains part of the active configuration and can receive events. Stateflows offers an event to the embedded Behavior first; if it does not consume the event, the event can bubble to the parent State Machine, subject to the event's no-bubbling settings.
.AddInitialState("Working", state => state
.AddDoActivity(activity => activity
.AddAcceptEventAction<Refresh>(_ => Task.CompletedTask)
)
.AddTransition<Complete>("Done")
)
.AddState("Done")This is the right form when the Activity or Action represents ongoing work associated with a State, rather than a single entry, exit, guard, or transition operation. The Behavior instance is tied to the active state configuration; on state entry Stateflows initializes the state-associated Behavior, and on exit it finalizes it.
AddSubmachine embeds another State Machine in a state. Like a do-Activity or do-Action, the submachine is a state-lifetime Behavior: it is initialized when the containing state is entered and finalized when that state is exited. It is not a fresh invocation for every event or transition. The submachine is a first-class member of the same behavioral group, so it can use parent/owner context APIs and Entities owned by the group.
.AddInitialState("Processing", state => state
.AddSubmachine<PaymentWorkflow>()
.AddTransition<PaymentCompleted>("Complete")
)
.AddState("Complete")For each event delivered to the containing State Machine, Stateflows first offers the event to an active embedded submachine that accepts it. If the submachine does not consume it, the event can be retransmitted to the containing State Machine. NoForwarding on the event type or in its headers can suppress this automatic forwarding. This lets a parent provide transitions for events not handled by the active submachine while keeping submachine-specific transitions local to the submachine.
Submachines can themselves embed further submachines or other state-lifetime Behaviors. In a nested group, the immediate containing Behavior is the parent, while the outer root remains the owner; this is why owner-scoped Entities and owner context APIs are useful when shared data must be addressable across nesting.
Other Add*Activity and Add*Action methods run a child Behavior for a particular invocation, not as a state-lifetime do-Behavior. Examples include AddOnEntryActivity / AddOnEntryAction, AddOnExitActivity / AddOnExitAction, AddGuardActivity / AddGuardAction, and AddEffectActivity / AddEffectAction (with corresponding default-transition and deferral variants).
These are invocation-scoped Behaviors, not persistent do-Behaviors that stay active with the State. Entry and exit invocations use generated instance IDs; guard and effect invocations use the current event ID. For example, an entry Behavior runs when the state is entered, while an effect Behavior runs for a transition effect. They still carry parent and owner relationship metadata. Use these forms for work that belongs to that lifecycle hook or transition operation.
| Form | When it starts | Lifetime relationship |
|---|---|---|
AddDoActivity / AddDoAction
|
State entry | Associated with the active State; finalized on state exit |
AddSubmachine |
State entry | Embedded State Machine remains active with the containing State; finalized on state exit |
Entry / exit / guard / effect / deferral Add*Activity and Add*Action
|
Each relevant hook or operation | Invocation-scoped Behavior, not a state-lifetime child |
The parent is the immediate Behavior that started the embedded Behavior. The owner is the root Behavior that owns the behavioral group. If a State Machine is itself embedded, its children have that State Machine as parent but inherit the same group owner. For a directly embedded child of a top-level State Machine, that State Machine is both parent and owner.
In an Activity node callback or an Action delegate, use TryGetParentBehaviorContext or TryGetOwnerBehaviorContext to obtain the dedicated context. These methods return false when the relationship is not available:
async context =>
{
if (context.TryGetParentBehaviorContext(out var parent))
{
parent.Send(new SomeEvent());
parent.Publish(new SomeNotification());
}
if (context.TryGetOwnerBehaviorContext(out var owner))
{
await owner.TryMutateAsync<ISharedOrder, AddUnits>(new AddUnits(1));
}
}Send is a one-way event send and returns no completion task. Publish publishes a notification as the selected parent or owner Behavior. The parent and owner contexts also provide subscription operations and Entity operations. Entity operations such as TryMutateAsync, TryGetProjectionAsync, TryGetFieldAsync, and TrySetFieldAsync are asynchronous and report whether they succeeded.
Choose the relationship deliberately: use parent for the immediate containing Behavior; use owner when the operation or data belongs to the whole group and must remain addressable through nested embeddings. If both identify the same Behavior, either API targets that Behavior.
Typed runtime workers can constructor-inject IParentBehaviorContext or IOwnerBehaviorContext directly when the relationship is required. Resolution fails when that relationship is absent; standalone-capable workers should use the callback's TryGet... methods instead. An embedded worker receives its own behavior/execution context, not its parent's current state or node context. See Dependency Injection.
Behaviors in a group do not share mutable CLR objects or execution context. Put shared durable state in an Entity owned by the group owner, then let children read its projections or apply mutation events through the owner context. The State Machine builder can register an Entity alongside its states:
using Stateflows.Common;
using Stateflows.Activities;
using Stateflows.Entities.Attributes;
using Stateflows.StateMachines;
public sealed record AddUnits(int Amount);
public interface ISharedOrder
{
[Field]
int Units { get; set; }
[Mutation]
void Add(AddUnits mutation) => Units += mutation.Amount;
[Projection]
int CurrentUnits => Units;
}public sealed class Order : IStateMachine
{
public static void Build(IStateMachineBuilder machine) => machine
.AddEntity<ISharedOrder>()
.AddInitialState("Working", state => state
.AddDoActivity(activity => activity
.AddInitial(node => node.AddControlFlow("update"))
.AddAction("update", async context =>
{
if (context.TryGetOwnerBehaviorContext(out var owner))
{
await owner.TryMutateAsync<ISharedOrder, AddUnits>(new AddUnits(1));
}
}, node => node.AddControlFlow<FinalNode>()
)
.AddFinal()
)
)
.AddState("Done");
}The Entity name is derived from the template type because registration omits a custom name; each owner instance has its own Entity instance. Another Behavior in that group can address the same Entity through IOwnerBehaviorContext:
if (context.TryGetOwnerBehaviorContext(out var owner))
{
var (success, units) =
await owner.TryGetProjectionAsync<ISharedOrder, int>();
}Entity operations still follow the Entity contract: mutations must be registered, fields have configured access, and projections define the read model. See Entities and Interacting with Entities for definition, access, and publication details. For constructor-injected values, [FromProjection<TTemplate>(EntityScope.Owner)] and [FromField<TTemplate>(..., EntityScope.Owner)] can resolve data from the group owner; the corresponding EntityScope.Parent targets the immediate parent.
Projection notifications can also be routed to a group's parent or owner. Configure [Projection(PublishScope = PublishScope.Owner)] when owner subscribers should receive a changed projection, or PublishScope.Parent for the immediate parent. These scopes select the behavior that publishes; they are distinct from [FromProjection(...)] constructor injection. See Entity Projections.
Home page Support Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0. © by Mikołaj Milewski, 2026
Overview
Installation
Runtimes
Orleans runtime
Concepts
Events and Structural Events
Messaging Patterns
Validation
Scheduling
Observability
Behaviors
Behavior Values
Dependency Injection
Behavioral Groups
State Machines
Concepts
State Identity
In-State Event Deferrals
Evaluation of Transitions
Exception Handling
Building blocks
Regions
States
State
Composite State
Orthogonal State
Final State
Pseudostates
Choice
Junction
Fork
Join
History
Transitions
Transition
Default Transition
Internal Transition
Activities
Concepts
Exception Handling
Implicit Fork and Join
Tokens
Building blocks
Nodes
Action Node
Decision Node
Merge Node
Initial Node
Final Node
Input Node
Output Node
Fork Node
Join Node
Accept Event Action Node
Send Event Action Node
Publish Event Action Node
Time Event Action Node
Data Store Node
Structured Activity Node
Iterative Activity Node
Parallel Activity Node
Flows
Data Flow
Control Flow
Actions
Entities
Fields
Mutations
Projections
Extensions
ClearScript
FluentValidation
JSON definitions
Minimal APIs
OneOf
OpenTelemetry
PlantUML
Scheduling