Home / Docs / Communication

Communication

Communication

FabrCore agents communicate through Orleans streams. Messages flow between agents using request/response patterns (AgentChat) and fire-and-forget events (AgentEvent).

A2AAgentProxy

A2AAgentProxy wraps a remote FabrCoreAgentProxy as a Microsoft Agent Framework AIAgent, allowing FabrCore's distributed Orleans agents to participate in Agent Framework workflows — sequential pipelines, parallel fan-out, hand-off patterns, and more.

Using A2AAgentProxy in a Workflow
[AgentAlias("Orchestrator")]
public class OrchestratorAgent : FabrCoreAgentProxy
{
    private A2AAgentProxy? supportAgent;
    private A2AAgentProxy? reviewAgent;

    public override async Task OnInitialize()
    {
        // Wrap remote FabrCore agents as AIAgent instances
        // Format: "agentType:agentHandle"
        supportAgent = new A2AAgentProxy(
            fabrAgentHost, "CustomerSupport:support1");
        reviewAgent = new A2AAgentProxy(
            fabrAgentHost, "ReviewAgent:reviewer1");
    }

    public override async Task<AgentMessage> OnMessage(AgentMessage message)
    {
        var response = message.Response();

        // Use in a Microsoft Agent Framework workflow
        var workflow = new WorkflowBuilder(supportAgent)
            .AddEdge(supportAgent, reviewAgent)
            .Build();

        var result = await InProcessExecution.RunAsync(
            workflow, new ChatMessage(ChatRole.User, message.Message));
        response.Message = result.Text;

        return response;
    }
}
Why A2AAgentProxy?

FabrCore agents run as Orleans grains on a distributed cluster. A2AAgentProxy bridges the gap by implementing the AIAgent interface, so your FabrCore agents can be used anywhere the Microsoft Agent Framework expects an agent — WorkflowBuilder, sequential pipelines, parallel fan-out, conditional routing, and more.

IFabrCoreAgentHost Messaging Methods

MethodDescription
SendAndReceiveMessage(AgentMessage)Send a request and wait for a response
SendMessage(AgentMessage)Send a one-way message (no response expected)
SendEvent(AgentMessage)Send a fire-and-forget event notification

AgentMessage Structure

PropertyTypeDescription
IdstringUnique message identifier (auto-generated GUID)
ToHandlestring?Target agent handle
FromHandlestring?Sender agent handle
OnBehalfOfHandlestring?Original sender when proxying
Channelstring?Message channel/topic
MessageTypestring?Custom message type identifier
Messagestring?Text content
KindMessageKindRequest, OneWay, or Response
DataTypestring?Type identifier for Data payload
Databyte[]?Binary data payload
FilesList<string>File identifiers
StateDictionary<string, string>?Custom key-value state
TraceIdstring?Distributed tracing identifier

Creating Response Messages

Response Helper
public override async Task<AgentMessage> OnMessage(AgentMessage message)
{
    var response = message.Response(); // Copies Id, TraceId, sets Kind=Response
    response.Message = "Your response here";
    return response;
}

Message Routing Patterns

All messages arrive at the OnMessage entry point. Use the message fields to route to the appropriate handler:

Common Routing Pattern
public override async Task<AgentMessage> OnMessage(AgentMessage message)
{
    // Route by channel
    if (string.Equals(message.Channel, "agent",
        StringComparison.OrdinalIgnoreCase))
        return await HandleAgentResponse(message);

    // Ignore fire-and-forget notifications
    if (message.Kind == MessageKind.OneWay)
        return message.Response();

    // Default: handle user messages
    return await HandleUserMessage(message);
}
FieldTypeUse For
Channelstring?Topic-based routing (e.g., "agent", "system")
MessageTypestring?Custom type identifiers (e.g., "thinking", "status")
KindMessageKindRequest vs OneWay vs Response
Keep It Simple

The explicit filtering pattern is intentional. Each agent has different routing rules — these 5-8 lines of if statements ARE your agent's business logic, not framework boilerplate. Resist the urge to over-abstract.

Progress Notifications

Send progress updates to the calling client during long-running operations using SendMessage with OneWay kind:

Sending Progress Updates
private string? _callerHandle;

public override async Task<AgentMessage> OnMessage(AgentMessage message)
{
    _callerHandle = message.FromHandle;

    await SendProgressAsync("Analyzing your request...");
    // ... do work
    await SendProgressAsync("Processing results...");
    // ... return response
}

private async Task SendProgressAsync(string text)
{
    if (_callerHandle is null) return;
    await fabrAgentHost.SendMessage(new AgentMessage
    {
        ToHandle = _callerHandle,
        FromHandle = fabrAgentHost.GetHandle(),
        Kind = MessageKind.OneWay,
        MessageType = "thinking",
        Message = text
    });
}
No Static State Needed

The incoming message's FromHandle provides the caller context. Track it as an instance field — no static dictionaries or helper classes required.

Events

Events are fire-and-forget messages handled by OnEvent:

Sending Events
await fabrAgentHost.SendEvent(new AgentMessage
{
    FromHandle = fabrAgentHost.GetHandle(),
    ToHandle = "monitor-agent",
    MessageType = "status-update",
    Message = "{ \"status\": \"processing\" }"
});
Handling Events
public override async Task OnEvent(AgentMessage message)
{
    switch (message.MessageType)
    {
        case "status-update":
            await HandleStatusUpdate(message);
            break;
        case "notification":
            await HandleNotification(message);
            break;
    }
}

Timers & Reminders

FabrCore supports two types of scheduled callbacks:

FeatureTimerReminder
PersistenceNo (lost on deactivation)Yes (survives restarts)
Minimum periodNone1 minute
Use caseFrequent, short-livedInfrequent, long-running
Timer Registration
public override async Task OnInitialize()
{
    fabrAgentHost.RegisterTimer(
        timerName: "heartbeat",
        messageType: "timer:heartbeat",
        message: null,
        dueTime: TimeSpan.FromSeconds(1),
        period: TimeSpan.FromSeconds(5));
}
Reminder Registration
await fabrAgentHost.RegisterReminder(
    reminderName: "hourly-report",
    messageType: "reminder:hourly-report",
    message: null,
    dueTime: TimeSpan.FromMinutes(1),
    period: TimeSpan.FromHours(1));
Documentation