microsoft/autogen · error · InvalidOperationException

Message must have a topic to be published.

Error message

Message must have a topic to be published.

What it means

Thrown by InProcessRuntime's publish servicer when a MessageEnvelope has no Topic set (envelope.Topic is a null Nullable<TopicId>). The in-process runtime routes purely by topic subscription matching, so a topic-less envelope cannot be delivered and is rejected.

Source

Thrown at dotnet/src/Microsoft.AutoGen/Core/InProcessRuntime.cs:42

    }

    private ValueTask ExecuteTracedAsync(Func<ValueTask> func)
    {
        // TODO: Bind tracing
        return func();
    }

    public InProcessRuntime()
    {
    }

    private ConcurrentQueue<MessageDelivery> messageDeliveryQueue = new();

    private async ValueTask PublishMessageServicer(MessageEnvelope envelope, CancellationToken deliveryToken)
    {
        if (!envelope.Topic.HasValue)
        {
            throw new InvalidOperationException("Message must have a topic to be published.");
        }

        TopicId topic = envelope.Topic.Value;
        List<Exception> exceptions = new();

        foreach (var subscription in this.subscriptions.Values.Where(subscription => subscription.Matches(topic)))
        {
            try
            {
                deliveryToken.ThrowIfCancellationRequested();

                AgentId? sender = envelope.Sender;

                CancellationTokenSource combinedSource = CancellationTokenSource.CreateLinkedTokenSource(envelope.Cancellation, deliveryToken);
                MessageContext messageContext = new(envelope.MessageId, combinedSource.Token)
                {
                    Sender = sender,
                    Topic = topic,

View on GitHub (pinned to 027ecf0a37)

Solutions

  1. Set envelope.Topic = new TopicId(type, source) before publishing
  2. Use the runtime's typed publish API (PublishMessageAsync(message, topic)) so the topic is a required parameter
  3. For direct agent-to-agent sends, use the send/request path rather than topic publish

Example fix

// before
var envelope = new MessageEnvelope(message);
await runtime.PublishMessageServicer(envelope, ct); // Topic unset -> throws

// after
var envelope = new MessageEnvelope(message) { Topic = new TopicId("mytopic", "mysource") };
await runtime.PublishMessageServicer(envelope, ct);
Defensive patterns

Strategy: validation

Validate before calling

if (envelope.Topic is null) throw new InvalidOperationException($"Envelope for {envelope.Message?.GetType().Name} needs a TopicId before publishing");

Type guard

static bool IsPublishable(MessageEnvelope e) => e.Topic.HasValue;

Try / catch

try { await runtime.PublishMessageServicer(envelope, ct); } catch (InvalidOperationException ex) when (ex.Message.Contains("must have a topic")) { _logger.LogError(ex, "Rejected topic-less envelope for {Type}", envelope.Message?.GetType().Name); }

Prevention

When it happens

Trigger: Calling InProcessRuntime publish APIs with a MessageEnvelope whose Topic was never assigned; constructing envelopes manually (new MessageEnvelope(message)) and passing to publish without setting Topic; wrapping/publishing code that drops the Topic field.

Common situations: Custom envelope-building helpers that forget Topic; sending messages intended for direct agent dispatch through the publish path instead of the send path; refactor from topic-required to optional-topic APIs.

Related errors


AI-assisted analysis of microsoft/autogen@027ecf0a37 (2026-08-15). Data as JSON: /api/errors/fcf57e6cf7688d3f. Report an issue: GitHub.