Lab 02 — MSAF Graph Workflow (typed edges, checkpointing, HITL)
Phase 23 · Lab 02 · Phase README · Warmup · Hitchhiker's
The problem
The Microsoft Agent Framework's headline new capability — the thing neither AutoGen nor Semantic Kernel had — is the graph-based Workflow: explicit orchestration you can reason about, replay, and audit, for the 90% of tasks whose structure is knowable (Phase 00's least-agentic principle, shipped as an API — "if you can write a function, do that"). This lab builds that engine faithfully: executors wired by type-safe edges with conditional routing, a shared context, a Pregel super-step runtime (reused from Phase 18), checkpointing (save + resume mid-workflow), and a human-in-the-loop node that pauses for input and resumes. Handlers are injected pure functions, so the engine is deterministic and offline.
The crux to get right: an edge's message_type is a CONTRACT (a mismatch raises
WorkflowTypeError), while its condition is the ROUTER (the edge fires only if the predicate
holds). Keeping them distinct is the whole point — conflating them turns a type bug into a silently
dropped message.
What you build
| Piece | What it does |
|---|---|
Executor(id, handler) | a node; handler(payload, ctx) -> output (an agent or a plain function) |
RequestInfoExecutor(id, prompt) | the human-in-the-loop node — reaching it suspends the workflow |
Edge.fires / Edge.check_type | router (condition) vs contract (type check that raises) |
WorkflowBuilder | add_executor, add_edge(src, dst, message_type=, condition=), set_start, build |
WorkflowContext.shared | session state every executor reads/writes; part of the checkpoint |
Workflow._drive | the super-step loop: deliver the queue, run/route, defer HITL, honor pause_after |
run / resume | run to completion/pause; resume from a Checkpoint, injecting human responses |
A node with no firing outgoing edge is a leaf — its output is collected as a workflow output.
File map
| File | Role |
|---|---|
lab.py | your implementation (dataclasses/builder given; fill the routing + super-step # TODOs) |
solution.py | reference + a worked example: typed pipeline, even/odd router, checkpoint/resume, HITL |
test_lab.py | 23 tests: linear typed flow, routing, WorkflowTypeError, shared state, checkpoint/resume, HITL pause/resume + missing-response error, cycle guard, determinism |
requirements.txt | pytest only |
Run it
pytest test_lab.py -v # your lab.py (red until you implement)
LAB_MODULE=solution pytest test_lab.py -v # the reference (green)
python solution.py # the typed-pipeline / router / checkpoint / HITL demo
Success criteria
- A linear workflow runs executors in order, passing typed data; the leaf output is collected.
- A conditional/router edge branches (even → one node, odd → another).
-
A payload of the wrong type on a typed edge raises
WorkflowTypeError; a matching type passes. -
Shared
WorkflowContextstate written by one executor is visible to a later one — and survives a checkpoint. -
run(input, pause_after=k)returnspausedwith aCheckpoint;resume(checkpoint)yields the same output as an uninterrupted run. -
A
RequestInfoExecutorpauses with aRequestInfoEvent;resume(checkpoint, {id: response})routes the response downstream; a missing response raisesWorkflowError. -
A cyclic graph hits
max_supersteps; runs are deterministic. All 23 tests pass under both modules.
How this maps to the real stack
WorkflowBuilder+ executors + typed edges is MSAF'sWorkflowBuilder/ executor graph. MSAF edges are genuinely type-safe; a mismatch is a runtime error at the wiring, not a silently-misrouted message — Semantic Kernel's type-safety DNA applied to orchestration.- The super-step runtime is Pregel/BSP, the same model MSAF Workflows (and LangGraph, Phase 18) run on. It is what makes a checkpoint a clean snapshot: the message queue + shared state captured at a super-step boundary.
Checkpoint+resumeis MSAF's checkpointing. In production it serializes to a checkpoint store (database/blob) so a different process can resume the workflow hours later — the durable-execution idea (Phase 08) at the orchestration layer.RequestInfoExecutoris MSAF's real human-in-the-loop node. The pattern — emit a request, checkpoint, returnpaused, get the answer out of band,resumewith the response — is exactly LangGraph'sinterrupt()+ checkpointer (Phase 18 Lab 03). HITL and durability are one mechanism.WorkflowContext.sharedis the workflow-scoped cousin of MSAF/SK session state (threads).
Limits of the miniature. The checkpoint is in-memory (no serialization to a store); executors are
sync pure functions (real ones are async and call models/tools/MCP); there is no distribution across
workers, no middleware/telemetry pipeline, and no streaming of events over the network. Type checking
is isinstance at edge-fire time, not a full static type system. The orchestration shape — typed
executors, router vs contract edges, super-steps, checkpoint/resume, HITL — is exactly this.
Extensions (your own machine)
- Serialize the checkpoint. Make
CheckpointJSON- or pickle-serializable, write it to disk, kill the process, reload it in a fresh process, andresume— the real durable-execution demo. - Fan-in / join. Add an executor that waits for messages from two upstream edges in the same super-step and merges them (concurrent fan-out then fan-in), with a reducer per the Phase 18 channel idea.
- Middleware. Wrap executor invocation with an interceptor list (logging, a guardrail, an authz check) — the SK/MSAF filters pattern (WARMUP §13).
- Typed via dataclasses. Replace
int/stredge types with real message dataclasses (ParsedEmail,AccountFacts) so the type contract carries domain meaning, and watch a wrong shape error. - Wire a real agent into an executor handler (one function swap) and keep the surrounding graph identical.
Interview / resume signal
"Built a Microsoft-Agent-Framework-style graph Workflow from scratch — executors wired by type-safe edges (a type contract that raises, distinct from a routing condition), a Pregel super-step runtime, checkpoint/resume that reproduces an uninterrupted run byte-for-byte, and a human-in-the-loop node that pauses and resumes with injected input. The key insight: HITL and durable checkpointing are the same mechanism — a pause at a super-step boundary — which is why the workflow survives a crash or a long human-approval wait without re-running completed work."