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:

  1. Identify SK steps – Locate classes inheriting KernelProcessStep and functions decorated with @kernel_function.

  2. Create AF Executors – Implement Executor subclasses with @handler decorated methods. Map KernelProcessStep input/output to WorkflowContext type 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
            )
  3. Rebuild the graph – Replace ProcessBuilder with WorkflowBuilder:

    • Define a start executor that receives initial input
    • Connect nodes using .add_edge(parent, child)
    • For nested processes, instantiate WorkflowExecutor and attach it like any other node
  4. Update runtime calls – Remove Kernel imports and start invocations. Execute via:

    async for event in outer_workflow.run(initial_message, stream=True):
        if event.type == "output":
            results.append(event.data)
  5. Update dependencies – Remove semantic-kernel from requirements.txt, replace OpenAIChatCompletion with OpenAIChatCompletionClient, and ensure agent-framework is installed from [python/packages/agent-framework].

  6. 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]SequentialBuilder implementation
  • [python/agent_framework/__init__.py] – Core exports including Executor and WorkflowBuilder
  • [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 KernelProcessStep with Executor subclasses using @handler decorators instead of @kernel_function
  • Convert SK events to typed AF messages via WorkflowContext[In, Out] using send_message() and yield_output()
  • Construct workflows using WorkflowBuilder.add_edge() or SequentialBuilder instead of ProcessBuilder event wiring
  • Execute using native Python async for loops with workflow.run(stream=True) rather than kernel-hosted runtimes
  • Nest workflows by passing WorkflowExecutor instances as nodes to parent builders
  • Update AI clients from SK services to direct OpenAIChatCompletionClient injection

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →