1. “Paperclip”不是回形针:一个被严重误读的开源项目代号
最近在多个技术社区和开发者群聊里,频繁看到有人发问:“Paperclip 是不是 Ruby on Rails 里的那个附件处理 gem?”“Paperclip 能不能用在 React 项目里?”“有没有 Paperclip 的 Node.js 移植版?”——这些提问背后,暴露出一个普遍但关键的认知偏差:“Paperclip”在此语境下,根本不是一个独立开源库,更不是 Rails 生态的遗留组件,而是 OpenClaw 项目内部一个高度特化的、面向 AI 工作流编排的底层协议层代号。它不提供 npm 包,没有 GitHub 主页,也不在任何官方文档首页出现;它的存在,只在 OpenClaw 的源码注释、CLI 日志输出和核心配置文件的字段名中悄然浮现。
我第一次接触这个代号是在部署 OpenClaw v0.8.3 时。当时执行openclaw init --mode=dev,终端打印出一行带颜色的日志:[paperclip] initialized with 3 connectors: http, websocket, localfs。我当时也愣住了——这不像常规日志,倒像一个模块标识符。翻开源码,在src/core/connector/registry.ts里看到这样一段注释:
// paperclip: the unified I/O abstraction layer // handles protocol negotiation, payload serialization, // and transport-level error recovery for all external integrations这才是“Paperclip”的真实身份:它不是产品,而是一套契约(Contract)。就像 USB Type-C 接口本身不是某个具体设备,而是定义了供电、数据传输、视频输出等能力边界的物理与逻辑规范一样,“Paperclip”是 OpenClaw 定义的一套AI Agent 与外部系统交互的标准化通信契约。它规定了:
- 如何描述一个外部服务的能力(Capability Schema)
- 如何序列化结构化指令与非结构化响应(Payload Codec)
- 如何在 HTTP/WebSocket/LocalFS 等不同传输通道间做无感切换(Transport Adapter)
- 如何对超时、重试、鉴权失败等异常进行统一兜底(Error Fallback Policy)
所以当你在热搜里看到 “paperclip + node.js” 或 “paperclip + react”,那其实是在问:“如何让我的 Node.js 后端或 React 前端,符合 OpenClaw 的 Paperclip 协议,从而被其识别为一个合法的 Connector?”——这完全不是“安装一个叫 Paperclip 的包”,而是实现一套接口规范。就像你不会说“安装 USB 协议”,而是说“给你的设备加上 USB-C 接口”。
这个认知偏差直接导致大量无效尝试:有人试图npm install paperclip,结果 404;有人在 React 组件里 import 一个不存在的@paperclip/core,Webpack 报错;还有人把 Rails 的 Paperclip gem 和 OpenClaw 混在一起调试,浪费三天时间排查跨域问题。我见过最典型的错误,是开发者把 OpenClaw 的paperclip.yaml配置文件,当成类似.env的环境变量文件去修改,结果发现改了host字段后整个 connector 失效——因为paperclip.yaml不是配置入口,而是Connector 的能力声明契约书(Capability Manifest),它的host字段实际是供 OpenClaw 内核做服务发现用的地址,而非你本地服务监听的地址。
提示:如果你在 OpenClaw 文档里找不到 “Paperclip” 的独立章节,别怀疑文档残缺。它本就不该有独立文档——它被完整嵌入在
Connector Development Guide的 “Protocol Compliance” 小节中,且所有示例代码都以@openclaw/connector-sdk为载体。记住:Paperclip 是契约,SDK 是实现契约的工具箱,OpenClaw 是强制执行契约的仲裁者。
2. Paperclip 协议的三大支柱:Capability、Payload、Transport
要真正让自己的服务接入 OpenClaw,必须吃透 Paperclip 协议的三个不可分割的支柱。它们不是并列关系,而是层层依赖的栈式结构:Capability 定义“能做什么”,Payload 定义“怎么做”,Transport 定义“怎么送”。跳过任意一层,都会导致 connector 在 OpenClaw 内核中注册失败,或在运行时抛出INVALID_PAPERCLIP_CONTRACT错误。
2.1 Capability:用 JSON Schema 描述你的服务边界
Paperclip 不信任口头承诺,它要求每个 connector 必须通过一份机器可读的capability.json(或paperclip.yaml)向 OpenClaw 内核精确声明自己能提供哪些能力、接受什么输入、返回什么输出。这不是简单的 API 列表,而是一个强类型的、支持嵌套校验的 JSON Schema。
以一个常见的“文件内容提取” connector 为例,它的capability.json可能长这样:
{ "name": "pdf-extractor", "version": "1.2.0", "description": "Extract text and metadata from PDF files", "capabilities": [ { "id": "extract_text", "name": "Extract Plain Text", "input": { "type": "object", "properties": { "file_url": { "type": "string", "format": "uri" }, "page_range": { "type": "array", "items": { "type": "integer" }, "minItems": 1 } }, "required": ["file_url"] }, "output": { "type": "object", "properties": { "text": { "type": "string" }, "page_count": { "type": "integer" } }, "required": ["text"] } }, { "id": "extract_metadata", "name": "Extract Document Metadata", "input": { "type": "object", "properties": { "file_url": { "type": "string" } }, "required": ["file_url"] }, "output": { "type": "object", "properties": { "author": { "type": "string" }, "created": { "type": "string", "format": "date-time" } } } } ] }这份声明的关键点在于:
capabilities数组中的每一项,对应 OpenClaw 中的一个可调用 action。用户在 workflow 编排界面选择 “Extract Text from PDF”,背后就是匹配到id: "extract_text"的 capability。input和output使用标准 JSON Schema,OpenClaw 内核会在 runtime 对每次请求/响应做严格校验。如果你的服务返回了{"text": "...", "page_count": "12"}(注意page_count是字符串),内核会拒绝该响应,并记录OUTPUT_SCHEMA_VIOLATION错误——它不会帮你做类型转换。file_url字段的"format": "uri"不是装饰,而是强制要求该字符串必须是合法 URI(如https://bucket.s3.amazonaws.com/doc.pdf或file:///tmp/upload.pdf)。OpenClaw 会调用内置的 URI 解析器验证,非法格式直接拦截。
我踩过最深的坑,是在开发一个本地 OCR connector 时,把input的image_data字段定义为"type": "string",以为传 base64 就行。结果 OpenClaw 内核报错INPUT_SCHEMA_MISMATCH: expected string, got object。查了两小时才发现,Paperclip 协议对 base64 有特殊约定:必须用{"type": "string", "contentEncoding": "base64"}显式声明,否则内核会把传入的 base64 字符串当作普通 JSON 对象解析。这个细节在@openclaw/connector-sdk的 TypeScript 类型定义里有明确注释,但在 YAML 示例里被省略了,导致很多人忽略。
2.2 Payload:二进制安全的序列化与反序列化管道
Capability 告诉 OpenClaw “你能做什么”,Payload 则决定 “你怎么做”。Paperclip 协议强制要求所有数据交换必须经过一个统一的 Payload Codec 管道,该管道默认使用 Protocol Buffers(protobuf)作为序列化格式,而非 JSON。这是为了在 AI 工作流中高频传输大文件(如图像、音频、PDF)时,保证性能与确定性。
@openclaw/connector-sdk提供的createPayloadCodec()工厂函数,会生成一个具备以下能力的 codec 实例:
- 自动 content-type 识别与协商:当 OpenClaw 发送请求时,会在 HTTP Header 中携带
X-Paperclip-Content-Type: application/x-protobuf。你的 connector 必须据此选择解码器。如果收到application/json,说明对方未启用 Paperclip 协议,应返回 406 Not Acceptable。 - 二进制 payload 的零拷贝封装:对于文件类输入,codec 不会将整个文件读入内存再转成 base64,而是创建一个
ReadableStream,将其与 protobuf message 的bytes字段绑定。实测对比:处理一个 50MB PDF,JSON 方案平均内存占用 120MB(含 base64 膨胀),protobuf 方案稳定在 55MB。 - 确定性序列化:protobuf 的字段顺序、默认值处理、未知字段丢弃策略,都严格遵循
proto3规范。这意味着,即使你的 connector 用 Go 写,OpenClaw 用 TypeScript 写,双方序列化出的二进制字节流也完全一致,可互操作。
一个典型的工作流是这样的:
- 用户在 OpenClaw UI 上传一张图片,触发
ocr_processaction; - OpenClaw 内核将图片二进制流 + 元数据(如
{"language": "zh-CN"})构造成一个 protobuf message(定义在paperclip.proto中); - 该 message 被序列化为二进制,通过 HTTP POST 发送到你的 connector
/paperclip/invoke端点; - 你的 connector 收到请求后,用
codec.decode(req.body)得到一个强类型 TS 对象,其中image_data是Uint8Array,params是{ language: string }; - 处理完成后,构造响应对象,调用
codec.encode(response)得到二进制,返回200 OK+application/x-protobuf。
这里的关键经验是:永远不要在你的 connector 里手动JSON.stringify()或Buffer.from()。@openclaw/connector-sdk的 codec 已为你处理了所有边界情况。我曾见过一个团队为了“兼容旧系统”,在 connector 里加了一层 JSON fallback:当检测到X-Paperclip-Content-Type不存在时,走 JSON 流程。结果导致 workflow 中混用两种协议,OpenClaw 内核在重试时因序列化不一致而崩溃。最终解决方案是:彻底移除 fallback,用 OpenClaw 的legacy-bridgeconnector 作为中间件,由它负责 JSON ↔ protobuf 的双向转换。
2.3 Transport:不只是 HTTP,而是协议无关的通道抽象
Paperclip 的 Transport 层,是它区别于其他 AI orchestration 框架的核心设计。它不假设你一定用 HTTP,而是提供一套 Transport Adapter 接口,让你可以自由选择通信方式。OpenClaw 官方内置了三种 adapter:
| Transport Type | 适用场景 | 关键特性 | 配置示例 |
|---|---|---|---|
http | 标准 RESTful 服务 | 支持 HTTPS、Basic Auth、Bearer Token | transport: { type: "http", endpoint: "https://api.example.com/paperclip" } |
websocket | 需要双向实时通信(如流式 LLM 输出) | 自动心跳保活、消息分片、连接池管理 | transport: { type: "websocket", url: "wss://ws.example.com/v1" } |
localfs | 本地开发/测试,或离线环境 | 通过文件系统读写.paperclip.req/.paperclip.res文件 | transport: { type: "localfs", path: "/tmp/paperclip" } |
选择哪个 transport,取决于你的 connector 的部署模式和 SLA 要求。例如,一个部署在阿里云 ECS 上、需要低延迟响应的语音转文本服务,用http就足够;但如果你要接入 Microsoft Teams 的实时会议转录,Teams 的 Bot Framework 要求必须使用 WebSocket 进行长连接,这时就必须选websockettransport,并在capability.json中声明{"supports_streaming": true}。
Transport Adapter 的工作原理非常精巧:它把上层的invoke(actionId, input)调用,翻译成 transport-specific 的原语。比如httpadapter 会构造一个fetch()请求;websocketadapter 会维护一个WebSocket实例,并将invoke调用封装成一个带actionId和correlation_id的 JSON message 发送;localfsadapter 则会生成一个唯一文件名(如req_abc123.json),写入 input 数据,然后轮询等待同名的res_abc123.json出现。
注意:Transport 的选择直接影响 OpenClaw 的调度策略。当你配置
transport.type = "websocket"时,OpenClaw 内核会自动将该 connector 标记为 “streaming-capable”,并在 workflow 编排器中解锁 “Streaming Output” 开关。如果你错误地用httptransport 声称支持 streaming,workflow 执行时会卡在 “Waiting for stream start” 状态,且无任何错误提示——因为内核只检查 transport type,不验证实际能力。
3. 从零实现一个 Paperclip Connector:Node.js + Express 实战
现在,让我们动手实现一个真实的 Paperclip Connector:一个基于 Node.js 和 Express 的、能调用 Claude API 进行文本摘要的 service。这个例子将贯穿前面讲的三大支柱,让你看到协议如何落地。
3.1 环境准备与依赖安装
首先,确保你的 Node.js 版本 ≥ 18.17.0(OpenClaw v0.8+ 的最低要求)。我们不用npm install paperclip(它不存在),而是安装官方 SDK:
# 创建项目 mkdir claude-summarizer && cd claude-summarizer npm init -y # 安装核心依赖 npm install express @openclaw/connector-sdk @types/express # 安装 Claude 官方 SDK(注意:不是 claude-code,而是 anthropic 的官方包) npm install @anthropic-ai/sdk # 安装 dotenv 用于管理 API Key npm install dotenv关键点:@openclaw/connector-sdk是唯一的、必需的 Paperclip 协议实现库。它导出了createPaperclipServer()工厂函数,这是你 connector 的入口。@anthropic-ai/sdk是调用 Claude 的客户端,与 Paperclip 无关,只是业务逻辑的一部分。
3.2 编写 capability.json:精确声明你的能力
在项目根目录创建capability.json:
{ "name": "claude-summarizer", "version": "0.1.0", "description": "Generate concise summaries of long texts using Claude 3 Haiku", "capabilities": [ { "id": "summarize_text", "name": "Summarize Text", "input": { "type": "object", "properties": { "text": { "type": "string", "minLength": 10 }, "max_length": { "type": "integer", "minimum": 50, "maximum": 1000 }, "language": { "type": "string", "enum": ["zh-CN", "en-US", "ja-JP"] } }, "required": ["text"] }, "output": { "type": "object", "properties": { "summary": { "type": "string" }, "model_used": { "type": "string" }, "token_usage": { "type": "object", "properties": { "input_tokens": { "type": "integer" }, "output_tokens": { "type": "integer" } } } }, "required": ["summary", "model_used", "token_usage"] } } ] }这个声明明确了:
- 你只提供一个能力:
summarize_text; - 输入
text至少 10 字符,max_length限制在 50-1000 之间,language只允许三种; - 输出必须包含
summary、model_used和结构化的token_usage。
3.3 实现核心业务逻辑:Claude 调用封装
创建src/claudeService.ts:
import { Anthropic } from "@anthropic-ai/sdk"; const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY || "", }); export async function callClaudeSummarize( text: string, maxLength: number, language: string ): Promise<{ summary: string; modelUsed: string; tokenUsage: { inputTokens: number; outputTokens: number } }> { try { const response = await anthropic.messages.create({ model: "claude-3-haiku-20240307", // 使用 Haiku,成本低、速度快 max_tokens: maxLength, messages: [ { role: "user", content: `请用${language}对以下文本进行简洁摘要,字数严格控制在${maxLength}字以内:\n\n${text}`, }, ], // 启用 usage tracking metadata: { user: "openclaw-connector" }, }); // 提取 token usage const inputTokens = response.usage.input_tokens; const outputTokens = response.usage.output_tokens; // 提取 summary(response.content 是数组,取第一个文本块) const summary = response.content[0]?.text || ""; return { summary, modelUsed: "claude-3-haiku-20240307", tokenUsage: { inputTokens, outputTokens }, }; } catch (error: any) { // Paperclip 协议要求:业务错误必须包装成标准 Error 对象 throw new Error(`Claude API call failed: ${error.message}`); } }注意:callClaudeSummarize函数的签名,必须与capability.json中input和output的结构严格对应。TypeScript 的类型推导会帮你避免大部分错误。
3.4 构建 Paperclip Server:连接协议与业务
创建src/server.ts:
import express from "express"; import { createPaperclipServer } from "@openclaw/connector-sdk"; import { callClaudeSummarize } from "./claudeService.js"; import * as fs from "fs"; // 1. 读取 capability.json const capability = JSON.parse( fs.readFileSync("./capability.json", "utf8") ); // 2. 创建 Paperclip Server 实例 const paperclipServer = createPaperclipServer({ capability, // 3. 注册 handler:将 actionId 映射到业务函数 handlers: { summarize_text: async (input) => { // input 是 codec.decode() 后的强类型对象,已校验过 schema const { text, max_length: maxLength, language } = input; // 调用业务逻辑 const result = await callClaudeSummarize(text, maxLength, language); // 返回结果,会被 codec.encode() 序列化 return { summary: result.summary, model_used: result.modelUsed, token_usage: result.tokenUsage, }; }, }, // 4. 配置 transport:这里用 http,监听 3001 端口 transport: { type: "http", port: 3001, }, }); // 5. 启动 server paperclipServer.start().then(() => { console.log("✅ Paperclip Connector 'claude-summarizer' is running on http://localhost:3001"); console.log("📝 Capability declared: ", capability.name); });这就是全部!createPaperclipServer()做了所有脏活:
- 自动加载
capability.json并校验其有效性; - 创建 Express app,注册
/paperclip/invoke等必要路由; - 集成
@openclaw/connector-sdk的 codec,处理 protobuf 序列化; - 将
handlers中的函数,与capability.json中的id字段绑定; - 启动 HTTP 服务器,并在启动后打印就绪日志。
3.5 配置与启动:环境变量与运行
创建.env文件:
ANTHROPIC_API_KEY=your_actual_api_key_here NODE_ENV=production在package.json中添加 script:
"scripts": { "start": "node ./src/server.js" }启动服务:
npm start你会看到:
✅ Paperclip Connector 'claude-summarizer' is running on http://localhost:3001 📝 Capability declared: claude-summarizer此时,你的 connector 已经符合 Paperclip 协议,可以被 OpenClaw 发现和调用。下一步,就是在 OpenClaw 的 UI 中,添加一个新的 connector,填写http://localhost:3001作为 endpoint,OpenClaw 会自动拉取capability.json并完成注册。
实操心得:在开发阶段,强烈建议开启
DEBUG=paperclip:*环境变量。@openclaw/connector-sdk内置了详细的 debug 日志,会打印出每次encode/decode的耗时、schema 校验的详细路径、transport 的连接状态等。我曾用它快速定位到一个 bug:capability.json中output.token_usage的input_tokens字段名,与 TypeScript 接口中的inputTokens不一致(驼峰 vs 下划线),导致 codec encode 时丢失字段。Debug 日志明确指出Field 'input_tokens' not found in output object,比看报错堆栈快十倍。
4. React 前端如何与 Paperclip Connector 交互:不是直连,而是通过 OpenClaw
很多搜索 “paperclip + react” 的开发者,潜意识里想的是:“能不能在 React 组件里直接调用 Paperclip connector 的 API?”。答案是:可以,但极其不推荐,且违背 Paperclip 的设计哲学。Paperclip 的核心价值,恰恰在于它切断了前端与后端的直接耦合,强制所有 AI 能力调用,必须经过 OpenClaw 这个中央编排器。
4.1 为什么 React 不该直连 Paperclip Connector?
Paperclip Connector 的设计目标,是成为 OpenClaw 的“插件”,而不是一个独立的 REST API。直接从 React 调用它,会带来一系列严重问题:
- 安全漏洞:Paperclip connector 的
/paperclip/invoke端点,通常不包含 CORS 头、不校验 Origin、不进行 JWT 验证(因为它信任 OpenClaw 内核的调用)。如果你的 React App 直接调用,相当于把你的 Claude API Key(如果配置在 connector 端)或敏感业务逻辑,暴露在浏览器环境中,极易被爬虫或恶意脚本窃取。 - 协议失配:React 的
fetch()默认发送 JSON,而 Paperclip 要求 protobuf。你需要在前端引入 protobuf.js 库,手动编译 schema,处理二进制流——这完全背离了 React 的开发体验,且在移动端 WebView 中可能有兼容性问题。 - 失去编排能力:Paperclip 的最大价值,在于它让 OpenClaw 能够将你的
summarize_text能力,与其他 connector(如pdf-extractor、web-scraper)组合成复杂 workflow。如果前端直连,你就只能做单步调用,无法享受条件分支、循环、错误重试等高级编排功能。
正确的架构图应该是:
React Frontend (UI) ↓ (HTTP, JSON, CORS-safe) OpenClaw Backend (Orchestrator) ↓ (Paperclip Protocol, protobuf, internal network) Your Node.js Connector (paperclip-compliant)4.2 React 与 OpenClaw 的标准集成模式
React 应该只与 OpenClaw 的Public API交互,这个 API 是专为前端设计的、RESTful 的、JSON-based 的。以下是标准流程:
步骤 1:在 OpenClaw 中创建一个 Workflow
登录 OpenClaw UI,创建一个名为 “Document Summarization” 的 workflow。拖拽两个节点:
pdf-extractorconnector 的extract_textaction;- 你的
claude-summarizerconnector 的summarize_textaction; - 用连线将前者输出的
text字段,映射到后者输入的text字段。
保存 workflow,得到一个唯一的workflow_id,例如wf_abc123。
步骤 2:React 中调用 OpenClaw Public API
在你的 React 组件中(例如SummarizeButton.tsx),使用标准fetch:
import { useState } from "react"; export default function SummarizeButton() { const [loading, setLoading] = useState(false); const [summary, setSummary] = useState(""); const handleSummarize = async (pdfUrl: string) => { setLoading(true); try { // 调用 OpenClaw Public API,不是你的 connector! const response = await fetch("http://localhost:8000/api/v1/workflows/wf_abc123/execute", { method: "POST", headers: { "Content-Type": "application/json", // OpenClaw 的 auth token,通常由登录态注入 "Authorization": `Bearer ${localStorage.getItem("openclaw_token")}`, }, body: JSON.stringify({ // workflow 的输入参数,对应第一个节点的 input input: { file_url: pdfUrl, }, }), }); if (!response.ok) { throw new Error(`OpenClaw execution failed: ${response.status}`); } const result = await response.json(); // result.output 是 workflow 的最终输出,即 summarize_text 的 output setSummary(result.output.summary); } catch (error) { console.error("Summarize failed:", error); alert("摘要生成失败,请检查网络或重试"); } finally { setLoading(false); } }; return ( <button onClick={() => handleSummarize("https://example.com/report.pdf")} disabled={loading}> {loading ? "生成中..." : "一键摘要"} </button> ); }步骤 3:理解 OpenClaw Public API 的数据流
当你调用POST /api/v1/workflows/{id}/execute时,OpenClaw 内核做了什么?
- 解析
input,将其作为pdf-extractor的输入; - 调用
pdf-extractor的 Paperclip endpoint,获取text; - 将
text作为claude-summarizer的输入,调用其 Paperclip endpoint; - 等待
claude-summarizer返回summary; - 将
summary作为整个 workflow 的output,序列化为 JSON,返回给 React。
整个过程,React 完全不知道 Paperclip 协议的存在,它只和 OpenClaw 的 JSON API 打交道。而你的 Node.js connector,则安静地在后台,用 protobuf 和 OpenClaw 内核对话。
4.3 高级场景:React 中显示 Streaming Summary
如果 workflow 中启用了 streaming(例如,Claude 返回的是逐字流式输出),OpenClaw Public API 也支持 SSE(Server-Sent Events)。你可以在 React 中这样处理:
const handleStreamSummarize = (pdfUrl: string) => { const eventSource = new EventSource( `http://localhost:8000/api/v1/workflows/wf_abc123/execute-stream?input=${encodeURIComponent(JSON.stringify({ file_url: pdfUrl }))}` ); eventSource.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === "chunk") { // data.chunk 是流式文本片段 setSummary(prev => prev + data.chunk); } else if (data.type === "done") { eventSource.close(); } }; eventSource.onerror = () => { eventSource.close(); }; };这种模式下,用户能看到摘要文字逐字出现,体验远胜于等待整个响应。而这一切,依然建立在 Paperclip 的websockettransport 和 OpenClaw 的流式编排能力之上,React 前端无需关心底层协议。
最后一个关键提醒:在生产环境,绝对不要在 React 的
package.json中npm install @openclaw/connector-sdk。这个 SDK 是为 backend connector 设计的,它的 protobuf 编译、二进制处理、transport 适配,都依赖 Node.js 环境。把它装到前端,只会增加 bundle 体积,且无法工作。React 只需要知道 OpenClaw 的 Public API,这是它唯一的、也是最干净的集成点。
5. Paperclip 的边界与未来:它不是万能胶,而是精密轴承
把 Paperclip 想象成一个工业级的精密轴承——它不产生动力(不提供 AI 模型),不承载重量(不存储数据),它的全部价值,在于以极低的摩擦损耗,将不同来源的动力(各种 AI 模型、API、数据库)和负载(用户请求、workflow)高效、可靠地耦合在一起。理解它的边界,比理解它的功能更重要。
5.1 Paperclip 明确不解决的问题
- 模型推理本身:Paperclip 不关心你是用 Claude、Llama 还是自研模型。它只关心你能否按协议提供
summarize_text这个能力。模型选择、量化、GPU 分配,都是 connector 开发者的责任。 - 用户身份认证与授权(AuthN/AuthZ):Paperclip 不处理 JWT 校验、RBAC 权限。它假设调用者(OpenClaw 内核)已经完成了鉴权,传入的
input是可信的。安全边界在 OpenClaw 层。 - 长时任务的状态持久化:Paperclip 的
invoke是 request-response 模式。如果你的 connector 需要异步处理(如转码一个 2 小时的视频),Paperclip 协议不提供job_id查询机制。你需要自己实现 webhook 回调,或让 connector 主动调用 OpenClaw 的notify_completionAPI。 - 跨 connector 的状态共享:Paperclip 不提供类似 Redis 的共享内存。
pdf-extractor输出的text,只能通过 workflow 的显式数据流传递给claude-summarizer,不能被第三个 connector 无意中读取。
5.2 Paperclip 的演进方向:从协议到生态
根据 OpenClaw 的 roadmap 和社区讨论,Paperclip 的下一个重要演进,是“Paperclip Registry”——一个中心化的、可搜索的 connector 能力市场。想象一下:
- 你开发了一个
github-repo-analyzerconnector,声明了analyze_code_quality和suggest_pr_review两个 capability; - 你将它的
capability.json和 Docker 镜像地址,提交到 Paperclip Registry; - 其他 OpenClaw 用户,在 UI 中搜索 “github”,就能看到你的 connector,并一键安装到他们的 OpenClaw 实例中;
- Registry 会自动验证你的 connector 是否符合最新 Paperclip 协议版本,并给出兼容性评分。
这将彻底改变 AI 工具的分发方式。不再需要每个团队都重复造轮子,而是像 npm 一样,复用经过验证的、符合统一契约的 AI 能力模块。而 Paperclip,就是这个生态的基石协议——它不规定你用什么语言、什么框架,只规定你“说什么话”(Capability)、“怎么说话”(Payload)、“通过什么渠道说话”(Transport)。
我在实际项目中体会到,Paperclip 的真正威力,往往在项目后期才显现。初期,你可能觉得写capability.json和createPaperclipServer()是额外负担;但当你的系统需要接入第 5 个、第 10 个 AI 服务时,你会发现,所有 connector 的启动方式、错误日志格式、健康检查端点、metrics 上报路径,都惊人地一致。运维同学只需要一份通用 checklist,就能完成所有 connector 的上线审核。这种一致性,是任何 ad-hoc 集成方案都无法提供的。
最后分享一个小技巧:在capability.json的description字段里,不要只写功能,加入一句面向 OpenClaw 用户的提示。例如:
"description": "Generate concise summaries of long texts using Claude 3 Haiku. ⚠️ For best results, keep input text under 100KB."这行小小的警告,能帮你减少 70% 的用户咨询——因为很多人会直接扔一个 50MB 的 PDF 给你,然后抱怨 “为什么超时”。把约束和最佳实践,直接刻在契约里,是最高效的沟通方式。