ChatDock Message Filter Callback

Eric Brasher February 23, 2026 at 7:50 AM 5 min read

ChatDock's OnMessageReceived parameter has changed from EventCallback<AgentMessage> to Func<AgentMessage, Task<bool>>?. The callback now controls whether an agent message appears in the chat history — return true to display, false to suppress.

Breaking Change

Consumers currently using EventCallback<AgentMessage> OnMessageReceived must update their callback signature to Func<AgentMessage, Task<bool>> and return a bool. If you don't need filtering, return Task.FromResult(true) to preserve existing behavior.

Why This Changed

Agents often send structured messages — status updates, state changes, telemetry — that parent components need to process but should not appear as chat bubbles. Previously there was no way to intercept these; they all rendered as raw JSON in the chat window.

With the new callback, the parent component decides per-message whether it belongs in the chat UI. When no callback is set, all messages display (backwards compatible for simple usage). The "thinking" message type continues to be handled internally by ChatDock.

Migration

Before:

Previous API (EventCallback)
<ChatDock OnMessageReceived="HandleMessage" ... />

@code {
    // Fire-and-forget — message already in chat by the time this runs
    private async Task HandleMessage(AgentMessage message)
    {
        await UpdateDashboard(message);
    }
}

After:

New API (Func<AgentMessage, Task<bool>>)
<ChatDock OnMessageReceived="HandleMessage" ... />

@code {
    private Task<bool> HandleMessage(AgentMessage message)
    {
        if (message.MessageType == "status")
        {
            // Process for activity log, state updates, etc.
            ProcessStatusUpdate(message);
            return Task.FromResult(false); // suppress from chat
        }

        return Task.FromResult(true); // show in chat
    }
}

If you don't need filtering, the minimal migration is changing the return type:

Minimal Migration
private Task<bool> HandleMessage(AgentMessage message)
{
    // Existing logic unchanged
    _activityLog.Insert(0, $"Agent: {message.Message}");
    return Task.FromResult(true); // preserve existing behavior
}

Message Type Conventions

ChatDock routes messages based on MessageType. The callback is invoked for all types except "thinking":

MessageTypeChatDock Behavior
"thinking"Shows as typing indicator (auto-fades). Handled internally — callback is not invoked.
null or "chat"Callback receives it. Displayed in chat by default.
"status"Callback receives it. Agent convention for status updates — suppressing recommended.
(any other value)Callback receives it. Consumer decides whether to display.

The "status" convention is useful for agents that report progress, state transitions, or telemetry. The parent component can process these for dashboards or logs without cluttering the chat window.

Practical Example: Activity Dashboard

A common pattern is an agent that sends both chat responses and status updates. The parent processes statuses for a sidebar activity feed while only showing conversational messages in the chat:

Dashboard.razor — Filtering by MessageType
@page "/dashboard"

<ChatDock UserHandle="@_userHandle"
          AgentHandle="dashboard-agent"
          AgentType="DashboardAgent"
          OnMessageReceived="HandleMessage" />

<div class="activity-sidebar">
    <h4>Activity</h4>
    @foreach (var entry in _statusLog)
    {
        <div class="activity-entry">@entry</div>
    }
</div>

@code {
    private string _userHandle = "user-123";
    private List<string> _statusLog = new();

    private Task<bool> HandleMessage(AgentMessage message)
    {
        switch (message.MessageType)
        {
            case "status":
                _statusLog.Insert(0,
                    $"[{DateTime.Now:HH:mm}] {message.Message}");
                return Task.FromResult(false);

            default:
                return Task.FromResult(true);
        }
    }
}

Sending Status Messages from Agents

On the agent side, set MessageType to "status" for messages that should be processed but not displayed:

Agent — Sending a Status Update
await fabrAgentHost.SendMessage(new AgentMessage
{
    FromHandle = fabrAgentHost.GetHandle(),
    ToHandle = userHandle,
    MessageType = "status",
    Message = "Indexing 42 documents..."
});

The parent's OnMessageReceived callback receives this message. If it returns false, the message never enters the chat history. The agent does not need to know whether the UI will display it.

Summary

  • OnMessageReceived changed from EventCallback<AgentMessage> to Func<AgentMessage, Task<bool>>?
  • Return true to display a message in chat, false to suppress it
  • When no callback is set, all messages display (backwards compatible)
  • "thinking" messages are still handled internally by ChatDock
  • Use MessageType = "status" for agent messages that should be processed but not shown as chat bubbles

Built with FabrCore on .NET 10.


Eric Brasher

Builder of FabrCore and OpenCaddis.