🦞 摘要:OpenClaw ACP(Agent Communication Protocol)是支撑多代理协作的核心通信协议,定义了代理之间、代理与网关之间的消息交换规范。本文从协议基础出发,系统讲解ACP的四种消息类型(请求/响应/通知/心跳)、JSON消息格式与字段定义、可靠性保证机制(确认/重试/幂等)、安全性设计(认证与加密)、性能优化策略(压缩/批量/流式),并通过三个实战案例------自定义ACP消息、ACP网关部署、协议扩展开发------帮助开发者深入掌握ACP协议,构建高效可靠的多代理系统。
目录
-
- 一、ACP协议基础
-
- [1.1 什么是ACP](#1.1 什么是ACP)
- [1.2 ACP的设计目标](#1.2 ACP的设计目标)
- [1.3 ACP在OpenClaw架构中的位置](#1.3 ACP在OpenClaw架构中的位置)
- [1.4 ACP与Binding路由的关系](#1.4 ACP与Binding路由的关系)
- 二、消息类型详解
-
- [2.1 请求消息(Request)](#2.1 请求消息(Request))
- [2.2 响应消息(Response)](#2.2 响应消息(Response))
- [2.3 通知消息(Notification)](#2.3 通知消息(Notification))
- [2.4 心跳消息(Heartbeat)](#2.4 心跳消息(Heartbeat))
- [2.5 消息类型对比](#2.5 消息类型对比)
- 三、消息格式规范
-
- [3.1 消息信封结构](#3.1 消息信封结构)
- [3.2 Header字段详解](#3.2 Header字段详解)
- [3.3 Payload字段详解](#3.3 Payload字段详解)
- [3.4 会话键(SessionKey)路由机制](#3.4 会话键(SessionKey)路由机制)
- [3.5 流式响应格式](#3.5 流式响应格式)
- 四、可靠性机制
-
- [4.1 ACK确认机制](#4.1 ACK确认机制)
- [4.2 重试策略](#4.2 重试策略)
- [4.3 幂等性保证](#4.3 幂等性保证)
- [4.4 消息持久化与恢复](#4.4 消息持久化与恢复)
- 五、安全性设计
-
- [5.1 认证体系](#5.1 认证体系)
- [5.2 通道级安全](#5.2 通道级安全)
- [5.3 上下文可见性模型](#5.3 上下文可见性模型)
- [5.4 安全审计](#5.4 安全审计)
- 六、性能优化
-
- [6.1 消息压缩](#6.1 消息压缩)
- [6.2 批量处理](#6.2 批量处理)
- [6.3 流式传输优化](#6.3 流式传输优化)
- [6.4 连接池与复用](#6.4 连接池与复用)
- [6.5 健康监控与自动恢复](#6.5 健康监控与自动恢复)
- 七、实战案例1:自定义ACP消息
-
- [7.1 场景描述](#7.1 场景描述)
- [7.2 定义消息Schema](#7.2 定义消息Schema)
- [7.3 发送自定义消息](#7.3 发送自定义消息)
- [7.4 注册消息处理器](#7.4 注册消息处理器)
- 八、实战案例2:ACP网关部署
-
- [8.1 场景描述](#8.1 场景描述)
- [8.2 完整配置](#8.2 完整配置)
- [8.3 部署验证步骤](#8.3 部署验证步骤)
- [8.4 监控与告警](#8.4 监控与告警)
- 九、实战案例3:协议扩展开发
-
- [9.1 场景描述](#9.1 场景描述)
- [9.2 扩展架构](#9.2 扩展架构)
- [9.3 实现扩展](#9.3 实现扩展)
- [9.4 注册扩展](#9.4 注册扩展)
- [9.5 使用扩展](#9.5 使用扩展)
- 十、最佳实践与总结
-
- [10.1 ACP协议最佳实践](#10.1 ACP协议最佳实践)
- [10.2 常见陷阱](#10.2 常见陷阱)
- [10.3 协议演进方向](#10.3 协议演进方向)
- [10.4 参考资源](#10.4 参考资源)
一、ACP协议基础
1.1 什么是ACP
ACP(Agent Communication Protocol)是OpenClaw多代理架构中的核心通信协议。在OpenClaw的设计哲学中,网关(Gateway) 是所有通信的枢纽------它连接着聊天应用、代理运行时、CLI工具、Web控制界面和移动节点。而ACP正是定义这些组件之间如何交换信息的一套规范化协议。
与传统的HTTP REST API不同,ACP采用了双向流式通信模型,更适合代理场景下的实时交互需求。ACP不仅承载了消息的传输,还内置了会话管理、路由分发、安全认证等关键能力。
💡 核心概念:在OpenClaw中,一个"代理"(Agent)是完整的作用域单元------包含工作区文件、身份配置(AGENTS.md/SOUL.md/USER.md)、认证档案、模型注册表和会话存储。ACP协议的使命就是在这些代理之间,以及代理与外部系统之间建立可靠的通信通道。
1.2 ACP的设计目标
ACP协议在设计之初就明确了以下核心目标:
| 设计目标 | 说明 | 实现方式 |
|---|---|---|
| 可靠性 | 确保消息不丢失、不重复处理 | ACK确认机制 + 幂等设计 |
| 实时性 | 支持流式响应和低延迟交互 | WebSocket双向流 + 分块传输 |
| 安全性 | 认证与加密保护通信安全 | Token认证 + TLS加密 |
| 可扩展性 | 支持自定义消息类型和协议扩展 | 插件式消息处理器 |
| 互操作性 | 跨平台、跨语言兼容 | JSON标准格式 + Schema验证 |
1.3 ACP在OpenClaw架构中的位置
#mermaid-svg-wCPdpe6oGmi63nEX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wCPdpe6oGmi63nEX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wCPdpe6oGmi63nEX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wCPdpe6oGmi63nEX .error-icon{fill:#552222;}#mermaid-svg-wCPdpe6oGmi63nEX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wCPdpe6oGmi63nEX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wCPdpe6oGmi63nEX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wCPdpe6oGmi63nEX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wCPdpe6oGmi63nEX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wCPdpe6oGmi63nEX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wCPdpe6oGmi63nEX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wCPdpe6oGmi63nEX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wCPdpe6oGmi63nEX .marker.cross{stroke:#333333;}#mermaid-svg-wCPdpe6oGmi63nEX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wCPdpe6oGmi63nEX p{margin:0;}#mermaid-svg-wCPdpe6oGmi63nEX .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wCPdpe6oGmi63nEX .cluster-label text{fill:#333;}#mermaid-svg-wCPdpe6oGmi63nEX .cluster-label span{color:#333;}#mermaid-svg-wCPdpe6oGmi63nEX .cluster-label span p{background-color:transparent;}#mermaid-svg-wCPdpe6oGmi63nEX .label text,#mermaid-svg-wCPdpe6oGmi63nEX span{fill:#333;color:#333;}#mermaid-svg-wCPdpe6oGmi63nEX .node rect,#mermaid-svg-wCPdpe6oGmi63nEX .node circle,#mermaid-svg-wCPdpe6oGmi63nEX .node ellipse,#mermaid-svg-wCPdpe6oGmi63nEX .node polygon,#mermaid-svg-wCPdpe6oGmi63nEX .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wCPdpe6oGmi63nEX .rough-node .label text,#mermaid-svg-wCPdpe6oGmi63nEX .node .label text,#mermaid-svg-wCPdpe6oGmi63nEX .image-shape .label,#mermaid-svg-wCPdpe6oGmi63nEX .icon-shape .label{text-anchor:middle;}#mermaid-svg-wCPdpe6oGmi63nEX .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wCPdpe6oGmi63nEX .rough-node .label,#mermaid-svg-wCPdpe6oGmi63nEX .node .label,#mermaid-svg-wCPdpe6oGmi63nEX .image-shape .label,#mermaid-svg-wCPdpe6oGmi63nEX .icon-shape .label{text-align:center;}#mermaid-svg-wCPdpe6oGmi63nEX .node.clickable{cursor:pointer;}#mermaid-svg-wCPdpe6oGmi63nEX .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wCPdpe6oGmi63nEX .arrowheadPath{fill:#333333;}#mermaid-svg-wCPdpe6oGmi63nEX .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wCPdpe6oGmi63nEX .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wCPdpe6oGmi63nEX .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wCPdpe6oGmi63nEX .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wCPdpe6oGmi63nEX .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wCPdpe6oGmi63nEX .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wCPdpe6oGmi63nEX .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wCPdpe6oGmi63nEX .cluster text{fill:#333;}#mermaid-svg-wCPdpe6oGmi63nEX .cluster span{color:#333;}#mermaid-svg-wCPdpe6oGmi63nEX div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wCPdpe6oGmi63nEX .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wCPdpe6oGmi63nEX rect.text{fill:none;stroke-width:0;}#mermaid-svg-wCPdpe6oGmi63nEX .icon-shape,#mermaid-svg-wCPdpe6oGmi63nEX .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wCPdpe6oGmi63nEX .icon-shape p,#mermaid-svg-wCPdpe6oGmi63nEX .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wCPdpe6oGmi63nEX .icon-shape .label rect,#mermaid-svg-wCPdpe6oGmi63nEX .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wCPdpe6oGmi63nEX .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wCPdpe6oGmi63nEX .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wCPdpe6oGmi63nEX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 工具层
代理层
网关层 - ACP核心
外部通道
Discord
Telegram
WhatsApp
Slack
飞书 Feishu
ACP路由引擎
会话管理器
安全认证层
消息序列化
Agent: main
Agent: coding
Agent: social
exec
browser
skills
memory
如图所示,ACP协议运行在网关层,是连接外部通道与内部代理的桥梁。所有来自Discord、Telegram、WhatsApp等通道的消息,都经过ACP路由引擎处理后分发到对应的代理实例。
1.4 ACP与Binding路由的关系
在OpenClaw的多代理模型中,Binding(绑定)是ACP路由的核心机制。每条Binding定义了一条从通道账户到代理实例的路由规则:
json5
{
bindings: [
{
agentId: "alex",
match: { channel: "whatsapp", peer: { kind: "direct", id: "+15551230001" } }
},
{
agentId: "mia",
match: { channel: "whatsapp", peer: { kind: "direct", id: "+15551230002" } }
}
]
}
当ACP收到一条消息时,路由引擎会按照以下优先级进行匹配:
- peer match:精确的DM/群组/通道ID匹配
- parentPeer match:线程继承匹配
- guildId + roles:Discord角色路由
- guildId:Discord服务器级别
- teamId:Slack团队级别
- accountId match:通道账户级别
- Channel-level match :
accountId: "*"通配 - Default agent:回退到默认代理
这种确定性、最高特异性优先的路由策略,确保了消息总是被路由到最合适的代理实例。
二、消息类型详解
ACP定义了四种核心消息类型,每种类型服务于不同的通信场景。
2.1 请求消息(Request)
请求消息是最基础的ACP消息类型,用于代理向其他代理或网关发起同步调用。请求消息要求接收方返回一个响应。
典型场景:
- 用户通过通道发送消息,触发代理处理
- 代理调用工具(tool use)
- 代理之间的协作请求
请求消息特征:
- 必须携带唯一
messageId用于关联响应 - 包含发送方
agentId和目标路由信息 - 可选的
timeout字段控制超时行为 - 支持
priority字段标记紧急程度
2.2 响应消息(Response)
响应消息是对请求消息的回复,携带处理结果或错误信息。ACP的响应模型支持流式响应------代理可以分多次发送响应片段,最终以完成标记结束。
典型场景:
- 代理返回生成的文本内容
- 工具调用返回执行结果
- 多代理协作中的中间结果传递
响应消息特征:
- 携带
correlationId关联原始请求 - 支持
streaming: true标记流式响应 - 包含
chunkIndex和isFinal控制流式分块 - 可携带
error字段表示处理失败
2.3 通知消息(Notification)
通知消息是一种即发即忘(fire-and-forget)的消息类型,发送方不期望接收方返回响应。通知消息用于单向的状态推送和事件广播。
典型场景:
- 代理状态变更通知(上线/下线/忙碌)
- 通道连接状态变化
- 定时任务触发(Cron/Heartbeat)
- 系统事件广播
通知消息特征:
- 不需要
correlationId - 携带
event字段标识事件类型 - 支持
broadcast: true进行广播 - 不触发ACK确认机制(可选)
2.4 心跳消息(Heartbeat)
心跳消息是ACP的保活机制,用于维持代理与网关之间的连接活跃度,并支持轻量级的状态同步。
典型场景:
- WebSocket连接保活
- 代理健康状态上报
- 通道连接状态检测
- 网关触发周期性检查任务
心跳消息特征:
- 固定间隔发送(默认30秒)
- 携带
status字段反映代理当前状态 - 网关可通过心跳响应下发配置变更
- 心跳超时触发自动重连
🔑 心跳与定时任务的分工:在OpenClaw中,心跳适合批量检查(如收件箱+日历+通知合并处理),允许时间略有偏差。而定时任务(Cron)适合精确定时的场景(如"每周一早上9点整")。两者互补而非替代。
Agent Gateway 通道客户端 Agent Gateway 通道客户端 #mermaid-svg-7UWMfGphsOHly9N4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7UWMfGphsOHly9N4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7UWMfGphsOHly9N4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7UWMfGphsOHly9N4 .error-icon{fill:#552222;}#mermaid-svg-7UWMfGphsOHly9N4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7UWMfGphsOHly9N4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7UWMfGphsOHly9N4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7UWMfGphsOHly9N4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7UWMfGphsOHly9N4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7UWMfGphsOHly9N4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7UWMfGphsOHly9N4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7UWMfGphsOHly9N4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7UWMfGphsOHly9N4 .marker.cross{stroke:#333333;}#mermaid-svg-7UWMfGphsOHly9N4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7UWMfGphsOHly9N4 p{margin:0;}#mermaid-svg-7UWMfGphsOHly9N4 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-7UWMfGphsOHly9N4 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-7UWMfGphsOHly9N4 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-7UWMfGphsOHly9N4 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-7UWMfGphsOHly9N4 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-7UWMfGphsOHly9N4 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-7UWMfGphsOHly9N4 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-7UWMfGphsOHly9N4 .sequenceNumber{fill:white;}#mermaid-svg-7UWMfGphsOHly9N4 #sequencenumber{fill:#333;}#mermaid-svg-7UWMfGphsOHly9N4 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-7UWMfGphsOHly9N4 .messageText{fill:#333;stroke:none;}#mermaid-svg-7UWMfGphsOHly9N4 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-7UWMfGphsOHly9N4 .labelText,#mermaid-svg-7UWMfGphsOHly9N4 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-7UWMfGphsOHly9N4 .loopText,#mermaid-svg-7UWMfGphsOHly9N4 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-7UWMfGphsOHly9N4 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-7UWMfGphsOHly9N4 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-7UWMfGphsOHly9N4 .noteText,#mermaid-svg-7UWMfGphsOHly9N4 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-7UWMfGphsOHly9N4 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-7UWMfGphsOHly9N4 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-7UWMfGphsOHly9N4 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-7UWMfGphsOHly9N4 .actorPopupMenu{position:absolute;}#mermaid-svg-7UWMfGphsOHly9N4 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-7UWMfGphsOHly9N4 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-7UWMfGphsOHly9N4 .actor-man circle,#mermaid-svg-7UWMfGphsOHly9N4 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-7UWMfGphsOHly9N4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 请求-响应模式 通知模式 心跳模式 Request (messageId: msg-001) Forward Request Response (correlationId: msg-001) Forward Response Notification (event: status_change) Forward Notification Heartbeat (status: active) Heartbeat ACK + config_update
2.5 消息类型对比
| 特征 | Request | Response | Notification | Heartbeat |
|---|---|---|---|---|
| 方向 | 双向(需要响应) | 单向(回应请求) | 单向(无需响应) | 双向(保活) |
| 可靠性 | ACK + 重试 | 关联请求可靠性 | 可选ACK | 自动重连 |
| 流式支持 | ❌ | ✅ | ❌ | ❌ |
| 典型负载 | 用户消息/工具调用 | 生成内容/执行结果 | 状态变更/事件 | 连接状态 |
| 优先级 | 可配置 | 继承请求 | 低 | 最高 |
三、消息格式规范
3.1 消息信封结构
所有ACP消息都遵循统一的信封(Envelope)格式,包含消息头(Header)和消息体(Payload)两部分:
json
{
"header": {
"messageId": "msg-7f8a9b2c-4e5d",
"correlationId": null,
"timestamp": 1718764800000,
"version": "1.0",
"type": "request",
"source": {
"agentId": "main",
"channel": "telegram",
"accountId": "default",
"peerId": "tg:123456789",
"sessionKey": "agent:main:tg:123456789"
},
"destination": {
"agentId": "coding",
"target": "agent"
},
"metadata": {
"priority": "normal",
"ttl": 30000,
"idempotencyKey": "idem-7f8a9b2c",
"traceId": "trace-a1b2c3d4"
}
},
"payload": {
"contentType": "text/plain",
"content": "帮我重构这段代码",
"attachments": []
}
}
3.2 Header字段详解
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
messageId |
string | ✅ | 消息唯一标识,UUID v4格式 |
correlationId |
string | ❌ | 关联的请求消息ID,响应消息必填 |
timestamp |
number | ✅ | 消息发送时间戳(毫秒) |
version |
string | ✅ | 协议版本号,当前为"1.0" |
type |
enum | ✅ | 消息类型:request/response/notification/heartbeat |
source |
object | ✅ | 消息来源信息 |
source.agentId |
string | ✅ | 发送方代理ID |
source.channel |
string | ❌ | 来源通道标识 |
source.accountId |
string | ❌ | 通道账户ID |
source.peerId |
string | ❌ | 发送者对等ID |
source.sessionKey |
string | ❌ | 会话路由键 |
destination |
object | ✅ | 消息目标信息 |
destination.agentId |
string | ❌ | 目标代理ID |
destination.target |
enum | ❌ | 目标类型:agent/gateway/broadcast |
metadata |
object | ❌ | 扩展元数据 |
metadata.priority |
enum | ❌ | 优先级:low/normal/high/critical |
metadata.ttl |
number | ❌ | 消息存活时间(毫秒) |
metadata.idempotencyKey |
string | ❌ | 幂等键,防止重复处理 |
metadata.traceId |
string | ❌ | 分布式追踪ID |
3.3 Payload字段详解
Payload部分承载消息的实际内容,支持多种内容类型:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
contentType |
string | ✅ | 内容MIME类型 |
content |
string | ✅ | 消息正文内容 |
attachments |
array | ❌ | 附件列表 |
attachments[].type |
enum | ❌ | 附件类型:image/audio/video/document |
attachments[].url |
string | ❌ | 附件URL |
attachments[].data |
string | ❌ | Base64编码的附件数据 |
attachments[].mimeType |
string | ❌ | 附件MIME类型 |
attachments[].size |
number | ❌ | 附件大小(字节) |
toolCalls |
array | ❌ | 工具调用请求列表 |
toolResults |
array | ❌ | 工具调用结果列表 |
streaming |
object | ❌ | 流式传输控制信息 |
3.4 会话键(SessionKey)路由机制
会话键是ACP路由的核心标识符,格式为:
agent:<agentId>:<mainKey>
其中mainKey的生成规则由session.dmScope配置决定:
| dmScope值 | mainKey格式 | 适用场景 |
|---|---|---|
main |
main |
单用户,所有会话共享 |
per-peer |
<peerId> |
多用户按发送者隔离 |
per-channel-peer |
<channel>:<peerId> |
多通道多用户隔离 |
per-account-channel-peer |
<account>:<channel>:<peerId> |
多账户多通道隔离 |
⚠️ 安全提示 :
sessionKey是路由选择器,不是 授权令牌。拥有某个sessionKey并不意味着获得了对应用户的权限。详见OpenClaw安全文档。
3.5 流式响应格式
ACP支持代理以流式方式返回长文本响应,这在代码生成、文章撰写等场景中尤为重要:
json
// 第一个分块
{
"header": {
"messageId": "resp-chunk-1",
"correlationId": "msg-7f8a9b2c-4e5d",
"type": "response"
},
"payload": {
"contentType": "text/markdown",
"content": "这是一段流式",
"streaming": {
"chunkIndex": 0,
"isFinal": false,
"totalChunks": null
}
}
}
// 最后一个分块
{
"header": {
"messageId": "resp-chunk-final",
"correlationId": "msg-7f8a9b2c-4e5d",
"type": "response"
},
"payload": {
"contentType": "text/markdown",
"content": "响应的结尾部分。",
"streaming": {
"chunkIndex": 5,
"isFinal": true,
"totalChunks": 6
}
}
}
四、可靠性机制
ACP协议在消息传输的各个环节都设计了可靠性保障,确保在不可靠的网络环境中依然能够正确交付消息。
4.1 ACK确认机制
ACP采用应用层ACK确认机制,与传输层的TCP ACK互补。每条请求消息和重要的通知消息都要求接收方在处理完成后返回ACK:
接收方 发送方 接收方 发送方 #mermaid-svg-d6kZXsvbLecHmzl3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-d6kZXsvbLecHmzl3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-d6kZXsvbLecHmzl3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-d6kZXsvbLecHmzl3 .error-icon{fill:#552222;}#mermaid-svg-d6kZXsvbLecHmzl3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-d6kZXsvbLecHmzl3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-d6kZXsvbLecHmzl3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-d6kZXsvbLecHmzl3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-d6kZXsvbLecHmzl3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-d6kZXsvbLecHmzl3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-d6kZXsvbLecHmzl3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-d6kZXsvbLecHmzl3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-d6kZXsvbLecHmzl3 .marker.cross{stroke:#333333;}#mermaid-svg-d6kZXsvbLecHmzl3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-d6kZXsvbLecHmzl3 p{margin:0;}#mermaid-svg-d6kZXsvbLecHmzl3 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-d6kZXsvbLecHmzl3 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-d6kZXsvbLecHmzl3 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-d6kZXsvbLecHmzl3 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-d6kZXsvbLecHmzl3 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-d6kZXsvbLecHmzl3 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-d6kZXsvbLecHmzl3 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-d6kZXsvbLecHmzl3 .sequenceNumber{fill:white;}#mermaid-svg-d6kZXsvbLecHmzl3 #sequencenumber{fill:#333;}#mermaid-svg-d6kZXsvbLecHmzl3 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-d6kZXsvbLecHmzl3 .messageText{fill:#333;stroke:none;}#mermaid-svg-d6kZXsvbLecHmzl3 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-d6kZXsvbLecHmzl3 .labelText,#mermaid-svg-d6kZXsvbLecHmzl3 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-d6kZXsvbLecHmzl3 .loopText,#mermaid-svg-d6kZXsvbLecHmzl3 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-d6kZXsvbLecHmzl3 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-d6kZXsvbLecHmzl3 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-d6kZXsvbLecHmzl3 .noteText,#mermaid-svg-d6kZXsvbLecHmzl3 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-d6kZXsvbLecHmzl3 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-d6kZXsvbLecHmzl3 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-d6kZXsvbLecHmzl3 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-d6kZXsvbLecHmzl3 .actorPopupMenu{position:absolute;}#mermaid-svg-d6kZXsvbLecHmzl3 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-d6kZXsvbLecHmzl3 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-d6kZXsvbLecHmzl3 .actor-man circle,#mermaid-svg-d6kZXsvbLecHmzl3 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-d6kZXsvbLecHmzl3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 处理消息中... Request (messageId: msg-001) ACK (correlationId: msg-001, status: received) Response (correlationId: msg-001) ACK (correlationId: resp-001, status: received)
ACK消息包含以下关键字段:
| 字段 | 说明 |
|---|---|
status |
确认状态:received/processing/completed/failed |
timestamp |
确认时间戳 |
error |
失败时的错误信息 |
4.2 重试策略
当发送方在指定超时时间内未收到ACK,ACP将按照指数退避策略进行重试:
python
# ACP重试策略伪代码
class ACPRetryPolicy:
def __init__(self):
self.max_retries = 3
self.base_delay_ms = 1000 # 初始延迟1秒
self.max_delay_ms = 30000 # 最大延迟30秒
self.backoff_multiplier = 2.0 # 退避倍数
self.jitter_factor = 0.1 # 抖动因子
def next_delay(self, attempt: int) -> int:
"""计算下一次重试的延迟时间"""
if attempt > self.max_retries:
raise MaxRetriesExceededError(
f"Exceeded max retries ({self.max_retries})"
)
# 指数退避 + 随机抖动
delay = self.base_delay_ms * (self.backoff_multiplier ** (attempt - 1))
delay = min(delay, self.max_delay_ms)
# 添加随机抖动防止惊群效应
jitter = delay * self.jitter_factor * (2 * random() - 1)
return int(delay + jitter)
def should_retry(self, error: Exception) -> bool:
"""判断错误是否可重试"""
retryable_types = {
TimeoutError,
ConnectionError,
ServiceUnavailableError
}
return isinstance(error, tuple(retryable_types))
上面的代码展示了ACP重试策略的核心逻辑:指数退避保证在服务端压力过大时不会雪崩,随机抖动防止多个客户端同时重试造成惊群效应,最大延迟上限则避免了过长的等待时间。
4.3 幂等性保证
ACP通过idempotencyKey字段实现消息级别的幂等性保证。相同的idempotencyKey只会被处理一次,即使消息因网络问题被重复投递:
幂等性实现原理:
- 发送方为每条消息生成唯一的
idempotencyKey(通常为UUID) - 接收方维护最近处理的键值缓存(TTL通常为5分钟)
- 收到消息时先检查缓存,若键已存在则直接返回之前的结果
- 处理完成后将键和结果写入缓存
幂等性适用场景:
| 场景 | 幂等要求 | 实现方式 |
|---|---|---|
| 工具调用 | 高 | idempotencyKey + 结果缓存 |
| 状态变更 | 高 | 乐观锁 + 版本号 |
| 消息发送 | 中 | 去重窗口 + fingerprint |
| 心跳检测 | 低 | 自然幂等(覆盖即可) |
4.4 消息持久化与恢复
ACP网关在消息处理的关键节点进行持久化,确保进程崩溃后可以恢复:
#mermaid-svg-1xMfKQ5MXIYyw0so{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1xMfKQ5MXIYyw0so .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1xMfKQ5MXIYyw0so .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1xMfKQ5MXIYyw0so .error-icon{fill:#552222;}#mermaid-svg-1xMfKQ5MXIYyw0so .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1xMfKQ5MXIYyw0so .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1xMfKQ5MXIYyw0so .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1xMfKQ5MXIYyw0so .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1xMfKQ5MXIYyw0so .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1xMfKQ5MXIYyw0so .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1xMfKQ5MXIYyw0so .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1xMfKQ5MXIYyw0so .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1xMfKQ5MXIYyw0so .marker.cross{stroke:#333333;}#mermaid-svg-1xMfKQ5MXIYyw0so svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1xMfKQ5MXIYyw0so p{margin:0;}#mermaid-svg-1xMfKQ5MXIYyw0so .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1xMfKQ5MXIYyw0so .cluster-label text{fill:#333;}#mermaid-svg-1xMfKQ5MXIYyw0so .cluster-label span{color:#333;}#mermaid-svg-1xMfKQ5MXIYyw0so .cluster-label span p{background-color:transparent;}#mermaid-svg-1xMfKQ5MXIYyw0so .label text,#mermaid-svg-1xMfKQ5MXIYyw0so span{fill:#333;color:#333;}#mermaid-svg-1xMfKQ5MXIYyw0so .node rect,#mermaid-svg-1xMfKQ5MXIYyw0so .node circle,#mermaid-svg-1xMfKQ5MXIYyw0so .node ellipse,#mermaid-svg-1xMfKQ5MXIYyw0so .node polygon,#mermaid-svg-1xMfKQ5MXIYyw0so .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1xMfKQ5MXIYyw0so .rough-node .label text,#mermaid-svg-1xMfKQ5MXIYyw0so .node .label text,#mermaid-svg-1xMfKQ5MXIYyw0so .image-shape .label,#mermaid-svg-1xMfKQ5MXIYyw0so .icon-shape .label{text-anchor:middle;}#mermaid-svg-1xMfKQ5MXIYyw0so .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1xMfKQ5MXIYyw0so .rough-node .label,#mermaid-svg-1xMfKQ5MXIYyw0so .node .label,#mermaid-svg-1xMfKQ5MXIYyw0so .image-shape .label,#mermaid-svg-1xMfKQ5MXIYyw0so .icon-shape .label{text-align:center;}#mermaid-svg-1xMfKQ5MXIYyw0so .node.clickable{cursor:pointer;}#mermaid-svg-1xMfKQ5MXIYyw0so .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1xMfKQ5MXIYyw0so .arrowheadPath{fill:#333333;}#mermaid-svg-1xMfKQ5MXIYyw0so .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1xMfKQ5MXIYyw0so .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1xMfKQ5MXIYyw0so .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1xMfKQ5MXIYyw0so .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1xMfKQ5MXIYyw0so .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1xMfKQ5MXIYyw0so .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1xMfKQ5MXIYyw0so .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1xMfKQ5MXIYyw0so .cluster text{fill:#333;}#mermaid-svg-1xMfKQ5MXIYyw0so .cluster span{color:#333;}#mermaid-svg-1xMfKQ5MXIYyw0so div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1xMfKQ5MXIYyw0so .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1xMfKQ5MXIYyw0so rect.text{fill:none;stroke-width:0;}#mermaid-svg-1xMfKQ5MXIYyw0so .icon-shape,#mermaid-svg-1xMfKQ5MXIYyw0so .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1xMfKQ5MXIYyw0so .icon-shape p,#mermaid-svg-1xMfKQ5MXIYyw0so .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1xMfKQ5MXIYyw0so .icon-shape .label rect,#mermaid-svg-1xMfKQ5MXIYyw0so .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1xMfKQ5MXIYyw0so .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1xMfKQ5MXIYyw0so .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1xMfKQ5MXIYyw0so :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 持久化
持久化
持久化
读取
读取
读取
消息接收
消息日志
路由分发
分发记录
代理处理
处理结果
响应发送
崩溃恢复
持久化策略采用WAL(Write-Ahead Log)模式:消息在处理前先写入日志,处理完成后标记为已完成。崩溃恢复时,网关重放未完成的消息,保证at-least-once投递语义。配合幂等机制,最终实现exactly-once处理语义。
五、安全性设计
ACP协议的安全性设计遵循OpenClaw的个人助手信任模型------一个网关实例对应一个可信操作者边界。在这个模型下,ACP实现了多层次的认证与加密机制。
5.1 认证体系
ACP支持以下认证模式:
| 认证模式 | 说明 | 适用场景 |
|---|---|---|
token |
共享密钥认证 | 单操作者部署 |
password |
密码认证 | 简单场景 |
trusted-proxy |
代理信任头认证 | 反向代理部署 |
device |
设备令牌认证 | 移动节点 |
mTLS |
双向TLS证书认证 | 企业级部署 |
Token认证流程:
json5
// openclaw.json 配置
{
gateway: {
auth: {
mode: "token",
token: "replace-with-long-random-token" // 至少32字符
}
}
}
- 客户端连接时在WebSocket握手或HTTP请求头中携带
Authorization: Bearer <token> - 网关验证token有效性
- 认证通过后建立会话,后续帧不再需要重复认证
- token变更时所有已建立连接失效,需要重新认证
5.2 通道级安全
每个通道都有独立的安全策略配置:
json5
{
channels: {
telegram: {
dmPolicy: "pairing", // 配对模式:新发送者需配对码
allowFrom: ["tg:123456789"],
groupPolicy: "allowlist",
healthMonitor: { enabled: true }
},
whatsapp: {
dmPolicy: "allowlist", // 白名单模式
allowFrom: ["+15555550123"],
groups: {
"*": { requireMention: true } // 群聊需要@提及
}
},
discord: {
dmPolicy: "pairing",
groupPolicy: "allowlist"
}
}
}
DM策略对比:
| 策略 | 行为 | 安全等级 |
|---|---|---|
pairing |
新发送者需配对码审批 | ⭐⭐⭐⭐ |
allowlist |
仅白名单内发送者 | ⭐⭐⭐⭐⭐ |
open |
允许所有DM(需allowFrom: ["*"]) |
⭐ |
disabled |
拒绝所有DM | ⭐⭐⭐⭐⭐⭐ |
5.3 上下文可见性模型
ACP区分两个独立的安全概念:
- 触发授权:谁可以触发代理(dmPolicy、groupPolicy、白名单、提及门控)
- 上下文可见性:什么补充上下文可以注入到模型输入中
contextVisibility设置控制补充上下文的过滤:
| 设置值 | 行为 |
|---|---|
all(默认) |
保留所有收到的补充上下文 |
allowlist |
仅保留白名单内发送者的补充上下文 |
allowlist_quote |
类似allowlist,但保留一条显式引用回复 |
5.4 安全审计
OpenClaw内置安全审计工具,建议定期运行:
bash
# 基础安全审计
openclaw security audit
# 深度审计(包含更多检查项)
openclaw security audit --deep
# 自动修复常见安全问题
openclaw security audit --fix
# JSON格式输出(便于CI/CD集成)
openclaw security audit --json
审计覆盖范围包括:入站访问策略、工具爆炸半径、文件系统权限漂移、执行审批配置、浏览器控制暴露、以及开放通道的工具暴露等。
🔒 安全原则 :OpenClaw的安全模型基于可信操作者边界。
sessionKey是路由选择器而非授权令牌;多个用户共享一个带工具权限的代理时,每个用户都能驱动该代理的全部权限集。详见安全文档。
六、性能优化
ACP协议在性能方面做了多层优化,从消息压缩到批量处理再到流式传输,确保在高负载场景下依然保持低延迟和高吞吐。
6.1 消息压缩
ACP支持在传输层对消息进行压缩,减少网络带宽消耗:
| 压缩算法 | 压缩率 | CPU开销 | 适用场景 |
|---|---|---|---|
| gzip | 高 | 中 | 大消息体(>1KB) |
| deflate | 中 | 低 | 中等消息体 |
| none | - | - | 小消息体(<256B) |
压缩决策基于消息体大小自动选择:小于256字节的消息不压缩,超过1KB的消息使用gzip,介于两者之间的使用deflate。
6.2 批量处理
ACP支持将多条消息合并为一次传输,减少网络往返次数:
json
{
"header": {
"messageId": "batch-7f8a9b2c",
"type": "batch",
"timestamp": 1718764800000,
"version": "1.0",
"source": { "agentId": "main" },
"destination": { "target": "gateway" }
},
"payload": {
"contentType": "application/x-acp-batch",
"content": null,
"batchItems": [
{
"messageId": "msg-001",
"type": "notification",
"event": "status_change",
"content": { "status": "processing" }
},
{
"messageId": "msg-002",
"type": "notification",
"event": "progress_update",
"content": { "percent": 45 }
},
{
"messageId": "msg-003",
"type": "response",
"correlationId": "req-previous",
"content": { "chunk": "中间结果" }
}
]
}
}
批量处理特别适用于以下场景:
- 心跳与状态更新合并发送
- 多个工具调用结果批量返回
- 日志/遥测数据周期性上报
- 多代理协作中的进度汇报
6.3 流式传输优化
ACP的流式传输针对长文本和代码生成场景进行了专门优化:
分块策略:
| 参数 | 默认值 | 说明 |
|---|---|---|
chunkSize |
512字符 | 每个分块的最大字符数 |
flushInterval |
100ms | 定时刷新间隔 |
maxBufferTime |
5000ms | 最大缓冲等待时间 |
enableCompression |
true | 是否压缩流式分块 |
背压控制:当客户端处理速度跟不上发送速度时,ACP实现了自动背压机制------发送方在接收方的缓冲区达到阈值时自动暂停发送,等待接收方消费后再继续。
6.4 连接池与复用
ACP在网关层维护连接池,避免频繁创建和销毁连接的开销:
python
# ACP连接池管理伪代码
class ACPConnectionPool:
"""管理到各代理实例的ACP连接池"""
def __init__(self, config):
self.max_connections_per_agent = config.get("maxPerAgent", 10)
self.idle_timeout_ms = config.get("idleTimeoutMs", 60000)
self.health_check_interval_ms = config.get("healthCheckMs", 30000)
self.pools = {} # agentId -> ConnectionPool
async def acquire(self, agent_id: str) -> Connection:
"""获取一个到指定代理的连接"""
pool = self.pools.get(agent_id)
if pool is None:
pool = self._create_pool(agent_id)
self.pools[agent_id] = pool
conn = await pool.acquire()
if not conn.is_healthy():
await pool.release(conn)
conn = await self._create_new_connection(agent_id)
return conn
async def release(self, agent_id: str, conn: Connection):
"""释放连接回连接池"""
pool = self.pools.get(agent_id)
if pool and conn.is_healthy():
await pool.release(conn)
else:
await conn.close()
async def _create_pool(self, agent_id: str):
"""为指定代理创建连接池"""
return ConnectionPool(
max_size=self.max_connections_per_agent,
idle_timeout=self.idle_timeout_ms,
health_check=self.health_check_interval_ms
)
连接池管理确保了高并发场景下的资源复用,同时通过健康检查机制自动剔除故障连接。max_connections_per_agent限制每个代理的最大连接数,防止单个代理占用过多资源。
6.5 健康监控与自动恢复
OpenClaw网关内置通道健康监控,可以自动检测和重启僵死的通道连接:
json5
{
gateway: {
channelHealthCheckMinutes: 5, // 每5分钟检查一次
channelStaleEventThresholdMinutes: 30, // 30分钟无事件视为僵死
channelMaxRestartsPerHour: 10 // 每小时最多重启10次
},
channels: {
telegram: {
healthMonitor: { enabled: true }, // 全局启用
accounts: {
alerts: {
healthMonitor: { enabled: false } // 特定账户禁用
}
}
}
}
}
七、实战案例1:自定义ACP消息
7.1 场景描述
假设我们需要实现一个代码审查代理,当主代理完成代码生成后,自动将代码发送给审查代理进行质量检查。这就需要定义自定义的ACP消息类型。
7.2 定义消息Schema
首先,我们为代码审查请求定义专用的消息格式:
json
{
"$schema": "https://openclaw.ai/schemas/acp/v1",
"type": "object",
"title": "CodeReviewRequest",
"properties": {
"header": {
"type": "object",
"properties": {
"messageId": { "type": "string", "format": "uuid" },
"type": { "const": "request" },
"source": {
"type": "object",
"properties": {
"agentId": { "type": "string" },
"sessionKey": { "type": "string" }
}
},
"destination": {
"type": "object",
"properties": {
"agentId": { "const": "code-reviewer" },
"target": { "const": "agent" }
}
},
"metadata": {
"type": "object",
"properties": {
"idempotencyKey": { "type": "string" },
"priority": { "enum": ["low", "normal", "high"] },
"customType": { "const": "code-review" }
}
}
}
},
"payload": {
"type": "object",
"properties": {
"contentType": { "const": "application/x-acp-code-review" },
"code": { "type": "string" },
"language": { "type": "string" },
"context": { "type": "string" },
"rules": {
"type": "array",
"items": { "type": "string" }
}
}
}
}
}
这个Schema定义了完整的代码审查请求消息结构。customType: "code-review"在metadata中标识了这是一个自定义消息类型,contentType: "application/x-acp-code-review"则使用自定义MIME类型来区分消息负载。
7.3 发送自定义消息
javascript
// 通过ACP发送代码审查请求
async function sendCodeReviewRequest(gateway, code, language, context) {
const message = {
header: {
messageId: crypto.randomUUID(),
timestamp: Date.now(),
version: "1.0",
type: "request",
source: {
agentId: "main",
sessionKey: "agent:main:telegram:123456"
},
destination: {
agentId: "code-reviewer",
target: "agent"
},
metadata: {
priority: "normal",
idempotencyKey: `review-${Date.now()}`,
customType: "code-review",
ttl: 60000 // 60秒超时
}
},
payload: {
contentType: "application/x-acp-code-review",
code: code,
language: language,
context: context,
rules: [
"security-vulnerability-check",
"performance-anti-pattern",
"code-style-consistency",
"error-handling-completeness"
]
}
};
const response = await gateway.acpSend(message);
if (response.header.metadata.customType === "code-review") {
return {
approved: response.payload.approved,
issues: response.payload.issues,
suggestions: response.payload.suggestions,
score: response.payload.score
};
}
throw new Error("Unexpected response type");
}
这段代码展示了自定义ACP消息的完整发送流程:构建消息信封、填充自定义payload、通过网关发送、解析自定义响应格式。idempotencyKey确保了重试安全,ttl控制了超时行为。
7.4 注册消息处理器
在审查代理端注册对应的处理器:
javascript
// 在code-reviewer代理中注册ACP消息处理器
class CodeReviewHandler implements ACPMessageHandler {
readonly customType = "code-review";
readonly supportedContentTypes = [
"application/x-acp-code-review"
];
async handle(message: ACPMessage): Promise<ACPMessage> {
const { code, language, context, rules } = message.payload;
// 执行代码审查逻辑
const reviewResult = await this.performReview(
code, language, context, rules
);
return {
header: {
messageId: crypto.randomUUID(),
correlationId: message.header.messageId,
timestamp: Date.now(),
version: "1.0",
type: "response",
source: { agentId: "code-reviewer" },
destination: { agentId: message.header.source.agentId },
metadata: { customType: "code-review" }
},
payload: {
contentType: "application/x-acp-code-review-result",
approved: reviewResult.approved,
issues: reviewResult.issues,
suggestions: reviewResult.suggestions,
score: reviewResult.score,
reviewedAt: new Date().toISOString()
}
};
}
private async performReview(code, lang, ctx, rules) {
// 实际审查逻辑...
return { approved: true, issues: [], suggestions: [], score: 95 };
}
}
// 注册到ACP路由引擎
gateway.acpRouter.register("code-review", new CodeReviewHandler());
八、实战案例2:ACP网关部署
8.1 场景描述
在生产环境中部署ACP网关,需要考虑多代理路由、安全策略、健康监控和性能调优等多个维度。本案例展示一个完整的多代理ACP网关部署配置。
8.2 完整配置
json5
// ~/.openclaw/openclaw.json - 生产环境ACP网关配置
{
// ===== 网关基础配置 =====
gateway: {
mode: "local",
bind: "loopback",
auth: {
mode: "token",
token: "${GATEWAY_AUTH_TOKEN}" // 从环境变量读取
},
channelHealthCheckMinutes: 5,
channelStaleEventThresholdMinutes: 30,
channelMaxRestartsPerHour: 10,
handshakeTimeoutMs: 15000,
push: {
apns: {
relay: { baseUrl: "https://ios-push-relay.openclaw.ai" }
}
}
},
// ===== 多代理配置 =====
agents: {
defaults: {
workspace: "~/.openclaw/workspace",
model: {
primary: "anthropic/claude-sonnet-4-6",
fallbacks: ["openai/gpt-5.4"]
},
skills: ["github", "weather", "search"],
sandbox: {
mode: "non-main",
scope: "agent"
}
},
list: [
{
id: "main",
workspace: "~/.openclaw/workspace-main"
},
{
id: "coding",
workspace: "~/.openclaw/workspace-coding",
skills: ["github", "code-review", "testing"]
},
{
id: "social",
workspace: "~/.openclaw/workspace-social",
skills: ["social-media", "content-gen"]
}
]
},
// ===== 路由绑定 =====
bindings: [
{
agentId: "main",
match: { channel: "whatsapp", accountId: "default" }
},
{
agentId: "coding",
match: { channel: "discord", accountId: "coding" }
},
{
agentId: "social",
match: { channel: "telegram", accountId: "social" }
},
{
agentId: "main",
match: { channel: "slack", peer: { kind: "direct", id: "U_MAIN_USER" } }
}
],
// ===== 通道配置 =====
channels: {
whatsapp: {
dmPolicy: "pairing",
allowFrom: ["+15555550123"],
groups: { "*": { requireMention: true } },
healthMonitor: { enabled: true }
},
discord: {
dmPolicy: "pairing",
groupPolicy: "allowlist",
accounts: {
default: {
token: "${DISCORD_BOT_TOKEN_MAIN}",
guilds: {
"123456789012345678": {
channels: {
"222222222222222222": { allow: true, requireMention: false }
}
}
}
},
coding: {
token: "${DISCORD_BOT_TOKEN_CODING}",
guilds: {
"123456789012345678": {
channels: {
"333333333333333333": { allow: true, requireMention: false }
}
}
}
}
}
},
telegram: {
accounts: {
default: {
botToken: "${TELEGRAM_BOT_TOKEN_MAIN}",
dmPolicy: "pairing"
},
social: {
botToken: "${TELEGRAM_BOT_TOKEN_SOCIAL}",
dmPolicy: "allowlist",
allowFrom: ["tg:123456789"]
}
}
}
},
// ===== 会话配置 =====
session: {
dmScope: "per-channel-peer",
threadBindings: {
enabled: true,
idleHours: 24,
maxAgeHours: 0
},
reset: {
mode: "daily",
atHour: 4,
idleMinutes: 120
}
},
// ===== 安全加固 =====
tools: {
profile: "messaging",
deny: ["group:automation", "group:runtime"],
fs: { workspaceOnly: true },
exec: { security: "allowlist", ask: "on-miss" },
elevated: { enabled: false }
},
// ===== 消息与群聊配置 =====
messages: {
visibleReplies: "automatic",
groupChat: {
visibleReplies: "message_tool",
unmentionedInbound: "room_event"
}
}
}
这份配置展示了一个完整的生产级ACP网关部署方案。关键设计决策包括:使用环境变量存储敏感token、多代理隔离通过binding路由、每个通道独立的安全策略、以及工具权限的最小化原则。
8.3 部署验证步骤
bash
# 步骤1:验证配置有效性
openclaw doctor
openclaw doctor --deep
# 步骤2:检查代理绑定
openclaw agents list --bindings
# 步骤3:验证通道连接
openclaw channels status --probe
# 步骤4:运行安全审计
openclaw security audit
# 步骤5:启动网关
openclaw gateway restart
# 步骤6:验证ACP消息路由
openclaw gateway status
8.4 监控与告警
#mermaid-svg-F9Dq0NVO1NROVbR4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-F9Dq0NVO1NROVbR4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-F9Dq0NVO1NROVbR4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-F9Dq0NVO1NROVbR4 .error-icon{fill:#552222;}#mermaid-svg-F9Dq0NVO1NROVbR4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-F9Dq0NVO1NROVbR4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-F9Dq0NVO1NROVbR4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-F9Dq0NVO1NROVbR4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-F9Dq0NVO1NROVbR4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-F9Dq0NVO1NROVbR4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-F9Dq0NVO1NROVbR4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-F9Dq0NVO1NROVbR4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-F9Dq0NVO1NROVbR4 .marker.cross{stroke:#333333;}#mermaid-svg-F9Dq0NVO1NROVbR4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-F9Dq0NVO1NROVbR4 p{margin:0;}#mermaid-svg-F9Dq0NVO1NROVbR4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-F9Dq0NVO1NROVbR4 .cluster-label text{fill:#333;}#mermaid-svg-F9Dq0NVO1NROVbR4 .cluster-label span{color:#333;}#mermaid-svg-F9Dq0NVO1NROVbR4 .cluster-label span p{background-color:transparent;}#mermaid-svg-F9Dq0NVO1NROVbR4 .label text,#mermaid-svg-F9Dq0NVO1NROVbR4 span{fill:#333;color:#333;}#mermaid-svg-F9Dq0NVO1NROVbR4 .node rect,#mermaid-svg-F9Dq0NVO1NROVbR4 .node circle,#mermaid-svg-F9Dq0NVO1NROVbR4 .node ellipse,#mermaid-svg-F9Dq0NVO1NROVbR4 .node polygon,#mermaid-svg-F9Dq0NVO1NROVbR4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-F9Dq0NVO1NROVbR4 .rough-node .label text,#mermaid-svg-F9Dq0NVO1NROVbR4 .node .label text,#mermaid-svg-F9Dq0NVO1NROVbR4 .image-shape .label,#mermaid-svg-F9Dq0NVO1NROVbR4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-F9Dq0NVO1NROVbR4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-F9Dq0NVO1NROVbR4 .rough-node .label,#mermaid-svg-F9Dq0NVO1NROVbR4 .node .label,#mermaid-svg-F9Dq0NVO1NROVbR4 .image-shape .label,#mermaid-svg-F9Dq0NVO1NROVbR4 .icon-shape .label{text-align:center;}#mermaid-svg-F9Dq0NVO1NROVbR4 .node.clickable{cursor:pointer;}#mermaid-svg-F9Dq0NVO1NROVbR4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-F9Dq0NVO1NROVbR4 .arrowheadPath{fill:#333333;}#mermaid-svg-F9Dq0NVO1NROVbR4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-F9Dq0NVO1NROVbR4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-F9Dq0NVO1NROVbR4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-F9Dq0NVO1NROVbR4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-F9Dq0NVO1NROVbR4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-F9Dq0NVO1NROVbR4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-F9Dq0NVO1NROVbR4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-F9Dq0NVO1NROVbR4 .cluster text{fill:#333;}#mermaid-svg-F9Dq0NVO1NROVbR4 .cluster span{color:#333;}#mermaid-svg-F9Dq0NVO1NROVbR4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-F9Dq0NVO1NROVbR4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-F9Dq0NVO1NROVbR4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-F9Dq0NVO1NROVbR4 .icon-shape,#mermaid-svg-F9Dq0NVO1NROVbR4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-F9Dq0NVO1NROVbR4 .icon-shape p,#mermaid-svg-F9Dq0NVO1NROVbR4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-F9Dq0NVO1NROVbR4 .icon-shape .label rect,#mermaid-svg-F9Dq0NVO1NROVbR4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-F9Dq0NVO1NROVbR4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-F9Dq0NVO1NROVbR4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-F9Dq0NVO1NROVbR4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 告警规则
监控指标
消息吞吐量
响应延迟P99
ACK超时率
重试次数
连接池使用率
通道健康状态
吞吐量下降 >50%
P99延迟 >5s
ACK超时率 >5%
重试率 >10%
连接池 >80%
通道僵死
通知操作者
九、实战案例3:协议扩展开发
9.1 场景描述
当ACP的标准消息类型无法满足业务需求时,开发者可以通过协议扩展机制添加自定义的消息类型、编解码器和中间件。本案例实现一个"工作流编排"扩展,支持多代理之间的结构化协作。
9.2 扩展架构
#mermaid-svg-vuO7qsL4yoJCjXU2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vuO7qsL4yoJCjXU2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vuO7qsL4yoJCjXU2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vuO7qsL4yoJCjXU2 .error-icon{fill:#552222;}#mermaid-svg-vuO7qsL4yoJCjXU2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vuO7qsL4yoJCjXU2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vuO7qsL4yoJCjXU2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vuO7qsL4yoJCjXU2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vuO7qsL4yoJCjXU2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vuO7qsL4yoJCjXU2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vuO7qsL4yoJCjXU2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vuO7qsL4yoJCjXU2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vuO7qsL4yoJCjXU2 .marker.cross{stroke:#333333;}#mermaid-svg-vuO7qsL4yoJCjXU2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vuO7qsL4yoJCjXU2 p{margin:0;}#mermaid-svg-vuO7qsL4yoJCjXU2 g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-vuO7qsL4yoJCjXU2 g.classGroup text .title{font-weight:bolder;}#mermaid-svg-vuO7qsL4yoJCjXU2 .cluster-label text{fill:#333;}#mermaid-svg-vuO7qsL4yoJCjXU2 .cluster-label span{color:#333;}#mermaid-svg-vuO7qsL4yoJCjXU2 .cluster-label span p{background-color:transparent;}#mermaid-svg-vuO7qsL4yoJCjXU2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vuO7qsL4yoJCjXU2 .cluster text{fill:#333;}#mermaid-svg-vuO7qsL4yoJCjXU2 .cluster span{color:#333;}#mermaid-svg-vuO7qsL4yoJCjXU2 .nodeLabel,#mermaid-svg-vuO7qsL4yoJCjXU2 .edgeLabel{color:#131300;}#mermaid-svg-vuO7qsL4yoJCjXU2 .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-vuO7qsL4yoJCjXU2 .label text{fill:#131300;}#mermaid-svg-vuO7qsL4yoJCjXU2 .labelBkg{background:#ECECFF;}#mermaid-svg-vuO7qsL4yoJCjXU2 .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-vuO7qsL4yoJCjXU2 .classTitle{font-weight:bolder;}#mermaid-svg-vuO7qsL4yoJCjXU2 .node rect,#mermaid-svg-vuO7qsL4yoJCjXU2 .node circle,#mermaid-svg-vuO7qsL4yoJCjXU2 .node ellipse,#mermaid-svg-vuO7qsL4yoJCjXU2 .node polygon,#mermaid-svg-vuO7qsL4yoJCjXU2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vuO7qsL4yoJCjXU2 .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-vuO7qsL4yoJCjXU2 g.clickable{cursor:pointer;}#mermaid-svg-vuO7qsL4yoJCjXU2 g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-vuO7qsL4yoJCjXU2 g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-vuO7qsL4yoJCjXU2 .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-vuO7qsL4yoJCjXU2 .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-vuO7qsL4yoJCjXU2 .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-vuO7qsL4yoJCjXU2 .dashed-line{stroke-dasharray:3;}#mermaid-svg-vuO7qsL4yoJCjXU2 .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-vuO7qsL4yoJCjXU2 #compositionStart,#mermaid-svg-vuO7qsL4yoJCjXU2 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-vuO7qsL4yoJCjXU2 #compositionEnd,#mermaid-svg-vuO7qsL4yoJCjXU2 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-vuO7qsL4yoJCjXU2 #dependencyStart,#mermaid-svg-vuO7qsL4yoJCjXU2 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-vuO7qsL4yoJCjXU2 #dependencyStart,#mermaid-svg-vuO7qsL4yoJCjXU2 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-vuO7qsL4yoJCjXU2 #extensionStart,#mermaid-svg-vuO7qsL4yoJCjXU2 .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-vuO7qsL4yoJCjXU2 #extensionEnd,#mermaid-svg-vuO7qsL4yoJCjXU2 .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-vuO7qsL4yoJCjXU2 #aggregationStart,#mermaid-svg-vuO7qsL4yoJCjXU2 .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-vuO7qsL4yoJCjXU2 #aggregationEnd,#mermaid-svg-vuO7qsL4yoJCjXU2 .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-vuO7qsL4yoJCjXU2 #lollipopStart,#mermaid-svg-vuO7qsL4yoJCjXU2 .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-vuO7qsL4yoJCjXU2 #lollipopEnd,#mermaid-svg-vuO7qsL4yoJCjXU2 .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-vuO7qsL4yoJCjXU2 .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-vuO7qsL4yoJCjXU2 .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vuO7qsL4yoJCjXU2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vuO7qsL4yoJCjXU2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vuO7qsL4yoJCjXU2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ACPExtension
+name: string
+version: string
+initialize(config)
+register(router)
+shutdown()
WorkflowExtension
+name = "workflow"
+version = "1.0.0"
-workflowEngine: WorkflowEngine
-stepRegistry: Map
+initialize(config)
+register(router)
+handleWorkflowRequest(message)
+handleStepComplete(message)
+handleWorkflowCancel(message)
WorkflowEngine
-activeWorkflows: Map
-persistenceLayer: Persistence
+createWorkflow(definition)
+executeStep(workflowId, stepId)
+completeStep(workflowId, stepId, result)
+cancelWorkflow(workflowId)
+getWorkflowStatus(workflowId)
ACPRouter
+registerHandler(type, handler)
+send(message)
+broadcast(message)
9.3 实现扩展
typescript
// workflow-extension.ts - 工作流编排ACP扩展
import type { ACPExtension, ACPMessage, ACPHandler } from '@openclaw/acp';
interface WorkflowStep {
stepId: string;
agentId: string;
action: string;
input: Record<string, unknown>;
timeout?: number;
retryPolicy?: { maxRetries: number; backoffMs: number };
}
interface WorkflowDefinition {
workflowId: string;
name: string;
steps: WorkflowStep[];
onFailure: 'stop' | 'skip' | 'retry';
onComplete?: string; // 通知目标
}
interface WorkflowState {
workflowId: string;
status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
currentStepIndex: number;
results: Map<string, unknown>;
startedAt: number;
updatedAt: number;
error?: string;
}
export class WorkflowExtension implements ACPExtension {
readonly name = 'workflow';
readonly version = '1.0.0';
private workflowEngine: WorkflowEngine;
private router: ACPRouter | null = null;
async initialize(config: Record<string, unknown>): Promise<void> {
this.workflowEngine = new WorkflowEngine({
maxConcurrentSteps: config.maxConcurrentSteps ?? 3,
defaultTimeout: config.defaultTimeout ?? 60000,
persistenceEnabled: config.persistenceEnabled ?? true,
});
console.log('[WorkflowExtension] Initialized with config:', config);
}
async register(router: ACPRouter): Promise<void> {
this.router = router;
// 注册自定义消息类型处理器
router.registerHandler('workflow-start', this.handleStart.bind(this));
router.registerHandler('workflow-step-complete', this.handleStepComplete.bind(this));
router.registerHandler('workflow-step-failed', this.handleStepFailed.bind(this));
router.registerHandler('workflow-cancel', this.handleCancel.bind(this));
router.registerHandler('workflow-status', this.handleStatusQuery.bind(this));
console.log('[WorkflowExtension] Registered 5 message handlers');
}
private async handleStart(message: ACPMessage): Promise<ACPMessage> {
const definition: WorkflowDefinition = message.payload.content;
// 验证工作流定义
if (!definition.steps || definition.steps.length === 0) {
return this.createErrorResponse(
message, 'Workflow must have at least one step'
);
}
// 创建并启动工作流
const state = await this.workflowEngine.createWorkflow(definition);
await this.executeNextStep(state);
return {
header: {
messageId: crypto.randomUUID(),
correlationId: message.header.messageId,
timestamp: Date.now(),
version: '1.0',
type: 'response',
source: { agentId: 'gateway' },
destination: { agentId: message.header.source.agentId },
metadata: { customType: 'workflow-started' }
},
payload: {
contentType: 'application/x-acp-workflow-state',
content: {
workflowId: state.workflowId,
status: state.status,
currentStepIndex: state.currentStepIndex,
startedAt: state.startedAt
}
}
};
}
private async executeNextStep(state: WorkflowState): Promise<void> {
if (!this.router) throw new Error('Router not initialized');
const definition = await this.workflowEngine.getDefinition(state.workflowId);
if (state.currentStepIndex >= definition.steps.length) {
// 工作流完成
state.status = 'completed';
await this.workflowEngine.updateState(state);
// 发送完成通知
if (definition.onComplete) {
await this.router.send({
header: {
messageId: crypto.randomUUID(),
timestamp: Date.now(),
version: '1.0',
type: 'notification',
source: { agentId: 'gateway' },
destination: { target: definition.onComplete },
metadata: { customType: 'workflow-completed' }
},
payload: {
contentType: 'application/x-acp-workflow-state',
content: {
workflowId: state.workflowId,
status: 'completed',
results: Object.fromEntries(state.results)
}
}
});
}
return;
}
const step = definition.steps[state.currentStepIndex];
// 发送步骤执行请求到目标代理
await this.router.send({
header: {
messageId: crypto.randomUUID(),
timestamp: Date.now(),
version: '1.0',
type: 'request',
source: { agentId: 'gateway' },
destination: { agentId: step.agentId, target: 'agent' },
metadata: {
customType: 'workflow-step-execute',
idempotencyKey: `step-${state.workflowId}-${step.stepId}`,
ttl: step.timeout ?? 60000
}
},
payload: {
contentType: 'application/x-acp-workflow-step',
content: {
workflowId: state.workflowId,
stepId: step.stepId,
action: step.action,
input: {
...step.input,
previousResults: Object.fromEntries(state.results)
}
}
}
});
}
private async handleStepComplete(message: ACPMessage): Promise<ACPMessage> {
const { workflowId, stepId, result } = message.payload.content;
const state = await this.workflowEngine.getState(workflowId);
if (!state || state.status !== 'running') {
return this.createErrorResponse(message, 'Invalid workflow state');
}
// 记录步骤结果
state.results.set(stepId, result);
state.currentStepIndex++;
state.updatedAt = Date.now();
await this.workflowEngine.updateState(state);
// 继续执行下一步
await this.executeNextStep(state);
return { /* ACK响应 */ } as ACPMessage;
}
private async handleStepFailed(message: ACPMessage): Promise<ACPMessage> {
const { workflowId, stepId, error } = message.payload.content;
const definition = await this.workflowEngine.getDefinition(workflowId);
switch (definition.onFailure) {
case 'stop':
await this.workflowEngine.updateState({
workflowId,
status: 'failed',
error: `Step ${stepId} failed: ${error}`
});
break;
case 'skip':
const state = await this.workflowEngine.getState(workflowId);
if (state) {
state.currentStepIndex++;
await this.workflowEngine.updateState(state);
await this.executeNextStep(state);
}
break;
case 'retry':
// 实现重试逻辑
await this.executeNextStep(
await this.workflowEngine.getState(workflowId)
);
break;
}
return { /* ACK响应 */ } as ACPMessage;
}
private async handleCancel(message: ACPMessage): Promise<ACPMessage> {
const { workflowId } = message.payload.content;
await this.workflowEngine.updateState({
workflowId,
status: 'cancelled',
updatedAt: Date.now()
});
return { /* ACK响应 */ } as ACPMessage;
}
private async handleStatusQuery(message: ACPMessage): Promise<ACPMessage> {
const { workflowId } = message.payload.content;
const state = await this.workflowEngine.getState(workflowId);
return {
header: {
messageId: crypto.randomUUID(),
correlationId: message.header.messageId,
timestamp: Date.now(),
version: '1.0',
type: 'response',
source: { agentId: 'gateway' },
destination: { agentId: message.header.source.agentId }
},
payload: {
contentType: 'application/x-acp-workflow-state',
content: state
}
};
}
private createErrorResponse(original: ACPMessage, error: string): ACPMessage {
return {
header: {
messageId: crypto.randomUUID(),
correlationId: original.header.messageId,
timestamp: Date.now(),
version: '1.0',
type: 'response',
source: { agentId: 'gateway' },
destination: { agentId: original.header.source.agentId }
},
payload: {
contentType: 'application/json',
content: { error }
}
};
}
async shutdown(): Promise<void> {
// 清理活跃工作流
await this.workflowEngine?.shutdown();
console.log('[WorkflowExtension] Shutdown complete');
}
}
这个WorkflowExtension实现了一个完整的工作流编排扩展,支持:顺序步骤执行、步骤完成/失败处理、工作流取消、状态查询、失败策略(停止/跳过/重试)、以及完成通知。通过register()方法向ACP路由引擎注册5个自定义消息处理器,实现了多代理之间的结构化协作。
9.4 注册扩展
在openclaw.json中注册协议扩展:
json5
{
plugins: {
entries: {
"acp-workflow": {
enabled: true,
config: {
maxConcurrentSteps: 3,
defaultTimeout: 60000,
persistenceEnabled: true
}
}
}
}
}
9.5 使用扩展
代理之间通过工作流消息进行协作:
javascript
// 主代理发起代码审查+测试工作流
const workflowMessage = {
header: {
messageId: crypto.randomUUID(),
timestamp: Date.now(),
version: "1.0",
type: "request",
source: { agentId: "main" },
destination: { target: "gateway" },
metadata: { customType: "workflow-start" }
},
payload: {
contentType: "application/x-acp-workflow-definition",
content: {
workflowId: `wf-${Date.now()}`,
name: "code-review-and-test",
steps: [
{
stepId: "review",
agentId: "code-reviewer",
action: "review-code",
input: { filePath: "src/main.ts" },
timeout: 30000
},
{
stepId: "test",
agentId: "coding",
action: "run-tests",
input: { testSuite: "unit" },
timeout: 60000
},
{
stepId: "deploy-check",
agentId: "coding",
action: "deployment-readiness",
input: { environment: "staging" },
timeout: 15000
}
],
onFailure: "stop",
onComplete: "agent:main"
}
}
};
十、最佳实践与总结
10.1 ACP协议最佳实践
基于以上内容,总结ACP协议在实践中的关键最佳实践:
🔧 可靠性方面:
- 为所有关键消息设置
idempotencyKey,确保重试安全 - 合理配置
ttl字段,避免消息长时间挂起 - 使用指数退避重试策略,避免雪崩效应
- 在关键节点进行消息持久化,支持崩溃恢复
🔒 安全方面:
- 使用
token认证模式而非open策略 - 设置
dmPolicy: "pairing"作为默认DM策略 - 群聊始终启用
requireMention: true - 定期运行
openclaw security audit - 将敏感配置(token等)存储在环境变量中,不要硬编码
⚡ 性能方面:
- 大消息体启用压缩
- 合并高频通知为批量消息
- 长文本响应使用流式传输
- 配置合理的连接池大小
- 启用通道健康监控
📐 架构方面:
- 遵循单一信任边界原则------一个网关一个操作者
- 使用
session.dmScope: "per-channel-peer"实现多用户隔离 - 代理间通过自定义消息类型协作,而非共享状态
- 协议扩展优先于修改核心协议
10.2 常见陷阱
| 陷阱 | 后果 | 正确做法 |
|---|---|---|
| 将sessionKey当作授权令牌 | 安全误判 | sessionKey仅用于路由 |
| 多个不信任用户共享一个代理 | 权限泄露 | 分离信任边界 |
| 未设置idempotencyKey | 重复处理 | 关键消息必设幂等键 |
| 心跳和定时任务混淆 | 架构混乱 | 心跳=批量检查,定时=精确调度 |
| 忽略contextVisibility | 上下文泄露 | 按需配置上下文过滤 |
| 直接修改核心协议 | 升级冲突 | 使用协议扩展机制 |
10.3 协议演进方向
ACP协议仍在持续演进,以下是一些值得关注的方向:
- 多代理协商协议:代理之间自动协商任务分配和结果合并
- 端到端加密:代理间消息的端到端加密支持
- Schema Registry:自定义消息类型的注册中心,支持发现和验证
- 可观测性增强:OpenTelemetry集成,分布式追踪标准化
- 自适应流控:基于网络状况和负载动态调整传输参数
10.4 参考资源
🦞 总结:ACP协议是OpenClaw多代理架构的通信基石。通过四种消息类型(请求/响应/通知/心跳)覆盖了从实时交互到保活检测的全部场景;JSON信封格式提供了统一且可扩展的消息规范;ACK确认、指数退避重试和幂等设计构成了三层可靠性保障;Token认证、通道策略和上下文可见性控制形成了纵深安全防御;压缩、批量、流式和连接池优化确保了高性能传输。掌握ACP协议,是构建可靠、安全、高效的多代理系统的关键一步。