Phase 16 - Lesson 03

Protocolos de Comunicación

Los agentes que no pueden hablar el mismo idioma no son un equipo. Son extraños gritando al vacío.

Tipo: Build Lenguajes: TypeScript Prerrequisitos: Phase 14 (Agent Engineering), Lesson 16.01 (Why Multi-Agent) Tiempo: ~120 minutos

Objetivos de Aprendizaje

  • Implementar el descubrimiento e invocación de herramientas de MCP para que los agentes puedan utilizar herramientas expuestas por servidores externos
  • Construir un Agent Card y un endpoint de tarea de A2A que permita a un agente delegar trabajo a otro a través de HTTP
  • Comparar MCP (acceso a herramientas), A2A (agente a agente), ACP (auditoría empresarial) y ANP (confianza descentralizada) y explicar qué protocolo resuelve cada problema
  • Conectar múltiples protocolos en un solo sistema donde los agentes descubren herramientas a través de MCP y delegan tareas a través de A2A

El Problema

Dividiste tu sistema en múltiples agentes. Un investigador, un programador, un revisor. Son excelentes en sus trabajos individuales. Pero ahora necesitas que realmente hablen entre ellos.

Tu primer intento es obvio: pasar strings de un lado a otro. El investigador devuelve un bloque de texto, el programador lo analiza como puede. Funciona hasta que el programador interpreta mal un resumen de investigación, o dos agentes entran en deadlock esperando al otro, o necesitas que colaboren agentes construidos por diferentes equipos. De repente, "simplemente pasar strings" se desmorona.

Este es el problema del protocolo de comunicación. Sin un contrato compartido sobre cómo los agentes intercambian información, los sistemas multiagente son frágiles, no auditables e imposibles de escalar más allá de un puñado de agentes que tú mismo escribiste.

El ecosistema de IA ha respondido con cuatro protocolos, cada uno de los cuales resuelve una parte diferente del problema:

  • MCP para el acceso a herramientas
  • A2A para la colaboración entre agentes (agente a agente)
  • ACP para la auditabilidad empresarial
  • ANP para la identidad descentralizada y la confianza

Esta lección profundiza en el tema. Leerás formatos de mensajes reales de cada especificación, construirás implementaciones funcionales y conectarás los cuatro en un sistema unificado.

El Concepto

El Panorama de los Protocolos

Piensa en estos cuatro protocolos como capas, cada una de las cuales aborda una pregunta diferente:

block-beta
  columns 1
  block:ANP["ANP — ¿Cómo confían los agentes en extraños?\nIdentidad descentralizada (DID), E2EE, meta-protocolo"]
  end
  block:A2A["A2A — ¿Cómo colaboran los agentes en objetivos?\nAgent Cards, ciclo de vida de tareas, streaming, negociación"]
  end
  block:ACP["ACP — ¿Cómo hablan los agentes en sistemas auditables?\nEjecuciones, metadados de trayectoria, continuidad de sesión"]
  end
  block:MCP["MCP — ¿Cómo usa un agente una herramienta?\nDescubrimiento de herramientas, ejecución, uso compartido de contexto"]
  end

  style ANP fill:#f3e8ff,stroke:#7c3aed
  style A2A fill:#dbeafe,stroke:#2563eb
  style ACP fill:#fef3c7,stroke:#d97706
  style MCP fill:#d1fae5,stroke:#059669

No son competidores. Resuelven diferentes problemas a diferentes niveles.

MCP (Recapitulación)

MCP se cubre en detalle en la Fase 13. Recapitulación rápida: MCP estandariza cómo se conecta un LLM a herramientas externas y fuentes de datos. Es un protocolo cliente-servidor donde el agente (cliente) descubre y llama a las herramientas expuestas por un servidor.

sequenceDiagram
    participant Agent as Agente (cliente)
    participant MCP1 as Servidor MCP<br/>(base de datos, API, archivos)

    Agent->>MCP1: listar herramientas
    MCP1-->>Agent: definiciones de herramientas
    Agent->>MCP1: llamar herramienta X
    MCP1-->>Agent: resultado

MCP es comunicación de agente a herramienta (agent-to-tool). No ayuda a los agentes a hablar entre sí.

A2A (Protocolo Agent2Agent)

Creado por: Google (ahora bajo la Linux Foundation como lf.a2a.v1) Versión de la especificación: 1.0.0 Problema: ¿Cómo colaboran, negocian y delegan tareas entre sí los agentes autónomos?

A2A es el protocolo para la colaboración entre agentes punto a punto (peer-to-peer). Mientras que MCP conecta un agente a las herramientas, A2A conecta un agente a otros agentes. Cada agente publica un Agent Card en una URL conocida (well-known), y otros agentes lo descubren, negocian con él y le delegan tareas.

Cómo Funciona A2A

sequenceDiagram
    participant Client as Agente Cliente
    participant Remote as Agente Remoto

    Client->>Remote: GET /.well-known/agent-card.json
    Remote-->>Client: Agent Card (habilidades, modos, seguridad)

    Client->>Remote: POST /message:send
    Remote-->>Client: Tarea (enviada/en ejecución)

    alt Polling
        Client->>Remote: GET /tasks/{id}
        Remote-->>Client: Estado de la tarea + artefactos
    else Streaming
        Client->>Remote: POST /message:stream
        Remote-->>Client: SSE: statusUpdate
        Remote-->>Client: SSE: artifactUpdate
        Remote-->>Client: SSE: completed
    end

El Agent Card Real

Así es como se ve un A2A Agent Card real en producción. Servido en GET /.well-known/agent-card.json:

{
  "name": "Research Agent",
  "description": "Searches documentation and summarizes findings",
  "version": "1.0.0",
  "supportedInterfaces": [
    {
      "url": "https://research-agent.example.com/a2a/v1",
      "protocolBinding": "JSONRPC",
      "protocolVersion": "1.0"
    },
    {
      "url": "https://research-agent.example.com/a2a/rest",
      "protocolBinding": "HTTP+JSON",
      "protocolVersion": "1.0"
    }
  ],
  "provider": {
    "organization": "Your Company",
    "url": "https://example.com"
  },
  "capabilities": {
    "streaming": true,
    "pushNotifications": false
  },
  "defaultInputModes": ["text/plain", "application/json"],
  "defaultOutputModes": ["text/plain", "application/json"],
  "skills": [
    {
      "id": "web-research",
      "name": "Web Research",
      "description": "Searches the web and synthesizes findings",
      "tags": ["research", "search", "summarization"],
      "examples": ["Research the latest changes in React 19"]
    },
    {
      "id": "doc-analysis",
      "name": "Documentation Analysis",
      "description": "Reads and analyzes technical documentation",
      "tags": ["docs", "analysis"],
      "inputModes": ["text/plain", "application/pdf"],
      "outputModes": ["application/json"]
    }
  ],
  "securitySchemes": {
    "bearer": {
      "httpAuthSecurityScheme": {
        "scheme": "Bearer",
        "bearerFormat": "JWT"
      }
    }
  },
  "security": [{ "bearer": [] }]
}

