跳转至正文
ColliePWA

05/Documentation

持久 agent(实验性)

在手机上显示你自己的持久 agent:快速入门、collie durable attach 与 sources.json、三个动词、token、Collie 保留的文件以及版本

实验性功能。 底层协议、SDK 和此页面可能会在后续的次要版本中发生变动,或该功能可能会被移除。目前仅 Collie 自身的示例支持该协议。退出 说明了决定其是否保留的条件。如果你未挂载 durable agent,Collie 的外观和工作方式与以往一致。

如果你使用 Pi Durable 或任何支持该协议的程序构建了 durable agent,Collie 可以在手机上展示它。无需任何 UI 代码,它即可获得审批提示、历史记录和聊天视图。

durable agent 是 session 的一个来源,其每个对话都是一个 session。术语说明见 术语表。Collie 不保存模型密钥:durable agent 使用其自己的 API 密钥或 ChatGPT 登录凭据,且 Anthropic 订阅不能通过第三方框架使用。

快速入门

一个十行的 durable agent、一个 collie durable attach,以及手机上的一张问题卡片。

你需要在本机运行 Collie 1.22.0 或更高版本,并配对手机(安装)。

durable agent 运行在 Linux 或 macOS 上,需要 Node 22.19 或更高版本,或 Bun 1.4 或更高版本。在 durable agent 的目录中安装 SDK:

npm install @colliepwa/durable-sdk@experimental
# or
bun add @colliepwa/durable-sdk@experimental

该软件包在 npm dist-tag experimental 下发布,绝非 latest,并遵循 ADR 0104 中的退出策略:如果 durable 线路废弃,该软件包即被弃用(ADR 0105)。

SDK 的 Pi Durable 入口已通过 Pi Durable 1.1.0 和 1.2.0 测试。Node 22.19 是 Pi Durable 1.2.0 的最低版本要求。

将此 durable agent 保存为 hello.mjs。它只有十行:

import { openCollie } from "@colliepwa/durable-sdk";

const socket = process.env.COLLIE_DURABLE_SOCKET ?? `${process.env.XDG_RUNTIME_DIR}/hello/app.sock`;
const collie = await openCollie({ socket, hostName: "hello", caps: { asks: true } });
const pane = collie.workspace("hello", { title: "Hello" }).tab("main", { title: "Questions" }).pane("deploy", { title: "Deploy", harness: "hello" });
collie.ready();
console.log(`listening ${collie.address}`);
const options = [{ id: "yes", label: "Yes" }, { id: "no", label: "No" }];
const answer = await pane.ask({ kind: "choice", toolCallId: "deploy-1", itemId: "deploy-1", title: "Deploy to staging?", options });
console.log(`answer: ${answer.optionIds[0]}`);
await collie.close();

使用 node hello.mjs 或 bun hello.mjs 运行它。它会输出 listening /run/user/1000/hello/app.sock 并等待。同一个 durable agent 在 Collie 的克隆中对应 examples/durable-member/hello.ts。

在 Node 上运行时,它会先多输出一行:Node 无法读取谁连接到了 durable agent 的套接字,因此属于你用户的任何进程都可以回答其问题。Linux 上的 Bun 可以读取,并会拒绝来自 durable agent 自身或其启动进程的回答。

在 macOS 上,请使用 Node 运行它。macOS 上的 Bun 需要 callerCheck: "off",而此 durable agent 未设置该项。macOS 没有 XDG_RUNTIME_DIR,因此请先将 COLLIE_DURABLE_SOCKET 设置为绝对路径。

在第二个终端中,将其挂载到你的 Collie,然后重启以便 Collie 读取新配置项:

collie durable attach hello --socket /run/user/1000/hello/app.sock
collie restart

使用 durable agent 输出的路径。在第一个命令中添加 --restart 可以在同一步骤中完成重启。--restart 会重启 Collie 桥接:你的手机会在几秒内重新连接,终端 session 保持运行。

在手机上打开 Collie。「Durable agents」部分会显示一行:hello,带有 Experimental 标记,并有一个待处理的问题。点击该行:它会打开 durable agent 的对话列表,其中对话 Deploy 正等待回答 "Deploy to staging?"。打开它并点击 Yes 或 No。durable agent 输出你的回答并退出。

要编写自己的 durable agent,请阅读 durable SDK 和 durable 线路。

挂载 durable agent

每个 durable agent 对应 sources.json 中的一个配置项,由 collie durable attach 写入。

collie durable attach desk --socket /run/user/1000/desk/app.sock
export EDGE_TOKEN=your-32-byte-random-token-here
collie durable attach edge --url https://edge.example.com --token-env EDGE_TOKEN
collie restart

在 Windows 上仅支持 HTTPS 形式:attach --socket 会以退出码 2 拒绝执行,并提示使用 --url 和 --token-env,因为私有套接字需要 Unix 用户 ID。

sources.json 位于 .env 旁的配置目录中:若由 Herdr 管理安装,则位于 Herdr 插件配置目录(~/.config/herdr/plugins/config/herdr.collie/),否则位于 ~/.config/collie/。attach 会输出其写入的路径。上面的两条命令会写入此文件:

[
  { "name": "desk", "kind": "durable", "socket": "/run/user/1000/desk/app.sock" },
  {
    "name": "edge",
    "kind": "durable",
    "url": "https://edge.example.com",
    "token": "your-32-byte-random-token-here"
  }
]
字段包含的内容
namedurable agent 在手机上显示的名称及其日志目录的名称。1 到 32 个字符:小写字母、数字和连字符,以字母或数字开头。在文件中唯一
kinddurable,当前唯一支持的类型
socketdurable agent 在本机上的私有 Unix 套接字绝对路径。不能与 url 一起使用
url部署在 TLS 之后的 durable agent 地址。不能与 socket 一起使用,且始终与 token 一起使用
tokenCollie 发送给 url 的 Bearer 令牌。仅与 url 一起使用(Token)

bridge 会在启动时读取该文件,因此条目在重启后生效。你的终端外观和工作方式与之前一致,位于 durable agent 旁边。

Collie 对该文件的校验很严格,因为被静默跳过的 durable agent 将永远不会显示:

  • 表中未命名的字段会导致 bridge 停止运行。 错误的 kind、违反规则的名称、重复使用的名称、同时存在 socket 和 url、两者皆无,或者缺少 token 的 url 也会导致同样的结果。
  • 损坏的文件会导致 bridge 在启动时停止并输出一行提示。 该行以 [bridge] FATAL: 开头,并指明条目和字段。collie logs 会显示该信息。attach 和 detach 也会拒绝覆盖无法读取的文件,因此拼写错误绝不会影响其他条目。
  • 文件权限保持为 0600 模式。 attach 会以这种方式写入。当文件权限更宽松时,bridge 会在启动时收紧权限并在日志中记录。
  • 名称不能与你的终端会话相同。 在大多数安装中为 default,在纯 durable 实例上,default 是其自身 durable agent 的名称。bridge 会在启动时停止并指出该条目。
  • 面对 Herdr 会话,durable agent 优先保留其名称。 当 Herdr 会话与 durable agent 同名时,手机端会显示 durable agent,隐藏该 Herdr 会话,并在日志中记录一次。

attach 按照以下规则检查套接字路径、URL 和令牌。手动写入的条目会在 bridge 访问 durable agent 时进行检查:若违反规则,届时会将 durable agent 显示为不可达并给出原因。

访问 durable agent 的两种方式

专用 socket带 token 的 https
durable agent 的运行位置本机,以你的用户身份运行任何你部署它的地方
防范机制该 socket 归你所有,模式为 0600仅凭 token 认证
谁可以作为你的手机端属于你当前用户的任何进程持有该 token 的任何人
Collie 的轮询频率手机端查看时每 1 秒一次,其他情况每 3 秒一次手机端查看时每 2 秒一次,其他情况每 10 秒一次

套接字传输适用于 Linux 和 macOS。在 Windows 上请使用 URL 和令牌。套接字路径必须是绝对路径且最多 103 字节。请将其保留在 $XDG_RUNTIME_DIR 下,不要放在 /tmp 下:Linux 上的 Collie 服务使用私有 /tmp 运行。

Collie 仅在整条路径均为私有时才会连接到套接字,因为该套接字不携带令牌。该套接字归你所有,没有用户组或其他权限。其所在文件夹归你所有且权限为 0700。路径的任何部分都不能是符号链接,且其上层文件夹除 /tmp 等带有 sticky 位的目录外,均不可被用户组或其他用户写入。SDK 会在套接字所在文件夹不存在时以 0700 模式创建它。

URL 必须以 https:// 开头。唯一的例外是同一台机器上的 durable agent 可以使用带端口的 http://127.0.0.1:<port> 或 http://localhost:<port>。Collie 会拒绝任何其他 http:// 地址,因为令牌将未经加密直接在网络中传输。

注意。 在 https 上,令牌是唯一的访问屏障。任何拥有该令牌的人都可以读取所有对话并回答所有问题,且宿主机可以看到这些文本。以此方式访问的 durable agent 应仅提供 Allow 和 Deny,绝不能提供 always-allow。

Durable agent 安全性 说明了 durable agent 可以执行的操作以及 Collie 记录的内容。

通过 https 进行的每次轮询都是一次可能由宿主服务商计费的请求,返回“未发生更改”的应答也计入其中。在没有打开手机端时,空闲轮询每 10 秒运行一次,因此 Collie 每天会发起 8,640 次请求。请查看你的宿主服务商套餐对此类用量的限制。

位于 URL 后面的 durable agent 必须支持 Collie 传输协议并检查 bearer 令牌。原生的 Pi Durable 演示版两者都不支持,因此 Collie 无法直接挂载它。

Collie 会轮询托管的 durable agent。在手机端处于查看状态时每 2 秒请求一次,否则每 10 秒请求一次。每次轮询都会唤醒处于休眠状态的 Durable Object,因此在 Collie 查看期间托管对象会保持唤醒状态。该协议不使用 WebSocket,因此 Durable Object 的休眠和 WebSocket 特性在此处不起作用。

Cloudflare 示例目前搁置,不属于发布版本的一部分。它位于 experiments/durable-member-cloudflare/。

三个动词

attach、detach 和 list 用于修改或读取 sources.json,除非收到要求,否则它们不会重启任何内容。

collie durable attach <name> --socket <path> [--restart]
collie durable attach <name> --url <url> --token-env <VAR> [--restart]
collie durable detach <name> [--restart]
collie durable list
动词参数输出内容退出代码
attach <name>--socket <path>,或者带 --token-env <VAR> 的 --url <url>。--restart。flag 的值可以是下一个词,也可以跟在 = 后面added <name> in <path>,或者对于已存在的名称使用 updated <name> in <path>。然后是 restart to apply: collie restart 以及要在手机上打开的 URL0 已写入。2 用法错误,或者路径、URL、token 违反了规则。1 文件无法读取。指定 --restart 时,返回重启的退出代码
detach <name>--restartremoved <name> from <path>,然后 restart to apply: collie restart0 已删除。2 用法错误。3 不存在具有该名称的 durable agent。1 文件无法读取。指定 --restart 时,返回重启的退出代码
list无每个 durable agent 一行:其名称、socket <path> 或 url <url> (token set),以及其状态。文件为空或缺失时显示 no durable agents attached (<path>)0。1 文件无法读取

collie durable 未指定动词或使用了未知动词时,打印用法并退出,代码为 2。指定 --restart 时,attach 和 detach 会执行与 collie restart 相同的重启操作。

list 会向正在运行的 bridge 查询每个条目,并最多等待 3 秒:

状态含义
loaded正在运行的 bridge 已加载此条目。这是 bridge 的反馈,而非 durable agent 本身的反馈:durable agent 可能仍无法访问。collie doctor 会查询 durable agent
not loaded (restart to apply)bridge 在你挂载之前已启动。运行 collie restart
refused (<status>)bridge 返回了此 HTTP 状态码而非状态
bridge not answering3 秒内未收到响应:Collie 已停止或正忙。collie status 可显示它是否正在运行

当 durable agent 未出现时,运行 collie doctor,它会指明失败的检查项。常见原因有三个:条目尚未加载(重启)、socket 不是私有的(socket-not-private),以及崩溃留下的残留 socket 文件(connection refused:将其删除并启动 durable agent)。Durable agent 故障排查 涵盖 durable agent 的各种情况。问题排查 涵盖 Collie 整体。

Token

url 条目携带其 token,且 token 是唯一的访问关卡。

openssl rand -hex 32

结果为 64 个十六进制字符,高于 32 字节的下限。

将 token 设置为 32 个或更多随机字节(如上所述),并为 durable agent 提供相同的值。attach 会从 --token-env 指定的变量中读取它,因此它不会出现在任何命令行和 shell 历史记录中。运行 attach 时必须已设置该变量。

attach 会将该值以纯文本形式复制到 sources.json 中。因此该文件与 token 具有相同的机密性:Collie 将其权限保持为 0600,配置文件夹的备份或同步都会携带该 token。Collie 会在每次请求中发送 token,且绝不会打印它:list 显示为 (token set)。

规则
Token去除首尾空白后为 32 到 4096 字节,不含空格的可打印 ASCII 字符
URLhttps://,或者带端口的 http://127.0.0.1:<port> 或 http://localhost:<port>。最多 2048 字节。不包含用户名、密码、查询参数或分段。仅限 durable agent 的基础路径:Collie 会自行添加 /session/v1

token 不会过期。若要更改它,请使用相同的名称和新 token 重新运行 attach,然后重启:

export EDGE_TOKEN=your-new-32-byte-random-token-here
collie durable attach edge --url https://edge.example.com --token-env EDGE_TOKEN
collie restart

attach 会打印 updated edge。在重启 Collie 之前,请先为 durable agent 提供新 token,否则手机端会提示 durable agent 拒绝了该 token。在轮换期间,durable agent 可以接受第二个 token,以确保双方都不会被锁定。停放的 Cloudflare 示例接收 COLLIE_TOKEN,并且在设置该项时也接收 COLLIE_TOKEN_NEXT(ADR 0103)。在 durable agent 上将新 token 设置为 COLLIE_TOKEN_NEXT,重新运行 attach 并重启 Collie,然后将该值移动到 COLLIE_TOKEN 并取消设置 COLLIE_TOKEN_NEXT。

单个 Collie 中仅有一个 durable agent

没有终端的实例(例如 Windows 主机或托管的 durable agent)可以从其 .env 驱动一个 durable agent。

# a durable agent on this machine, as you
COLLIE_MUX=durable
COLLIE_DURABLE_SOCKET=/run/user/1000/durable/session.sock

# or a durable agent you deployed behind TLS
COLLIE_MUX=durable
COLLIE_DURABLE_URL=https://durable-member.example.com
COLLIE_DURABLE_TOKEN=your-32-byte-random-token-here

此类实例将其 durable agent 命名为 default,该 durable agent 即为其主会话。sources.json 中的条目仍会以其他名称在其旁运行。

.envconfig.toml包含的内容
COLLIE_MUX=durable[mux] mux使该实例仅运行 durable agent
COLLIE_DURABLE_SOCKET[mux] durable_socketdurable agent 的专用套接字。不可与 URL 同时配置
COLLIE_DURABLE_URL[mux] durable_urlTLS 保护下的 durable agent 地址。必须附带令牌
COLLIE_DURABLE_TOKEN[mux] durable_token用于 URL 的 Bearer 令牌。保存该令牌的文件权限必须为 0600

配置套接字,或配置带有令牌的 URL,二者不可兼得。若同时配置两者、配置了 URL 却无令牌、或配置了令牌却无 URL,均属于配置错误:Collie 会启动,在日志中记录该错误,并将 durable agent 显示为不可达及对应原因。

旧版本会直接将纯 durable 实例的日志保存在 <state>/durable/ 中。自此版本首次启动起,它们会一次性迁移到 durable/default/,并在日志中记录一行。

要更换令牌,请将新令牌部署到 durable agent,在 .env 中设置该令牌,然后运行 collie restart。纯 durable 实例会使用其主机名加入 crew,因此当它与 lead 共享主机时,请使用 collie crew join <lead-address> --label durable 为其指定名称。

若要暂时关闭 sources.json 中的所有 durable agent,请在 .env 中设置 COLLIE_PROGRAMS=off 并运行 collie restart。随后 bridge 会忽略该文件,记录一行日志,并以不包含 durable agent 的安装状态运行,因此手机上不会显示 Durable agents 部分。collie doctor 和 collie durable list 会提示 durable agent 已关闭。该文件保持原样:取消设置该变量并重启即可重新启用 durable agent。该变量不会影响上述旧版环境配置形式。

