OpenClaw ACP模式:代理通信协议深度解析

🦞 摘要: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收到一条消息时,路由引擎会按照以下优先级进行匹配:

  1. peer match:精确的DM/群组/通道ID匹配
  2. parentPeer match:线程继承匹配
  3. guildId + roles:Discord角色路由
  4. guildId:Discord服务器级别
  5. teamId:Slack团队级别
  6. accountId match:通道账户级别
  7. Channel-level matchaccountId: "*" 通配
  8. Default agent:回退到默认代理

这种确定性、最高特异性优先的路由策略,确保了消息总是被路由到最合适的代理实例。


二、消息类型详解

ACP定义了四种核心消息类型,每种类型服务于不同的通信场景。

2.1 请求消息(Request)

请求消息是最基础的ACP消息类型,用于代理向其他代理或网关发起同步调用。请求消息要求接收方返回一个响应。

典型场景

  • 用户通过通道发送消息,触发代理处理
  • 代理调用工具(tool use)
  • 代理之间的协作请求

请求消息特征

  • 必须携带唯一messageId用于关联响应
  • 包含发送方agentId和目标路由信息
  • 可选的timeout字段控制超时行为
  • 支持priority字段标记紧急程度

2.2 响应消息(Response)

响应消息是对请求消息的回复,携带处理结果或错误信息。ACP的响应模型支持流式响应------代理可以分多次发送响应片段,最终以完成标记结束。

典型场景

  • 代理返回生成的文本内容
  • 工具调用返回执行结果
  • 多代理协作中的中间结果传递

响应消息特征

  • 携带correlationId关联原始请求
  • 支持streaming: true标记流式响应
  • 包含chunkIndexisFinal控制流式分块
  • 可携带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只会被处理一次,即使消息因网络问题被重复投递:

幂等性实现原理

  1. 发送方为每条消息生成唯一的idempotencyKey(通常为UUID)
  2. 接收方维护最近处理的键值缓存(TTL通常为5分钟)
  3. 收到消息时先检查缓存,若键已存在则直接返回之前的结果
  4. 处理完成后将键和结果写入缓存

幂等性适用场景

场景 幂等要求 实现方式
工具调用 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字符
    }
  }
}
  1. 客户端连接时在WebSocket握手或HTTP请求头中携带Authorization: Bearer <token>
  2. 网关验证token有效性
  3. 认证通过后建立会话,后续帧不再需要重复认证
  4. 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协议仍在持续演进,以下是一些值得关注的方向:

  1. 多代理协商协议:代理之间自动协商任务分配和结果合并
  2. 端到端加密:代理间消息的端到端加密支持
  3. Schema Registry:自定义消息类型的注册中心,支持发现和验证
  4. 可观测性增强:OpenTelemetry集成,分布式追踪标准化
  5. 自适应流控:基于网络状况和负载动态调整传输参数

10.4 参考资源


🦞 总结:ACP协议是OpenClaw多代理架构的通信基石。通过四种消息类型(请求/响应/通知/心跳)覆盖了从实时交互到保活检测的全部场景;JSON信封格式提供了统一且可扩展的消息规范;ACK确认、指数退避重试和幂等设计构成了三层可靠性保障;Token认证、通道策略和上下文可见性控制形成了纵深安全防御;压缩、批量、流式和连接池优化确保了高性能传输。掌握ACP协议,是构建可靠、安全、高效的多代理系统的关键一步。