Detalles clave a tener en cuenta:

  • Las habilidades (skills) son lo que un agente puede hacer. Cada una tiene un ID, etiquetas y tipos MIME de entrada/salida soportados. Así es como un agente cliente decide si este agente remoto puede manejar su solicitud.
  • supportedInterfaces enumera múltiples vinculaciones de protocolo. Un solo agente puede hablar JSON-RPC, REST y gRPC simultáneamente.
  • La seguridad (security) está integrada en el card. El cliente sabe qué autenticación necesita antes de realizar una sola solicitud.

Ciclo de Vida de la Tarea

Las tareas son la unidad central de trabajo en A2A. Se mueven a través de estados definidos:

stateDiagram-v2
    [*] --> submitted
    submitted --> working
    working --> input_required: requiere más información
    input_required --> working: cliente envía datos
    working --> completed: éxito
    working --> failed: error
    working --> canceled: el cliente cancela
    submitted --> rejected: el agente rechaza

    completed --> [*]
    failed --> [*]
    canceled --> [*]
    rejected --> [*]

    note right of completed: Los estados terminales son inmutables.\nLos seguimientos crean nuevas tareas\ndentro del mismo contextId.

Los 8 estados (la especificación también define UNSPECIFIED como un centinela, omitido aquí):

Estado ¿Terminal? Significado
TASK_STATE_SUBMITTED No Reconocida, pero aún no en proceso
TASK_STATE_WORKING No Procesándose activamente
TASK_STATE_INPUT_REQUIRED No El agente necesita más información del cliente
TASK_STATE_AUTH_REQUIRED No Se requiere autenticación
TASK_STATE_COMPLETED Finalizada con éxito
TASK_STATE_FAILED Finalizada con error
TASK_STATE_CANCELED Cancelada antes de su finalización
TASK_STATE_REJECTED El agente rechazó la tarea

Una vez que una tarea alcanza un estado terminal, es inmutable. No se permiten más mensajes. Los seguimientos crean una nueva tarea dentro del mismo contextId.

Formato del Mensaje (Wire Format)

A2A utiliza JSON-RPC 2.0. Así es como se ve un intercambio de mensajes real:

El cliente envía una tarea:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "SendMessage",
  "params": {
    "message": {
      "messageId": "msg-001",
      "role": "ROLE_USER",
      "parts": [{ "text": "Research React 19 compiler features" }]
    },
    "configuration": {
      "acceptedOutputModes": ["text/plain", "application/json"],
      "historyLength": 10
    }
  }
}

El agente responde con una tarea:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "task": {
      "id": "task-abc-123",
      "contextId": "ctx-xyz-789",
      "status": {
        "state": "TASK_STATE_COMPLETED",
        "timestamp": "2026-03-27T10:30:00Z"
      },
      "artifacts": [
        {
          "artifactId": "art-001",
          "name": "research-results",
          "parts": [{
            "data": {
              "findings": [
                "React 19 compiler auto-memoizes components",
                "No more manual useMemo/useCallback needed",
                "Compiler runs at build time, not runtime"
              ]
            },
            "mediaType": "application/json"
          }]
        }
      ]
    }
  }
}

Streaming a través de SSE:

POST /message:stream HTTP/1.1
Content-Type: application/json
A2A-Version: 1.0

data: {"task":{"id":"task-123","status":{"state":"TASK_STATE_WORKING"}}}

data: {"statusUpdate":{"taskId":"task-123","status":{"state":"TASK_STATE_WORKING","message":{"role":"ROLE_AGENT","parts":[{"text":"Searching documentation..."}]}}}}

data: {"artifactUpdate":{"taskId":"task-123","artifact":{"artifactId":"art-1","parts":[{"text":"partial findings..."}]},"append":true,"lastChunk":false}}

data: {"statusUpdate":{"taskId":"task-123","status":{"state":"TASK_STATE_COMPLETED"}}}

ACP (Agent Communication Protocol)

Creado por: IBM / BeeAI Versión de la especificación: 0.2.0 (OpenAPI 3.1.1) Estado: Fusionándose con A2A bajo la Linux Foundation Problema: ¿Cómo se comunican los agentes con total auditabilidad, continuidad de sesión y seguimiento de trayectoria?

ACP es el protocolo empresarial. A diferencia de lo que afirman muchos resúmenes, ACP no utiliza JSON-LD. Es una API REST/JSON directa definida mediante OpenAPI. Lo que lo hace especial es TrajectoryMetadata: cada respuesta del agente puede llevar un registro detallado de los pasos de razonamiento y las llamadas a herramientas que la produjeron.

sequenceDiagram
    participant Client
    participant ACP as Agente ACP
    participant Audit as Registro de Auditoría

    Client->>ACP: POST /runs (modo: sync)
    ACP->>ACP: Procesar solicitud...
    ACP->>Audit: Registrar trayectoria:<br/>razonamiento + llamadas a herramientas
    ACP-->>Client: Respuesta + TrajectoryMetadata
    Note over Audit: Cada paso registrado:<br/>tool_name, tool_input,<br/>tool_output, razonamiento

Descubrimiento de Agentes en ACP

ACP define cuatro métodos de descubrimiento:

graph LR
    A[Descubrimiento de Agentes] --> B["Runtime<br/>GET /agents"]
    A --> C["Abierto<br/>.well-known/agent.yml"]
    A --> D["Registro<br/>Catálogo centralizado"]
    A --> E["Incrustado<br/>Etiquetas de contenedor"]

    style B fill:#dbeafe,stroke:#2563eb
    style C fill:#d1fae5,stroke:#059669
    style D fill:#fef3c7,stroke:#d97706
    style E fill:#f3e8ff,stroke:#7c3aed

El AgentManifest es más simple que el Agent Card de A2A:

{
  "name": "summarizer",
  "description": "Summarizes documents with source citations",
  "input_content_types": ["text/plain", "application/pdf"],
  "output_content_types": ["text/plain", "application/json"],
  "metadata": {
    "tags": ["summarization", "RAG"],
    "framework": "BeeAI",
    "capabilities": [
      {
        "name": "Document Summarization",
        "description": "Condenses long documents into key points"
      }
    ],
    "recommended_models": ["llama3.3:70b-instruct-fp16"],
    "license": "Apache-2.0",
    "programming_language": "Python"
  }
}

Ciclo de Vida de la Ejecución (Run)

ACP utiliza "Runs" (Ejecuciones) en lugar de "Tasks" (Tareas). Un Run es una ejecución del agente con tres modos:

