How to Migrate from Semantic Kernel to Agent Framework: A Complete Guide
TLDR: To migrate from Semantic Kernel to the Agent Framework, replace KernelProcessStep subclasses with Executor classes using the @handler decorator, convert ProcessBuilder graph definitions to WorkflowBuilder edges, and execute workflows natively via workflow.run() instead of using the SK kernel runtime.
The microsoft/agent-framework repository offers a native Python alternative to Semantic Kernel's process orchestration model, eliminating the need for an external kernel while maintaining a graph-based execution philosophy. Both frameworks compose AI agents through directed graphs, but the Agent Framework uses typed message passing via WorkflowContext rather than SK's event emission model. This guide maps each Semantic Kernel construct to its Agent Framework equivalent using production code from the official migration samples.
Core Abstraction Mapping
Understanding the conceptual differences between the two frameworks simplifies the migration:
| Aspect | Semantic Kernel | Agent Framework |
|---|---|---|
| Primary abstraction | KernelProcess – directed graph of steps |
Workflow – graph of Executor nodes |
| Step definition | Sub-class KernelProcessStep with @kernel_function |
Sub-class Executor with @handler |
| Message passing | Events (ProcessEvent) with visibility levels |
Typed messages via WorkflowContext[In, Out] |
| Orchestration | ProcessBuilder + kernel-hosted start runtime |
WorkflowBuilder + native WorkflowExecutor |
| Nested workflows | Sub-processes via add_step_from_process |
WorkflowExecutor instances linked via add_edge |
| AI clients | OpenAIChatCompletion SK service |
OpenAIChatCompletionClient direct injection |
Migrating a Nested Process
The migration transforms a parent-child process hierarchy into a workflow containing nested WorkflowExecutor instances.
Semantic Kernel Implementation
In Semantic Kernel, nested processes require registering a sub-process as a step:
# From: python/samples/semantic-kernel-migration/processes/nested_process.py
process_builder = _create_linear_process("Outer")
nested_process_step = process_builder.add_step_from_process(
_create_linear_process("Inner")
)
process_builder.steps[1].on_event(
ProcessEvents.OUTPUT_READY_INTERNAL.value
).send_event_to(
nested_process_step.where_input_event_is(ProcessEvents.START_PROCESS.value)
)
Agent Framework Implementation
The Agent Framework treats nested workflows as first-class nodes:
# From: python/samples/semantic-kernel-migration/processes/nested_process.py
inner_executor = _build_inner_workflow() # Returns WorkflowExecutor
outer_workflow = (
WorkflowBuilder(start_executor=kickoff)
.add_edge(kickoff, outer_echo)
.add_edge(outer_echo, outer_repeat)
.add_edge(outer_repeat, inner_executor) # Nested workflow as node
.add_edge(inner_executor, collector)
.build()
)
Key implementation files include [python/samples/semantic-kernel-migration/processes/nested_process.py] for the side-by-side comparison and [python/packages/orchestrations/tests/test_sequential.py] for unit test validation.
Migrating Sequential Orchestration
For linear agent chains, replace SequentialOrchestration with SequentialBuilder from [python/packages/orchestrations/_orchestration.py].
Semantic Kernel Sequential Pattern
# Requires SK kernel and runtime
sequential_orchestration = SequentialOrchestration(
members=build_semantic_kernel_agents(),
agent_response_callback=sk_agent_response_callback,
)
runtime = InProcessRuntime()
await sequential_orchestration.invoke(task=prompt, runtime=runtime)
Agent Framework Sequential Pattern
# Native Python execution, no kernel required
writer = Agent(client=client, instructions=..., name="writer")
reviewer = Agent(client=client, instructions=..., name="reviewer")
workflow = SequentialBuilder(participants=[writer, reviewer]).build()
async for event in workflow.run(prompt, stream=True):
if event.type == "output":
# Process final messages
Reference implementation available in [python/samples/semantic-kernel-migration/orchestrations/sequential.py].
Step-by-Step Migration Guide
Follow these six steps to convert existing Semantic Kernel processes:
-
Identify SK steps – Locate classes inheriting
KernelProcessStepand functions decorated with@kernel_function. -
Create AF Executors – Implement
Executorsubclasses with@handlerdecorated methods. MapKernelProcessStepinput/output toWorkflowContexttype parameters:class OuterRepeatExecutor(Executor): def __init__(self, *, inner_target_id: str) -> None: super().__init__(id="outer_repeat") self._inner_target_id = inner_target_id @handler async def repeat( self, payload: RepeatPayload, ctx: WorkflowContext[RepeatPayload] ) -> None: repeated = " ".join([payload.message] * payload.count) await ctx.send_message( RepeatPayload(message=repeated, count=2), target_id=self._inner_target_id ) -
Rebuild the graph – Replace
ProcessBuilderwithWorkflowBuilder:- Define a start executor that receives initial input
- Connect nodes using
.add_edge(parent, child) - For nested processes, instantiate
WorkflowExecutorand attach it like any other node
-
Update runtime calls – Remove
Kernelimports andstartinvocations. Execute via:async for event in outer_workflow.run(initial_message, stream=True): if event.type == "output": results.append(event.data) -
Update dependencies – Remove
semantic-kernelfromrequirements.txt, replaceOpenAIChatCompletionwithOpenAIChatCompletionClient, and ensureagent-frameworkis installed from[python/packages/agent-framework]. -
Validate – Run AF unit tests in
[python/packages/orchestrations/tests/]to verify functional parity with original SK behavior.
Complete Migration Example: Nested Process
The following implementation from [python/samples/semantic-kernel-migration/processes/nested_process.py] demonstrates KickoffExecutor, message passing via ctx.send_message(), and final output collection via ctx.yield_output():
import asyncio
from dataclasses import dataclass
from typing import Never, cast, Sequence
from agent_framework import Executor, WorkflowBuilder, WorkflowContext, WorkflowExecutor, handler
from dotenv import load_dotenv
load_dotenv()
@dataclass
class RepeatPayload:
message: str
count: int = 2
class KickoffExecutor(Executor):
@handler
async def start(self, message: str, ctx: WorkflowContext[RepeatPayload]) -> None:
await ctx.send_message(RepeatPayload(message=message, count=2))
class OuterEchoExecutor(Executor):
@handler
async def echo(self, payload: RepeatPayload, ctx: WorkflowContext[RepeatPayload]) -> None:
await ctx.send_message(payload)
class OuterRepeatExecutor(Executor):
def __init__(self, *, inner_target_id: str) -> None:
super().__init__(id="outer_repeat")
self._inner_target_id = inner_target_id
@handler
async def repeat(self, payload: RepeatPayload, ctx: WorkflowContext[RepeatPayload]) -> None:
repeated = " ".join([payload.message] * payload.count)
await ctx.send_message(
RepeatPayload(message=repeated, count=2),
target_id=self._inner_target_id
)
class InnerEchoExecutor(Executor):
@handler
async def echo(self, payload: RepeatPayload, ctx: WorkflowContext[RepeatPayload]) -> None:
await ctx.send_message(payload)
class InnerRepeatExecutor(Executor):
@handler
async def repeat(self, payload: RepeatPayload, ctx: WorkflowContext[Never, str]) -> None:
repeated = " ".join([payload.message] * payload.count)
await ctx.yield_output(repeated)
class CollectResultExecutor(Executor):
@handler
async def collect(self, result: str, ctx: WorkflowContext[Never, str]) -> None:
await ctx.yield_output(result)
def _build_inner_workflow() -> WorkflowExecutor:
inner_echo = InnerEchoExecutor()
inner_repeat = InnerRepeatExecutor()
inner_wf = WorkflowBuilder(
start_executor=inner_echo
).add_edge(inner_echo, inner_repeat).build()
return WorkflowExecutor(inner_wf, id="inner_workflow")
async def run_agent_framework_nested_workflow(initial_message: str) -> Sequence[str]:
inner_executor = _build_inner_workflow()
kickoff = KickoffExecutor()
outer_echo = OuterEchoExecutor()
outer_repeat = OuterRepeatExecutor(inner_target_id=inner_executor.id)
collector = CollectResultExecutor()
outer_wf = (
WorkflowBuilder(start_executor=kickoff)
.add_edge(kickoff, outer_echo)
.add_edge(outer_echo, outer_repeat)
.add_edge(outer_repeat, inner_executor)
.add_edge(inner_executor, collector)
.build()
)
results: list[str] = []
async for ev in outer_wf.run(initial_message, stream=True):
if ev.type == "output":
results.append(cast(str, ev.data))
return results
This implementation produces identical output to the Semantic Kernel version: "Test Test Test Test".
Key Implementation Files
Reference these files when migrating your own implementations:
[python/samples/semantic-kernel-migration/processes/nested_process.py]– Side-by-side nested workflow comparison[python/samples/semantic-kernel-migration/orchestrations/sequential.py]– Linear agent chain migration[python/packages/orchestrations/_orchestration.py]–SequentialBuilderimplementation[python/agent_framework/__init__.py]– Core exports includingExecutorandWorkflowBuilder[python/packages/orchestrations/tests/test_sequential.py]– Unit tests for workflow patterns[python/packages/orchestrations/tests/test_orchestration_request_info.py]– Orchestration request validation
Summary
- Replace
KernelProcessStepwithExecutorsubclasses using@handlerdecorators instead of@kernel_function - Convert SK events to typed AF messages via
WorkflowContext[In, Out]usingsend_message()andyield_output() - Construct workflows using
WorkflowBuilder.add_edge()orSequentialBuilderinstead ofProcessBuilderevent wiring - Execute using native Python
async forloops withworkflow.run(stream=True)rather than kernel-hosted runtimes - Nest workflows by passing
WorkflowExecutorinstances as nodes to parent builders - Update AI clients from SK services to direct
OpenAIChatCompletionClientinjection
Frequently Asked Questions
What replaces KernelProcessStep in the Agent Framework?
The Executor class replaces KernelProcessStep. While SK steps use @kernel_function to expose capabilities, AF executors use the @handler decorator on async methods that receive typed payloads and a WorkflowContext for message operations. The main entry point exports are defined in [python/agent_framework/__init__.py].
How do Semantic Kernel events map to Agent Framework constructs?
SK's ProcessEvent (with Public/Internal visibility) maps to typed messages passed via ctx.send_message(). Instead of event names and string payloads, AF uses Python dataclasses as message types with generic WorkflowContext[In, Out] typing, enabling compile-time safety for workflow edges.
Can I migrate gradually or does it require a full rewrite?
Migration requires a full rewrite of the orchestration layer because the runtime models are fundamentally different: SK requires a kernel-hosted process runtime while AF uses native Python async execution. However, the business logic inside your step functions translates directly to handler methods, and the graph structure in ProcessBuilder ports one-to-one to WorkflowBuilder edges.
Where are the official migration samples located?
The repository provides side-by-side implementations in [python/samples/semantic-kernel-migration/], specifically [python/samples/semantic-kernel-migration/processes/nested_process.py] for hierarchical workflows and [python/samples/semantic-kernel-migration/orchestrations/sequential.py] for linear agent chains. Additional validation tests reside in [python/packages/orchestrations/tests/test_sequential.py].
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →