DEV Community

Cover image for AI Agents in Go: Exploring Agent-to-Agent (A2A) Protocols in AI Ecosystems
Beryl Christine Atieno
Beryl Christine Atieno

Posted on

AI Agents in Go: Exploring Agent-to-Agent (A2A) Protocols in AI Ecosystems

In multi-agent systems, effective communication is what transforms isolated algorithms into coordinated intelligence. Agent-to-Agent (A2A) protocols define the structured way autonomous agents exchange information, make requests, and respond to one another. Much like human communication relies on shared languages and rules, agents rely on these protocols to ensure that messages are understood consistently across systems. By defining message formats, interaction patterns, and behavior expectations, A2A protocols enable agents to collaborate, negotiate, and act toward shared goals without central control. This structured communication allows systems to scale, adapt, and interoperate across diverse environments; from AI marketplaces and IoT networks to digital identity frameworks.

In this post, I’ll walk you through how I built a simple customer profile generating agent. The agent uses Gemini AI to generate a target consumer profile based on a business idea. I designed the profiling logic, and wired everything together to interact seamlessly with Telex, a messaging platform, using A2A protocol.

Architecture Overview

The project is divided into three main packages:

  • profiler/ — Handles communication with Gemini and parses generated profiles.

  • a2a/ — Implements the Telex A2A (Agent-to-Agent) communication protocol and request handling.

  • main.go — Bootstraps the HTTP server and wires everything together.

The profiling logic

At the heart of the agent is the GeminiClient. It connects to Google’s Generative AI API (Gemini) and crafts a very specific prompt:

func (g *GeminiClient) buildPrompt(businessIdea string) string {
    return fmt.Sprintf(`You are an expert market researcher. Based ONLY on the business idea "%s", generate a SINGLE, concise customer profile.
    The output MUST be a single line in the format "key: value, key: value, ..."`, businessIdea)
}
Enter fullscreen mode Exit fullscreen mode

A response might look like..

age: 25-35, gender: female, location: urban, occupation: social media manager,
income: $60k-80k, pain_points: time constraints, product overload,
motivations: sustainability, simplicity, interests: skincare, yoga, travel, channel: Instagram
Enter fullscreen mode Exit fullscreen mode

The agent then splits and maps this text into a structured Go model (CustomerProfile), making it machine-readable for downstream integrations.

func (g *GeminiClient) parseSimpleProfile(text string) (*models.CustomerProfile, error) {
    profile := models.CustomerProfile{}

    // Clean up any extraneous whitespace
    text = strings.TrimSpace(text)

    // Split the entire string by ", " to get key-value pairs
    pairs := strings.Split(text, ", ")

    // A simple map to hold the extracted data
    data := make(map[string]string)

    for _, pair := range pairs {
        parts := strings.SplitN(pair, ": ", 2)
        if len(parts) == 2 {
            key := strings.ToLower(strings.TrimSpace(parts[0]))
            value := strings.TrimSpace(parts[1])
            data[key] = value
        }
    }

    // Map the simple string data to the struct fields
    profile.Age = data["age"]
    profile.Gender = data["gender"]
    profile.Location = data["location"]
    profile.Occupation = data["occupation"]
    profile.Income = data["income"]

    // Split comma-separated lists for arrays in your struct
    profile.PainPoints = strings.Split(data["pain_points"], ",")
    profile.Motivations = strings.Split(data["motivations"], ",")
    profile.Interests = strings.Split(data["interests"], ",")

    profile.PreferredChannels = []string{data["channel"]}


    return &profile, nil
}
Enter fullscreen mode Exit fullscreen mode

The A2A Handler Package

The a2a package forms the backbone of agent communication in this project. It implements the A2A protocol, providing a consistent structure for how agents send, receive, and interpret messages. The design focuses on simplicity, extensibility, and reliability, allowing different agents to interact over HTTP while maintaining protocol integrity.

At its core, the package defines a message schema and a handler that processes incoming agent messages. Each message can contain multiple parts, such as text or structured data, representing different elements of an agent conversation.

Message Structure

Each A2AMessage is defined with a unique identifier and an array of message parts. This allows agents to exchange multi-modal data (e.g., text, JSON payloads) in a single structured request.

type A2AMessage struct {
    ID    string      `json:"id"`
    Parts []MessagePart `json:"parts"`
}

type MessagePart struct {
    Kind string           `json:"kind"`
    Text *string          `json:"text,omitempty"`
    Data json.RawMessage  `json:"data,omitempty"`
}
Enter fullscreen mode Exit fullscreen mode

The Kind field determines how the handler interprets the part, for example, "text" parts are treated as plain messages, while "data" parts may include structured content like conversation context or business idea metadata.

Handler Design

The A2AHandler is designed to process messages from other agents, extract useful data, and generate appropriate responses. It uses dependency injection for modularity, ensuring that logic like parsing, validation, and business reasoning can be swapped or extended.

type A2AHandler struct {
    mu sync.Mutex
}

func NewA2AHandler() *A2AHandler {
    return &A2AHandler{}
}
Enter fullscreen mode Exit fullscreen mode

Incoming messages are processed through a dedicated route. The handler reads each part of the message and reconstructs the complete intent behind the request.

func (h *A2AHandler) HandleMessage(c *gin.Context) {
    var msg A2AMessage
    if err := c.BindJSON(&msg); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid A2A message"})
        return
    }

    businessIdea := h.extractBusinessIdea(msg)
    response := h.generateProfile(businessIdea)

    c.JSON(http.StatusOK, gin.H{"response": response})
}
Enter fullscreen mode Exit fullscreen mode