Modo Comportamiento
sync Bloqueante. La respuesta contiene el resultado completo.
async Devuelve 202 inmediatamente. Realiza polling en GET /runs/{id} para obtener el estado.
stream Stream SSE. Los eventos se disparan a medida que el agente trabaja.
stateDiagram-v2
    [*] --> created
    created --> in_progress
    in_progress --> completed: éxito
    in_progress --> failed: error
    in_progress --> awaiting: requiere entrada
    awaiting --> in_progress: el cliente reanuda
    in_progress --> cancelling: cancelar solicitud
    cancelling --> cancelled

    completed --> [*]
    failed --> [*]
    cancelled --> [*]

TrajectoryMetadata (La Trilha de Auditoría)

Este es el diferenciador clave de ACP. Cada parte del mensaje puede incluir metadados que muestran exactamente lo que hizo el agente:

{
  "role": "agent/researcher",
  "parts": [
    {
      "content_type": "text/plain",
      "content": "The weather in San Francisco is 72F and sunny.",
      "metadata": {
        "kind": "trajectory",
        "message": "I need to check the weather for this location",
        "tool_name": "weather_api",
        "tool_input": { "location": "San Francisco, CA" },
        "tool_output": { "temperature": 72, "condition": "sunny" }
      }
    }
  ]
}

Para las industrias reguladas, esto vale oro. Cada respuesta viene con una cadena de razonamiento demostrable: qué herramientas se llamaron, qué entradas se utilizaron y qué salidas se recibieron. Sin caja negra.

ACP también admite CitationMetadata para la atribución de fuentes:

{
  "kind": "citation",
  "start_index": 0,
  "end_index": 47,
  "url": "https://weather.gov/sf",
  "title": "NWS San Francisco Forecast"
}

ANP (Agent Network Protocol)

Creado por: Comunidad de código abierto (fundado por GaoWei Chang) Repositorio: github.com/agent-network-protocol/AgentNetworkProtocol Problema: ¿Cómo confían entre sí los agentes de diferentes organizaciones sin una autoridad central?

ANP es el protocolo de identidad descentralizada. Construye la confianza utilizando W3C Decentralized Identifiers (DIDs) y cifrado de extremo a extremo (E2EE). A diferencia de A2A, donde descubres agentes a través de endpoints conocidos, ANP permite a los agentes probar su identidad criptográficamente.

ANP tiene tres capas:

graph TB
    subgraph Layer3["Capa 3: Protocolo de Aplicación"]
        AD[Documentos de Descripción de Agentes]
        DISC[Endpoints de descubrimiento]
    end
    subgraph Layer2["Capa 2: Meta-Protocolo"]
        NEG[Negociación de protocolo basada en IA]
        CODE[Generación dinámica de código]
    end
    subgraph Layer1["Capa 1: Identidad y Comunicación Segura"]
        DID["did:wba (W3C DID)"]
        HPKE[HPKE E2EE - RFC 9180]
        SIG[Verificación de firma]
    end

    Layer3 --> Layer2
    Layer2 --> Layer1

    style Layer1 fill:#d1fae5,stroke:#059669
    style Layer2 fill:#dbeafe,stroke:#2563eb
    style Layer3 fill:#f3e8ff,stroke:#7c3aed

Documentos DID (Estructura Real)

ANP utiliza un método DID personalizado llamado did:wba (Web-Based Agent). El DID did:wba:example.com:user:alice se resuelve en https://example.com/user/alice/did.json:

{
  "@context": [
    "https://www.w3.org/ns/did/v1",
    "https://w3id.org/security/suites/jws-2020/v1",
    "https://w3id.org/security/suites/secp256k1-2019/v1"
  ],
  "id": "did:wba:example.com:user:alice",
  "verificationMethod": [
    {
      "id": "did:wba:example.com:user:alice#key-1",
      "type": "EcdsaSecp256k1VerificationKey2019",
      "controller": "did:wba:example.com:user:alice",
      "publicKeyJwk": {
        "crv": "secp256k1",
        "x": "NtngWpJUr-rlNNbs0u-Aa8e16OwSJu6UiFf0Rdo1oJ4",
        "y": "qN1jKupJlFsPFc1UkWinqljv4YE0mq_Ickwnjgasvmo",
        "kty": "EC"
      }
    },
    {
      "id": "did:wba:example.com:user:alice#key-x25519-1",
      "type": "X25519KeyAgreementKey2019",
      "controller": "did:wba:example.com:user:alice",
      "publicKeyMultibase": "z9hFgmPVfmBZwRvFEyniQDBkz9LmV7gDEqytWyGZLmDXE"
    }
  ],
  "authentication": [
    "did:wba:example.com:user:alice#key-1"
  ],
  "keyAgreement": [
    "did:wba:example.com:user:alice#key-x25519-1"
  ],
  "humanAuthorization": [
    "did:wba:example.com:user:alice#key-1"
  ],
  "service": [
    {
      "id": "did:wba:example.com:user:alice#agent-description",
      "type": "AgentDescription",
      "serviceEndpoint": "https://example.com/agents/alice/ad.json"
    }
  ]
}

Detalles clave a tener en cuenta:

  • Se impone la separación de llaves. Las llaves de firma (secp256k1) están separadas de las llaves de cifrado (X25519).
  • humanAuthorization es exclusivo de ANP. Estas llaves requieren una aprobación humana explícita (biométrica, contraseña, HSM) antes de su uso. Las operaciones de alto riesgo, como las transferencias de fondos, pasan por esta ruta.
  • Las llaves de keyAgreement se utilizan para el cifrado de extremo a extremo HPKE (RFC 9180).
  • La sección service apunta al documento de Descripción del Agente.

Cómo Funciona la Confianza en ANP

ANP no utiliza una red de confianza (web-of-trust) ni un gráfico de respaldo. La confianza es bilateral y se verifica por interacción:

sequenceDiagram
    participant A as Agente A
    participant Domain as Dominio del Agente A
    participant B as Agente B

    A->>B: Solicitud HTTP + DID + firma
    B->>Domain: Obtener documento DID (HTTPS)
    Domain-->>B: Documento DID + llave pública
    B->>B: Verificar firma con llave pública
    B-->>A: Emitir token de acceso
    A->>B: Solicitudes posteriores usan el token
    Note over A,B: Confianza = Verificación de dominio TLS<br/>+ Verificación de firma DID<br/>+ Principio de menor confianza

La confianza proviene de tres fuentes:

  1. TLS a nivel de dominio verifica el host del documento DID
  2. Firmas criptográficas de DID verifican la identidad del agente
  3. Principio de menor confianza otorga sólo los permisos mínimos

No hay propagación de confianza basada en chismes ni puntuación de PageRank. Verificas a cada agente directamente a través de su DID.

