通信议定书
Type: Build
Languages: TypeScript
Prerequisites: Phase 14 (Agent Engineering), Lesson 16.01 (Why Multi-Agent)
Time: ~120 minutes
学习目标
- 实现MCP工具的发现和调用,使代理人可以使用外部服务器暴露的工具
- 构建一个A2A代理卡和任务终点,允许一个代理通过HTTP将工作委托给另一个
- 比较MCP (工具访问),A2A (代理对代理),ACP (企业审计),和ANP (分散信任) 并解释哪个协议解决哪个问题
- 通过MCP发现工具并通过A2A授权任务的代理人将多个协议连接到一个系统中
问题
你把系统分成多个代理人,一个研究人员,一个编码人员,一个评论员. 他们在各自的工作上很擅长.
你的第一个尝试是显而易见的:传递字符串.研究人员返回一个文本,编码器尽可能分析它.它运作直到编码器误解研究摘要,或两个代理人等待彼此,或者你需要由不同的团队构建的代理人合作.突然"只传递字符串"崩.
没有共享合同, 代理交换信息的方式, 多代理系统是脆弱的,无法审视的,
人工智能生态系统使用了四个协议来解决问题:
- MCP工具的访问
- A2A代理人与代理人的合作
- ACP企业审计能力
- ANP实现分散的身份和信任
这一课深入,你将从每个规范中读取真实的电线格式,构建工作实现,
概念
议定书的景观
想象这些四个协议是层次的,
flowchart TD ANP["ANP — How do agents trust strangers?<br/>Decentralized identity (DID), E2EE, meta-protocol"] A2A["A2A — How do agents collaborate on goals?<br/>Agent Cards, task lifecycle, streaming, negotiation"] ACP["ACP — How do agents talk in auditable systems?<br/>Runs, trajectory metadata, session continuity"] MCP["MCP — How does an agent use a tool?<br/>Tool discovery, execution, context sharing"] style ANP fill:#f3e8ff,stroke:#7c3aed style A2A fill:#dbeafe,stroke:#2563eb style ACP fill:#fef3c7,stroke:#d97706 style MCP fill:#d1fae5,stroke:#059669
他们不是竞争对手,而是在不同层面解决不同的问题.
经过回调的 MCP
简单简单:MCP标准化了 LLM与外部工具和数据来源的连接方式.client-server协议中,代理 (客户端) 发现并调用服务器暴露的工具.
sequenceDiagram
participant Agent as Agent (client)
participant MCP1 as MCP Server<br/>(database, API, files)
Agent->>MCP1: list tools
MCP1-->>Agent: tool definitions
Agent->>MCP1: call tool X
MCP1-->>Agent: result股是agent-to-tool没有帮助代理人互相交谈.
其他类型的产品
Created by:谷歌 (现在在Linux基金会下)lf.a2a.v1)
Spec version:1.0.1
Problem:如何自主代理人合作,谈判,并委托任务?
A2A是协议peer-to-peer agent collaboration任何代理都会发布一个信息,Agent Card其他代理人发现,与谈判,并委托任务.
#### 如何使用A2A
sequenceDiagram
participant Client as Client Agent
participant Remote as Remote Agent
Client->>Remote: GET /.well-known/agent-card.json
Remote-->>Client: Agent Card (skills, modes, security)
Client->>Remote: POST /message:send (returnImmediately)
Remote-->>Client: Task (TASK_STATE_SUBMITTED or TASK_STATE_WORKING)
alt Polling
Client->>Remote: GET /tasks/{id}
Remote-->>Client: Task status + artifacts
else Streaming
Client->>Remote: POST /message:stream
Remote-->>Client: SSE: statusUpdate
Remote-->>Client: SSE: artifactUpdate
Remote-->>Client: SSE: statusUpdate TASK_STATE_COMPLETED, stream closes
end#### 真正的代理卡
作为一个A2A代理卡,GET /.well-known/agent-card.json其他:
json{
"name": "Research Agent",
"description": "Searches documentation and summarizes findings",
"version": "1.0.0",
"supportedInterfaces": [
{
"url": "https: TOK0
"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: TOK2
},
"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"
}
}
},
"securityRequirements": [{ "schemes": { "bearer": { "list": [] } } }]
}值得注意的关键点:
- Skills客户端代理可以做什么.每个都有一个ID,标签,以及支持的输入/输出MIME类型. 这就是客户端代理决定该远程代理是否可以处理其请求.
- supportedInterfaces一个代理可以同时使用 JSON-RPC,REST和gRPC.
- Security卡内嵌在:
securitySchemes每个方案的名称和securityRequirements客户在提出单项请求之前知道需要什么作者.
#### 任务生命周期
任务是A2A中的核心工作单位.它们通过定义状态移动 (图表下降了TASK_STATE_预写每个状态都在线上运行):
stateDiagram-v2
[*] --> SUBMITTED
SUBMITTED --> WORKING
WORKING --> INPUT_REQUIRED: needs more info
INPUT_REQUIRED --> WORKING: client sends data
WORKING --> COMPLETED: success
WORKING --> FAILED: error
WORKING --> CANCELED: client cancels
SUBMITTED --> REJECTED: agent declines
COMPLETED --> [*]
FAILED --> [*]
CANCELED --> [*]
REJECTED --> [*]
note right of COMPLETED
Terminal states are immutable.
Follow-ups create new tasks
within the same contextId.
end note标准也定义了UNSPECIFIED作为哨兵,在此未删除):
| State | Terminal? | Meaning |
|---|---|---|
TASK_STATE_SUBMITTED | No | Acknowledged, not yet processing |
TASK_STATE_WORKING | No | Actively being processed |
TASK_STATE_INPUT_REQUIRED | No | Agent needs more info from client |
TASK_STATE_AUTH_REQUIRED | No | Authentication needed |
TASK_STATE_COMPLETED | Yes | Finished successfully |
TASK_STATE_FAILED | Yes | Finished with error |
TASK_STATE_CANCELED | Yes | Canceled before completion |
TASK_STATE_REJECTED | Yes | Agent declined the task |
一旦任务达到终端状态,它是不可改变的. 没有更多的消息. 后续创建一个新的任务在同一任务中.contextId现在,我们要去.
#### 电缆格式
简单的信息交换方式是这样的:
Client sends a message:
json{
"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
}
}
}Agent responds with a task:
json{
"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 via SSE:
textPOST /message:stream HTTP/1.1
Content-Type: application/a2a+json
A2A-Version: 1.0
data: {"task":{"id":"task-123","contextId":"ctx-123","status":{"state":"TASK_STATE_WORKING"}}}
data: {"statusUpdate":{"taskId":"task-123","contextId":"ctx-123","status":{"state":"TASK_STATE_WORKING","message":{"messageId":"msg-002","role":"ROLE_AGENT","parts":[{"text":"Searching documentation..."}]}}}}
data: {"artifactUpdate":{"taskId":"task-123","contextId":"ctx-123","artifact":{"artifactId":"art-1","parts":[{"text":"partial findings..."}]},"append":true,"lastChunk":false}}
data: {"statusUpdate":{"taskId":"task-123","contextId":"ctx-123","status":{"state":"TASK_STATE_COMPLETED"}}}亚太地区 (代理通信议定书)
Created by:国际电脑/比亚
Spec version:其他技术
Status:在Linux基金会下合并到A2A
Problem:如何通过完整的审计,会议连续性和轨迹跟踪来通信?
ACP是enterprise protocol与许多总结所说的不同,ACP确实是not简单的REST/JSON API是通过OpenAPI定义的.TrajectoryMetadata答案的每个代理人都能记录出其产生的推理步骤和工具调用.
sequenceDiagram
participant Client
participant ACP as ACP Agent
participant Audit as Audit Log
Client->>ACP: POST /runs (mode: sync)
ACP->>ACP: Process request...
ACP->>Audit: Log trajectory:<br/>reasoning + tool calls
ACP-->>Client: Response + TrajectoryMetadata
Note over Audit: Every step recorded:<br/>tool_name, tool_input,<br/>tool_output, reasoning#### 发现代理在ACP
亚太地区的发现方法有四种:
graph LR
A[Agent Discovery] --> B["Runtime<br/>GET /agents"]
A --> C["Open<br/>.well-known/agent.yml"]
A --> D["Registry<br/>Centralized catalog"]
A --> E["Embedded<br/>Container labels"]
style B fill:#dbeafe,stroke:#2563eb
style C fill:#d1fae5,stroke:#059669
style D fill:#fef3c7,stroke:#d97706
style E fill:#f3e8ff,stroke:#7c3aed其他AgentManifest简单于A2A的代理卡:
json{
"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"
}
}#### 运行生命周期
运行是执行代理的三个模式:
| Mode | Behavior |
|---|---|
sync | Blocking. Response contains the complete result. |
async | Returns 202 immediately. Poll GET /runs/{id} for status. |
stream | SSE stream. Events fire as the agent works. |
stateDiagram-v2
[*] --> created
created --> in_progress
in_progress --> completed: success
in_progress --> failed: error
in_progress --> awaiting: needs input
awaiting --> in_progress: client resumes
in_progress --> cancelling: cancel request
cancelling --> cancelled
completed --> [*]
failed --> [*]
cancelled --> [*]#### 轨迹 计量数据 (审计轨迹)
任何信息部分都能包含显示代理所做的事情的元数据:
json{
"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" }
}
}
]
}对于受监管的行业来说,这是黄金. 每个答案都伴随着一个可证明的推理链:
非洲国家及地区的发展CitationMetadata对于源归因:
json{
"kind": "citation",
"start_index": 0,
"end_index": 47,
"url": "https: TOK0
"title": "NWS San Francisco Forecast"
}代理网络协议 (ANP)
Created by:开源社区 (由高伟长创立)
Repo: github.com/agent-network-protocol/AgentNetworkProtocol
Problem:如何让不同组织的代理人,
美国国家安全局decentralized identity protocol通过W3C分散识别器 (DID) 和端到端加密建立信任.与A2A不同,在A2A中通过已知终端点发现代理人,ANP允许代理人通过加密证明他们的身份.
ANP有三个层:
graph TB
subgraph Layer3["Layer 3: Application Protocol"]
AD[Agent Description Documents]
DISC[Discovery endpoints]
end
subgraph Layer2["Layer 2: Meta-Protocol"]
NEG[AI-powered protocol negotiation]
CODE[Dynamic code generation]
end
subgraph Layer1["Layer 1: Identity & Secure Communication"]
DID["did:wba (W3C DID)"]
HPKE[HPKE E2EE - RFC 9180]
SIG[Signature verification]
end
Layer3 --> Layer2
Layer2 --> Layer1
style Layer1 fill:#d1fae5,stroke:#059669
style Layer2 fill:#dbeafe,stroke:#2563eb
style Layer3 fill:#f3e8ff,stroke:#7c3aed#### 关于"实体结构"的文件
ANP使用一个名为 did:wba据悉,这次的调查结果是did:wba:example.com:user:alice解决问题https://example.com/user/alice/did.json其他:
json{
"@context": [
"https: TOK0
"https://w3id.org/security/suites/jws-2020/v1",
"https: TOK2
],
"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 TOK4
"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 TOK6
],
"humanAuthorization": [
"did:wba:example.com:user:alice#key-1"
],
"service": [
{
"id": "did:wba:example.com:user:alice TOK8
"type": "AgentDescription",
"serviceEndpoint": "https://example.com/agents/alice/ad.json"
}
]
}值得注意的关键点:
- Key separation签字密钥 (secp256k1) 与加密密钥 (X25519) 分开.
humanAuthorization通过这些密钥,使用者必须在使用前明确的人类批准 (生物识别,密码,HSM).keyAgreement密钥用于HPKE端到端加密 (RFC 9180).- 其他service部分链接到代理描述文件.
#### 如何在ANP中建立信任
美国国家安全局not使用信任网或认可图.信任是双边的,并且每次互动都被验证:
sequenceDiagram
participant A as Agent A
participant Domain as Agent A's Domain
participant B as Agent B
A->>B: HTTP request + DID + signature
B->>Domain: Fetch DID document (HTTPS)
Domain-->>B: DID document + public key
B->>B: Verify signature with public key
B-->>A: Issue access token
A->>B: Subsequent requests use token
Note over A,B: Trust = TLS domain verification<br/>+ DID signature verification<br/>+ Principle of least trust信心来自三个来源:
- Domain-level TLS验证了DID文件的主机
- DID cryptographic signatures验证代理人的身份
- Principle of least trust仅授予最低许可
没有基于言的信任传播或页面排名评分.
#### 标签协议谈判
两位来自不同生态系统的代理人会面时,他们不需要预先达成的数据格式.
json{
"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 Agent A
participant B as Agent B
A->>B: protocolNegotiation (candidateProtocols)
B->>A: protocolNegotiation (counter-proposal)
A->>B: protocolNegotiation (accepted)
Note over A,B: Agents dynamically generate code<br/>to handle the agreed format.<br/>Max 10 rounds, then timeout.代理人往前回 (最大10次发射) 直到他们达成协议,然后动态生成代码来处理它.negotiating现在rejected现在accepted现在timeout现在,我们要去.
这意味着两个从未见过的代理人可以在没有人预先定义共享方案的情况下找到如何沟通.
较量 (已修正)
| MCP | A2A | ACP | ANP | |
|---|---|---|---|---|
| Created by | Anthropic | Google / Linux Foundation | IBM / BeeAI | Community |
| Spec format | JSON-RPC | JSON-RPC / REST / gRPC | OpenAPI 3.1 (REST) | JSON-RPC |
| Primary use | Agent to Tool | Agent to Agent | Agent to Agent | Agent to Agent |
| Discovery | Tool listing | /.well-known/agent-card.json | GET /agents, /.well-known/agent.yml | /.well-known/agent-descriptions, DID service endpoints |
| Identity | Implicit (local) | Security schemes (OAuth, mTLS) | Server-level | W3C DID (did:wba) with E2EE |
| Audit trail | N/A | Basic (task history) | TrajectoryMetadata (tool calls, reasoning) | Not formally specified |
| State machine | N/A | 9 task states | 7 run states | N/A |
| Streaming | N/A | SSE | SSE | Transport-agnostic |
| Unique feature | Tool schemas | Agent Cards + Skills | Trajectory audit trail | Meta-protocol negotiation |
| Best for | Tools & data | Dynamic collaboration | Regulated industries | Cross-org trust |
| Status | Stable | Stable (v1.0) | Merging into A2A | Active development |
他们如何合作
实际的企业系统使用多种:
graph TB
subgraph org["Your Organization"]
RA[Research Agent] <-->|A2A| CA[Coding Agent]
RA -->|MCP| SS[Search Server]
CA -->|MCP| GS[GitHub Server]
AUDIT["All agent responses carry<br/>ACP TrajectoryMetadata"]
end
subgraph ext["External (DID verified via 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连接每个代理到其工具
- A2A管理代理人之间的合作 (内部和外部)
- ACP封装响应为可审计的轨迹元数据
- ANP提供身份验证,为你无法控制的代理人
建立它
第一个步骤:核心信息类型
我们定义了将数据映射到实际协议中使用的类型:
typescriptimport crypto from "node:crypto";
type MessageRole = "ROLE_USER" | "ROLE_AGENT";
type MessagePart =
| { text: string }
| { data: unknown; mediaType: string }
| { url: string; filename: 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, [{ text }]);
}注意:MessagePart像A2A 1.0一样,现有的字段 (text现在data其他url) 表示部分是什么;没有kind标签TrajectoryEntry采集了基于ACP的轨迹数据的推理链.
步骤2:A2A代理卡和注册表
建立一个与A2A的真实规格相匹配的发现代理:
typescripttype Skill = {
id: string;
name: string;
description: string;
tags: string[];
inputModes: string[];
outputModes: string[];
};
type AgentInterface = {
url: string;
protocolBinding: string;
protocolVersion: string;
};
type AgentCard = {
name: string;
description: string;
version: string;
supportedInterfaces: AgentInterface[];
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()];
}
}通过技能标签,输入MIME类型或名称,就像真正的A2A规范支持的那样.
步骤3:A2A任务生命周期
构建一个完整的任务状态机:
typescripttype TaskState =
| "TASK_STATE_SUBMITTED"
| "TASK_STATE_WORKING"
| "TASK_STATE_INPUT_REQUIRED"
| "TASK_STATE_AUTH_REQUIRED"
| "TASK_STATE_COMPLETED"
| "TASK_STATE_FAILED"
| "TASK_STATE_CANCELED"
| "TASK_STATE_REJECTED";
const TERMINAL_STATES: TaskState[] = [
"TASK_STATE_COMPLETED",
"TASK_STATE_FAILED",
"TASK_STATE_CANCELED",
"TASK_STATE_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 =
| { statusUpdate: { taskId: string; status: TaskStatus } }
| {
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: "TASK_STATE_REJECTED",
timestamp: Date.now(),
message: textMessage("ROLE_AGENT", `No handler for ${agentName}`),
};
return task;
}
const task = this.createTask(contextId);
task.history.push(message);
task.status = { state: "TASK_STATE_SUBMITTED", timestamp: Date.now() };
this.processTask(task, handler, message).catch((err) => {
task.status = {
state: "TASK_STATE_FAILED",
timestamp: Date.now(),
message: textMessage("ROLE_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: "TASK_STATE_CANCELED", timestamp: Date.now() };
this.emit(taskId, {
statusUpdate: { taskId, status: task.status },
});
return true;
}
private createTask(contextId?: string): Task {
const task: Task = {
id: crypto.randomUUID(),
contextId: contextId ?? crypto.randomUUID(),
status: { state: "TASK_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: "TASK_STATE_WORKING", timestamp: Date.now() };
this.emit(task.id, {
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 ("statusUpdate" in event) {
task.status = event.statusUpdate.status;
}
if ("artifactUpdate" in event) {
const update = event.artifactUpdate;
const existing = task.artifacts.find(
(a) => a.id === update.artifact.id
);
if (existing && update.append) {
existing.parts.push(...update.artifact.parts);
} else {
task.artifacts.push(update.artifact);
}
}
this.emit(task.id, event);
}
} catch (err) {
task.status = {
state: "TASK_STATE_FAILED",
timestamp: Date.now(),
message: textMessage("ROLE_AGENT", String(err)),
};
this.emit(task.id, {
statusUpdate: { taskId: task.id, status: task.status },
});
}
}
private emit(taskId: string, event: TaskEvent) {
for (const listener of this.listeners.get(taskId) ?? []) {
listener(event);
}
}
}这实现了真正的A2A任务生命周期: TASK_STATE_SUBMITTED现在TASK_STATE_WORKING现在TASK_STATE_INPUT_REQUIRED处理器是产生异步生成器.statusUpdate其他artifactUpdate事件,SSE流带的包装.
步骤4:ACP风格审计之路
包装通信与轨迹跟踪:
typescripttype 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) : [];
}
}每个代理执行都会产生一个完整的审计输入:进入的,出出的,以及工具调用和推理步骤的完整轨迹.你可以按代理,按会议或单个运行查询.
步骤5:ANP风格身份验证
建立基于DID的身份和验证:
typescripttype 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");
}这反映了ANP的真实身份模式:代理人拥有独立的身份验证,关键协议和人权授权密钥的DID文件.IdentityRegistry模拟了DID分辨率 (在生产中,这将是HTTP将其带到代理域).
步骤 6: 协议门户
连接所有四个协议到一个统一的系统:
graph LR
REQ[Incoming Request] --> ANP_V{ANP: Verify DID}
ANP_V -->|Valid| A2A_D{A2A: Discover Agent}
ANP_V -->|Invalid| REJECT[Reject]
A2A_D -->|Found| ACP_A[ACP: Audit Run]
A2A_D -->|Not Found| REJECT
ACP_A --> A2A_T[A2A: Create Task]
A2A_T --> RESULT[Task + Audit Entry]
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:#2563ebtypescriptclass 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
);
}
}门户在一个电话中做了四件事:
- ANP:通过DID签名验证调用者的身份
- A2A: 发现目标代理和检查能力
- ACP: 结执行过程在一个轨迹的审计轨迹中
- A2A: 创建一个任务,使用完整的生命周期跟踪
七步:把所有东西都放在一起
typescriptasync function protocolDemo() {
const registry = new AgentRegistry();
registry.register({
name: "researcher",
description: "Searches and summarizes findings",
version: "1.0.0",
supportedInterfaces: [
{
url: "https: TOK0
protocolBinding: "JSONRPC",
protocolVersion: "1.0",
},
],
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",
supportedInterfaces: [
{
url: "https://coder.local/a2a/v1",
protocolBinding: "JSONRPC",
protocolVersion: "1.0",
},
],
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 {
statusUpdate: {
taskId: task.id,
status: {
state: "TASK_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 {
artifactUpdate: {
taskId: task.id,
artifact: {
id: crypto.randomUUID(),
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",
],
sources: ["react.dev/blog/react-19"],
},
mediaType: "application/json",
},
],
},
append: false,
lastChunk: true,
},
};
yield {
statusUpdate: {
taskId: task.id,
status: {
state: "TASK_STATE_COMPLETED" as const,
timestamp: Date.now(),
},
},
};
}
);
auditRunner.registerAgent("researcher", async () => ({
output: [
textMessage("ROLE_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("ROLE_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;
});发生错误
协议解决了幸福的道路.
Schema drift.代理A发布了代理卡广告application/json编辑器:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON 版本:JSON:JSON:JSON:JSON:JSON:JSON:JSON:JSON:JSON:JSON:JSON:JSON:JSON:JSON:JSON:JSON:JSON:JSON:JSON:JSON:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:J:version由于这个原因,我在 Agent Cards 上.
State machine violations.经理提供了TASK_STATE_COMPLETED您的代码默默地放弃更新或抛弃. 修复:在放弃之前检查终端状态. TaskManager通过"break在终端状态之后.
Trust resolution failures.代理A试图验证B代理的DID,但B代理的域名是下载的.DID文件不能得到.你是否未能打开 (接受未经验证的代理) 或未能关闭 (拒绝一切)?ANP建议使用最小信任原则关闭.
Trajectory bloat.记录ACP轨迹是强大的,但昂贵的.一个复杂的代理每次运行中进行200次工具调用,产生大量的审计输入.
Discovery thundering herd.50名代理人全部查询GET /agents解决问题:缓存TTL的代理卡,按发现间隔,或者使用基于推的注册而不是投票.
用它
实际实施
A2A谷歌的果是最成熟的.official spec如果您的代理人需要动态的发现和合作,请从这里开始.
ACP现在,我们正在将公司融入A2A.BeeAI project通过使用A2A作为运输工具,使用ACP模式 (轨迹记录,运行生命周期).
ANP它们是最实验性的.community repo通过使用Python SDK (AgentConnect) 进行交易,该概念是真正的新概念.
MCP如果您希望代理使用工具,MCP是标准.
选择正确的协议
graph TD
START{Do agents need<br/>to use tools?}
START -->|Yes| MCP_R[Use MCP]
START -->|No| TALK{Do agents need to<br/>talk to each other?}
TALK -->|No| NONE[You don't need<br/>a protocol]
TALK -->|Yes| AUDIT{Need audit trails<br/>for compliance?}
AUDIT -->|Yes| ACP_R[A2A + ACP<br/>trajectory patterns]
AUDIT -->|No| ORG{All agents<br/>within your org?}
ORG -->|Yes| A2A_R[A2A<br/>Agent Cards + Tasks]
ORG -->|No| INFRA{Shared<br/>infrastructure?}
INFRA -->|Yes| BROKER[A2A + message broker]
INFRA -->|No| ANP_R[ANP + A2A<br/>DID verification]
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运送它
这一课产生了:
code/main.ts-- 完成四个协议模式的实施outputs/prompt-protocol-selector.md-- 提示帮助你选择系统的协议
运动
- Multi-hop task delegation.扩大
TaskManager调查人员接收一个任务,向两个专业代理"搜索"和"总结"的子任务,等待两者完成,然后将结果合并到自己的文物中.
- Streaming audit trail.修改
AuditableRunner为了支持流媒体模式.AuditEntry随着轨迹输入的增加,实时更新. 使用一个异步生成器,生成审计快照.
- DID rotation.添加键旋转到
IdentityRegistry代理人应能够发布一个新的DID文件,同时保持更新的密钥.previousDid验证者应在宽限期内接受当前和前钥匙的签名.
- Protocol negotiation.执行ANP的元协议概念.
protocolNegotiation通过"JSON-RPC"和"REST"的形式,他们可以使用一个模拟形式或时间限制.TaskManager或AuditableRunner他们使用.
- Rate-limited discovery.添加一个
RateLimitedRegistry模拟一个响的群体100名代理在启动时发现彼此,并测量差异.
关键词
| Term | What people say | What it actually means |
|---|---|---|
| MCP | "The protocol for AI tools" | A client-server protocol for agents to discover and use tools. Agent-to-tool, not agent-to-agent. |
| A2A | "Google's agent protocol" | A peer-to-peer protocol for agent collaboration under the Linux Foundation. Discovery via Agent Cards, 9-state task lifecycle, streaming via SSE. Supports JSON-RPC, REST, and gRPC bindings. |
| ACP | "Enterprise agent messaging" | IBM/BeeAI's REST API for agent runs with TrajectoryMetadata: every response carries the full chain of reasoning and tool calls. Merging into A2A. |
| ANP | "Decentralized agent identity" | A community protocol using did:wba (DID) for cryptographic identity, HPKE for E2EE, and AI-powered meta-protocol negotiation for agents that have never seen each other. |
| Agent Card | "An agent's business card" | A JSON document at /.well-known/agent-card.json describing skills, supported MIME types, security schemes, and protocol bindings. |
| DID | "Decentralized ID" | W3C standard for cryptographically verifiable identities hosted on the agent's own domain. ANP uses did:wba method. |
| TrajectoryMetadata | "The audit receipt" | ACP's mechanism for attaching reasoning steps, tool calls, and their inputs/outputs to every agent response. |
| Meta-protocol | "Agents negotiating how to talk" | ANP's approach where agents use natural language to dynamically agree on data formats, then generate code to handle them. |
| Task | "A unit of work" | A2A's stateful object tracking work from submission through completion. Immutable once terminal. |
进一步阅读
- Google A2A specification--官方规格和SDK (v1.0.1,Linux Foundation)
- IBM/BeeAI ACP specification-- 代理运行和轨迹元数据的OpenAPI 3.1规范
- Agent Network Protocol--基于DID的身份,E2EE,元协议谈判
- Model Context Protocol docs-- 人公司的MCP规范 (包括13期)
- W3C Decentralized Identifiers-- ANP的身份标准
- RFC 9180 (HPKE)-- ANP用于E2EE的加密方案
- FIPA Agent Communication Language作为现代代理协议的学术前.
This free lesson is part of the AI Engineering from Scratch curriculum. Read the full explanation, run the lesson code, and verify the result in the interactive reader or from the repository source.
Browse the complete course catalog or open this lesson on GitHub.