Skip to content

IGraphExecutor

The IGraphExecutor interface defines the public contract for executing graphs built on top of Semantic Kernel. It abstracts the execution engine to enable mocking, dependency inversion, and consistent behavior across different implementations.

Overview

IGraphExecutor serves as the core execution contract that orchestrates graph workflows. It manages the complete execution lifecycle, from initialization to completion, while providing safeguards against infinite loops and ensuring predictable behavior.

Contract and Semantics

Core Execution Method

The primary method ExecuteAsync orchestrates graph execution from a configured start node until completion or cancellation:

Task<FunctionResult> ExecuteAsync(
    Kernel kernel,
    KernelArguments arguments,
    CancellationToken cancellationToken = default);

Parameters: * kernel: The Semantic Kernel instance used to resolve functions, prompts, memory, and other services * arguments: The execution state and inputs as KernelArguments; updated values may be written during execution * cancellationToken: A token for cooperative cancellation

Returns: A FunctionResult representing the terminal result of the graph execution

Exceptions: * ArgumentNullException: Thrown if kernel or arguments is null * OperationCanceledException: Thrown if the operation is canceled via cancellationToken

Node Execution Methods

ExecuteNodeAsync

Executes a single graph node in isolation:

Task<FunctionResult> ExecuteNodeAsync(
    IGraphNode node,
    Kernel kernel,
    KernelArguments arguments,
    CancellationToken cancellationToken = default);

This method is useful for testing individual nodes or implementing custom execution strategies.

ExecuteGraphAsync

Executes a graph composed of provided nodes:

Task<FunctionResult> ExecuteGraphAsync(
    IEnumerable<IGraphNode> nodes,
    Kernel kernel,
    KernelArguments arguments,
    CancellationToken cancellationToken = default);

This method supports conditional routing, branching, or early termination depending on node types and runtime conditions.

Properties

  • Name: A human-readable logical name for the executor instance, useful for logging, diagnostics, or multi-executor scenarios

Execution Semantics

Lifecycle Management

The executor follows a structured execution lifecycle:

  1. Initialization: Creates execution context with immutable options snapshot
  2. Validation: Optionally validates graph integrity before execution
  3. Plan Compilation: May compile and cache structural execution plans
  4. Node Execution: Processes nodes sequentially with lifecycle hooks
  5. Completion: Returns final result or propagates exceptions

State Management

  • KernelArguments: Treated as the authoritative execution state
  • GraphState: Wrapper around KernelArguments with additional metadata
  • Execution Context: Captures immutable options and execution metadata
  • State Propagation: Updates are consistently propagated across nodes

Safety Features

  • Loop Prevention: Built-in safeguards against infinite loops
  • Timeout Support: Configurable execution timeouts
  • Cancellation: Cooperative cancellation via CancellationToken
  • Resource Limits: Configurable maximum execution steps
  • Integrity Validation: Optional graph structure validation

Thread Safety

Instances are generally safe to reuse across executions if no mutable per-run state is kept on the instance itself. The interface does not guarantee thread safety for concurrent executions.

Implementation Requirements

Implementations are expected to:

  • Validate inputs and honor the provided Kernel configuration and policies
  • Treat KernelArguments as the authoritative execution state
  • Support cooperative cancellation via CancellationToken
  • Emit rich logging/telemetry as configured elsewhere in the system
  • Handle exceptions appropriately and propagate cancellation

Usage Examples

Basic Execution

Kernel kernel = BuildKernel();
KernelArguments args = new() { ["input"] = "Hello" };
IGraphExecutor executor = GetExecutor();
FunctionResult result = await executor.ExecuteAsync(kernel, args, CancellationToken.None);

With Cancellation

using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
var result = await executor.ExecuteAsync(kernel, arguments, cts.Token);

Single Node Execution

var node = executor.GetNode("specificNode");
var result = await executor.ExecuteNodeAsync(node, kernel, arguments, cancellationToken);

Custom Node Sequence

var customNodes = new[] { node1, node2, node3 };
var result = await executor.ExecuteGraphAsync(customNodes, kernel, arguments, cancellationToken);

Runnable Example

The following is a minimal, fully-runnable example that demonstrates creating a simple graph, adding two function nodes, connecting them, and executing the graph using GraphExecutor. This example is the same code shipped in the examples folder and has been tested to compile and run.

// Minimal runnable example demonstrating how to construct a GraphExecutor,
// add function nodes, connect them, set a start node and execute the graph.
using Microsoft.SemanticKernel;
using SemanticKernel.Graph.Core;
using SemanticKernel.Graph.Nodes;
using SemanticKernel.Graph.State;
using System.Threading;

public static class GraphExecutorExample
{
    /// <summary>
    /// Entry point for the docs example. Call from example runner.
    /// </summary>
    public static async Task RunAsync()
    {
        // Create a minimal kernel for function invocation.
        var kernel = Kernel.CreateBuilder().Build();

        // Create simple kernel functions as lightweight delegates to be used by nodes.
        var fnA = KernelFunctionFactory.CreateFromMethod((KernelArguments args) =>
        {
            // Simple function: returns a greeting string
            return "Hello from A";
        }, "FnA");

        var fnB = KernelFunctionFactory.CreateFromMethod((KernelArguments args) =>
        {
            // Echo function: appends text to previous state
            var prev = args.ContainsName("message") ? args["message"]?.ToString() : string.Empty;
            return $"B received: {prev}";
        }, "FnB");

        // Wrap functions into graph nodes
        var nodeA = new FunctionGraphNode(fnA, "nodeA", "Start node A");
        var nodeB = new FunctionGraphNode(fnB, "nodeB", "Receiver node B");

        // Create executor and add nodes
        var executor = new GraphExecutor("ExampleGraph", "A tiny demo graph for docs");
        executor.AddNode(nodeA).AddNode(nodeB);

        // Connect A -> B and set start node
        executor.Connect("nodeA", "nodeB");
        executor.SetStartNode("nodeA");

        // Prepare initial kernel arguments / graph state
        var args = new KernelArguments();
        args["message"] = "Initial message";

        // Execute graph
        var result = await executor.ExecuteAsync(kernel, args, CancellationToken.None);

        // Print final result
        Console.WriteLine("Graph execution completed.");
        Console.WriteLine($"Final result: {result.GetValue<string>()}");
    }
}
  • GraphExecutor: The main implementation of this interface
  • GraphExecutionContext: Maintains execution state and coordination
  • GraphExecutionOptions: Immutable execution configuration snapshot
  • GraphState: Wrapper around KernelArguments with execution metadata

See Also