Negociación de Meta-Protocolo

Esta es la característica más novedosa de ANP. Cuando dos agentes de diferentes ecosistemas se encuentran, no necesitan formatos de datos acordados previamente. Negocian en lenguaje natural:

{
  "action": "protocolNegotiation",
  "sequenceId": 0,
  "candidateProtocols": "I can communicate using:\n1. JSON-RPC with hotel booking schema\n2. REST with OpenAPI 3.1 spec\n3. Natural language over HTTP",
  "modificationSummary": "Initial proposal",
  "status": "negotiating"
}
sequenceDiagram
    participant A as Agente A
    participant B as Agente B

    A->>B: protocolNegotiation (candidateProtocols)
    B->>A: protocolNegotiation (contrapropuesta)
    A->>B: protocolNegotiation (aceptado)
    Note over A,B: Los agentes generan código dinámicamente<br/>para manejar el formato acordado.<br/>Máximo 10 rondas, luego timeout.

Los agentes van y vienen (máximo 10 rondas) hasta que acuerdan un formato, luego generan código dinámicamente para manejarlo. Valores de estado: negotiating, rejected, accepted, timeout.

Esto significa que dos agentes que nunca se han visto antes pueden descubrir cómo comunicarse sin que nadie defina previamente un esquema compartido.

Comparación (Corregida)

MCP A2A ACP ANP
Creado por Anthropic Google / Linux Foundation IBM / BeeAI Comunidad
Formato de especificación JSON-RPC JSON-RPC / REST / gRPC OpenAPI 3.1 (REST) JSON-RPC
Uso principal Agente a Herramienta Agente a Agente Agente a Agente Agente a Agente
Descubrimiento Listado de herramientas /.well-known/agent-card.json GET /agents, /.well-known/agent.yml /.well-known/agent-descriptions, endpoints de servicio DID
Identidad Implícita (local) Esquemas de seguridad (OAuth, mTLS) Nivel de servidor W3C DID (did:wba) con E2EE
Trayectoria de auditoría N/A Básica (historial de tareas) TrajectoryMetadata (llamadas a herramientas, razonamiento) En desarrollo (no especificado formalmente)
Máquina de estados N/A 9 estados de tarea 7 estados de ejecución (run) N/A
Streaming N/A SSE SSE Agnóstico del transporte
Característica exclusiva Esquemas de herramientas Agent Cards + Skills Trayectoria de auditoría de trayectoria Negociación de meta-protocolo
Mejor para Herramientas y datos Colaboración dinámica Industrias reguladas Confianza entre organizaciones
Estado Estable Estable (v1.0) Fusionándose con A2A Desarrollo activo

Cómo Funcionan Juntos

Estos protocolos no se excluyen mutuamente. Un sistema corporativo realista utiliza múltiples:

graph TB
    subgraph org["Tu Organización"]
        RA[Research Agent] <-->|A2A| CA[Coding Agent]
        RA -->|MCP| SS[Search Server]
        CA -->|MCP| GS[GitHub Server]
        AUDIT["Todas las respuestas de los agentes llevan<br/>ACP TrajectoryMetadata"]
    end

    subgraph ext["Externo (DID verificado a través de ANP)"]
        EA[External Agent]
        PA[Partner Agent]
    end

    RA <-->|ANP + A2A| EA
    CA <-->|ANP + A2A| PA

    style org fill:#f8fafc,stroke:#334155
    style ext fill:#fef2f2,stroke:#991b1b
    style AUDIT fill:#fef3c7,stroke:#d97706
  • MCP conecta a cada agente con sus herramientas
  • A2A maneja la colaboración entre agentes (internos y externos)
  • ACP envuelve las respuestas en metadados de trayectoria para auditabilidad
  • ANP proporciona verificación de identidad para agentes que no controlas

Build It

Paso 1: Tipos de Mensajes Principales

Todo sistema multiagente comienza con un formato de mensaje. Definimos tipos que se mapean con los que usan los protocolos reales:

import crypto from "node:crypto";

type MessageRole = "user" | "agent";

type MessagePart =
  | { kind: "text"; text: string }
  | { kind: "data"; data: unknown; mediaType: string }
  | { kind: "file"; name: string; url: string; mediaType: string };

type TrajectoryEntry = {
  reasoning: string;
  toolName?: string;
  toolInput?: unknown;
  toolOutput?: unknown;
  timestamp: number;
};

type AgentMessage = {
  id: string;
  role: MessageRole;
  parts: MessagePart[];
  trajectory?: TrajectoryEntry[];
  replyTo?: string;
  timestamp: number;
};

function createMessage(
  role: MessageRole,
  parts: MessagePart[],
  replyTo?: string
): AgentMessage {
  return {
    id: crypto.randomUUID(),
    role,
    parts,
    replyTo,
    timestamp: Date.now(),
  };
}

function textMessage(role: MessageRole, text: string): AgentMessage {
  return createMessage(role, [{ kind: "text", text }]);
}

Nota: MessagePart es multimodal (texto, datos estructurados, archivos) al igual que las especificaciones reales de A2A y ACP. TrajectoryEntry captura la cadena de razonamiento, coincidiendo con TrajectoryMetadata de ACP.

Paso 2: A2A Agent Card y Registro

Construye un descubrimiento de agentes que coincida con la especificación real de A2A:

type Skill = {
  id: string;
  name: string;
  description: string;
  tags: string[];
  inputModes: string[];
  outputModes: string[];
};

type AgentCard = {
  name: string;
  description: string;
  version: string;
  url: string;
  capabilities: {
    streaming: boolean;
    pushNotifications: boolean;
  };
  defaultInputModes: string[];
  defaultOutputModes: string[];
  skills: Skill[];
};

class AgentRegistry {
  private cards: Map<string, AgentCard> = new Map();

  register(card: AgentCard) {
    this.cards.set(card.name, card);
  }

  discoverBySkillTag(tag: string): AgentCard[] {
    return [...this.cards.values()].filter((card) =>
      card.skills.some((skill) => skill.tags.includes(tag))
    );
  }

  discoverByInputMode(mimeType: string): AgentCard[] {
    return [...this.cards.values()].filter(
      (card) =>
        card.defaultInputModes.includes(mimeType) ||
        card.skills.some((skill) => skill.inputModes.includes(mimeType))
    );
  }

  resolve(name: string): AgentCard | undefined {
    return this.cards.get(name);
  }

  listAll(): AgentCard[] {
    return [...this.cards.values()];
  }
}

Esto es sustancialmente más rico que un simple mapa de nombre a capacidad. Puedes descubrir agentes por etiquetas de habilidad, por tipos MIME de entrada o por nombre, tal como lo admite la especificación A2A real.

Paso 3: Ciclo de Vida de la Tarea A2A

Construye la máquina de estados de tarea completa:

