Redirect Agent-to-Agent Communication — MAF Fundamentals
← Back to Tutorials

12. Agent-to-Agent Communication

Why Agent-to-Agent Communication?

Real-world workflows require specialized agents collaborating. Instead of one monolithic agent, you create a network of specialists that discover, negotiate, and delegate tasks among themselves. This pattern enables scalability, autonomy, and fault isolation.

Communication Patterns

PatternDescriptionWhen to Use
Direct HandoffAgent passes control to another agent explicitlyClear escalation paths, hierarchical teams
Pub/Sub (Broadcast)Agent publishes a task; subscribers bid or claimOpen task queues, load balancing
Message BrokerAgents communicate via a shared queue or busDecoupled systems, async workflows
BlackboardShared state space where agents read/writeCollaborative problem solving

Direct Handoff in MAF

MAF orchestrators natively support agent-to-agent handoffs. The orchestrator routes context and controls the conversation flow:

const triageAgent = new Agent({
  name: "Triage",
  instructions: "Route users to the right specialist: Billing or Support.",
  tools: [
    new Tool({
      name: "transferToBilling",
      handler: (ctx) => orchestrator.handoff("Billing", ctx)
    }),
    new Tool({
      name: "transferToSupport",
      handler: (ctx) => orchestrator.handoff("Support", ctx)
    })
  ]
});

const billingAgent = new Agent({
  name: "Billing",
  instructions: "Handle billing inquiries, invoices, and payments."
});

const supportAgent = new Agent({
  name: "Support",
  instructions: "Handle technical support and troubleshooting."
});

const orchestrator = new Orchestrator({
  agents: [triageAgent, billingAgent, supportAgent],
  defaultAgent: triageAgent
});

Message Broker Pattern with Azure Service Bus

For loosely coupled agent networks, use a message broker. Agents communicate asynchronously through topics and queues:

const { ServiceBusClient } = require("@azure/service-bus");

async function publishTask(task) {
  const sender = sbClient.createSender("agent-tasks");
  await sender.sendMessages({
    body: task,
    sessionId: task.type // enables session-based processing
  });
}

class OrderProcessorAgent {
  async handleMessage(message) {
    const order = message.body;
    // Process order, then publish to next stage
    await publishTask({ type: "fulfillment", orderId: order.id });
  }
}

Decentralized Agent Discovery

Agents can register themselves in a service registry and discover peers dynamically:

class AgentRegistry {
  constructor() {
    this.agents = new Map();
  }

  register(name, capabilities, endpoint) {
    this.agents.set(name, { capabilities, endpoint, status: "available" });
  }

  findAgents(capability) {
    return Array.from(this.agents.entries())
      .filter(([_, agent]) => agent.capabilities.includes(capability))
      .map(([name, agent]) => ({ name, endpoint: agent.endpoint }));
  }
}

// Agents discover peers at runtime
const registry = new AgentRegistry();
registry.register("DataAnalyzer", ["analysis", "charting"], "http://analyzer:5001");
registry.register("ReportGenerator", ["reporting"], "http://reporter:5002");

const analysts = registry.findAgents("analysis");

Negotiation and Task Allocation

In advanced networks, agents negotiate who handles a task. A common approach is a contract-net protocol:

class ContractNetProtocol {
  async solicitBids(task, availableAgents) {
    const bids = await Promise.all(
      availableAgents.map(agent => this.requestBid(agent, task))
    );
    const winner = bids.reduce((best, bid) =>
      bid.confidence > best.confidence ? bid : best
    );
    return winner;
  }

  async requestBid(agent, task) {
    const response = await fetch(`${agent.endpoint}/bid`, {
      method: "POST",
      body: JSON.stringify(task)
    });
    const { confidence, eta } = await response.json();
    return { agent, confidence, eta };
  }
}

Observability in Agent Networks

Distributed tracing is critical. Use correlation IDs and OpenTelemetry to trace messages across agents:

const tracer = opentelemetry.trace.getTracer("agent-network");

async function handleMessage(message) {
  const span = tracer.startSpan("agent.process", {
    attributes: {
      "agent.name": this.name,
      "message.type": message.type,
      "correlation.id": message.correlationId
    }
  });
  try { await this.process(message); }
  finally { span.end(); }
}
💡 Key Insight: Agent-to-agent communication shifts the architecture from "orchestrator-driven" to "agent-driven" — agents decide who to talk to, making the system more resilient and scalable.
✏️ Exercise: Design a four-agent network for a pizza delivery service: OrderTaker, Kitchen, Delivery, and Feedback agents. Describe which communication pattern each pair uses and how they handle a "runner out of stock" scenario.