microsoft/semantic-kernel · error · KernelException

Step {this.Name} received message from Step named '{message.

Error message

Step {this.Name} received message from Step named '{message.SourceId}' with group Id '{message.GroupId}' that is not registered.

What it means

Thrown by LocalStep.HandleMessageAsync when a message has a non-empty GroupId that is not found in the step's _edgeGroupProcessors dictionary. Edge group processors are built from _stepInfo.IncomingEdgeGroups during construction; a GroupId on an incoming message that doesn't match any registered group means the source step's edge declared a group that the target step never registered.

Source

Thrown at dotnet/src/Experimental/Process.LocalRuntime/LocalStep.cs:196

        Verify.NotNull(message, nameof(message));

        // Lazy one-time initialization of the step before processing a message
        await this._initializeTask.Value.ConfigureAwait(false);

        if (this._functions is null || this._inputs is null || this._initialInputs is null)
        {
            throw new KernelException("The step has not been initialized.").Log(this._logger);
        }

        string messageLogParameters = string.Join(", ", message.Values.Select(kvp => $"{kvp.Key}: {kvp.Value}"));
        this._logger.LogDebug("Received message from '{SourceId}' targeting function '{FunctionName}' and parameters '{Parameters}'.", message.SourceId, message.FunctionName, messageLogParameters);

        if (!string.IsNullOrEmpty(message.GroupId))
        {
            this._logger.LogDebug("Step {StepName} received message from Step named '{SourceId}' with group Id '{GroupId}'.", this.Name, message.SourceId, message.GroupId);
            if (!this._edgeGroupProcessors.TryGetValue(message.GroupId, out LocalEdgeGroupProcessor? edgeGroupProcessor) || edgeGroupProcessor is null)
            {
                throw new KernelException($"Step {this.Name} received message from Step named '{message.SourceId}' with group Id '{message.GroupId}' that is not registered.").Log(this._logger);
            }

            if (!edgeGroupProcessor.TryGetResult(message, out Dictionary<string, object?>? result))
            {
                // The edge group processor has not received all required messages yet.
                return;
            }

            // The edge group processor has received all required messages and has produced a result.
            message = message with { Values = result ?? [] };
        }

        // Add the message values to the inputs for the function
        this.AssignStepFunctionParameterValues(message);

        // If we're still waiting for inputs on all of our functions then don't do anything.
        List<string> invocableFunctions = this._inputs.Where(i => i.Value != null && i.Value.All(v => v.Value != null)).Select(i => i.Key).ToList();
        var missingKeys = this._inputs.Where(i => i.Value is null || i.Value.Any(v => v.Value is null));

View on GitHub (pinned to c028a0c7dc)

Solutions

  1. Ensure every GroupId used on source-side edges has a matching KernelProcessEdgeGroup entry in the target step's IncomingEdgeGroups.
  2. Verify the GroupId string matches exactly (case-sensitive) between the edge and the group definition.
  3. Check that the edge's target step id is correct so messages reach the step that owns the group.

Example fix

// before - edge declares group 'fanin1' but target step has no such group
source.OnEvent("evt").To(target, "func", groupId: "fanin1");
// after - register the group on the target step
target.IncomingEdgeGroups["fanin1"] = new KernelProcessEdgeGroup("fanin1") { MessageSources = [...] };
Defensive patterns

Strategy: validation

Validate before calling

// Validate that every GroupId on incoming edges is registered on the target step
var registeredGroups = step.IncomingEdgeGroups?.Keys.ToHashSet() ?? new HashSet<string>();
foreach (var edgeList in process.Edges.Values)
{
    foreach (var edge in edgeList)
    {
        if (edge.OutputTarget is KernelProcessFunctionTarget ft && ft.StepId == step.State.Id && !string.IsNullOrEmpty(ft.GroupId))
        {
            if (!registeredGroups.Contains(ft.GroupId)) { /* configuration error */ }
        }
    }
}

Prevention

When it happens

Trigger: A source step's outgoing edge specifies a GroupId, and the message carries it to a target step, but the target step's IncomingEdgeGroups does not contain an entry for that GroupId. The lookup at line 194 fails.

Common situations: Adding a GroupId to an edge on the source side without declaring the corresponding KernelProcessEdgeGroup on the target step; renaming a GroupId on one side but not the other; using the wrong target step id so the message reaches a step that doesn't know about the group.

Related errors


AI-assisted analysis of microsoft/semantic-kernel@c028a0c7dc (2026-08-13). Data as JSON: /api/errors/0a641d9ed8a3a20c. Report an issue: GitHub.