Skip to content

Commit 0b49430

Browse files
TaoChenOSUCopilot
andauthored
Add workflow advanced execution mode doc (#930)
* Add workflow advanced execution mode doc * Address PR review comments on execution modes doc - Add 'Step execution' row to the comparison table - Add IMPORTANT note about crossRunShareable requirement for concurrent runs - Clarify Python execution model similarity to Lockstep mode Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent a1c1e5b commit 0b49430

2 files changed

Lines changed: 147 additions & 0 deletions

File tree

agent-framework/TOC.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ items:
158158
items:
159159
- name: Agent Executor
160160
href: workflows/advanced/agent-executor.md
161+
- name: Execution Modes
162+
href: workflows/advanced/execution-modes.md
161163
- name: Resettable Executors
162164
href: workflows/advanced/resettable-executors.md
163165
- name: Integrations
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
---
2+
title: Workflow Execution Modes
3+
description: Deep dive into the OffThread and Lockstep execution modes for .NET workflows.
4+
zone_pivot_groups: programming-languages
5+
author: TaoChenOSU
6+
ms.topic: conceptual
7+
ms.author: taochen
8+
ms.date: 03/18/2026
9+
ms.service: agent-framework
10+
---
11+
12+
<!--
13+
Language parity table – keep in sync when adding/removing sections.
14+
15+
| Section | C# | Python | Notes |
16+
|------------------------|:--:|:------:|----------------|
17+
| Overview | ✅ | ❌ | C#-specific |
18+
| OffThread | ✅ | ❌ | C#-specific |
19+
| Lockstep | ✅ | ❌ | C#-specific |
20+
| Choosing a Mode | ✅ | ❌ | C#-specific |
21+
| Non-Streaming | ✅ | ❌ | C#-specific |
22+
| Not applicable notice | ❌ | ✅ | Python-specific |
23+
-->
24+
25+
# Workflow Execution Modes
26+
27+
::: zone pivot="programming-language-csharp"
28+
29+
When running a workflow in .NET, the **execution mode** controls how supersteps are processed and how events are delivered to the consumer. The `InProcessExecution` class exposes two execution modes: **OffThread** and **Lockstep**.
30+
31+
## Overview
32+
33+
| | OffThread (Default) | Lockstep |
34+
|---|---|---|
35+
| **Superstep execution** | Background thread | Consumer's thread |
36+
| **Event delivery** | Immediate, as events are raised | Batched after each superstep completes |
37+
| **Step execution** | Independent of event processing | Paused until batched events are consumed |
38+
| **Concurrency** | Consumer reads events while supersteps run | Consumer and superstep execution alternate |
39+
| **Best for** | Real-time streaming, production scenarios | Testing, debugging, deterministic ordering |
40+
41+
## OffThread
42+
43+
OffThread is the **default** execution mode. Supersteps run on a background thread, and events stream out immediately as they are raised via a channel-based implementation.
44+
45+
```csharp
46+
// OffThread is the default — these are equivalent:
47+
await using StreamingRun run = await InProcessExecution.RunStreamingAsync(workflow, input);
48+
await using StreamingRun run = await InProcessExecution.OffThread.RunStreamingAsync(workflow, input);
49+
```
50+
51+
### How it works
52+
53+
1. A background task runs supersteps continuously while messages are pending.
54+
2. As executors yield outputs or events, the resulting `WorkflowEvent` objects are written to an unbounded `Channel<WorkflowEvent>`.
55+
3. The consumer reads events from the channel via `WatchStreamAsync`, receiving them in real-time as they are produced.
56+
4. When all supersteps are complete and no messages remain, the run halts with an `Idle` or `PendingRequests` status.
57+
58+
Because the superstep loop and the consumer run concurrently, events appear as soon as they are raised — there is no buffering delay. This makes OffThread ideal for streaming scenarios where low-latency event delivery matters, such as displaying token-by-token updates in a UI.
59+
60+
### Concurrent runs
61+
62+
OffThread also supports a **concurrent** variant that allows multiple runs to share the same workflow instance simultaneously:
63+
64+
```csharp
65+
await using StreamingRun run = await InProcessExecution.Concurrent.RunStreamingAsync(workflow, input);
66+
```
67+
68+
> [!IMPORTANT]
69+
> Concurrent execution requires that all executors in the workflow be declared `crossRunShareable` (on the constructor) or be provided as factory methods.
70+
71+
## Lockstep
72+
73+
In Lockstep mode, supersteps run in the **consumer's thread** rather than on a background task. Events are accumulated during each superstep and emitted as a batch after the superstep completes.
74+
75+
```csharp
76+
await using StreamingRun run = await InProcessExecution.Lockstep.RunStreamingAsync(workflow, input);
77+
```
78+
79+
### How it works
80+
81+
1. The consumer calls `WatchStreamAsync`, which drives the execution loop.
82+
2. A superstep runs to completion, and events are accumulated in a queue.
83+
3. After the superstep finishes, all queued events are yielded to the consumer.
84+
4. The next superstep begins only after the consumer has received all events from the previous one.
85+
86+
This alternating pattern means the consumer and the workflow engine never run simultaneously. Event delivery is deterministic — all events from a superstep are guaranteed to arrive before any events from the next superstep.
87+
88+
### When to use Lockstep
89+
90+
Lockstep is useful when:
91+
92+
- **Testing** — deterministic event ordering makes assertions straightforward.
93+
- **Debugging** — step-through debugging is easier when execution stays on the consumer's thread.
94+
- **Ordered processing** — scenarios where you need to fully process one superstep's events before the next superstep begins.
95+
96+
## Choosing an Execution Mode
97+
98+
For most production scenarios, the default **OffThread** mode is recommended. It provides the best responsiveness and allows the workflow to continue processing while the consumer handles events.
99+
100+
Use **Lockstep** when deterministic behavior is more important than performance, such as in unit tests or debugging sessions.
101+
102+
```csharp
103+
// Production: OffThread (default)
104+
await using StreamingRun run = await InProcessExecution.RunStreamingAsync(workflow, input);
105+
106+
// Testing: Lockstep for deterministic behavior
107+
await using StreamingRun run = await InProcessExecution.Lockstep.RunStreamingAsync(workflow, input);
108+
```
109+
110+
## Non-Streaming Execution
111+
112+
Both execution modes support non-streaming execution via `RunAsync`. In non-streaming mode, the workflow runs to completion and collects all events into a `Run` object rather than streaming them incrementally:
113+
114+
```csharp
115+
Run run = await InProcessExecution.RunAsync(workflow, input);
116+
117+
// Access all emitted events
118+
foreach (WorkflowEvent evt in run.OutgoingEvents)
119+
{
120+
// Process events
121+
}
122+
```
123+
124+
Because non-streaming execution collects all events after completion, the real-time event delivery benefit of OffThread does not apply. The primary difference between modes in non-streaming scenarios is **threading**: OffThread runs supersteps on a background thread, freeing the calling thread while awaiting completion, whereas Lockstep runs supersteps on the caller's thread, blocking it until the workflow finishes.
125+
126+
Non-streaming execution uses the default OffThread mode. To use Lockstep with non-streaming execution:
127+
128+
```csharp
129+
Run run = await InProcessExecution.Lockstep.RunAsync(workflow, input);
130+
```
131+
132+
## Next steps
133+
134+
> [!div class="nextstepaction"]
135+
> [Workflow Builder & Execution](../workflows.md)
136+
137+
::: zone-end
138+
139+
::: zone pivot="programming-language-python"
140+
141+
Execution modes are not applicable to Python workflows. Python workflows use a single execution model that handles superstep processing and event delivery through an asynchronous generator. This model is similar to the .NET Lockstep mode — steps don't advance unless the consumer is actively pulling events from the generator.
142+
143+
For information on running Python workflows, see [Workflow Builder & Execution](../workflows.md).
144+
145+
::: zone-end

0 commit comments

Comments
 (0)