type TaskState =
  | "submitted"
  | "working"
  | "input-required"
  | "auth-required"
  | "completed"
  | "failed"
  | "canceled"
  | "rejected";

const TERMINAL_STATES: TaskState[] = [
  "completed",
  "failed",
  "canceled",
  "rejected",
];

type TaskStatus = {
  state: TaskState;
  message?: AgentMessage;
  timestamp: number;
};

type Artifact = {
  id: string;
  name: string;
  parts: MessagePart[];
};

type Task = {
  id: string;
  contextId: string;
  status: TaskStatus;
  artifacts: Artifact[];
  history: AgentMessage[];
};

type TaskEvent =
  | { kind: "statusUpdate"; taskId: string; status: TaskStatus }
  | {
      kind: "artifactUpdate";
      taskId: string;
      artifact: Artifact;
      append: boolean;
      lastChunk: boolean;
    };

type TaskHandler = (
  task: Task,
  message: AgentMessage
) => AsyncGenerator<TaskEvent>;

class TaskManager {
  private tasks: Map<string, Task> = new Map();
  private handlers: Map<string, TaskHandler> = new Map();
  private listeners: Map<string, ((event: TaskEvent) => void)[]> = new Map();

  registerHandler(agentName: string, handler: TaskHandler) {
    this.handlers.set(agentName, handler);
  }

  subscribe(taskId: string, listener: (event: TaskEvent) => void) {
    const existing = this.listeners.get(taskId) ?? [];
    existing.push(listener);
    this.listeners.set(taskId, existing);
  }

  async sendMessage(
    agentName: string,
    message: AgentMessage,
    contextId?: string
  ): Promise<Task> {
    const handler = this.handlers.get(agentName);
    if (!handler) {
      const task = this.createTask(contextId);
      task.status = {
        state: "rejected",
        timestamp: Date.now(),
        message: textMessage("agent", `No handler for ${agentName}`),
      };
      return task;
    }

    const task = this.createTask(contextId);
    task.history.push(message);
    task.status = { state: "submitted", timestamp: Date.now() };

    this.processTask(task, handler, message).catch((err) => {
      task.status = {
        state: "failed",
        timestamp: Date.now(),
        message: textMessage("agent", String(err)),
      };
    });
    return task;
  }

  getTask(taskId: string): Task | undefined {
    return this.tasks.get(taskId);
  }

  cancelTask(taskId: string): boolean {
    const task = this.tasks.get(taskId);
    if (!task || TERMINAL_STATES.includes(task.status.state)) return false;
    task.status = { state: "canceled", timestamp: Date.now() };
    this.emit(taskId, {
      kind: "statusUpdate",
      taskId,
      status: task.status,
    });
    return true;
  }

  private createTask(contextId?: string): Task {
    const task: Task = {
      id: crypto.randomUUID(),
      contextId: contextId ?? crypto.randomUUID(),
      status: { state: "submitted", timestamp: Date.now() },
      artifacts: [],
      history: [],
    };
    this.tasks.set(task.id, task);
    return task;
  }

  private async processTask(
    task: Task,
    handler: TaskHandler,
    message: AgentMessage
  ) {
    task.status = { state: "working", timestamp: Date.now() };
    this.emit(task.id, {
      kind: "statusUpdate",
      taskId: task.id,
      status: task.status,
    });

    try {
      for await (const event of handler(task, message)) {
        if (TERMINAL_STATES.includes(task.status.state)) break;

        if (event.kind === "statusUpdate") {
          task.status = event.status;
        }
        if (event.kind === "artifactUpdate") {
          const existing = task.artifacts.find(
            (a) => a.id === event.artifact.id
          );
          if (existing && event.append) {
            existing.parts.push(...event.artifact.parts);
          } else {
            task.artifacts.push(event.artifact);
          }
        }
        this.emit(task.id, event);
      }
    } catch (err) {
      task.status = {
        state: "failed",
        timestamp: Date.now(),
        message: textMessage("agent", String(err)),
      };
      this.emit(task.id, {
        kind: "statusUpdate",
        taskId: task.id,
        status: task.status,
      });
    }
  }

  private emit(taskId: string, event: TaskEvent) {
    for (const listener of this.listeners.get(taskId) ?? []) {
      listener(event);
    }
  }
}

Esto implementa el ciclo de vida real de la tarea A2A: enviada, en ejecución, entrada requerida, estados terminales. Los manejadores son generadores asíncronos que producen eventos (actualizaciones de estado y fragmentos de artefactos) que coinciden con el modelo de streaming SSE.

Paso 4: Trayectoria de Auditoría Estilo ACP

Envuelve la comunicación con el seguimiento de trayectoria:

type AuditEntry = {
  runId: string;
  agentName: string;
  input: AgentMessage[];
  output: AgentMessage[];
  trajectory: TrajectoryEntry[];
  status: "created" | "in-progress" | "completed" | "failed" | "awaiting";
  startedAt: number;
  completedAt?: number;
  sessionId?: string;
};

class AuditableRunner {
  private log: AuditEntry[] = [];
  private handlers: Map<
    string,
    (input: AgentMessage[]) => Promise<{
      output: AgentMessage[];
      trajectory: TrajectoryEntry[];
    }>
  > = new Map();

  registerAgent(
    name: string,
    handler: (input: AgentMessage[]) => Promise<{
      output: AgentMessage[];
      trajectory: TrajectoryEntry[];
    }>
  ) {
    this.handlers.set(name, handler);
  }

  async run(
    agentName: string,
    input: AgentMessage[],
    sessionId?: string
  ): Promise<AuditEntry> {
    const entry: AuditEntry = {
      runId: crypto.randomUUID(),
      agentName,
      input: structuredClone(input),
      output: [],
      trajectory: [],
      status: "created",
      startedAt: Date.now(),
      sessionId,
    };
    this.log.push(entry);

    const handler = this.handlers.get(agentName);
    if (!handler) {
      entry.status = "failed";
      return entry;
    }

    entry.status = "in-progress";
    try {
      const result = await handler(input);
      entry.output = structuredClone(result.output);
      entry.trajectory = structuredClone(result.trajectory);
      entry.status = "completed";
      entry.completedAt = Date.now();
    } catch (err) {
      entry.status = "failed";
      entry.trajectory.push({
        reasoning: `Error: ${String(err)}`,
        timestamp: Date.now(),
      });
      entry.completedAt = Date.now();
    }
    return entry;
  }

  getFullAuditLog(): AuditEntry[] {
    return structuredClone(this.log);
  }

  getAuditLogForAgent(agentName: string): AuditEntry[] {
    return structuredClone(
      this.log.filter((e) => e.agentName === agentName)
    );
  }