Here, the extractBusinessIdea method scans through the message parts to find relevant text or data:

func (h *A2AHandler) extractBusinessIdea(msg A2AMessage) string {
    var texts []string
    for _, part := range msg.Parts {
        if part.Kind == "text" && part.Text != nil && *part.Text != "" {
            texts = append(texts, *part.Text)
        }
    }
    return strings.Join(texts, " ")
}
Enter fullscreen mode Exit fullscreen mode

This modular approach allows the handler to later evolve to interpret other part types, such as "intent", "metadata", or "context", without rewriting the communication logic.

Design Rationale

The handler was built to achieve three main goals:

  1. Protocol Consistency: All agents exchange data in a predictable message format, reducing parsing errors and ambiguity.
  2. Extensibility: The modular structure makes it easy to add new message kinds or integrate AI reasoning layers later.
  3. Scalability: Using lightweight HTTP endpoints via Gin allows multiple agents to communicate efficiently, even when distributed across different services or environments.

By encapsulating communication logic in the a2a package, this project achieves a clean separation between message handling, business logic, and agent behavior, a key principle in designing interoperable multi-agent systems.

Integration with Telex

The Customer Profiler Agent integrates with Telex through a declarative workflow configuration that defines how the agent is discovered, described, and executed within the Telex ecosystem. Instead of manual registration or static service discovery, Telex reads the workflow file, written in JSON, to understand the agent’s purpose, category, capabilities, and endpoint.

Telex uses this configuration to automatically connect to the agent’s A2A interface, defined at
POST /a2a/profiler. Through this endpoint, Telex sends structured Agent-to-Agent (A2A) messages containing business ideas, which the profiler analyzes to generate customer personas. The workflow ensures that requests and responses follow a standardized format, making the profiler interoperable with other Telex-based agents.

A simplified version of the workflow file looks like this:

{
  "id": "customer_profiler",
  "name": "Customer Profiler",
  "category": "business",
  "description": "AI-powered customer profiler that predicts ideal customer personas",
  "nodes": [
    {
      "id": "profiler_agent",
      "type": "a2a/generic-a2a-node",
      "url": "https://overparticular-lissette-myographic.ngrok-free.dev/a2a/profiler",
      "parameters": {
        "blocking": true,
        "timeout": 30000
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

When Telex runs this workflow, it discovers the agent via the provided URL and executes it as a node of type a2a/generic-a2a-node.
This node type tells Telex that the target endpoint supports the Agent-to-Agent protocol, enabling direct message exchange with automatic JSON serialization and deserialization. The "blocking": true flag ensures Telex waits for the agent’s full response before continuing workflow execution, while "timeout": 30000 defines a safe execution window (in milliseconds).

In essence, this configuration allows Telex to

  1. Discover the agent dynamically through its exposed endpoint.
  2. Send A2A messages describing a business idea.
  3. Receive structured customer profiles as a response.

An example of a response in Telex logs:

2025-11-03 06:28:28 - RESPONSE
{
  "jsonrpc": "2.0",
  "id": "e148455b7ef64f5a946c29c94507eb9e",
  "result": {
    "id": "e148455b7ef64f5a946c29c94507eb9e",
    "status": {
      "state": "completed",
      "timestamp": "2025-11-03T05:28:27Z",
      "message": {
        "kind": "message",
        "role": "agent",
        "parts": [
          {
            "kind": "text",
            "text": "# Customer Profile for: a car wash business customer profile for a natural hair salon business a salon for braiding in the U.S A business for dog walking A business for dog walking\n\n**Demographics:**\n- Age: 25-55\n- Gender: female\n- Location: Suburban\n- Occupation: Professional\n- Income: $60k-120k\n\n**Pain Points:**\n- lack of time\n\n**Motivations:**\n- reliable service\n\n**Interests:**\n- fitness\n\n**Preferred Channels:**\n- Facebook\n"
          }
        ]
      }
    },
    "artifacts": [
      {
        "artifactId": "6ef2cd8f-29c2-4cde-ae19-26358ae43765",
        "name": "Customer Profile Data",
        "parts": [
          {
            "kind": "text",
            "text": "{\"profiles\":[{\"age\":\"25-55\",\"gender\":\"female\",\"location\":\"Suburban\",\"occupation\":\"Professional\",\"income\":\"$60k-120k\",\"motivations\":[\"reliable service\"],\"interests\":[\"fitness\"],\"pain_points\":[\"lack of time\"],\"buying_behaviors\":null,\"preferred_channels\":[\"Facebook\"]}]}"
          }
        ]
      }
    ],
    "kind": "task"
  }
}
Enter fullscreen mode Exit fullscreen mode

Telex reads the artifacts.parts in the response to display the agent's response to the user's message.

This tight integration enables Telex to orchestrate intelligent workflows, where the Customer Profiler can collaborate with other agents such as market analyzers, product planners, or campaign optimizers, without additional infrastructure setup.

Conclusion

The implementation of the Agent-to-Agent (A2A) protocol in this project demonstrates the power of structured communication in autonomous systems. By defining a clear message schema and modular handler, agents can exchange data, interpret intent, and collaborate without human intervention or centralized control. The a2a package serves as a robust foundation for this interaction, ensuring consistency, scalability, and flexibility in message processing.

Top comments (0)