Skip to content

Commit f0be4d3

Browse files
Merge pull request #341 from MicrosoftDocs/main639038620450971543sync_temp
Repo sync for protected branch
2 parents 1b722c9 + bc455f1 commit f0be4d3

File tree

2 files changed

+56
-12
lines changed

2 files changed

+56
-12
lines changed

agent-framework/tutorials/workflows/simple-sequential-workflow.md

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,9 @@ In this tutorial, you'll create a workflow with two executors:
203203

204204
The workflow demonstrates core concepts like:
205205

206-
- Using the `@executor` decorator to create workflow nodes
206+
- Two ways to define a unit of work (an executor node):
207+
1. A custom class that subclasses `Executor` with an async method marked by `@handler`
208+
2. A standalone async function decorated with `@executor`
207209
- Connecting executors with `WorkflowBuilder`
208210
- Passing data between steps with `ctx.send_message()`
209211
- Yielding final output with `ctx.yield_output()`
@@ -260,13 +262,16 @@ class UpperCase(Executor):
260262

261263
**Key Points:**
262264

263-
- The `@executor` decorator registers this function as a workflow node
264-
- `WorkflowContext[str]` indicates this executor sends a string downstream by specifying the first type parameter
265-
- `ctx.send_message()` passes data to the next step
265+
- Subclassing `Executor` lets you define a named node with lifecycle hooks if needed
266+
- The `@handler` decorator marks the async method that does the work
267+
- The handler signature follows a contract:
268+
- First parameter is the typed input to this node (here: `text: str`)
269+
- Second parameter is a `WorkflowContext[T_Out]`, where `T_Out` is the type of data this node will emit via `ctx.send_message()` (here: `str`)
270+
- Within a handler you typically compute a result and forward it to downstream nodes using `ctx.send_message(result)`
266271

267272
### Step 3: Create the Second Executor
268273

269-
Create an executor that reverses the text and yields the final output from a method decorated with `@executor`:
274+
For simple steps you can skip subclassing and define an async function with the same signature pattern (typed input + `WorkflowContext`) and decorate it with `@executor`. This creates a fully functional node that can be wired into a flow:
270275