  getAuditLogForSession(sessionId: string): AuditEntry[] {
    return structuredClone(
      this.log.filter((e) => e.sessionId === sessionId)
    );
  }

  getTrajectoryForRun(runId: string): TrajectoryEntry[] {
    const entry = this.log.find((e) => e.runId === runId);
    return entry ? structuredClone(entry.trajectory) : [];
  }
}

Cada ejecución del agente produce una entrada de auditoría completa: lo que ingresó, lo que salió y la trayectoria completa de llamadas a herramientas y pasos de razonamiento intermedios. Puedes consultar por agente, por sesión o por ejecución individual.

Paso 5: Verificación de Identidad Estilo ANP

Construye una identidad basada en DID y verificación:

type VerificationMethod = {
  id: string;
  type: string;
  controller: string;
  publicKeyDer: string;
};

type DIDDocument = {
  id: string;
  verificationMethod: VerificationMethod[];
  authentication: string[];
  keyAgreement: string[];
  humanAuthorization: string[];
  service: { id: string; type: string; serviceEndpoint: string }[];
};

type AgentIdentity = {
  did: string;
  document: DIDDocument;
  privateKey: crypto.KeyObject;
  publicKey: crypto.KeyObject;
};

class IdentityRegistry {
  private documents: Map<string, DIDDocument> = new Map();

  publish(doc: DIDDocument) {
    this.documents.set(doc.id, doc);
  }

  resolve(did: string): DIDDocument | undefined {
    return this.documents.get(did);
  }

  verify(did: string, signature: string, payload: string): boolean {
    const doc = this.documents.get(did);
    if (!doc) return false;

    const authKeyIds = doc.authentication;
    const authKeys = doc.verificationMethod.filter((vm) =>
      authKeyIds.includes(vm.id)
    );

    for (const key of authKeys) {
      const publicKey = crypto.createPublicKey({
        key: Buffer.from(key.publicKeyDer, "base64"),
        format: "der",
        type: "spki",
      });
      const isValid = crypto.verify(
        null,
        Buffer.from(payload),
        publicKey,
        Buffer.from(signature, "hex")
      );
      if (isValid) return true;
    }
    return false;
  }

  requiresHumanAuth(did: string, operationKeyId: string): boolean {
    const doc = this.documents.get(did);
    if (!doc) return false;
    return doc.humanAuthorization.includes(operationKeyId);
  }
}

function createIdentity(domain: string, agentName: string): AgentIdentity {
  const did = `did:wba:${domain}:agent:${agentName}`;
  const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");

  const publicKeyDer = publicKey
    .export({ format: "der", type: "spki" })
    .toString("base64");

  const keyId = `${did}#key-1`;
  const encKeyId = `${did}#key-x25519-1`;

  const document: DIDDocument = {
    id: did,
    verificationMethod: [
      {
        id: keyId,
        type: "Ed25519VerificationKey2020",
        controller: did,
        publicKeyDer,
      },
      {
        id: encKeyId,
        type: "X25519KeyAgreementKey2019",
        controller: did,
        publicKeyDer,
      },
    ],
    authentication: [keyId],
    keyAgreement: [encKeyId],
    humanAuthorization: [],
    service: [
      {
        id: `${did}#agent-description`,
        type: "AgentDescription",
        serviceEndpoint: `https://${domain}/agents/${agentName}/ad.json`,
      },
    ],
  };

  return { did, document, privateKey, publicKey };
}

function signPayload(identity: AgentIdentity, payload: string): string {
  return crypto
    .sign(null, Buffer.from(payload), identity.privateKey)
    .toString("hex");
}

Esto refleja el modelo de identidad real de ANP: los agentes tienen documentos DID con llaves de autenticación, acuerdo de llaves (key agreement) y autorización humana separadas. El IdentityRegistry simula la resolución de DID (en producción serían peticiones HTTP al dominio del agente).

Paso 6: Protocol Gateway

Conecta los cuatro protocolos en un sistema unificado:

graph LR
    REQ[Solicitud Entrante] --> ANP_V{ANP: Verificar DID}
    ANP_V -->|Válido| A2A_D{A2A: Descubrir Agente}
    ANP_V -->|Inválido| REJECT[Rechazar]
    A2A_D -->|Encontrado| ACP_A[ACP: Auditar Ejecución]
    A2A_D -->|No Encontrado| REJECT
    ACP_A --> A2A_T[A2A: Crear Tarea]
    A2A_T --> RESULT[Tarea + Entrada de Auditoría]

    style ANP_V fill:#d1fae5,stroke:#059669
    style A2A_D fill:#dbeafe,stroke:#2563eb
    style ACP_A fill:#fef3c7,stroke:#d97706
    style A2A_T fill:#dbeafe,stroke:#2563eb
class ProtocolGateway {
  private registry: AgentRegistry;
  private taskManager: TaskManager;
  private auditRunner: AuditableRunner;
  private identityRegistry: IdentityRegistry;

  constructor(
    registry: AgentRegistry,
    taskManager: TaskManager,
    auditRunner: AuditableRunner,
    identityRegistry: IdentityRegistry
  ) {
    this.registry = registry;
    this.taskManager = taskManager;
    this.auditRunner = auditRunner;
    this.identityRegistry = identityRegistry;
  }

  async delegateTask(
    fromDid: string,
    signature: string,
    targetAgent: string,
    message: AgentMessage,
    sessionId?: string
  ): Promise<{ task: Task; audit: AuditEntry } | { error: string }> {
    if (!this.identityRegistry.verify(fromDid, signature, message.id)) {
      return { error: "Identity verification failed" };
    }

    const card = this.registry.resolve(targetAgent);
    if (!card) {
      return { error: `Agent ${targetAgent} not found in registry` };
    }

    const audit = await this.auditRunner.run(
      targetAgent,
      [message],
      sessionId
    );
    const task = await this.taskManager.sendMessage(targetAgent, message);

    return { task, audit };
  }

  discoverAndDelegate(
    fromDid: string,
    signature: string,
    skillTag: string,
    message: AgentMessage
  ): Promise<{ task: Task; audit: AuditEntry } | { error: string }> {
    const candidates = this.registry.discoverBySkillTag(skillTag);
    if (candidates.length === 0) {
      return Promise.resolve({
        error: `No agents found with skill tag: ${skillTag}`,
      });
    }
    return this.delegateTask(
      fromDid,
      signature,
      candidates[0].name,
      message
    );
  }
}

El gateway realiza cuatro cosas en una sola llamada:

  1. ANP: Verifica la identidad del remitente a través de la firma DID
  2. A2A: Descubre el agente de destino y verifica sus capacidades
  3. ACP: Envuelve la ejecución en una trayectoria de auditoría
  4. A2A: Crea una tarea con seguimiento completo del ciclo de vida

Paso 7: Conectando Todo

