最近看到开源项目 Cumora:它提供聊天、Agent、任务看板和日历,但真正吸引我的是 BYOA(Bring Your Own Agent)模式。服务端只负责协作和调度,Agent 则运行在自己的机器上,继续使用本地的 Codex 或 Claude。

这和懒猫微服的 LightOS 几乎是天然组合:LPK 承载稳定的 Web 与数据层,LightOS 承载需要工作目录、登录态和长期运行能力的 Agent。

这篇记录一次完整的移植和实测过程。

先划清边界

Cumora 有两条 Agent 运行路线:

  1. Cloud Managed:服务端动态创建 Kubernetes Pod。
  2. BYOA:用户自己的 Computer 运行 cumora agent computer

第一条依赖 Kubernetes、PVC、FUSE、设备插件和较高容器权限,并不适合直接塞进普通 LPK。我的目标因此很明确:只做 BYOA 版,不移植 Cloud Agent。

最终架构如下:

浏览器
  │ Lazycat SSO
  ▼
Cumora LPK
  ├─ React SPA + Express API + WebSocket
  ├─ PostgreSQL(持久化)
  ├─ Redis AOF(消息与在线状态)
  └─ uploads(本地附件)
           ▲
           │ .lzcapp 内网 + SSE
           │
LightOS: teec-dev
  └─ cumora agent computer
       ├─ Codex CLI
       └─ Claude CLI

这样做的好处是,模型登录态、代码仓库、工作目录和 Agent 记忆都留在 LightOS;Cumora LPK 不需要拿到模型 API Key,也不需要创建高权限 Pod。

LPK 里放了什么

测试包拆成三个服务:

  • cumora:Node.js 服务,同时提供 SPA、API 和 WebSocket。
  • postgres:保存用户、对话、Agent、任务和配置。
  • redis:负责广播、Presence 和唤醒队列,启用 AOF。

持久化目录分别绑定到:

/lzcapp/var/postgres
/lzcapp/var/redis
/lzcapp/var/uploads

登录也做了懒猫适配。浏览器经过懒猫应用入口后,Cumora 使用可信的 X-HC-User-ID 换取自己的 Session,因此打开页面即可免密登录。API 没有加入 public_path,BYOA daemon 走内网服务地址,并在配对后使用自己的设备 Token。

此外,我增加了 CUMORA_BYOA_ONLY=true 硬开关。即使以后误改套餐或 Agent 配置,调度器也会在调用 kubectl 前停止,不会意外进入 Cloud Agent 路径。

真正花时间的不是打包

源码能构建,不等于应用能稳定启动。这次主要遇到三个问题。

1. Docker Hub 镜像拉取卡住

Cumora 本体镜像已经打进 LPK,但 PostgreSQL 和 Redis 最初直接引用 Docker Hub。安装成功后,应用在首次启动阶段一直等待外部镜像。

解决方式是改用懒猫 Registry 中已经存在的固定镜像。这样既避开外网拉取,也让版本可重复。

2. PostgreSQL 初始化与时区

首次尝试使用的 PostgreSQL 镜像把系统时区写成了 PRC,后续启动时配置校验失败。最终换成懒猫 Registry 的 PostgreSQL 17 Alpine 镜像,并显式设置:

TZ=Asia/Shanghai

测试期间产生的失败初始化目录也先整体移走,再用干净数据目录重新初始化,避免拿半成品数据库继续试。

3. 服务启动存在竞争

depends_on 只保证容器启动顺序,不保证 PostgreSQL 已经能接受连接。实际日志先后出现过:

getaddrinfo EAI_AGAIN postgres
connect ECONNREFUSED postgres:5432

我扩展了数据库启动重试,把 DNS 暂时失败和连接拒绝都视为可恢复错误,使用指数退避重试。这样整套应用重启时,Cumora 会等待数据库就绪,而不是直接退出。

LightOS 如何接入 Cumora

这是最关键的一段。浏览器访问的是外部地址,但 LightOS daemon 应优先使用懒猫应用间的 .lzcapp 内网地址。

内网服务地址规则是:

http://<service>.<package-id>.lzcapp:<port>

本次应用的三个值是:

service    = cumora
package-id = cloud.lazycat.app.cumora.byoa.poc
port       = 5181

所以完整服务端地址为:

http://cumora.cloud.lazycat.app.cumora.byoa.poc.lzcapp:5181

先在 Cumora 页面进入 Computer 管理,点击添加 Computer,取得配对 Token。然后在 LightOS 终端执行:

npx cumora@latest agent computer \
  --pair PAIR_TOKEN_HERE \
  --server http://cumora.cloud.lazycat.app.cumora.byoa.poc.lzcapp:5181 \
  --engine codex

PAIR_TOKEN_HERE 替换为页面生成的 Token。这里最容易漏掉的就是 --server:不指定时,CLI 会使用 Cumora 默认服务端;在自托管环境里必须明确指向自己的 .lzcapp 地址。

配对成功后,建议把 daemon 安装成 LightOS 的用户服务:

npx cumora@latest agent computer \
  --install-service \
  --server http://cumora.cloud.lazycat.app.cumora.byoa.poc.lzcapp:5181

检查状态与日志:

npx cumora@latest agent computer --status
journalctl --user -u cumora -f

需要手动重启时:

systemctl --user restart cumora

LightOS 已开启用户服务常驻后,即使终端关闭,daemon 仍会继续运行;实例或服务重启后也会自动恢复。

我做了哪些真实验证

这次没有停在“页面能打开”。完整链路验证是:

  1. teec-dev 配对为 Cumora Computer。
  2. Atlas、Bram、Iris、Nova 四个 Agent 建立 SSE wake stream。
  3. 在页面给 Atlas 发送消息。
  4. Atlas 在 LightOS 上唤醒本地 Codex。
  5. Codex 通过 Cumora CLI 回复:“Cumora BYOA 链路正常”。

随后我分别重启了 cumora 用户服务和整套 LPK。Computer 自动恢复在线,四条 SSE 重新连接,之前的消息仍在,证明 systemd 常驻、PostgreSQL 持久化和内网连接都生效。

结论

BYOA 的核心不是“把 Agent 放到另一台机器”,而是把职责拆对:

  • LPK 负责稳定、可访问、可持久化的协作服务。
  • LightOS 负责有状态、可操作本地文件、能长期运行的 Agent。
  • .lzcapp 负责盒内直连,避免为 daemon 额外开放公网 API。

这套组合保留了本地 Agent 的能力和隐私边界,又获得了一个随时可访问的协作界面。至少对个人使用来说,LightOS 的确是很合适的 BYOA Computer。