Paperclip 进程适配器怎么用:把任意 shell 命令接进 Agent 编排,代价是什么

2026-08-17

如果你手里的 agent 不是 Claude Code、不是 Codex,而是一段自己用 Python 攒起来的循环,接到 Paperclip 上的第一反应通常是:那我得先给它写个适配器吧?其实不一定。Paperclip 内置了一个叫 process 的适配器,它做的事情只有一件——把你配置的那条 shell 命令当子进程拉起来,跑完为止。

官方文档对它的定位写得很直白:执行任意 shell 命令,适合简单脚本、一次性任务,以及构建在自定义框架上的 agent。换句话说,只要你的运行时能被一条命令调起来,它就能接。

但这条路不是白走的。process 换来了接入成本几乎为零,代价落在两个地方:一是官方明确说了有两种场景别用它,二是它在运行转录里能给你看的东西比 CLI 类适配器少一截。下面按文档把这两面都摊开。

它在整条链路里处在哪一环

先把上下文补齐。按官方对适配器的总体说明,适配器是 Paperclip 编排层与 agent 运行时之间的桥,每种适配器知道怎么调起一类 AI agent 并把结果收回来。一次心跳(heartbeat)触发时,Paperclip 做四件事:查出这个 agent 的 adapterTypeadapterConfig,带着执行上下文调用适配器的 execute(),由适配器去 spawn 或调用 agent 运行时,最后适配器捕获 stdout、解析用量与成本数据、返回一个结构化结果。

process 就是这套流程里最薄的一层实现。文档给出的执行步骤是:

  1. Paperclip 把配置的命令作为子进程 spawn 起来;
  2. 注入标准的 Paperclip 环境变量(文档举了 PAPERCLIP_AGENT_IDPAPERCLIP_API_KEY,并写了 etc.,说明不止这两个);
  3. 进程一路跑到结束;
  4. 退出码决定这次运行算成功还是失败。

第四条是最需要提前想清楚的一条。你的脚本内部无论怎么判断”这次任务其实没做成”,只要它最后 exit 0,在 Paperclip 这边就是一次成功的运行。反过来,脚本里一个未捕获的异常让解释器以非零码退出,这次运行就被记成失败。所以接 process 之前,先把脚本的退出码语义捋一遍——这是它和编排层之间唯一的成败信号。想弄清楚这个信号后续会驱动什么,可以顺着 Agent 在 Paperclip 里怎么被驱动 往下看。

第二条同样值得展开。环境变量是注入进来的,意味着你的脚本不需要自己去哪里读密钥:拿 PAPERCLIP_API_KEY 就能回头调 Paperclip 的 API,拿 PAPERCLIP_AGENT_ID 就知道自己是以哪个 agent 的身份在跑。文档里那个”跑一段 Python 脚本”的例子,说明也正是这句——脚本用注入的环境变量去认证 Paperclip API 并完成工作。

四个配置项,一个必填

adapterConfig 一共就四个字段:

字段类型是否必填说明
commandstring要执行的 shell 命令
cwdstring工作目录
envobject环境变量
timeoutSecnumber进程超时时间

官方给的完整示例是这样一个跑 Python 脚本的 agent:

{
  "adapterType": "process",
  "adapterConfig": {
    "command": "python3 /path/to/agent.py",
    "cwd": "/path/to/workspace",
    "timeoutSec": 300
  }
}

四个字段里,cwdtimeoutSec 是最容易被跳过、又最容易在半夜出事的两个。cwd 不写,脚本相对路径读文件的行为就取决于 Paperclip 进程自己在哪儿,示例里明确把它指到了工作区目录,照做即可。timeoutSec 不写,文档没有说明默认值是多少、是否等于不限时——所以你自己写的循环如果有卡住的可能(等一个不会返回的 HTTP 请求、等一个不会有人回的输入),最好显式给个上限,别指望默认值兜底。

env 是对象形式的追加环境变量。注意它是在标准 Paperclip 变量之外你自己要加的那些(模型密钥、内部服务地址之类),文档没有说明它与注入变量同名时谁覆盖谁,稳妥做法是别用 PAPERCLIP_ 前缀去撞名。至于哪些变量属于 Paperclip 部署层面的配置,那是另一套清单,和这里的 env 不是一回事。

官方直接写出来的两条劝退线

文档难得地专门列了一节 “When Not to Use”,两条:

一是需要跨多次运行保持会话持久化的,别用 process,改用 claude_localcodex_local

二是agent 需要在心跳之间保留对话上下文的,也别用。

这两条其实指向同一件事:process 每次心跳都是把命令重新拉起来跑一遍,进程结束状态就散了。你要想让它”记得”上一轮聊到哪儿,只能自己在脚本里把上下文落到磁盘或数据库再读回来——那本质上是你在重新实现会话管理,而不是适配器给你的。

