microsoft/semantic-kernel · error · InvalidOperationException

Attempt to build a workflow node from step with no Id

Error message

Attempt to build a workflow node from step with no Id

What it means

Thrown by `BuildNode` for a non-agent step when `step.InnerStepType.AssemblyQualifiedName` is null or whitespace. Despite the message text mentioning 'no Id', the real precondition is that the step's underlying .NET type must resolve to a full assembly-qualified name so it can be recorded in the workflow's `Agent.Type`. It is an InvalidOperationException raised before constructing the `Node`.

Source

Thrown at dotnet/src/Experimental/Process.Core/Workflow/WorkflowBuilder.cs:416

        }

        workflow.Orchestration = orchestration;
        return Task.FromResult(workflow);
    }

    private static Node BuildNode(KernelProcessStepInfo step, List<OrchestrationStep> orchestrationSteps)
    {
        Verify.NotNullOrWhiteSpace(step?.State?.Id, nameof(step.State.Id));

        if (step is KernelProcessAgentStep agentStep)
        {
            return BuildAgentNode(agentStep, orchestrationSteps);
        }

        var innerStepTypeString = step.InnerStepType.AssemblyQualifiedName;
        if (string.IsNullOrWhiteSpace(innerStepTypeString))
        {
            throw new InvalidOperationException("Attempt to build a workflow node from step with no Id");
        }

        var node = new Node()
        {
            Id = step.State.Id,
            Type = "dotnet",
            Agent = new AgentDefinition()
            {
                Type = innerStepTypeString,
                Id = step.State.Id
            }
        };

        foreach (var edge in step.Edges)
        {
            OrchestrationStep orchestrationStep = new()
            {
                ListenFor = new ListenCondition()

View on GitHub (pinned to c028a0c7dc)

Solutions

  1. Ensure each step uses a concrete, named, assembly-resident type with a non-null `AssemblyQualifiedName`.
  2. Avoid anonymous/dynamic proxy types as step inner types; wrap logic in a real named class.
  3. Verify the assembly defining the step type is loaded in the current AppDomain/context before building.

Example fix

// before
process.AddStepFromType(RuntimeEmitHelper.GenerateProxy());
// after
process.AddStepFromType<MyNamedStep>();
Defensive patterns

Strategy: validation

Validate before calling

foreach (var step in process.Steps)
{
    if (string.IsNullOrWhiteSpace(step.InnerStepType?.AssemblyQualifiedName))
        throw new InvalidOperationException($"Step '{step.State?.Id}' has an InnerStepType without an AssemblyQualifiedName; use a concrete named type.");
}

Type guard

bool HasAssemblyQualifiedName(KernelProcessStepInfo step) =>
    !string.IsNullOrWhiteSpace(step?.InnerStepType?.AssemblyQualifiedName);

Try / catch

try { await WorkflowBuilder.BuildWorkflow(process); }
catch (InvalidOperationException ex) when (ex.Message.Contains("no Id"))
{ _logger.LogError(ex, "A step's InnerStepType has no AssemblyQualifiedName."); throw; }

Prevention

When it happens

Trigger: Pass a `KernelProcessStepInfo` whose `InnerStepType` is a dynamic/anonymous/generic-open type with no resolvable `AssemblyQualifiedName`; load a step from a serialized form that lost its type binding; reference a step type from an unloaded assembly.

Common situations: Using dynamically generated step types (e.g. proxy/emitted types) without stable assembly identity; partial trust or reflection-restricted contexts that suppress `AssemblyQualifiedName`; version/assembly-load issues where the type object is incomplete.

Related errors


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