microsoft/semantic-kernel · error · ArgumentException

The agent {agent.Name ?? agent.Id} cannot have a handoff to

Error message

The agent {agent.Name ?? agent.Id} cannot have a handoff to itself.

What it means

Thrown by the HandoffActor constructor when the handoffs map already contains the agent's own name/id. A self-handoff is a no-op that would loop forever (the agent handing control back to itself), so it is rejected at construction with ArgumentException.

Source

Thrown at dotnet/src/Agents/Orchestration/Handoff/HandoffActor.cs:45

    private string? _handoffAgent;
    private string? _taskSummary;

    /// <summary>
    /// Initializes a new instance of the <see cref="HandoffActor"/> class.
    /// </summary>
    /// <param name="id">The unique identifier of the agent.</param>
    /// <param name="runtime">The runtime associated with the agent.</param>
    /// <param name="context">The orchestration context.</param>
    /// <param name="agent">An <see cref="Agent"/>.</param>
    /// <param name="handoffs">The handoffs available to this agent</param>
    /// <param name="resultHandoff">The handoff agent for capturing the result.</param>
    /// <param name="logger">The logger to use for the actor</param>
    public HandoffActor(AgentId id, IAgentRuntime runtime, OrchestrationContext context, Agent agent, HandoffLookup handoffs, AgentType resultHandoff, ILogger<HandoffActor>? logger = null)
        : base(id, runtime, context, agent, logger)
    {
        if (handoffs.ContainsKey(agent.Name ?? agent.Id))
        {
            throw new ArgumentException($"The agent {agent.Name ?? agent.Id} cannot have a handoff to itself.", nameof(handoffs));
        }

        this._cache = [];
        this._handoffs = handoffs;
        this._resultHandoff = resultHandoff;
    }

    /// <summary>
    /// Gets or sets the callback to be invoked for interactive input.
    /// </summary>
    public OrchestrationInteractiveCallback? InteractiveCallback { get; init; }

    /// <inheritdoc/>
    protected override bool ResponseCallbackFilter(ChatMessageContent response) => response.Role == AuthorRole.Tool;

    /// <inheritdoc/>
    protected override AgentInvokeOptions CreateInvokeOptions(Func<ChatMessageContent, Task> messageHandler)
    {

View on GitHub (pinned to c028a0c7dc)

Solutions

  1. Remove the self-reference from the agent's handoff map.
  2. When generating handoff maps programmatically, exclude the agent itself.

Example fix

// before
var handoffs = new HandoffLookup { ["writer"] = new() { ["writer"] = new(/*...*/) } };

// after — do not let an agent hand off to itself
var handoffs = new HandoffLookup { ["writer"] = new() { ["reviewer"] = new(/*...*/) } };
Defensive patterns

Strategy: validation

Validate before calling

// Build handoff maps from the member list, excluding self
foreach (var agent in agents)
{
    var targets = desiredTargets[agent.Name].Where(n => n != (agent.Name ?? agent.Id));
    handoffs[agent.Name] = new(targets.Select(n => new Handoff(n)));
}

Prevention

When it happens

Trigger: Defining a HandoffOrchestration where an agent's handoff dictionary includes its own Name (or Id when Name is null) as a key.

Common situations: Auto-generated handoff maps that copy every agent into every map; a typo using the agent's own name as a target.

Related errors


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