async function protocolDemo() {
  const registry = new AgentRegistry();
  registry.register({
    name: "researcher",
    description: "Searches and summarizes findings",
    version: "1.0.0",
    url: "https://researcher.local/a2a/v1",
    capabilities: { streaming: true, pushNotifications: false },
    defaultInputModes: ["text/plain"],
    defaultOutputModes: ["text/plain", "application/json"],
    skills: [
      {
        id: "web-research",
        name: "Web Research",
        description: "Searches the web",
        tags: ["research", "search", "summarization"],
        inputModes: ["text/plain"],
        outputModes: ["application/json"],
      },
    ],
  });
  registry.register({
    name: "coder",
    description: "Writes code from specs",
    version: "1.0.0",
    url: "https://coder.local/a2a/v1",
    capabilities: { streaming: false, pushNotifications: false },
    defaultInputModes: ["text/plain", "application/json"],
    defaultOutputModes: ["text/plain"],
    skills: [
      {
        id: "code-gen",
        name: "Code Generation",
        description: "Generates code",
        tags: ["coding", "generation"],
        inputModes: ["text/plain", "application/json"],
        outputModes: ["text/plain"],
      },
    ],
  });

  const taskManager = new TaskManager();
  const auditRunner = new AuditableRunner();

  const researchTrajectory: TrajectoryEntry[] = [];

  taskManager.registerHandler(
    "researcher",
    async function* (task, message) {
      yield {
        kind: "statusUpdate" as const,
        taskId: task.id,
        status: { state: "working" as const, timestamp: Date.now() },
      };

      researchTrajectory.push({
        reasoning: "Searching for React 19 documentation",
        toolName: "web_search",
        toolInput: { query: "React 19 compiler features" },
        toolOutput: {
          results: ["react.dev/blog/react-19", "github.com/react/react"],
        },
        timestamp: Date.now(),
      });

      researchTrajectory.push({
        reasoning: "Extracting key findings from search results",
        toolName: "doc_analysis",
        toolInput: { url: "react.dev/blog/react-19" },
        toolOutput: {
          summary:
            "React 19 compiler auto-memoizes, no manual useMemo needed",
        },
        timestamp: Date.now(),
      });

      yield {
        kind: "artifactUpdate" as const,
        taskId: task.id,
        artifact: {
          id: crypto.randomUUID(),
          name: "research-results",
          parts: [
            {
              kind: "data" as const,
              data: {
                findings: [
                  "React 19 compiler auto-memoizes components",
                  "No more manual useMemo/useCallback needed",
                  "Compiler runs at build time, not runtime",
                ],
                sources: ["react.dev/blog/react-19"],
              },
              mediaType: "application/json",
            },
          ],
        },
        append: false,
        lastChunk: true,
      };

      yield {
        kind: "statusUpdate" as const,
        taskId: task.id,
        status: { state: "completed" as const, timestamp: Date.now() },
      };
    }
  );

  auditRunner.registerAgent("researcher", async () => ({
    output: [
      textMessage("agent", "React 19 compiler auto-memoizes components"),
    ],
    trajectory: researchTrajectory,
  }));

  const identityRegistry = new IdentityRegistry();

  const coderIdentity = createIdentity("coder.local", "coder");
  const researcherIdentity = createIdentity("researcher.local", "researcher");

  identityRegistry.publish(coderIdentity.document);
  identityRegistry.publish(researcherIdentity.document);

  const gateway = new ProtocolGateway(
    registry,
    taskManager,
    auditRunner,
    identityRegistry
  );

  console.log("=== Protocol Demo ===\n");

  console.log("1. Agent Discovery (A2A)");
  const researchAgents = registry.discoverBySkillTag("research");
  console.log(
    `   Found ${researchAgents.length} agent(s):`,
    researchAgents.map((a) => a.name)
  );

  console.log("\n2. Identity Verification (ANP)");
  const message = textMessage("user", "Research React 19 compiler features");
  const signature = signPayload(coderIdentity, message.id);
  const verified = identityRegistry.verify(
    coderIdentity.did,
    signature,
    message.id
  );
  console.log(`   Coder DID: ${coderIdentity.did}`);
  console.log(`   Signature verified: ${verified}`);

  console.log("\n3. Task Delegation (A2A + ACP + ANP)");
  const result = await gateway.delegateTask(
    coderIdentity.did,
    signature,
    "researcher",
    message,
    "session-001"
  );

  if ("error" in result) {
    console.log(`   Error: ${result.error}`);
    return;
  }

  console.log(`   Task ID: ${result.task.id}`);
  console.log(`   Task state: ${result.task.status.state}`);
  console.log(`   Artifacts: ${result.task.artifacts.length}`);

  console.log("\n4. Audit Trail (ACP)");
  console.log(`   Run ID: ${result.audit.runId}`);
  console.log(`   Status: ${result.audit.status}`);
  console.log(`   Trajectory steps: ${result.audit.trajectory.length}`);
  for (const step of result.audit.trajectory) {
    console.log(`     - ${step.reasoning}`);
    if (step.toolName) {
      console.log(`       Tool: ${step.toolName}`);
    }
  }

  console.log("\n5. Full Audit Log");
  const fullLog = auditRunner.getFullAuditLog();
  console.log(`   Total runs: ${fullLog.length}`);
  for (const entry of fullLog) {
    const duration = entry.completedAt
      ? `${entry.completedAt - entry.startedAt}ms`
      : "in-progress";
    console.log(`   ${entry.agentName}: ${entry.status} (${duration})`);
  }
}

protocolDemo().catch((err) => {
  console.error("Protocol demo failed:", err);
  process.exitCode = 1;
});

Qué Puede Salir Mal

Los protocolos resuelven el camino feliz (happy path). Esto es lo que se rompe en producción:

Desviación de esquema (Schema drift). El agente A publica un Agent Card anunciando la salida application/json. Pero el esquema JSON cambia entre versiones. El agente B analiza el formato antiguo y obtiene basura. Solución: versiona tus habilidades y esquemas de salida. La especificación A2A admite version en los Agent Cards por este motivo.

Violaciones de la máquina de estados. Un manejador de agente produce un evento completed e intenta producir más artefactos. La tarea es inmutable. Tu código descarta silenciosamente las actualizaciones o genera un error. Solución: verifica el estado terminal antes de producir nuevos eventos. El TaskManager de arriba impone esto con la instrucción break después de los estados terminales.

Fallos en la resolución de confianza. El agente A intenta verificar el DID del agente B, pero el dominio del agente B está caído. El documento DID no se puede obtener. ¿Permites el acceso (aceptas agentes no verificados) o bloqueas (rechazas todo)? ANP recomienda bloquear el acceso (fail closed) con el principio de menor confianza.

