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

PieceWhat 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_typerouter (condition) vs contract (type check that raises)
WorkflowBuilderadd_executor, add_edge(src, dst, message_type=, condition=), set_start, build
WorkflowContext.sharedsession state every executor reads/writes; part of the checkpoint
Workflow._drivethe super-step loop: deliver the queue, run/route, defer HITL, honor pause_after
run / resumerun 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

FileRole
lab.pyyour implementation (dataclasses/builder given; fill the routing + super-step # TODOs)
solution.pyreference + a worked example: typed pipeline, even/odd router, checkpoint/resume, HITL
test_lab.py23 tests: linear typed flow, routing, WorkflowTypeError, shared state, checkpoint/resume, HITL pause/resume + missing-response error, cycle guard, determinism
requirements.txtpytest 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 WorkflowContext state written by one executor is visible to a later one — and survives a checkpoint.
  • run(input, pause_after=k) returns paused with a Checkpoint; resume(checkpoint) yields the same output as an uninterrupted run.
  • A RequestInfoExecutor pauses with a RequestInfoEvent; resume(checkpoint, {id: response}) routes the response downstream; a missing response raises WorkflowError.
  • 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's WorkflowBuilder / 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 + resume is 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.
  • RequestInfoExecutor is MSAF's real human-in-the-loop node. The pattern — emit a request, checkpoint, return paused, get the answer out of band, resume with the response — is exactly LangGraph's interrupt() + checkpointer (Phase 18 Lab 03). HITL and durability are one mechanism.
  • WorkflowContext.shared is 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 Checkpoint JSON- or pickle-serializable, write it to disk, kill the process, reload it in a fresh process, and resume — 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/str edge 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."