Paperclip 进程适配器怎么用:把任意 shell 命令接进 Agent 编排,代价是什么
如果你手里的 agent 不是 Claude Code、不是 Codex,而是一段自己用 Python 攒起来的循环,接到 Paperclip 上的第一反应通常是:那我得先给它写个适配器吧?其实不一定。Paperclip 内置了一个叫 process 的适配器,它做的事情只有一件——把你配置的那条 shell 命令当子进程拉起来,跑完为止。
官方文档对它的定位写得很直白:执行任意 shell 命令,适合简单脚本、一次性任务,以及构建在自定义框架上的 agent。换句话说,只要你的运行时能被一条命令调起来,它就能接。
但这条路不是白走的。process 换来了接入成本几乎为零,代价落在两个地方:一是官方明确说了有两种场景别用它,二是它在运行转录里能给你看的东西比 CLI 类适配器少一截。下面按文档把这两面都摊开。
它在整条链路里处在哪一环
先把上下文补齐。按官方对适配器的总体说明,适配器是 Paperclip 编排层与 agent 运行时之间的桥,每种适配器知道怎么调起一类 AI agent 并把结果收回来。一次心跳(heartbeat)触发时,Paperclip 做四件事:查出这个 agent 的 adapterType 和 adapterConfig,带着执行上下文调用适配器的 execute(),由适配器去 spawn 或调用 agent 运行时,最后适配器捕获 stdout、解析用量与成本数据、返回一个结构化结果。
process 就是这套流程里最薄的一层实现。文档给出的执行步骤是:
- Paperclip 把配置的命令作为子进程 spawn 起来;
- 注入标准的 Paperclip 环境变量(文档举了
PAPERCLIP_AGENT_ID、PAPERCLIP_API_KEY,并写了 etc.,说明不止这两个); - 进程一路跑到结束;
- 退出码决定这次运行算成功还是失败。
第四条是最需要提前想清楚的一条。你的脚本内部无论怎么判断”这次任务其实没做成”,只要它最后 exit 0,在 Paperclip 这边就是一次成功的运行。反过来,脚本里一个未捕获的异常让解释器以非零码退出,这次运行就被记成失败。所以接 process 之前,先把脚本的退出码语义捋一遍——这是它和编排层之间唯一的成败信号。想弄清楚这个信号后续会驱动什么,可以顺着 Agent 在 Paperclip 里怎么被驱动 往下看。
第二条同样值得展开。环境变量是注入进来的,意味着你的脚本不需要自己去哪里读密钥:拿 PAPERCLIP_API_KEY 就能回头调 Paperclip 的 API,拿 PAPERCLIP_AGENT_ID 就知道自己是以哪个 agent 的身份在跑。文档里那个”跑一段 Python 脚本”的例子,说明也正是这句——脚本用注入的环境变量去认证 Paperclip API 并完成工作。
四个配置项,一个必填
adapterConfig 一共就四个字段:
| 字段 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
command | string | 是 | 要执行的 shell 命令 |
cwd | string | 否 | 工作目录 |
env | object | 否 | 环境变量 |
timeoutSec | number | 否 | 进程超时时间 |
官方给的完整示例是这样一个跑 Python 脚本的 agent:
{
"adapterType": "process",
"adapterConfig": {
"command": "python3 /path/to/agent.py",
"cwd": "/path/to/workspace",
"timeoutSec": 300
}
}
四个字段里,cwd 和 timeoutSec 是最容易被跳过、又最容易在半夜出事的两个。cwd 不写,脚本相对路径读文件的行为就取决于 Paperclip 进程自己在哪儿,示例里明确把它指到了工作区目录,照做即可。timeoutSec 不写,文档没有说明默认值是多少、是否等于不限时——所以你自己写的循环如果有卡住的可能(等一个不会返回的 HTTP 请求、等一个不会有人回的输入),最好显式给个上限,别指望默认值兜底。
env 是对象形式的追加环境变量。注意它是在标准 Paperclip 变量之外你自己要加的那些(模型密钥、内部服务地址之类),文档没有说明它与注入变量同名时谁覆盖谁,稳妥做法是别用 PAPERCLIP_ 前缀去撞名。至于哪些变量属于 Paperclip 部署层面的配置,那是另一套清单,和这里的 env 不是一回事。
官方直接写出来的两条劝退线
文档难得地专门列了一节 “When Not to Use”,两条:
一是需要跨多次运行保持会话持久化的,别用 process,改用 claude_local 或 codex_local。
二是agent 需要在心跳之间保留对话上下文的,也别用。
这两条其实指向同一件事:process 每次心跳都是把命令重新拉起来跑一遍,进程结束状态就散了。你要想让它”记得”上一轮聊到哪儿,只能自己在脚本里把上下文落到磁盘或数据库再读回来——那本质上是你在重新实现会话管理,而不是适配器给你的。
对应地,“When to Use” 那节给的三个场景都符合这个形状:跑一段调用 Paperclip API 的 Python 脚本、执行一个自定义 agent 循环、任何能被 shell 命令调起来的运行时。共同点是每次执行自成一体,不依赖上一次留下的进程内状态。
代价在转录:你只能看到裸 stdout
这是我认为选型时最该提前知道、又最容易被忽略的一点。
官方文档专门讲了”反馈粒度”(Feedback Granularity):每个适配器的 stdout 都会被流式送进运行日志并在 UI 里实时渲染,但你能看到多细,取决于这个适配器发出的事件流。文档把它分成三档:
| 档位 | 覆盖的适配器 | 你能看到什么 |
|---|---|---|
| 原生 ACP 引擎 | claude_local / codex_local / gemini_local 且 engine 设为 acp | 完整结构化事件流:会话身份、进度与上下文窗口用量、思考与回复的 token 增量、工具调用及其状态更新、停止原因、错误码与可重试性 |
| CLI 包装类 | claude_local、codex_local、cursor、opencode_local 等 | 解析各 CLI 自己的流式 JSON:助手文本、工具调用与结果、最终用量与成本摘要,粒度受限于 CLI 打印了什么 |
| 通用适配器 | process、http | 纯 stdout/stderr 文本行,没有结构化转录,只能看到原始输出 |
process 稳稳落在最低那一档。这意味着:运行卡了十分钟,你在界面上能依据的只有脚本自己打印了什么;出了问题要定位,也只能靠你在脚本里埋的日志。
由此引出一个很实际的建议:既然转录只有裸输出,那脚本的打印内容就是你唯一的可观测面。开始一个阶段打一行、调外部服务前后打一行、异常时把关键上下文打全,别只留一个堆栈。这不是 Paperclip 的要求,是这一档反馈粒度下的自然结论。
另外,UI 侧还有个配套机制:外部适配器可以自带一个 UI 解析器(UI Parser),告诉 Paperclip 的 Web UI 怎么渲染自己的 stdout;没有的话就走通用 shell 解析器。也就是说,如果你后来觉得裸输出实在不够看,把这套脚本包装成一个自带 UI 解析器的外部适配器,是官方留好的升级路径,路怎么走见 自己写一个适配器。
和 http、和自建适配器怎么分
文档在”如何选适配器”里给的判据很短,照抄如下:需要跑一段脚本或命令,用 process;需要调一个自定义的外部服务,用 http;需要编码类 agent,用 claude_local、codex_local、opencode_local、hermes_local,或者以外部插件方式装 droid_local;需要更多定制,就自己创建适配器或做成外部适配器插件。
所以边界其实是按”东西在哪儿跑”划的:代码跟 Paperclip 在同一台机器上、能用一条命令拉起来,就是 process 的活;agent 在别的地方跑、只暴露一个 HTTP 端点,那是 HTTP 适配器接入自建 Agent 的场景。至于内置适配器一共有哪几类、各自的类型键是什么,见 五类适配器分别怎么接。
什么时候别选它,以及文档没说的部分
先说不适用的情况,除了官方那两条劝退线之外,还有两类值得自己掂量:
- 希望在界面上看到工具调用逐步展开的。这需要结构化事件流,
process给不了,只能换到 ACP 档或 CLI 包装档的适配器。 - 希望适配器自动帮你算用量与成本的。总览里说适配器会”捕获 stdout、解析用量与成本数据”,但
process这一档官方文档并未说明它从裸输出里能解析出什么成本信息。别默认它会自动记账。
再说文档里确实留白的几处,接之前最好按自己的环境实测确认:
timeoutSec不填时的默认行为(是否有默认上限)——官方文档未说明。- 超时触发时子进程是被怎样终止的、是否有宽限期——官方文档未说明。
command是否经过 shell 解释(管道、&&、变量展开是否可用)——官方文档只写了”要执行的 shell 命令”,没有进一步说明解析方式。env与注入的 Paperclip 标准变量同名时的优先级——官方文档未说明。
这几条都属于”猜错了不会立刻报错、但会在某次长任务上咬你一口”的类型。稳妥的做法是把命令写得尽量朴素:一个可执行文件加参数,复杂逻辑放进脚本内部,不要在 command 字符串里堆 shell 技巧;超时显式写;退出码由脚本自己严格控制。
真正把它用顺的形态,大概是这样:一个自成一体、跑完就退出、退出码干净、日志打得够密的脚本。它不该记得上一轮,也不该指望编排层替它管状态。如果你发现自己开始在脚本里造会话存储,那说明选型该换了——官方那两条劝退线,就是给这个时候准备的。
延伸阅读
- 从头读起:Paperclip 是什么:一个自己不跑 Agent 的控制平面,怎么管住一整家 AI 公司
- 本专题共 40 篇,完整分组目录见专题页
- 给 Paperclip 自己写一个适配器:外部插件包的结构、执行契约与 UI 解析器
- Paperclip 里的 Agent 不是常驻进程:心跳、唤醒与一次运行链路
本文依据 Paperclip 官方仓库(github.com/paperclipai/paperclip,MIT 协议)的 docs/ 用户文档
与 doc/ 下的规范、运维与连接器手册整理,核对日 2026-08-17。
我们没有部署或运行过 Paperclip,因此不涉及界面外观与操作手感;
部分规范文档描述的是目标架构而非当前实现,文中已就地标注,不构成对实际行为的保证。
请以仓库最新内容为准。