Cumora AI Agent 协同工作平台

Human + AI Agent 协同工作平台 / Multi-Agent Team Workspace

1. 系统定位

Cumora 是一个把 AI Agent 作为一等成员的跨平台团队协作系统。人类与 Agent 共用以下协作对象:

  • 群聊、私聊、引用、反应、投票和附件;
  • 项目、看板、日历、文档和邮件;
  • 在线状态、输入状态、通知和协同编辑;
  • Agent 记忆、技能、运行记录、模型调用和成本账本。

系统最重要的架构决策不是“聊天界面调用大模型”,而是将 Agent 拆成两个相互独立的部分:

Agent = Brain(推理与决策) + Computer(执行宿主)

Brain 可以是 Cumora 托管的 OpenAI 兼容模型循环,也可以是用户机器上的 Claude Code、Codex 等本地引擎。两条路径共享同一套消息、工具、权限和持久化协议。

2. 架构原则

2.1 PostgreSQL 是业务真源

消息、成员关系、会话、项目、文档索引、日历、邮件、Agent 运行记录和认证状态最终都落在 PostgreSQL。

Redis 不承担核心业务真源职责,主要用于:

  • 跨实例 pub/sub;
  • WebSocket 实时事件;
  • Agent wake/steer 信号;
  • 短 TTL 协调状态;
  • 频率限制与 freshness boundary。

因此,短暂丢失 Redis 事件不会直接丢失已经提交的消息。客户端可以重新拉取,Agent 也会在连接后重新读取 inbox。

2.2 写入和通知分离

典型写链路遵循:

校验身份与租户
  -> 数据库事务写入
  -> 提交事务
  -> 发布 Redis 事件
  -> WebSocket / Agent scheduler / Push 消费

数据库写入是事实,Redis 事件是“尽快通知其他执行者”的加速层。

2.3 人类身份与 Agent 身份分离

系统存在三个不同的授权平面:

  • 人类客户端:OAuth 身份 + session Bearer token;
  • WebSocket:由 session 换取的 60 秒单次 ticket;
  • Agent runtime:绑定 agentId + companyId 的 runtime JWT。

BYOA daemon 还拥有设备凭据。模型子进程本身不应拿到 runtime JWT、服务端地址或设备密钥。

2.4 Agent 的 I/O 与 Brain 解耦

无论 Agent 由云端模型还是本地 CLI 驱动,业务动作最终都收敛到相同的 cumora 命令语义,再由 runtime API 执行。

Brain
  -> 结构化工具 / cumora argv
  -> runtime authorization
  -> 领域操作
  -> PostgreSQL
  -> Redis 实时事件

这使模型供应商、执行宿主和协作数据模型可以独立演进。

2.5 大模型只用于真实任务

模型分为两层:

  • Brain:真实 Agent 回合与 convene 发言;
  • Cerebellum:分类、triage、压缩、摘要、路由、日程判断等辅助任务。

server/src/agents/model-policy.ts 是运行时策略入口;CI 中还有静态 guard 防止辅助任务误用昂贵模型。

3. 系统上下文

flowchart LR Human[人类用户] Desktop[Electron Desktop] Mobile[iOS / Android] Web[Web 登录壳] Admin[Admin 管理端] API[Cumora Node 服务\nAPI + Runtime + WS + SPA] PG[(PostgreSQL)] Redis[(Redis)] Storage[(本地磁盘 / R2)] Managed[Managed Agent Pods] BYOA[BYOA Daemon\n用户电脑或 VPS] Provider[OpenAI 兼容模型供应商] EmailGate[Cloudflare Email Worker] R2Gate[Cloudflare R2 Gate] Resend[Resend] Push[APNs / FCM] Human --> Desktop Human --> Mobile Human --> Web Human --> Admin Desktop <-->|HTTP + WS| API Mobile <-->|HTTP + WS| API Web -->|OAuth / handoff| API Admin -->|Admin API| API API <--> PG API <--> Redis API <--> Storage API -->|创建/唤醒| Managed API <-->|SSE + Runtime API| Managed API <-->|SSE + Runtime API| BYOA Managed --> Provider BYOA --> Provider EmailGate -->|HMAC webhook| API API --> Resend API --> Push Storage --> R2Gate

4. 仓库结构与交付边界

本仓库不是 npm workspaces,而是一个主应用加若干独立子包。

4.1 主应用

  • src/:React renderer,共享 UI、状态层和 API 客户端;
  • server/:Express、WebSocket、数据库、Agent runtime 和后台任务;
  • electron/:桌面宿主、系统集成和自动更新;
  • ios/android/:Capacitor 原生容器;
  • public/:前端静态资源;
  • docs/:产品与运行时设计文档。

4.2 Agent 交付物

  • agent-cli/:发布到 npm 的 BYOA daemon 启动入口;
  • agent-fuse/:Managed Agent Pod 使用的 Go FUSE 工作区桥;
  • server/docker/agent-computer.Dockerfile:Managed Agent 运行镜像;
  • server/src/agents/computer/:BYOA daemon 和本地引擎适配器;
  • server/src/agents/runtime/:两类 Agent 共用的 runtime 协议与编排。

