Files
codex-app-server/docs/cloud-architecture.md
T

248 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 云端架构设计(第三阶段)
日期:2026-10-26。范围是用户 2026-10-26 01:06 列出的 8 项:
远程沙箱、云端派发、GitHub 提 PR 和 CI、Slack 触发、推送通知、
多用户协作和权限、跨线程记忆和项目知识库、模型 fallback 和
真实 token 成本统计。
先说一句总的:这 8 项不是 host.py 再加几个命令能长出来的,
它们共用一个前提——**一个常驻的控制面**。没有控制面,派发、
多用户、推送、GitHub 回调都无处落脚。所以下面先定控制面,
再按依赖关系分 4 个子阶段,每项都写清楚:复用现有 host.py
的什么、还缺什么外部条件、风险和诚实边界。
## 总架构
```
CLI / Web / Slack / GitHub webhook
│ (HTTPS, 带用户身份)
▼
API 网关 ── 认证 (OAuth / API key) ── 权限 (RBAC, 见 §6)
│
▼
控制面:任务队列 + 状态库 (Postgres / SQLite 起步)
├─ 派发器:选空闲 worker,按优先级、依赖、预算派发
├─ 记忆/KB 服务:线程摘要 + 项目索引 (§7)
├─ 计费服务:模型 fallback + 成本统计 (§8)
└─ 通知服务:任务完成/失败/评审请求 → Slack/推送 (§5)
│
▼
Worker 池(每个 worker 跑一个 host.py + 一个 codex app-server)
│
▼
沙箱(每任务独立,见 §1)→ git worktree / 容器 → 产出分支
│
▼
合并门:自动验测 → GitHub PR + CI (§3) → 合并 / 回滚
```
**复用原则**:控制面不重写 agent 逻辑。worker 就是现在的
host.py:dispatch / run / goal / review / merge / review-comment /
fix-review / doctor / failures 都已实测可用,控制面只负责
“谁可以派、派给谁、状态在哪、结果通知谁”。重写 host 等于
把已验证的 19 个测试扔掉。
**常驻前提(诚实边界)**:这个 VM 会被平台定时回收,
WeChat 桥和 host daemon 都是前车之鉴。控制面跑在这台 VM
上只能算“演示”,生产要一台常驻机器(用户自己的 Mac/
服务器)或一个托管服务(Fly.io / Render / 云服务器)。
Muse cron 只能做定时检查,不能替代控制面。
## 1. 远程沙箱
目标:每个任务在隔离环境里跑,跑坏了不污染宿主、不偷
别的任务或用户的密钥。
- 现状:codex 自带沙箱(workspace-write / read-only,
bubblewrap),host.py 已实测 read-only 确实挡写、审批
deny 确实挡工具。这是**本地沙箱**,不是远程沙箱。
- 远程沙箱的三种实现,按成本递增:
1. 同机容器:每任务一个容器(Docker),worktree 挂载进
容器,网络默认断、只放行模型 API。**本 VM 没有 Docker,
这条路要先装 Docker 或换机器**。
2. 云容器:每任务起一个 Fly.io / 云服务器容器,跑完即毁。
隔离最好,成本按任务时长算,需要云账号和预算上限。
3. 专用 worker 机器:每个 worker 一台轻量 VM,任务结束
快照/重建。隔离和成本介于两者之间。
- 安全硬规则(不管选哪种):沙箱里不放宿主密钥,模型
key 只走 worker 的环境变量且不进日志;沙箱只能写自己的
worktree;网络 egress 白名单(模型 API + git 远端);
任务超时强制 kill;产出只以 git 分支形式出来,不直接
写主仓。
## 2. 云端派发
目标:任务不用在用户电脑上跑,提交后控制面排队、派发、
重试、取消,状态随时可查。
- 队列:任务 = {id, 项目, 提示, 模型优先级, 依赖, 优先级,
预算, 幂等键}。幂等键防重复提交(Slack 重发、GitHub
webhook 重发都只建一个任务)。
- 状态机:queued → running → completed / failed / interrupted
/ merged / merge-failed,和 host.py 现有状态对齐;
worker 心跳丢失超过阈值 → 标 interrupted,可续跑(已有的
recover 逻辑直接复用,thread 在 CODEX_HOME 里不丢)。
- 派发策略:先依赖拓扑(已有的 topo_sort),再优先级,
再预算(max_daily_turns + 成本上限,见 §8),worker
空闲才派,不超发。
- 取消:用户取消 → worker 收到 turn/interrupt → 状态
interrupted,分支和 worktree 保留,不静默丢。
## 3. GitHub 提 PR 和 CI
目标:任务做完不是只合到本地主分支,而是走标准 PR 流程,
CI 过了才合。
- 现状有利:本机有 `git` 和 `gh`,host.py 的 merge 已有
自动验测 + 回滚(本地 unittest),这套逻辑可以直接当
PR 的“本地预检”。
- 流程:任务分支 → push 到远端 → `gh pr create`(标题、
任务描述、评审评论自动带上)→ CI 跑
`.github/workflows/host-verify.yml`(host.py 生成,跑
unittest)→ `gh pr checks` 轮询 → 绿了才 `gh pr merge`,
红了把 CI 日志贴回任务并标 merge-failed,不强行合。
- 还需要用户给的:GitHub 仓库远端、一个 PAT 或 OAuth
授权(走 Secure Vault,绝不写进文件或聊天记录);
PR 评论触发 fix-review 靠 GitHub webhook 回调到控制面
(需要控制面有公网地址,本地 VM 演示时可用 webhook
转发工具顶一下,生产不行)。
- 边界:CI 是 GitHub Actions 的,不是 host.py 自己跑的;
host.py 只负责生成 workflow、读 checks 结果、按结果
合或不合。
## 4. Slack 触发
目标:在 Slack 里就能派任务、查状态、收完成通知,不用
开终端。
- 触发方式(从易到难):Slash 命令 `/codex <任务>` 派发;
频道里 @bot 查状态;交互按钮“批准合并 / 丢弃”直接调
控制面的 merge / discard(按钮点击要带用户身份和权限
校验,不能谁点都算数)。
- 安全:Slack 签名密钥验签(防伪造请求),Slack 用户
ID 映射到 host 用户(见 §6),频道权限 = 项目权限,
私有项目不在公开频道里回贴 diff。
- 还需要用户给的:一个 Slack workspace 管理员装 Slack App,
签名密钥和 Bot Token 走 Secure Vault;控制面要有公网
回调地址(同 GitHub webhook 的前提)。
## 5. 推送通知
目标:任务完成、失败、需要评审时,用户手机上能收到,
不用一直盯着终端。
- 事件(只发状态变化,不每轮刷屏,和本地看门狗的通知
纪律一致):completed、failed、goal-unmet、merge-failed、
review-requested(有新评审评论)。
- 通道(按接入成本排):Slack DM/频道(和 §4 同一套)、
Webhook(通用)、邮件、Web Push / APNs / FCM(真手机
推送,需要应用证书和用户订阅,成本最高)。
- 现实一点的起点:对本用户,Muse 聊天通知 + Slack 已能
覆盖“手机收到”;真 APNs/FCM 是产品级才需要的最后
一步,不要一开始就做。
- 用户偏好:每用户可设“即时 / 摘要 / 静默”,失败和
merge-failed 默认即时,completed 默认可摘要。
## 6. 多用户协作和权限
目标:多人用一个控制面,互相看不到不该看的项目,审批
和合并有据可查。
- 认证:OAuth(GitHub / Google 登录,推荐 GitHub,因为
和 §3 同一套身份)或每用户 API key;会话用 JWT,
短期有效 + 刷新。
- 权限(RBAC,每项目独立):
- owner:管成员、管计费、删项目;
- maintainer:派任务、批准合并、改配置;
- contributor:派任务、写评审评论;
- viewer:只读任务、diff、日志。
- 隔离:状态库按项目过滤(行级过滤,不是靠前端藏),
沙箱按用户+任务隔离,模型 key 按用户隔离(不共用一
个中转站 key),日志里不出现别的用户的任务内容。
- 审计:谁派了、谁批准了、谁合并了、谁改了配置,全部
记审计日志,不可删;这是多用户和单用户最大的区别,
单用户可以靠 host-run.log,多用户不行。
## 7. 跨线程记忆和项目知识库
目标:新任务不用从零开始,能用上以前线程的结论和项目
的既有知识,但不把无关历史全塞进上下文。
- 两层,分开做:
1. **线程记忆**:每回合结束,host 写一条摘要(目标、
结论、改了哪些文件、遗留问题),存在记忆库;新任务
派发时,按项目 + 相关文件召回最近的摘要,注入提示
前缀,不注入全文。摘要可删、可纠正(用户纠正一条,
旧摘要要标废弃,不能留着误导下一轮)。
2. **项目知识库**:索引仓库文件、README、docs、历史
任务、评审评论。起步用 SQLite FTS(全文检索,零外
部依赖),够用;以后再上 embedding(需要向量库和
embedding 模型,成本和运维都上一个台阶)。
- 检索时机:派发时 top-k 相关文件/文档注入提示;回合中
给 agent 一个 MCP search 工具,让它自己查 KB(比一次
性全注入省 token)。
- 隐私边界:KB 按项目隔离,不跨项目召回;记忆是项目
的,不是某个用户的私人笔记,除非用户明确标私有。
## 8. 模型 fallback 和真实 token 成本统计
目标:主模型挂了自动换备选,成本是真数不是估算,且
每项成本能对到具体任务。
- **先纠正一个旧说法**:之前说“协议本身不暴露 token 数”
是不准确的。查 2026-10-26 的 v2 schema:协议里有
`account/usage/read` 方法,返回 `ThreadUsage`
(estimatedUsageCreditsMicros / estimatedUsageUsdMicros)
和 `TokenUsageBreakdown`(input / output / reasoning /
cached / total)。但有两个前提:(a) 它是 ChatGPT 账号
的计费路线,(b) 中转站(xcmapi)走的是 API key 路线,
计费路线“可用时”才返回——**中转站上到底能不能读到,
必须实测,不能先写进承诺**。
- 落地顺序:
1. host 的 Server 事件循环先接住回合里的 usage 通知
(如果有),按回合记 input/output/total 到任务记录;
2. 每回合结束调一次 `account/usage/read`(threadId),
记 estimatedUsageUsdMicros;这个调用本身不跑模型,
不花模型钱;
3. 如果中转站上读不到(返回空/不可用),再走 provider
计费接口:OpenAI 的 /usage、xcmapi 的账单接口(如果
有,拿用户的中转站 key 临时测一次,不存文件);
4. 都读不到时才用估算:prompt + 输出字符数 / 4,并
**明确标“估算”**,绝不冒充真数。
- Fallback:配置里写模型优先级列表,例如
`[gpt-6.1-sol, gpt-6-sol, gpt-5.6-sol]`;主模型返回
404 / 429 / 5xx / 超时时,自动试下一个,任务记录里
记“实际用的模型”,fallback 发生时通知一次,不静默
降级;预算(回合数 + 成本上限)按实际模型累计,超
预算就停,不自动换更贵的模型续跑。
- 成本报告:按任务 / 按项目 / 按天汇总 token、美元、
回合数、fallback 次数,报告里真数和估算分开列。
## 分阶段落地(按依赖,不按热闹程度)
- **3A 控制面 + 多用户 + 记忆/KB**:API + 认证 + RBAC +
状态库 + 任务队列 + 线程摘要 + SQLite FTS 知识库。
不需要云账号、GitHub、Slack,可在本机先跑通。这是
后面所有项的地基,先做它。
- **3B 远程沙箱 + 云端派发**:worker 池 + 容器沙箱 +
心跳/中断/取消。需要 Docker 或云容器账号,是 3A 之后
的第一块硬骨头。
- **3C GitHub PR/CI + Slack + 推送**:外部集成三件套,
需要 GitHub PAT、Slack App、公网回调地址,三者可以
并行,但都依赖 3A 的认证和权限。
- **3D 模型 fallback + 真实成本**:usage 读取实测 +
fallback 列表 + 成本报告。依赖 3A 的任务记录,不依赖
3B/3C,可以和 3C 并行。
## 需要用户定的事(现在就可以定,不用等代码)
1. 控制面跑在哪:常驻机器(自己的 Mac/服务器)还是
托管服务(Fly.io / Render / 云服务器)?这决定 3B
的沙箱选型和 3C 的回调地址。
2. GitHub:用哪个仓库、给不给 PAT(走 Secure Vault)?
3. Slack:有没有 workspace 管理员权限装 App?
4. 推送:Slack + Muse 通知够不够,还是要真手机推送
(APNs/FCM)?
5. 计费实测:同不同意用中转站 key 临时测一次
`account/usage/read`(不存 key,只看返回有没有数)?