271276
```python
272277
@executor(id="reverse_text_executor")
@@ -280,9 +285,12 @@ async def reverse_text(text: str, ctx: WorkflowContext[Never, str]) -> None:
280285

281286
**Key Points:**
282287

283-
- `WorkflowContext[Never, str]` indicates this is a terminal executor that does not send any messages by specifying `Never` as the first type parameter but produce workflow outputs by specifying `str` as the second parameter
284-
- `ctx.yield_output()` provides the final workflow result
285-
- The workflow completes when it becomes idle
288+
- The `@executor` decorator transforms a standalone async function into a workflow node
289+
- The `WorkflowContext` is parameterized with two types:
290+
- `T_Out = Never`: this node does not send messages to downstream nodes
291+
- `T_W_Out = str`: this node yields workflow output of type `str`
292+
- Terminal nodes yield outputs using `ctx.yield_output()` to provide workflow results
293+
- The workflow completes when it becomes idle (no more work to do)
286294

287295
### Step 4: Build the Workflow
288296

@@ -336,12 +344,22 @@ Workflow completed with result: DLROW OLLEH
336344

337345
## Key Concepts Explained
338346

347+
### Two Ways to Define Executors
348+
349+
1. **Custom class (subclassing `Executor`)**: Best when you need lifecycle hooks or complex state. Define an async method with the `@handler` decorator.
350+
2. **Function-based (`@executor` decorator)**: Best for simple steps. Define a standalone async function with the same signature pattern.
351+
352+
Both approaches use the same handler signature:
353+
- First parameter: the typed input to this node
354+
- Second parameter: a `WorkflowContext[T_Out, T_W_Out]`
355+
339356
### Workflow Context Types
340357

341358
The `WorkflowContext` generic type defines what data flows between executors:
342359

343-
- `WorkflowContext[str]` - Sends a string to the next executor
344-
- `WorkflowContext[Never, str]` - Terminal executor that yields workflow output of type string
360+
- `WorkflowContext[T_Out]` - Used for nodes that send messages of type `T_Out` to downstream nodes via `ctx.send_message()`
361+
- `WorkflowContext[T_Out, T_W_Out]` - Used for nodes that also yield workflow output of type `T_W_Out` via `ctx.yield_output()`
362+
- `WorkflowContext` without type parameters is equivalent to `WorkflowContext[Never, Never]`, meaning this node neither sends messages to downstream nodes nor yields workflow output
345363

346364
### Event Types
347365

agent-framework/user-guide/workflows/core-concepts/workflows.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,17 @@ The framework performs comprehensive validation when building workflows:
126126

127127
### Execution Model
128128

129-
The framework uses a modified [Pregel](https://kowshik.github.io/JPregel/pregel_paper.pdf) execution model with clear data flow semantics and superstep-based processing.
129+
The framework uses a modified [Pregel](https://kowshik.github.io/JPregel/pregel_paper.pdf) execution model, a Bulk Synchronous Parallel (BSP) approach with clear data flow semantics and superstep-based processing.
130130

131131
### Pregel-Style Supersteps
132132

133-
Workflow execution is organized into discrete supersteps, where each superstep processes all available messages in parallel:
133+
Workflow execution is organized into discrete supersteps. A superstep is an atomic unit of execution where:
134+
135+
1. All pending messages from the previous superstep are collected
136+
2. Messages are routed to their target executors based on edge definitions
137+
3. All target executors run concurrently within the superstep
138+
4. The superstep waits for all executors to complete before advancing to the next superstep
139+
5. Any new messages emitted by executors are queued for the next superstep
134140

135141
```text
136142
Superstep N:
@@ -140,15 +146,35 @@ Superstep N:
140146
│ Messages │ │ & Conditions │ │ Executors │
141147
└─────────────────┘ └─────────────────┘ └─────────────────┘
142148
149+
│ (barrier: wait for all)
143150
┌─────────────────┐ ┌─────────────────┐ │
144151
│ Start Next │◀───│ Emit Events & │◀────────────┘
145152
│ Superstep │ │ New Messages │
146153
└─────────────────┘ └─────────────────┘
147154
```
148155

156+
### Superstep Synchronization Barrier
157+
158+
The most important characteristic of the Pregel model is the synchronization barrier between supersteps. Within a single superstep, all triggered executors run in parallel, but the workflow will not advance to the next superstep until every executor in the current superstep completes.
159+
160+
This has important implications for fan-out patterns: if you fan out to multiple paths and one path contains a chain of executors while another is a single long-running executor, the chained path cannot advance to its next step until the long-running executor completes. All executors triggered in the same superstep must finish before any downstream executors can begin.
161+
162+
### Why Superstep Synchronization?
163+
164+
The BSP model provides important guarantees:
165+
166+
- **Deterministic execution**: Given the same input, the workflow always executes in the same order
167+
- **Reliable checkpointing**: State can be saved at superstep boundaries for fault tolerance
168+
- **Simpler reasoning**: No race conditions between supersteps; each superstep sees a consistent view of messages
169+
170+
### Working with the Superstep Model
171+
172+
If you need truly independent parallel paths that don't block each other, consider consolidating sequential steps into a single executor. Instead of chaining multiple executors (e.g., `step1 -> step2 -> step3`), combine that logic into one executor that performs all steps internally. This way, both parallel paths execute within a single superstep and complete in the time of the slowest path.
173+
149174
### Key Execution Characteristics
150175

151176
- **Superstep Isolation**: All executors in a superstep run concurrently without interfering with each other
177+
- **Synchronization Barrier**: The workflow waits for all executors in a superstep to complete before advancing
152178
- **Message Delivery**: Messages are delivered in parallel to all matching edges
153179
- **Event Streaming**: Events are emitted in real-time as executors complete processing
154180
- **Type Safety**: Runtime type validation ensures messages are routed to compatible handlers

0 commit comments

Comments
 (0)