Skip to content

zzorphcreator/ai-agent-protocol

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 

Repository files navigation

ai-agent-protocol

Protocol and DSL for communication among AI Agents

An AI agentic framework consists of multiple autonomous agents—AI models, software components, or potentially human participants—that collaborate to achieve specific goals. For these agents to interoperate effectively, especially when developed by different parties, we need an open standard protocol for communication. This protocol must be: Well-defined and public: Allowing any agent adhering to it to participate.

Transport-agnostic: Focusing on message format and semantics, not the underlying transmission mechanism (e.g., HTTP, WebSockets, MQTT).

Simple yet extensible: Covering core communication needs while allowing future enhancements.

The protocol will be specified in a DSL, a language tailored to the domain of agent communication. The DSL will use abstract constructs (e.g., types, enums) that map naturally to programming language features, ensuring translatability. Below, I’ll define the protocol’s components—message structure, message types, and semantics—followed by a Scala example.

DSL Specification for the Agent Communication Protocol The DSL defines the structure and behavior of messages exchanged between agents. Here’s the complete specification:

Basic Types

type AgentID = String        // Unique identifier for an agent, e.g., "agentA" or a URI
type MessageID = String      // Unique identifier for a message, e.g., "msg123"
type Timestamp = String      // ISO 8601 formatted timestamp, e.g., "2023-10-25T10:00:00Z"
type Content = String        // Message payload, e.g., JSON string, XML, or plain text
  • AgentID: Identifies agents uniquely. While a simple string suffices for this example, it could be extended to a URI (e.g., "agent://example.com/myagent").
  • Content: Kept as a string for flexibility; the format is specified by a content_type field.

Message Types

enum MessageType {
    REQUEST,     // Sender asks receiver to perform an action
    RESPONSE,    // Sender replies to a request or query
    INFORM,      // Sender shares information with receiver
    QUERY,       // Sender requests information from receiver
    ACKNOWLEDGE, // Sender confirms receipt of a message
    ERROR        // Sender reports an error
}

These types, inspired by standards like FIPA ACL, define the intent of each message and guide the expected interaction patterns.

Message Structure

type Message = {
    header: {
        version: String,          // Protocol version, e.g., "1.0"
        sender: AgentID,          // Who sent the message
        receiver: AgentID,        // Intended recipient
        type: MessageType,        // Type of message (from enum)
        id: MessageID,            // Unique message identifier
        timestamp: Timestamp,     // When the message was sent
        content_type: String,     // Format of body, e.g., "application/json"
        reply_to: MessageID?,     // Links to a prior message (optional)
        conversation_id: String?  // Groups related messages (optional)
    },
    body: Content                // The actual payload
}
  • Header: Contains metadata essential for routing and processing.
    • version: Allows protocol evolution (e.g., "1.0").
    • content_type: Specifies how to interpret the body (e.g., "application/json", "text/plain").
    • reply_to and conversation_id: Optional fields for tracking message threads.
  • Body: The payload, kept abstract as a string to support various formats.

Semantics of Message Types The DSL includes informal descriptions of each message type’s purpose and expected behavior:

  • REQUEST:

    • Purpose: The sender requests the receiver to perform an action specified in the body.
    • Expected Response: A RESPONSE message with reply_to set to this message’s id and matching conversation_id.
  • RESPONSE:

    • Purpose: The sender provides a response to a prior REQUEST or QUERY, typically including results or status.
    • Expected Response: None
  • INFORM:

    • Purpose: The sender shares information with the receiver, not expecting a reply.
    • Expected Response: None
  • QUERY:

    • Purpose: The sender asks the receiver for information.
    • Expected Response: An INFORM message with the requested data
  • ACKNOWLEDGE:

    • Purpose: The sender confirms receipt of a message, useful for reliability.
    • Expected Response: None
  • ERROR:

    • Purpose: The sender reports an error, possibly linked to a prior message via reply_to.
    • Expected Response: None