Infiltración de trayectoria (Trajectory bloat). El registro de trayectoria de ACP es potente pero costoso. Un agente complejo que realiza 200 llamadas a herramientas por ejecución produce entradas de auditoría masivas. Solución: registra la trayectoria en niveles de detalle configurables. Registra los nombres de las herramientas y las entradas/salidas para cumplimiento normativo, y omite los pasos de razonamiento para cargas de trabajo no reguladas.

Avalancha de descubrimiento (Thundering herd). 50 agentes consultan GET /agents simultáneamente al inicio. Solución: almacena en caché los Agent Cards con TTL, escala los intervalos de descubrimiento o utiliza registro basado en push en lugar de polling.

Use It

Implementaciones Reales

A2A es el más maduro. La especificación oficial de Google es de código abierto bajo la Linux Foundation. Cuenta con SDKs para Python y TypeScript. Si tus agentes necesitan descubrimiento dinámico y colaboración, comienza aquí.

ACP se está fusionando con A2A. El proyecto BeeAI de IBM creó ACP como una alternativa enfocada primero en REST, pero el concepto de metadados de trayectoria está siendo absorbido por el ecosistema A2A. Utiliza patrones de ACP (registro de trayectoria, ciclo de vida de ejecución) incluso si utilizas A2A como transporte.

ANP es el más experimental. El repositorio de la comunidad tiene un SDK de Python (AgentConnect). El concepto de negociación de meta-protocolo es genuinamente novedoso. Vale la pena seguirlo para implementaciones de agentes entre organizaciones.

MCP ya se cubrió en la Fase 13. Si deseas que los agentes utilicen herramientas, MCP es el estándar.

Elegir el Protocolo Correcto

graph TD
    START{¿Los agentes necesitan<br/>usar herramientas?}
    START -->|Sí| MCP_R[Usa MCP]
    START -->|No| TALK{¿Los agentes necesitan<br/>hablar entre sí?}
    TALK -->|No| NONE[No necesitas<br/>un protocolo]
    TALK -->|Sí| AUDIT{¿Necesitas trayectorias de<br/>auditoría para cumplimiento?}
    AUDIT -->|Sí| ACP_R[A2A + ACP<br/>patrones de trayectoria]
    AUDIT -->|No| ORG{¿Todos los agentes<br/>están en tu org?}
    ORG -->|Sí| A2A_R[A2A<br/>Agent Cards + Tareas]
    ORG -->|No| INFRA{¿Comparten<br/>infraestructura?}
    INFRA -->|Sí| BROKER[A2A + message broker]
    INFRA -->|No| ANP_R[ANP + A2A<br/>verificación de DID]

    style MCP_R fill:#d1fae5,stroke:#059669
    style A2A_R fill:#dbeafe,stroke:#2563eb
    style ACP_R fill:#fef3c7,stroke:#d97706
    style ANP_R fill:#f3e8ff,stroke:#7c3aed
    style BROKER fill:#e0e7ff,stroke:#4338ca

Ship It

Esta lección produce:

  • code/main.ts -- implementación completa de los cuatro patrones de protocolo
  • outputs/prompt-protocol-selector.md -- un prompt que te ayuda a elegir protocolos para tu sistema

Ejercicios

  1. Delegación de tareas de múltiples saltos (Multi-hop). Extiende el TaskManager para que el manejador de un agente pueda delegar subtareas a otros agentes. El investigador recibe una tarea, delega las subtareas "buscar" y "resumir" a dos agentes especialistas, espera a que ambos terminen y luego fusiona los resultados en sus propios artefactos.

  2. Trayectoria de auditoría en tiempo real (Streaming). Modifica el AuditableRunner para admitir el modo streaming. En lugar de esperar el resultado completo, produce actualizaciones de AuditEntry en tiempo real a medida que se agregan entradas de trayectoria. Utiliza un generador asíncrono que produzca snapshots de auditoría.

  3. Rotación de DID. Agrega rotación de llaves a IdentityRegistry. Un agente debe poder publicar un nuevo documento DID con llaves actualizadas, manteniendo una referencia a previousDid. Los verificadores deben aceptar firmas tanto de la llave actual como de la anterior durante un período de gracia.

  4. Negociación de protocolo. Implementa el concepto de meta-protocolo de ANP. Dos agentes intercambian mensajes protocolNegotiation con formatos candidatos (por ejemplo, "Puedo hablar JSON-RPC" frente a "Prefiero REST"). Después de un máximo de 3 rondas, acuerdan un formato o se produce un timeout. El formato acordado determina qué TaskManager o AuditableRunner utilizan.

  5. Descubrimiento con límite de tasa (Rate-limiting). Agrega un wrapper RateLimitedRegistry que almacene en caché las consultas de Agent Cards con un TTL configurable y limite las consultas de descubrimiento por agente por segundo. Simula una avalancha (thundering herd) de 100 agentes que se descubren entre sí al inicio y mide la diferencia.

Términos Clave

Término Qué dice la gente Qué significa realmente
MCP "El protocolo para herramientas de IA" Un protocolo cliente-servidor para que los agentes descubran y utilicen herramientas. De agente a herramienta, no de agente a agente.
A2A "El protocolo de agentes de Google" Un protocolo punto a punto para la colaboración de agentes bajo la Linux Foundation. Descubrimiento a través de Agent Cards, ciclo de vida de tareas de 9 estados, streaming a través de SSE. Admite vinculaciones JSON-RPC, REST y gRPC.
ACP "Mensajería empresarial para agentes" API REST de IBM/BeeAI para ejecuciones (runs) de agentes con TrajectoryMetadata: cada respuesta lleva la cadena completa de razonamiento y llamadas a herramientas. Fusionándose con A2A.
ANP "Identidad descentralizada de agentes" Un protocolo comunitario que utiliza did:wba (DID) para identidad criptográfica, HPKE para E2EE y negociación de meta-protocolo basada en IA para agentes que nunca se han visto antes.
Agent Card "La tarjeta de presentación del agente" Un documento JSON en /.well-known/agent-card.json que describe habilidades, tipos MIME admitidos, esquemas de seguridad y vinculaciones de protocolo.
DID "ID descentralizado" Estándar de W3C para identidades criptográficamente verificables alojadas en el propio dominio del agente. ANP utiliza el método did:wba.
TrajectoryMetadata "El recibo de auditoría" Mecanismo de ACP para adjuntar pasos de razonamiento, llamadas a herramientas y sus entradas/salidas a cada respuesta del agente.
Meta-protocolo "Agentes negociando cómo hablar" Enfoque de ANP donde los agentes utilizan lenguaje natural para acordar dinámicamente formatos de datos y luego generan código para manejarlos.
Tarea "Una unidad de trabajo" El objeto con estado de A2A que rastea el trabajo desde su envío hasta su finalización. Inmutable una vez terminal.

Lecturas Adicionales

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).