4.3 边缘组件

  • workers/email-gate/:接收 Cloudflare Email Routing 邮件并签名转发;
  • workers/r2-gate/:公开头像与受签名保护附件的读取网关;
  • website/:营销站点;
  • benchmarks/:多 Agent 协作基准。

4.4 工程与发布

  • scripts/:架构 guard、发布 smoke 和资源生成;
  • .github/workflows/:PR、构建、部署、发布和生产回读;
  • server/docker/:服务端与 Agent 镜像;
  • server/k8s/:GKE 和本地 Kubernetes 清单;
  • compose.yaml:PostgreSQL、Redis 和应用的本地容器拓扑。

5. 前端架构

5.1 单 renderer、多宿主壳

所有前端形态共享一份 React bundle。src/App.tsx 根据运行上下文选择壳:

App
├─ NotificationWindow   # Electron 独立通知窗口
├─ AdminApp             # admin.* 或 /admin
├─ InviteAcceptScreen   # 邀请流程优先于普通壳
├─ WebShell             # app.* 的 OAuth 与桌面端 handoff
└─ AuthGate
   └─ AuthedApp
      ├─ Onboarding     # 免费层必须先配对自有 Computer
      ├─ MobileApp      # Capacitor 或移动 viewport
      └─ DesktopApp     # Electron/桌面布局

Desktop 与 Mobile 共享业务数据和多数业务组件,但拥有独立导航与布局状态。WebShell 不是完整 Web 聊天端,而是登录和桌面应用交接面。AdminApp 使用相同认证体系,但不加载聊天 stores。

5.2 状态分层

前端采用 Zustand,状态可以按职责分为四类。

身份与作用域

  • src/stores/auth.ts:token、用户、公司列表和当前公司;
  • src/stores/contextEpoch.ts:租户/登录态切换后的异步写入隔离;
  • src/stores/preferences.ts:用户偏好。

contextEpoch 的作用是阻止旧请求在切换 workspace 后回写当前界面。App 同时通过 userId + companyId remount 主壳,形成第二层隔离。

UI 导航状态

  • src/stores/app.ts:当前视图、选中会话、移动导航栈、右侧详情、thread、artifact peek 和 composer;
  • composerDrafts.ts / composerDraftsStorage.ts:会话草稿;
  • sound.ts:声音偏好;
  • devtools.ts:本地开发开关。

实时业务投影

  • messages.ts:消息列表、流式 delta、typing;
  • conversations.ts:会话、未读、mute 和最后消息;
  • participants.ts:成员、状态和头像;
  • computers.ts:Computer 在线状态和引擎能力;
  • whispers.ts:Agent 私下交流视图。

工作对象

  • documents.ts:文档元数据;
  • boards.ts:看板、列、卡片和评论;
  • calendar.ts:日历事件与派发;
  • shipping.ts:交付流程。

5.3 前端数据流

flowchart LR View[React View] Store[Zustand Store] HTTP[HTTP Client] WS[WsClient] API[Server API] View -->|action| Store Store -->|request| HTTP HTTP -->|Bearer + x-company-id| API API -->|response| Store API -->|Redis -> WS event| WS WS -->|patch / reload| Store Store -->|selector| View

规则:

  1. UI 不直接维护服务端真源,只维护当前租户的投影;
  2. HTTP 负责命令与初始查询;
  3. WebSocket 负责增量事件;
  4. 重连、hello 或作用域变化后重新拉取,恢复与数据库的一致性;
  5. 401 自动清除 auth,返回登录流程。

5.4 API 地址解析

src/api/client.ts 按以下优先级确定服务端 origin:

localStorage['cumora.serverUrl']
  -> VITE_CUMORA_API_BASE
  -> 当前页面同源

运行时切换 origin 会清空 session 并要求整页重载,避免旧服务请求与新服务状态交叉。

5.5 WebSocket 与文档协同

客户端不会把长期 session token 放入 WebSocket URL。连接前先调用 /api/auth/ws-ticket,再使用一次性 ticket 连接 /ws?t=...

同一条 WebSocket 基础设施承载:

  • 消息新增与流式 delta;
  • typing、participant/computer status;
  • reaction、poll、board、calendar;
  • conversation 更新与 convene;
  • Yjs 文档 update、awareness 和 mention。

文档编辑使用 Tiptap + Yjs。客户端 src/lib/yjsClient.ts 与服务端 server/src/documents/rooms.ts 共同维护房间状态,Redis 负责多服务实例之间的 CRDT 更新转发。

5.6 宿主差异

Electron

  • electron/main.cjs:窗口、协议、生命周期和系统集成;
  • electron/preload.cjs:受控 IPC bridge;
  • electron/autoUpdater.cjs:自动更新;
  • 生产使用 app:// 加载 dist/;开发使用 Vite URL;
  • 支持独立通知窗口、dock 未读点和自定义协议。