Design Notes

  • Transport-Agnostic: The DSL defines message format and semantics, not how messages are sent (e.g., direct, via broker).

  • Simplicity: Security (e.g., authentication, encryption) and advanced features (e.g., capability negotiation) are omitted for this core version but can be added via optional header fields or implementation-specific logic.

  • Extensibility: The version field and optional fields like reply_to support future enhancements.

This DSL uses a syntax resembling type definitions in programming languages, making it straightforward to translate into languages like Scala, Python, or Java.

Example of Using the DSL in Scala Below, I’ll demonstrate how to implement and use this protocol in Scala by translating the DSL into code and showing a simple interaction: an agent sending a REQUEST and another responding with a RESPONSE. Scala Implementation First, define the message structure using Scala’s case classes and sealed traits:

import java.time.Instant

// Basic types
case class AgentID(id: String)
case class MessageID(id: String)
case class Timestamp(time: String) // Using String for simplicity; could use Instant

// Message types as a sealed trait hierarchy
sealed trait MessageType
object MessageType {
    case object REQUEST extends MessageType
    case object RESPONSE extends MessageType
    case object INFORM extends MessageType
    case object QUERY extends MessageType
    case object ACKNOWLEDGE extends MessageType
    case object ERROR extends MessageType
}

// Header and Message case classes
case class Header(
    version: String,
    sender: AgentID,
    receiver: AgentID,
    `type`: MessageType,         // Backticks needed as 'type' is a Scala keyword
    id: MessageID,
    timestamp: Timestamp,
    contentType: String,
    replyTo: Option[MessageID] = None,
    conversationId: Option[String] = None
)

case class Message(header: Header, body: String)

Creating and Sending a REQUEST Agent A sends a REQUEST to Agent B to perform an action:

// Define agents and message details
val sender = AgentID("agentA")
val receiver = AgentID("agentB")
val messageId = MessageID("msg123")
val timestamp = Timestamp(Instant.now().toString)
val conversationId = "conv456"

// Create the REQUEST header
val requestHeader = Header(
    version = "1.0",
    sender = sender,
    receiver = receiver,
    `type` = MessageType.REQUEST,
    id = messageId,
    timestamp = timestamp,
    contentType = "application/json",
    conversationId = Some(conversationId)
)

// Define the body as a JSON string
val requestBody = """{"action": "doSomething", "params": {"param1": "value1"}}"""
val requestMessage = Message(requestHeader, requestBody)

// Simulated send function (transport-specific implementation omitted)
def sendMessage(msg: Message): Unit = {
    println(s"Sending: $msg") // Placeholder for actual transport logic
}

sendMessage(requestMessage)

Receiving and Responding Agent B receives the REQUEST and sends a RESPONSE:

// Simulated receive function (returns the request message for this example)
def receiveMessage(): Message = requestMessage // In practice, this would listen on a transport

val receivedMsg = receiveMessage()

// Process based on message type
receivedMsg.header.`type` match {
    case MessageType.REQUEST =>
        // Create a RESPONSE
        val responseHeader = Header(
            version = "1.0",
            sender = AgentID("agentB"),
            receiver = receivedMsg.header.sender,
            `type` = MessageType.RESPONSE,
            id = MessageID("msg124"),
            timestamp = Timestamp(Instant.now().toString),
            contentType = "application/json",
            replyTo = Some(receivedMsg.header.id),
            conversationId = receivedMsg.header.conversationId
        )
        val responseBody = """{"status": "success", "result": "done"}"""
        val responseMessage = Message(responseHeader, responseBody)
        sendMessage(responseMessage)
    
    case _ =>
        println("Unhandled message type")
}

Output Explanation

  • REQUEST: Agent A sends a message asking Agent B to "doSomething" with a parameter "value1".

  • RESPONSE: Agent B replies with a success status and result, linking it to the original request via replyTo and conversationId.

Notes on the Example

  • Transport: The sendMessage and receiveMessage functions are placeholders. In a real system, they’d use a transport like HTTP or a message queue, serializing the Message (e.g., to JSON).

  • Content: The body is a JSON string, as indicated by contentType. Agents must parse it according to this type.

  • Simplicity: Error handling and serialization are omitted for brevity but would be critical in practice.

About

Protocol and DSL for communication among AI Agents

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors