Implementation:Microsoft Semantic kernel KernelProcessEvent Emit
| Knowledge Sources | |
|---|---|
| Domains | Process_Orchestration, Event_Driven_Architecture |
| Last Updated | 2026-02-11 19:00 GMT |
Overview
Pattern for implementing conditional branching, loops, and termination within process workflows by selectively emitting KernelProcessEvent objects from step functions via context.EmitEventAsync.
Description
KernelProcessStepContext.EmitEventAsync is the mechanism by which a process step communicates control flow decisions to the process runtime. By choosing which KernelProcessEvent to emit (and whether to emit one at all), a step function determines which downstream path the process takes. This pattern, combined with the event routing configuration in the process builder, implements conditional branching, iterative loops, and process termination.
The KernelProcessEvent record carries an Id property (used by the routing layer to determine the target) and an optional Data property (passed as input to the target function). The Visibility property controls whether the event is visible to parent processes or remains internal to the current process scope.
Usage
Use context.EmitEventAsync within any [KernelFunction] method on a KernelProcessStep when you need to control which downstream steps execute next. Emit different event Ids to implement branching, emit events that route back to the current step's upstream to create loops, and rely on StopProcess() routes in the builder to implement termination.
Code Reference
Source Location
- Repository: semantic-kernel
- Sample:
dotnet/samples/GettingStartedWithProcesses/Step01/Step01_Processes.cs:L42-65
Key Signatures
// KernelProcessStepContext - injected into step functions
public sealed class KernelProcessStepContext
{
// Emit an event with a KernelProcessEvent object
public ValueTask EmitEventAsync(KernelProcessEvent processEvent);
// Emit an event with individual parameters
public ValueTask EmitEventAsync(
string eventId,
object? data = null,
KernelProcessEventVisibility visibility = KernelProcessEventVisibility.Internal);
}
// KernelProcessEvent - the event payload
public record KernelProcessEvent
{
public string Id { get; init; } = string.Empty;
public object? Data { get; init; }
public KernelProcessEventVisibility Visibility { get; set; }
= KernelProcessEventVisibility.Internal;
}
Import
using Microsoft.SemanticKernel;
I/O Contract
EmitEventAsync Inputs
| Name | Type | Required | Description |
|---|---|---|---|
| processEvent | KernelProcessEvent |
Yes | The event to emit. The Id determines routing; Data carries the payload. |
KernelProcessEvent Properties
| Name | Type | Default | Description |
|---|---|---|---|
| Id | string |
"" |
The event identifier used by the routing layer to find target steps. |
| Data | object? |
null |
Optional data payload passed to the target step's function parameter. |
| Visibility | KernelProcessEventVisibility |
Internal |
Controls whether the event is visible to parent processes (Public) or stays internal. |
Usage Examples
Conditional Branching
A step evaluates a condition and emits different events to route to different downstream steps. The process builder maps each event Id to its target.
// Step definition with conditional branching
public sealed class ChatUserInputStep : KernelProcessStep
{
[KernelFunction]
public async ValueTask GetUserInputAsync(KernelProcessStepContext context)
{
string userMessage = Console.ReadLine() ?? "";
if (string.Equals(userMessage, "exit", StringComparison.OrdinalIgnoreCase))
{
// Emit exit event - routed to StopProcess()
await context.EmitEventAsync(
new KernelProcessEvent { Id = "Exit", Data = userMessage });
return;
}
// Emit input event - routed to the response step
await context.EmitEventAsync(
new KernelProcessEvent { Id = "UserInputReceived", Data = userMessage });
}
}
// Corresponding process routing
userInputStep
.OnEvent("Exit")
.StopProcess();
userInputStep
.OnEvent("UserInputReceived")
.SendEventTo(new ProcessFunctionTargetBuilder(responseStep, parameterName: "userMessage"));
Iterative Loop
A conversational loop is created by routing the response step's output back to the user input step. The loop continues until the user types "exit", which triggers a different event routed to StopProcess().
// Process definition creating a loop
ProcessBuilder process = new("ChatBot");
var introStep = process.AddStepFromType<IntroStep>();
var userInputStep = process.AddStepFromType<ChatUserInputStep>();
var responseStep = process.AddStepFromType<ChatBotResponseStep>();
// Entry point
process
.OnInputEvent("startProcess")
.SendEventTo(new ProcessFunctionTargetBuilder(introStep));
// Intro leads to user input
introStep
.OnFunctionResult()
.SendEventTo(new ProcessFunctionTargetBuilder(userInputStep));
// Exit branch - terminates the loop
userInputStep
.OnEvent("Exit")
.StopProcess();
// Input branch - goes to response step
userInputStep
.OnEvent("UserInputReceived")
.SendEventTo(new ProcessFunctionTargetBuilder(responseStep, parameterName: "userMessage"));
// Response routes back to user input - creates the loop
responseStep
.OnEvent("AssistantResponseGenerated")
.SendEventTo(new ProcessFunctionTargetBuilder(userInputStep));
Step Emitting Events with Data
The Data property carries information from one step to the next. The data is bound to the target function parameter specified in the routing.
public sealed class ChatBotResponseStep : KernelProcessStep<ChatBotState>
{
internal ChatBotState? _state;
public override ValueTask ActivateAsync(KernelProcessStepState<ChatBotState> state)
{
_state = state.State;
return ValueTask.CompletedTask;
}
[KernelFunction("GetChatResponse")]
public async Task GetChatResponseAsync(
KernelProcessStepContext context,
string userMessage,
Kernel _kernel)
{
_state!.ChatMessages.Add(new(AuthorRole.User, userMessage));
IChatCompletionService chatService =
_kernel.Services.GetRequiredService<IChatCompletionService>();
ChatMessageContent response =
await chatService.GetChatMessageContentAsync(_state.ChatMessages);
_state.ChatMessages.Add(response);
// Emit event with the response data as payload
await context.EmitEventAsync(
new KernelProcessEvent
{
Id = "AssistantResponseGenerated",
Data = response
});
}
}
Process Termination via StopProcess
Termination is achieved by routing an event to StopProcess() in the process builder. The step itself does not call any special termination API; it simply emits the event that the builder maps to termination.
// In the process builder: mail service completion stops the process
mailServiceStep
.OnEvent("MailServiceSent")
.StopProcess();
// The step itself just emits the event normally
public sealed class MailServiceStep : KernelProcessStep
{
[KernelFunction]
public async ValueTask SendMailAsync(KernelProcessStepContext context, string message)
{
Console.WriteLine($"Sending mail: {message}");
await context.EmitEventAsync(
new KernelProcessEvent { Id = "MailServiceSent" });
}
}