Collie 保存 durable agent 数据的位置

durable agent 保存其对话记录。Collie 在其状态文件夹中保存缓存、回执及其推送记忆。

状态文件夹为 ~/.local/state/collie/,若设置了 COLLIE_STATE_DIR 则为该路径。<program> 为 sources.json 中的名称,或为 default。每个 durable agent 的文件夹权限为 0700,每个日志的权限为 0600。

文件包含的内容保留时长
durable/<program>/<id>.jsonlChat 对某个活动对话的副本,其中 <id> 为 base64url 编码的对话 ID超过 8 MiB 后截断保留最新的 2000 条消息。所有日志合计保持在 256 MiB 以下:优先删除最早的对话,并在日志中记录一行
durable/<program>/<id>.cursor.jsonCollie 停止读取的位置,以便重启后在此处继续与对应的日志相同
answer-receipts.json手机发送的每条回答:其请求 ID、所回答的 ID 以及结果。不包含键入的文本一周,至多保留最新的 1000 条。仍在传输中的回答至少保留一天
control-receipts.json每次 send、steer、stop 和 belt 操作:其请求 ID 及结果。文本仅保留摘要同上
starter-receipts.json每次点击 starter 及其打开的对话同上
durable-asks.json已推送的问题记录(按主机、durable agent 和工具调用区分),以确保重启后不会重复推送问题关闭后保留一小时。至多 2000 条

缓存是 Chat 能快速打开并在 durable agent 响应缓慢时仍可正常工作的原因。已关闭的对话始终直接从 durable agent 读取,绝不读取自缓存。

durable agent 仅保留长对话的最新部分。若在 Collie 读取前它已丢弃了对话开头,Chat 会在对应位置显示一行文字:"Older messages were left out: the durable agent no longer keeps them." 手机端未丢失任何内容:Collie 从未获取过这些消息。

手机显示的内容

仪表盘上的 Durable agents 部分,以及每个 durable agent 对应的对话列表。

每个 durable agent 在 Durable agents 部分各占一行,包含其名称、Experimental 徽标、运行时以及运行在本地还是别处。右侧统计其未结对话数,并以需要您处理的颜色高亮等待您操作的对话。有待处理问题时,该部分会移至您的 space 之上。正在启动或不可达的 durable agent 会在计数位置显示状态说明。令牌被拒和传输协议版本被拒在手机上有专门的提示;collie doctor 会列出所有其他原因。

点击某一行可打开该 durable agent 的对话列表,类似于即时通讯应用的聊天列表。等待您处理的排在最前,其次是其他未结对话,最后是带有 Closed 标签的已结对话。未结对话在 Chat 中打开,已结对话以只读方式打开。durable agent 在其握手信息中声明其支持的功能(asks、starters、send、steer、abort、rename、close、actions、history、reopen),手机仅针对其声明的功能渲染对应控件。durable agent 没有屏幕,因此没有 Terminal 视图,也不支持按键输入。

向 durable agent 发送消息时,queued 标签是 Collie 自身对轮次的判定。Collie 不会显示 Pi 收件箱中排队的内容。休眠直至到期时间的任务会显示为空闲或工作中,绝不显示为等待中。详见 会话。

问题卡片会标明 durable agent 的名称,并在 durable agent 设置了超时时显示剩余时间。在 durable agent 中做出决定的问题或超时的问题会在聊天中留下一行。手机上的持久 agent 用图表展示了其中的每一种情况。

通过 bridge 进行 Send 和 Ask