对应地,“When to Use” 那节给的三个场景都符合这个形状:跑一段调用 Paperclip API 的 Python 脚本、执行一个自定义 agent 循环、任何能被 shell 命令调起来的运行时。共同点是每次执行自成一体,不依赖上一次留下的进程内状态。

代价在转录:你只能看到裸 stdout

这是我认为选型时最该提前知道、又最容易被忽略的一点。

官方文档专门讲了”反馈粒度”(Feedback Granularity):每个适配器的 stdout 都会被流式送进运行日志并在 UI 里实时渲染,但你能看到多细,取决于这个适配器发出的事件流。文档把它分成三档:

档位覆盖的适配器你能看到什么
原生 ACP 引擎claude_local / codex_local / gemini_localengine 设为 acp完整结构化事件流:会话身份、进度与上下文窗口用量、思考与回复的 token 增量、工具调用及其状态更新、停止原因、错误码与可重试性
CLI 包装类claude_localcodex_localcursoropencode_local解析各 CLI 自己的流式 JSON:助手文本、工具调用与结果、最终用量与成本摘要,粒度受限于 CLI 打印了什么
通用适配器processhttp纯 stdout/stderr 文本行,没有结构化转录,只能看到原始输出

process 稳稳落在最低那一档。这意味着:运行卡了十分钟,你在界面上能依据的只有脚本自己打印了什么;出了问题要定位,也只能靠你在脚本里埋的日志。

由此引出一个很实际的建议:既然转录只有裸输出,那脚本的打印内容就是你唯一的可观测面。开始一个阶段打一行、调外部服务前后打一行、异常时把关键上下文打全,别只留一个堆栈。这不是 Paperclip 的要求,是这一档反馈粒度下的自然结论。

另外,UI 侧还有个配套机制:外部适配器可以自带一个 UI 解析器(UI Parser),告诉 Paperclip 的 Web UI 怎么渲染自己的 stdout;没有的话就走通用 shell 解析器。也就是说,如果你后来觉得裸输出实在不够看,把这套脚本包装成一个自带 UI 解析器的外部适配器,是官方留好的升级路径,路怎么走见 自己写一个适配器

和 http、和自建适配器怎么分

文档在”如何选适配器”里给的判据很短,照抄如下:需要跑一段脚本或命令,用 process;需要调一个自定义的外部服务,用 http;需要编码类 agent,用 claude_localcodex_localopencode_localhermes_local,或者以外部插件方式装 droid_local;需要更多定制,就自己创建适配器或做成外部适配器插件。

所以边界其实是按”东西在哪儿跑”划的:代码跟 Paperclip 在同一台机器上、能用一条命令拉起来,就是 process 的活;agent 在别的地方跑、只暴露一个 HTTP 端点,那是 HTTP 适配器接入自建 Agent 的场景。至于内置适配器一共有哪几类、各自的类型键是什么,见 五类适配器分别怎么接

什么时候别选它,以及文档没说的部分

先说不适用的情况,除了官方那两条劝退线之外,还有两类值得自己掂量:

  • 希望在界面上看到工具调用逐步展开的。这需要结构化事件流,process 给不了,只能换到 ACP 档或 CLI 包装档的适配器。
  • 希望适配器自动帮你算用量与成本的。总览里说适配器会”捕获 stdout、解析用量与成本数据”,但 process 这一档官方文档并未说明它从裸输出里能解析出什么成本信息。别默认它会自动记账。

再说文档里确实留白的几处,接之前最好按自己的环境实测确认:

  • timeoutSec 不填时的默认行为(是否有默认上限)——官方文档未说明。
  • 超时触发时子进程是被怎样终止的、是否有宽限期——官方文档未说明。
  • command 是否经过 shell 解释(管道、&&、变量展开是否可用)——官方文档只写了”要执行的 shell 命令”,没有进一步说明解析方式。
  • env 与注入的 Paperclip 标准变量同名时的优先级——官方文档未说明。

这几条都属于”猜错了不会立刻报错、但会在某次长任务上咬你一口”的类型。稳妥的做法是把命令写得尽量朴素:一个可执行文件加参数,复杂逻辑放进脚本内部,不要在 command 字符串里堆 shell 技巧;超时显式写;退出码由脚本自己严格控制。

真正把它用顺的形态,大概是这样:一个自成一体、跑完就退出、退出码干净、日志打得够密的脚本。它不该记得上一轮,也不该指望编排层替它管状态。如果你发现自己开始在脚本里造会话存储,那说明选型该换了——官方那两条劝退线,就是给这个时候准备的。

延伸阅读


本文依据 Paperclip 官方仓库(github.com/paperclipai/paperclip,MIT 协议)的 docs/ 用户文档 与 doc/ 下的规范、运维与连接器手册整理,核对日 2026-08-17。 我们没有部署或运行过 Paperclip,因此不涉及界面外观与操作手感; 部分规范文档描述的是目标架构而非当前实现,文中已就地标注,不构成对实际行为的保证。 请以仓库最新内容为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。