microsoft/semantic-kernel · error · InvalidOperationException

Message must have a receiver to be sent.

Error message

Message must have a receiver to be sent.

What it means

SendMessageServicerAsync (the internal servicer wired by ForSend) throws InvalidOperationException if envelope.Receiver has no value. The public SendMessageAsync always sets the recipient via ForSend, so this throw indicates a MessageEnvelope was routed through the send servicer without a receiver — i.e. internal/custom misuse rather than normal API usage.

Source

Thrown at dotnet/src/Agents/Runtime/InProcess/InProcessRuntime.cs:401

            };

            AgentId agentId = subscription.MapToAgent(topic);
            if (!this.DeliverToSelf && sender.HasValue && sender == agentId)
            {
                return;
            }

            IHostableAgent agent = await this.EnsureAgentAsync(agentId).ConfigureAwait(false);

            await agent.OnMessageAsync(envelope.Message, messageContext).ConfigureAwait(false);
        }
    }

    private async ValueTask<object?> SendMessageServicerAsync(MessageEnvelope envelope, CancellationToken deliveryToken)
    {
        if (!envelope.Receiver.HasValue)
        {
            throw new InvalidOperationException("Message must have a receiver to be sent.");
        }

        using CancellationTokenSource combinedSource = CancellationTokenSource.CreateLinkedTokenSource(envelope.Cancellation, deliveryToken);
        MessageContext messageContext = new(envelope.MessageId, combinedSource.Token)
        {
            Sender = envelope.Sender,
            IsRpc = false
        };

        AgentId receiver = envelope.Receiver.Value;
        IHostableAgent agent = await this.EnsureAgentAsync(receiver).ConfigureAwait(false);

        return await agent.OnMessageAsync(envelope.Message, messageContext).ConfigureAwait(false);
    }

    private async ValueTask<IHostableAgent> EnsureAgentAsync(AgentId agentId)
    {
        if (!this.agentInstances.TryGetValue(agentId, out IHostableAgent? agent))

View on GitHub (pinned to c028a0c7dc)

Solutions

  1. Always send via the public SendMessageAsync(message, recipient, ...) which guarantees the receiver is set.
  2. When building a MessageEnvelope manually for send, always provide a recipient AgentId to ForSend.
  3. Keep publish and send servicers/servicing paths separate.

Example fix

// before (custom plumbing)
var env = new MessageEnvelope(msg, id, ct);
env.ForSend(recipient: null, sendServicer); // receiver never set

// after
await runtime.SendMessageAsync(msg, new AgentId("worker", "1"));
Defensive patterns

Strategy: validation

Validate before calling

// Always send through the public API which sets the receiver.
await runtime.SendMessageAsync(message, new AgentId("worker", "1"));
// If building an envelope manually, ensure a recipient AgentId is supplied to ForSend.

Type guard

bool HasReceiver(MessageEnvelope env) => env.Receiver.HasValue;

Prevention

When it happens

Trigger: Constructing a MessageEnvelope and wiring ForSend without a receiver; a custom servicer reusing SendMessageServicerAsync on a publish envelope; envelope mutation that clears the receiver before servicing.

Common situations: Extending the runtime and mis-wiring servicers; custom delivery plumbing bugs; passing null recipient through internal paths.

Related errors


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