durable agent 没有屏幕,因此 Collie 不会为其读取屏幕。bridge 改为读取自身的状态。durable agent 获得与终端窗格相同的 Send 和 Ask。

  • 状态。 轮次来自其窗格状态。问题来自其最早的未决 ask。输入框始终为空,因为 durable agent 没有输入框。手机在获取每个窗格时都会收到该状态,结构与终端窗格相同。
  • 一次发送。 POST /api/send 从手机获取请求 id。bridge 将请求写入磁盘并锁定会话。然后,针对它所读取的轮次,将文本向 durable agent 发送一次。它的应答即为凭据,且收据中会标明它所获取的请求 id。在轮次进行期间,durable agent 会保留该文本以在该轮次之后使用,收据显示为 queued。
  • 结果。 发送的结束状态为 sent、带原因的 refused 或 unknown。unknown 表示 durable agent 未表明已接收该文本。在重新发送前请先查看聊天。当 durable agent 将发送内容写入会话时,该发送变为 effective。其 user 项会标明该次发送的请求 id。只要有任何未决问题(即使是 Collie 无法读取的问题),普通发送就会被拒绝。回答该问题,或使用 Steer 或 Stop。在你查看之后才开始的轮次会拒绝此次发送,并且没有任何内容会送达 durable agent。
  • 一个问题。 POST /api/ask/<id> 回答它。该 ask id 仅供单次使用。回答携带你所看到问题的哈希。bridge 将其与 durable agent 当前列出的问题进行比对。当两者不一致时,任何内容都不会送达 durable agent。回复会显示当前状态的问题。回答通过其自身的 ask id 发送给 durable agent。绝不会发送任何密钥。收据显示为 channel: program 并保留该哈希。
  • 卡片上保留的内容。 部分问题无法通过单次点击完成,因此状态将其显示为 Collie 无法读取的问题(modal: unreadable)。你在其专属卡片上回答它们。- 文本问题保留在 durable agent 自身的卡片上,因为回答是你输入的一行内容。- 多项选择保留在 durable agent 自身的卡片上,因为回答包含多个选项。- 表单保留在 durable agent 自身的卡片上,因为回答是一组字段。- “始终允许”绝不是契约选项。其授权在该问题之后依然有效。因此 durable agent 的卡片会先要求进行第二次点击,通过 /api/ask 的单次点击不得跳过该点击。
  • 计数器。 collie doctor 在一行中统计这些读取和结果:program。

在此机器上,手机使用这些路由。普通发送走 /api/send,点击问题卡片走 /api/ask。编辑器在大拇指旁以 durable agent 的原话告知结果。当问题阻塞发送时,会显示一个跳转至其卡片的按钮。窗格的“最近发送”列表显示每张收据,排队的发送会注明状态。Steer 和 Stop 保持在会话自身的路由上,因为 steer 不属于 Send Protocol。crew 成员上的 durable agent 在进行所有写入时均保持使用会话自身的路由。每个路由都会在文本送达 durable agent 之前检查轮次。对单个会话的所有写入共享同一个锁。发送运行时,Steer 和卡片回答会因繁忙而被拒绝。Stop 会等待该锁,最多等到其时间限制,因此挂起的发送绝不会将 Stop 挡在外面。

版本

Collie 和 durable agent 在 hello 握手时协商传输协议版本,目前只有一个:版本 1。

Collie 支持 1..1。当版本 2 发布时,Collie 将在其后的一个次要版本中继续接受版本 1,再随后的次要版本则会弃用它。目前没有旧版本需要兼容。

当两个范围没有交集时,Collie 不再从 durable agent 读取任何内容。其日志会为每个拒绝的范围记录一行,例如 [durable] The durable agent speaks wire v2, this Collie speaks v1. Update Collie, or the agent's SDK. 手机上 durable agent 的主页会在警告框中显示相同的句子,其在仪表盘上的对应行会显示“版本不匹配”而非“无法访问”。collie doctor 在其 durable-hello 行显示两个范围:incompatible: the durable agent speaks 2..3, this Collie speaks 1..1。每当 durable agent 启动一次,Collie 也会向其发送一个带有自身版本的 hello,以便 durable agent 能在其侧记录该不匹配。请更新较旧的一方。

退出

仅当本仓库之外有人使用时,该特性才会保留。

到 1.24.0 版本发布时,必须满足以下两个条件之一:存在支持该传输协议的公共仓库,或者存在关于 durable agent 的外部 issue 或 discussion。如果两者都不存在,durable 相关的代码将移出正式发布并进入 experiments/,且此页面将被移除(ADR 0104)。移除时,sources.json 条目将在一个次要版本内被忽略并输出一行日志,socket 仍可用于你的 durable agent,npm 包将被废弃而非删除(ADR 0105)。

在 GitHub 上编辑本页

The rest of the documentation