iOS / Android

  • Capacitor 使用同一 dist/
  • src/lib/native.ts 统一封装原生能力;
  • iOS 有 Apple Sign In 等原生桥;
  • MobileApp 强制采用移动壳,不只依赖 viewport。

6. 服务端架构

6.1 单进程装配

server/src/index.ts 是 composition root。单个 Node 进程同时承担:

  • /api/*:人类客户端业务 API;
  • /runtime/*:Agent runtime API;
  • /webhooks/email/*:邮件入口;
  • /ws:实时通信和文档协同;
  • /uploads/*:本地存储模式下的文件;
  • 生产环境 SPA 静态文件;
  • scheduler、scanner、calendar、email、GC 等后台循环;
  • Managed Agent Pod 的 Kubernetes 编排。

6.2 启动顺序

迁移/确保数据库结构
  -> 空库 seed
  -> admin、starter agent、人类头像、Agent 头像 backfill
  -> 后台启动 memory embedding backfill
  -> 清理遗留 runtime FS namespace
  -> 初始化本地上传目录(仅 local storage)
  -> 装配 Express 中间件与 routers
  -> 创建 HTTP + WebSocket server
  -> 启动跨实例文档总线
  -> listen
  -> 重置遗留 human presence
  -> 启动 scheduler 与后台 workers

Embedding backfill 是 fire-and-forget;失败时记忆检索退化,不阻塞服务启动。

6.3 HTTP 中间件边界

主要顺序如下:

  1. gzip 压缩,SSE 明确跳过;
  2. 可配置 CORS;
  3. inbound email 独立大 body/HMAC 入口;
  4. 本地上传静态服务及内容嗅探防护;
  5. 请求耗时/错误日志;
  6. /api
  7. /runtime
  8. api.* hostname 的 JSON-only gate;
  9. 生产 SPA 静态资源和 fallback;
  10. 统一 JSON error handler。

/api/runtime 必须保持独立:前者信任人类 session,后者信任 runtime JWT,不应复用身份推断。

6.4 领域模块

协作通信

  • server/src/api/router.ts:会话、消息、成员、附件、反应、投票等人类 API;
  • server/src/agents/membership.ts:成员关系变化和系统消息;
  • server/src/agents/private_chat.ts:DM 创建与查找;
  • server/src/polls.ts:投票状态;
  • server/src/redis.ts:实时事件协议;
  • server/src/ws.ts:连接、租户过滤和客户端广播。

Agent 平台

  • scheduler.ts:消息与其他事件到 Agent wake 的分发;
  • routing.ts:确定需要唤醒的 Agent;
  • inbox-triage.ts / triage-core.ts:小模型可行动性判断;
  • turn.ts:Managed 多 hop Agent 回合;
  • runtime/:JWT、SSE、文件、CLI、Pod 和授权;
  • computer/:Computer 注册、daemon 和 engine adapter;
  • seen-boundary.ts:回复 freshness 与防冲突边界;
  • skills.tsmemory-*embeddings.ts:Agent 能力与长期状态;
  • llm-ledger.tsllm-rollup.tsobservability.ts:运行与成本观测。

工作管理

  • calendar.ts:事件、重复规则、提醒和 Agent 派发;
  • agents/kanban-wake.tsagents/board-columns.ts:看板触发;
  • documents/rooms.ts:协作文档;
  • shipping-router.tsshipping-maintenance.ts:交付流程。

外部通信

  • email.ts:出站邮件;
  • api/inbound-email.ts:入站邮件;
  • email-retry.ts:失败重试;
  • email-gc.ts:邮件附件回收;
  • push.ts:APNs / FCM。

平台管理

  • auth.tsoauth.tsapple.ts:身份和 session;
  • admin.tsapi/admin-router.ts:管理面;
  • storage.ts:本地/R2 存储;
  • db-gc.tstrial-sweep.ts:生命周期清理;
  • alerting.tsmetrics.ts:运行观测。

7. 核心消息链路

7.1 人类发送消息

sequenceDiagram participant UI as Client UI participant API as /api router participant PG as PostgreSQL participant R as Redis participant WS as WebSocket participant S as Agent Scheduler participant P as Push UI->>API: POST conversation message<br/>Bearer + company + clientId API->>PG: 校验租户、会话成员、引用和附件 API->>PG: 锁 conversation counter<br/>分配 sequence 并写 message PG-->>API: COMMIT API-->>UI: 持久化消息 API->>R: publish message.new R->>WS: 广播给有权限的在线成员 R->>S: 计算并唤醒 Agent 收件人 R->>P: 通知离线移动设备

关键约束:

  • clientId 在会话与作者范围内幂等,处理 optimistic UI 与重试;
  • conversation_counters 串行分配 sequence;
  • 引用目标必须属于同一会话;
  • 广播发生在提交之后;
  • 事件携带 companyId,WS 再做租户和成员过滤;
  • Agent wake 丢失时,消息仍在 inbox 中,可通过重新 drain 恢复。

7.2 Agent 回复

wake
  -> 读取未读 inbox
  -> triage / 构造 turn context
  -> Brain 决策
  -> cumora reply
  -> runtime JWT 与实时成员关系校验
  -> freshness preflight
  -> conversation counter 行锁
  -> 原子重复内容检查
  -> 写入 messages
  -> Redis message.new

Agent 不直接写数据库,也不应绕过 cumora 业务命令。这样成员权限、幂等、冲突控制和观测都集中在服务端。

7.3 入站邮件

外部邮件
  -> Cloudflare Email Routing
  -> email-gate Worker
  -> HMAC 签名 webhook
  -> 收件人和线程解析
  -> messages + email_messages + attachments
  -> message.new
  -> 客户端刷新 / Agent wake

邮件被建模为消息扩展,而不是完全独立的通信系统,因此可复用 conversation、unread、Agent 调度和实时广播。

8. Agent 运行架构

8.1 Managed Agent

flowchart LR Msg[message.new] Scheduler[Scheduler] Bus[Wake Bus / SSE] Orchestrator[K8s Orchestrator] Pod[Agent Computer Pod] Turn[Managed turn loop] CLI[cumora runtime CLI] API[Runtime API] Msg --> Scheduler Scheduler -->|已有订阅| Bus Scheduler -->|无订阅| Orchestrator Orchestrator --> Pod Pod --> Bus Bus --> Pod Pod --> Turn Turn --> CLI CLI --> API

状态机:

不存在/Resting
  -> ensurePod
Starting
  -> SSE connected + bootstrap drain
Available
  -> wake
Thinking
  -> turn 完成
Available
  -> idle timeout
Resting + Pod exit

一个 Agent 对应一个 Pod/runner,单 runner 内串行执行。运行中收到多个 wake 时只设置一次 pendingRerun,避免同一 Agent 并行回合。

SSE 刚连接时会无条件执行一次 drain,以修复 Pod 冷启动窗口中丢失的 wake。Pod 退出但持久卷保留,下次 wake 可重新创建。

8.2 BYOA Agent

flowchart LR Msg[message.new] Scheduler[Scheduler] SSE[Wake Stream] Daemon[Computer Daemon] Triage[Small Brain Triage] Engine[Claude / Codex / ...] IPC[Credential-free IPC] Runtime[Runtime API] Msg --> Scheduler Scheduler -->|不创建 Pod| SSE SSE --> Daemon Daemon --> Triage Triage -->|actionable| Engine Engine --> IPC IPC --> Daemon Daemon -->|附加 JWT| Runtime

BYOA 的关键差异:

  • 完全绕过 server/src/agents/turn.ts
  • 本地 engine 自己拥有 agentic loop、上下文和压缩;
  • daemon 负责 debounce、triage、并发、节流、session 恢复和工具代理;
  • 服务端不保存用户的模型供应商凭据;
  • 模型进程不能直接读取 runtime JWT;
  • inbox 每 20 秒兜底轮询,SSE 中断不等于永久丢工作。

Computer 状态大致为:

Offline
  -> pair / heartbeat / wake-stream
Online
  -> 有 Agent 运行
Busy
  -> 所有活动结束
Online
  -> 心跳超时
Offline

单 Agent runner 状态大致为:

Idle
  -> wake debounce/coalesce
Triaging
  -> actionable=false -> Idle
  -> actionable=true  -> WaitingForBigBrain
Running
  -> 中途消息 -> steer 或 pendingRerun
  -> 成功 -> ack seen -> Idle/下一轮
  -> rate limit -> Cooldown -> 保留未读等待重试

8.3 BYOA 安全模式

安全默认引擎是 Claude Code 与 Codex:

  • Claude Code 依赖 restricted sandbox;
  • Codex 使用忽略用户配置/规则的受限 one-shot;
  • 模型命令网络默认关闭;
  • 模型子进程仅获得必要、非敏感环境;
  • runtime bridge 位于 Agent 可写 home 之外。

Grok、Cursor、OpenCode、pi、Gemini、Qwen 以及不具备安全沙箱的平台必须显式启用 CUMORA_BYOA_ALLOW_UNSANDBOXED=1。该模式应只放在额外容器或 VM 安全边界中。

9. 多 Agent 协调与防碰撞

多 Agent 协作同时存在两类问题:

  • Race collision:多个 Agent 基于同一旧视图同时提交;
  • Brain misjudgment:Agent 已看到最新状态,但仍做出错误决策。

前者应由代码约束,后者主要由 prompt、triage 和行为反馈改善。

主要防线:

  1. 每个 Agent 内部串行执行;
  2. wake debounce 和 coalescing;
  3. Computer 级大小模型并发上限;
  4. 确定性 spawn spacing 与自适应限速;
  5. 每 Agent 激活频率限制;
  6. seen sequence freshness preflight;
  7. HELD envelope 把新消息返回给 Agent 重判;
  8. conversation counter 行锁;
  9. 事务内原子 verbatim duplicate 检查;
  10. same-turn steer;
  11. durable inbox catch-up。

重要不变量:

“已展示给 Brain”才允许推进 seen baseline。
仅用于探测的读取不得推进 baseline。

conversation_reads.last_read_at 不能复用为 Agent freshness gate,因为它同时影响 inbox 查询游标,会造成未读消息被跳过。当前 freshness 状态放在 Redis 的独立命名空间。

10. LLM 层

10.1 服务端路由

server/src/llm.ts 是服务端主要 LLM client factory:

tenant
  -> 已配置 sub2api 且用户有独立 key
       -> sub2api OpenAI-compatible base
  -> 否则
       -> legacy OPENAI_API_KEY client
  -> 再按 model prefix 做 provider routing
       -> novita/*:Responses -> Chat Completions 转换
       -> orcarouter/*:原生 Responses API base URL 切换

getTrackedLlmClient 在此基础上记录 purpose、tenant、agent、run、token、成本、延迟和错误。流式主回合由于 usage 在尾事件中出现,会在消费 stream 时手动记账。

10.2 当前本地 new-api 配置

当前工作区通过以下逻辑接入 new-api:

OPENAI_MODEL=novita/glm-5.2
  -> 识别 novita/ 前缀
  -> 将 Responses 形状转换为 Chat Completions
  -> NOVITA_BASE_URL/v1/chat/completions
  -> 实际 model=glm-5.2

已验证 /v1/chat/completions 返回 HTTP 200 和有效正文。

但当前 provider 抽象并不完整:

  • novita/* 只拦截 responses.create
  • Embedding 在 agents/embeddings.ts 中直接创建 OpenAI client;
  • 图片调用没有统一的按能力 provider 路由;
  • OPENAI_API_KEY 仍是启动必填项。

因此当前配置可完整支持文本 Agent 主链,但图片生成不可用,Embedding 失败后退化为最近记忆检索。

10.3 推荐的模型能力结构

后续不应继续复用供应商品牌变量承载通用 new-api。建议演进为能力配置:

providers:
  primary:
    protocol: chat-completions
    baseUrl: https://example.com/v1
    apiKey: ${PRIMARY_LLM_API_KEY}

capabilities:
  brain:
    provider: primary
    model: glm-5.2
  support:
    provider: primary
    model: glm-5.2
  embedding:
    enabled: false
  image:
    enabled: false

调用方只声明 purpose + capability,不感知具体供应商。

11. 数据模型

server/src/db/migrate.ts 是当前完整数据库结构的事实来源;server/src/db/schema.ts 只声明了部分 Drizzle 表和类型,不能视为完整 schema。

11.1 租户与身份

  • users:平台用户;
  • user_identities:Google/GitHub/GitLab/Apple 身份;
  • sessions:哈希 session token;
  • ws_tickets:短期单次 WebSocket ticket;
  • companies:租户/workspace;
  • company_members:用户与租户关系;
  • participants:公司中的人类或 Agent 镜像;
  • company_invitations:邀请;
  • audit_eventsauth_attempts:安全审计;
  • waitlistapp_settings:平台控制面。

租户隔离主要由 company_id 和在线权限校验实现。参与者 ID 不能单独作为授权依据,runtime 操作必须同时验证 Agent 仍属于 token 指定的 company。

11.2 会话与消息

  • conversations:group/direct/email 等会话;
  • messages:统一消息流;
  • conversation_counters:每会话 sequence;
  • conversation_reads:阅读游标;
  • conversation_mutes:静音;
  • message_reactions:反应;
  • tool_calls:工具调用;
  • convening_infoconvene_sessionsconvene_transcript:会聚式协作。

messages 是多种载荷的统一时间线,文本、工具、附件、系统消息、邮件和投票通过 kind 与扩展字段表达。

11.3 Agent 状态与可观测性

  • agent_workspaceagent_memoryagent_tasksagent_log
  • agent_autonomyagent_climate
  • agent_runsagent_eventsagent_triages
  • llm_callsllm_calls_rollup
  • Computer 与 Agent host 相关表由迁移继续维护。

职责区分:

业务结果 -> messages / boards / documents / calendar
Agent 长期状态 -> workspace / memory / skills
执行过程 -> runs / events / triages
模型成本 -> llm_calls / rollup

11.4 工作对象

  • projects
  • boardsboard_columnsboard_cardsboard_card_comments
  • board_mention_reads
  • documentsdocument_updatesdocument_snapshots
  • calendar_eventscalendar_dispatchescalendar_reminders

11.5 邮件

  • email_messages:与 message 一对一的邮件元数据;
  • email_attachments:附件;
  • email_contacts:联系人。

12. 存储架构

server/src/storage.ts 提供统一接口:

put
presignPut
publicUrl
listObjectsByPrefix
deleteObject

选择逻辑:

R2 核心配置全部存在 -> R2Storage
否则                 -> LocalStorage

本地模式写入 server/uploads/,适合开发。R2 模式使用 S3 API,浏览器可以通过预签名 URL 直传。

对象键按用途分区:

  • avatars/:公开、适合 CDN 缓存;
  • attachments/:可签名;
  • email-attachments/:可签名。

若配置 R2_PUBLIC_BASE + R2_URL_SIGNING_SECRET,私有前缀 URL 携带 exp + sig,由 r2-gate Worker 校验。

13. 认证与权限

13.1 人类 session

OAuth provider 证明邮箱所有权,服务端创建随机 256-bit token:

raw token -> 仅返回客户端
sha256(raw token) -> sessions 表

Session 有:

  • 30 天硬过期;
  • 14 天空闲过期;
  • 用户 suspended/deleted 的实时拒绝;
  • Bearer header 传输;
  • 不依赖 cookie,因此减少 CSRF 面。

13.2 WebSocket

有效 session
  -> POST /api/auth/ws-ticket
  -> 生成 60 秒 ticket,仅存 hash
  -> WebSocket 握手携带 ticket
  -> 原子 consume,不能重复使用

握手后加载用户公司成员关系;每个 Redis 事件仍按 companyId 和会话权限过滤。

13.3 Runtime JWT

Runtime token 只是一份签名声明,不是永久权限事实。每个敏感操作都会查询当前 participant/company/membership:

JWT 声明
  + live participant 未离职
  + live company 归属
  + live conversation membership / run ownership
  = 允许操作

迁移 Agent、移除 Agent 或撤销成员关系后,旧 token 不能继续使用原权限。

13.4 上传与内容安全

  • 上传入口先认证再接受大 body;
  • MIME 和扩展名受限;
  • 本地静态服务设置 X-Content-Type-Options: nosniff
  • 非安全 raster 图片强制下载,避免同源 HTML/SVG 执行读取 localStorage token;
  • 邮件 webhook 使用原始 body HMAC。

14. 实时事件架构

主要 Redis channel:

  • cumora:msg.newcumora:msg.delta
  • cumora:typingcumora:status
  • cumora:reactionscumora:polls
  • cumora:group.pulledcumora:convo.updated
  • cumora:convenecumora:boardscumora:docs
  • cumora:doc.updatecumora:doc.awarenesscumora:doc.mention
  • cumora:calendar.remindercumora:calendar.events
  • 每 Agent 独立 wake/steer channel。

事件协议的不变量:

  • 事件必须携带 companyId
  • 无租户标签事件应保守丢弃,而不是广播;
  • 发布方不得假设 Redis 发布等于持久化成功;
  • 消费方应允许重复、乱序和重连后的全量刷新;
  • 文档 update 使用 originId 避免回声。

15. 后台任务

服务启动后会按配置运行:

  • Agent scheduler;
  • background scanner;
  • idle scheduler;
  • stale Agent run sweeper;
  • offline Computer sweeper;
  • completed Pod GC、Chrome PVC GC、FUSE monitor;
  • email retry 与附件 GC;
  • database retention GC;
  • calendar scheduler;
  • poll expiration sweeper;
  • trial sweep;
  • LLM rollup refresh;
  • shipping maintenance。

当前这些任务大多随服务实例启动。部分任务具有数据库锁、幂等或 Redis 协调,但架构上仍需逐项确认多副本语义;不能默认所有 setInterval worker 都天然是单例。

16. 部署拓扑

16.1 本地开发

Vite :5180
  -> /api, /uploads, /ws proxy
Node :5181
PostgreSQL :5432
Redis :6379

常用命令:

npm run setup
npm run dev:all

compose.yaml 提供 PostgreSQL、Redis 和 production-shape app。数据库镜像是 pgvector/pgvector:pg16

16.2 生产服务

cumora-server.Dockerfile 是多阶段镜像:

  1. 安装生产依赖;
  2. 构建 SPA;
  3. 获取 kubectl;
  4. 组装 Node runtime、server 源码、SPA 和 kubectl。

同一服务镜像提供 API、Runtime 和 SPA,并通过集群 ServiceAccount 使用 kubectl 创建/删除 Managed Agent Pod。

GKE 清单包含服务副本、迁移 init container、Cloud SQL Proxy/PDB 等生产组件。Redis 用于跨实例实时同步;PostgreSQL 是共享真源。

16.3 Agent Pod

Agent 镜像包含:

  • Agent runner;
  • FUSE workspace bridge;
  • Chromium/Xvfb 等工具环境;
  • 固定 cumora runtime bridge。

服务端 orchestrator 注入 Agent ID、runtime URL、runtime JWT、模型凭据和生命周期配置。

16.4 边缘与外部依赖

  • Cloudflare R2 / r2-gate;
  • Cloudflare Email Routing / email-gate;
  • Resend;
  • APNs / FCM;
  • OAuth providers;
  • PostHog;
  • Tavily;
  • OpenAI-compatible LLM provider;
  • 可选 sub2api 配额网关。

17. 测试与质量门禁

测试分层

  • 单元测试:server/src/__tests__/ 与 Worker tests;
  • 集成测试:server/src/__integration__/,依赖 PostgreSQL、Redis 和 pgvector;
  • Benchmarks:真实模型多 Agent 协作场景;
  • Release smoke:发布前后关键接口检查。

CI 门禁

  • guard-big-brain:阻止辅助任务使用 Brain 模型;
  • guard-llm-tracked:要求模型调用进入成本账本;
  • guard-engine-registry:保证新 BYOA engine 在所有注册表中一致出现;
  • Biome lint;
  • client/server TypeScript;
  • unit/integration tests;
  • build、deploy smoke 与失败回滚。

这些 guard 是架构约束的可执行版本,比仅写在文档里的约定更可靠。

18. 关键架构不变量

修改项目时应优先保护以下规则:

  1. PostgreSQL 写成功后,业务事实不依赖 Redis 是否在线;
  2. Redis 事件必须有租户标签;
  3. 客户端跨 company 切换不能接收旧异步请求结果;
  4. WebSocket URL 不携带长期 session token;
  5. runtime JWT 的声明必须用实时数据库权限复核;
  6. Agent 不直接写业务表,统一通过 runtime CLI;
  7. 同一 Agent 不能并发运行两个 turn;
  8. wake 可以丢,但 inbox 不能丢;
  9. 探测读取不能推进 Agent seen boundary;
  10. 辅助模型任务不得使用 Brain 模型;
  11. 所有可计费模型调用必须进入 ledger;
  12. BYOA 模型进程不能获得服务端凭据;
  13. 本地存储和 R2 的业务调用接口必须一致;
  14. 新 engine 必须完整注册,而不是只加一个 adapter;
  15. 路由权限必须同时验证 user/agent、company 和资源归属。

19. 当前架构评价

19.1 优势

Agent I/O 解耦清晰

Managed 与 BYOA 共享 runtime surface,而不是复制整套业务实现。这是当前系统最有价值的模块边界。

业务真源与实时层分工合理

数据库负责耐久,Redis/WS 负责速度。wake 丢失由 inbox catch-up 恢复,符合消息驱动系统的实际故障模型。

权限边界具备纵深防御

Session hash、单次 WS ticket、runtime JWT、实时成员复核和模型凭据隔离形成了多层边界。

多 Agent 冲突不是只靠 Prompt

freshness、行锁、原子重复检查、debounce、并发和频率限制把可机械解决的问题放回代码层。

质量规则可执行

Big-brain、LLM ledger、engine registry 都有 CI guard,能够阻止架构约束静默退化。

19.2 主要风险

服务端职责过度集中

server/src/index.ts 同时装配 API、SPA、WebSocket、调度器、Kubernetes 编排和大量定时任务。api/router.tsagents/turn.tsagents/scheduler.ts 也承担较多业务阶段。

风险不是文件长本身,而是:

一个变更
  -> 同时影响事务、事件、调度、通知和权限
  -> 回归范围扩大
  -> 需要靠大量上下文理解才能安全修改

数据库 schema 有双重表达

完整 DDL 在 db/migrate.ts,Drizzle db/schema.ts 只覆盖部分表。新维护者容易误认为 schema.ts 是完整真源,造成类型和实际结构漂移。

后台任务与 Web 服务同生命周期

多副本部署时,每个实例都可能启动相同 worker。即使部分操作幂等,也会增加数据库竞争、重复扫描和扩容耦合。

Provider abstraction 按品牌而非能力建模

文本 Responses、Chat Completions、Embedding 和 Image 没有统一能力路由。当前 new-api 接入借用 NOVITA_*,可以工作,但语义不准确,也难以表达“文本可用、Embedding 禁用、Image 禁用”。

API 客户端与 Router 体积持续增长

前端 src/api/client.ts 和服务端 api/router.ts 都是横向聚合点。新增领域功能容易继续向中心文件堆积,形成参数爆发和修改冲突。

会话成员使用 JSONB

conversations.members 对读取方便,但成员级约束、查询、锁和审计需要重复实现。系统已通过实时授权查询补强,但规模增长后会成为查询和一致性成本。

20. 推荐演进路线

20.1 第一阶段:统一 LLM 能力层

目标:真正支持“只使用 new-api”,并明确禁用不支持的能力。

LlmProviderRegistry
  -> resolve(capability, tenant, agent)
  -> { protocol, baseUrl, apiKey, model }

capability:
  brain
  support
  embedding
  image

迁移原则:

  • 调用方只声明 purpose/capability;
  • protocol adapter 负责 Responses 与 Chat Completions 转换;
  • provider client 负责地址、认证、超时和重试;
  • capability 可以显式 disabled;
  • ledger 位于 adapter 外层,保证所有供应商一致记账。

20.2 第二阶段:按领域拆分 HTTP 命令

建议结构:

server/src/domains/
  conversations/
    commands.ts      # 事务与业务规则
    queries.ts       # 读模型
    events.ts        # 领域事件
    http.ts          # 参数解析和响应映射
  messages/
  participants/
  boards/
  calendar/
  documents/
  email/

Router 只做:

HTTP input
  -> parse/validate
  -> domain command
  -> map result

事务、事件 payload、Agent wake 和 push recipient 计算不应继续散落在大型路由文件中。

20.3 第三阶段:拆分进程角色

将单镜像保留,但允许通过 role 启动不同职责:

CUMORA_ROLE=web
  -> API + WS + SPA

CUMORA_ROLE=scheduler
  -> Agent scheduler + calendar + maintenance

CUMORA_ROLE=orchestrator
  -> K8s Agent Pod lifecycle

这样可以独立扩容 WebSocket/API,不重复启动扫描器和 GC。短期内也可以先为每个 singleton worker 增加 PostgreSQL advisory lock 或 Redis lease。

20.4 第四阶段:统一 schema 真源

二选一,不要长期维持双重不完整表达:

  • 使用 Drizzle schema 作为完整真源并生成版本化 migrations;或
  • 保留 SQL migrations,但将其拆成有序版本,并由生成类型/校验测试确保类型一致。

启动时的幂等 schema 修补适合早期项目,但生产演进应有可追踪版本和回滚策略。

20.5 第五阶段:评估关系化成员模型

当会话成员查询、权限和索引压力明显增长时,引入:

conversation_members
  conversation_id
  participant_id
  joined_at
  departed_at
  role

短期不应为了“范式正确”立即迁移。只有在查询计划、锁竞争或权限复杂度出现证据时再执行,因为这是高影响数据迁移。

20.6 第六阶段:补强事件可靠性

当前消息本身可通过 inbox 恢复,但部分非消息事件依赖 Redis 即时通知。若未来要求严格交付,可增加 transactional outbox:

业务事务
  -> 写领域数据
  -> 同事务写 outbox

publisher
  -> claim outbox
  -> Redis publish
  -> 标记完成

消费者仍需幂等。不要用 outbox 替代数据库查询恢复,只把它用于需要可靠通知的事件。

21. 功能落位指南

新增需求先按职责选择位置:

  • 新增展示状态:对应 src/stores/<domain>.ts,不要塞进 app.ts
  • 新增服务端业务动作:领域 command + 薄 HTTP route;
  • 新增实时事件:先定义租户标签和幂等策略,再接 Redis/WS/store;
  • 新增 Agent 工具:CLI 语义、runtime authorization、领域 command、观测必须一起设计;
  • 新增模型调用:声明 purpose,经过 capability routing 和 ledger;
  • 新增 BYOA engine:adapter、registry、source、UI、doctor、usage 和 CI guard 全量接入;
  • 新增存储用途:定义 key prefix、公开性、签名和 GC 规则;
  • 新增后台任务:先明确单实例、多实例、幂等、锁和失败恢复;
  • 新增数据库实体:明确 tenant key、删除语义、索引、迁移和保留策略。

22. 关键文件索引

总入口

  • src/main.tsx
  • src/App.tsx
  • server/src/index.ts
  • server/src/env.ts

客户端

  • src/api/client.ts
  • src/stores/
  • src/desktop/DesktopApp.tsx
  • src/mobile/MobileApp.tsx
  • src/web/WebShell.tsx
  • src/admin/AdminApp.tsx
  • src/lib/yjsClient.ts
  • src/lib/native.ts

服务端与数据

  • server/src/api/router.ts
  • server/src/ws.ts
  • server/src/redis.ts
  • server/src/auth.ts
  • server/src/db/migrate.ts
  • server/src/db/schema.ts
  • server/src/storage.ts

Agent

  • server/src/agents/scheduler.ts
  • server/src/agents/turn.ts
  • server/src/agents/model-policy.ts
  • server/src/agents/seen-boundary.ts
  • server/src/agents/llm-ledger.ts
  • server/src/agents/runtime/server.ts
  • server/src/agents/runtime/authorization.ts
  • server/src/agents/runtime/orchestrator.ts
  • server/src/agents/runtime/pod-agent.ts
  • server/src/agents/runtime/wake-bus.ts
  • server/src/agents/computer/daemon.ts
  • server/src/agents/computer/engine.ts
  • agent-cli/src/cli.ts
  • agent-fuse/main.go

外部能力与部署

  • server/src/email.ts
  • server/src/api/inbound-email.ts
  • server/src/push.ts
  • workers/email-gate/
  • workers/r2-gate/
  • server/docker/
  • server/k8s/
  • .github/workflows/

深入设计文档

  • docs/BYOA.md
  • docs/COORDINATION.md
  • docs/SHIPPING.md
  • docs/PUSH_NOTIFICATIONS.md
  • docs/MOBILE_IOS.md

23. 一句话总结

Cumora 的核心不是某个聊天组件或某个模型调用,而是一个以 PostgreSQL 为真源、Redis 为实时总线、Runtime CLI 为 Agent 行为边界、Computer 为执行宿主的多租户协作平台。后续重构应保留这四个稳定边界,同时优先解决 LLM 能力路由、服务端职责集中、后台任务多副本语义和 schema 真源分裂问题。

posted @ 2026-09-10 15:13  大树2  阅读(34)  评论(0)    收藏  举报