worker: 从未启动的任务自动续跑回落全新跑
Codex app-server(无 GUI 版,照搬开源核心 + 自建云端控制面)
目标分两层,不要混成一层:
- 不重写 agent 循环:把 Codex 开源能给到的全部原样接过来, 做成一个可以被任何客户端(pi agent、你自己的 App、脚本) 调用的 app-server。
- 自建控制面:在官方 agent 之上加一层常驻服务——项目、 任务队列、worker 池、评审与合并、权限与配额、成本与记忆、 网页命令台——让多任务、多用户、远程派发能真正跑起来, 而不是只停在本地单机演示。
项目现状(2026-10-29,main 93c25a9,PR #130 已合并部署)
旧版 README 只写到本地 host 层,云端还写着“演示、不是真实 云服务”——那句话已经过期。本轮更新以生产现状为准:
- 生产已上线:控制面跑在常驻主机上,Postgres 为后端,
公网入口
https://api.diante.me,命令台https://api.diante.me/console(GET /health可查backend=postgres、worker_ok与当前 commit)。 - 命令台是 React 版:源码在
cloud/console-react/(Vite + React 19 + TypeScript),构建产物是单文件cloud/console.html, 只改src再npm run build:console,产物绝不手改。 当前视觉是一页式 GitHub 风格(PR #129)+ 按宽度自适应分栏 (PR #130):细顶栏 + 6 张数据卡片 + 派发、任务、对话、项目、 环境、Workers、技能、自动化、分诊、管理区、API Key 全部铺在 一页;窄屏 <900px 一栏堆叠,中屏 900–1399px 两栏(左:任务 列表 + 项目,右:其余),宽屏 ≥1400px 三栏(左 310px、中弹性、 右 330px),页面最大宽 1560px,盒子按内容高度不拉伸。 此前 Muse 风格四栏固定布局已被它取代,不要再按旧截图改。 - 执行层是 Docker + gVisor:生产任务走 Docker 执行器,
生产 autoscaler 起的 worker 带
--docker-runtime runsc;内层 codex 沙箱在生产容器里仍是danger-full-access(见下“诚实边界”),隔离靠外层容器 + 租户目录 + egress 白名单,不是双层沙箱。 - 模型走中转网关:生产默认模型已改为
gpt-5.6-terra(原gpt-6.1-sol在 2026-10-27 实测持续 503; 价目表与/models优先级列表未动,terra 成本进 unpriced)。 系统 key 与中转站是设计本身,不做 OpenAI 官方账号对接, 也不做 per-user 中转 key。 - 已落地的控制面能力:OAuth 真实登录(GitHub/Google)、
项目 RBAC 与 API key 管理、任务队列与 worker 心跳、自动扩容
(协调员保护)、租户配额与 USD 上限、成本统计、自动记忆、
网页搜索 MCP(SearXNG 路)、任务继续消息持久化、子任务
默认模型/强度、计划模式、followup 与 fork、评审队列与
PR/CI 门。细节与 API 见
cloud/README.md与docs/。 - 仍未证明 / 暂搁置的:40 人并行(2026-10-29 用户令先绕过, 串行分波次或 18 人规模代替,40 人仍记未证明);第二台主机的 真跨机 HA(现有 HA 验证是同主机双进程模拟);G10 断点 provider 修法只在 PR #127,未合并、未部署。
一句话架构
浏览器 / 客户端 / GitHub webhook
│ HTTPS + X-API-Key(OAuth 登录、webhook 靠签名)
▼
cloud/server.py(控制面,默认 127.0.0.1:8765,生产经 nginx + TLS)
│ 项目 / 任务 / 队列 / 权限 / 配额 / 成本 / 记忆 / 评审
▼
cloud/worker.py + autoscaler(worker 池,local / docker 执行器)
│ 调 host.py:dispatch / run / goal / review / merge
▼
client.py ──► codex app-server(官方二进制,codex-core 原样)
│ thread/start · turn/start · 事件流 · 审批请求
▼
agent 循环 + 沙箱 + 工具 + MCP + skills + 线程持久化
本仓库自己写的有三层:client.py(协议客户端)、host.py
(本地宿主编排)、cloud/(云端控制面 + worker + 命令台)。
agent 循环、沙箱、工具执行、上下文压缩、线程存储,全部是官方
codex 二进制里的,不是我复刻的简化版。
实测版本:codex-cli 0.164.0-alpha.9,协议定义就是从它
导出的,见 protocol/。
目录
start.sh:启动官方 app-server,支持 stdio / unix / ws 三种传输。client.py:零依赖 Python 客户端,演示完整链路。run_parallel.py:宿主层多 agent 编排(见下文“子代理”)。host.py:本地宿主编排(dispatch / review / merge / goal / automations / daemon / health,见下文“宿主编排”)。cloud/:云端控制面(server.py控制面、worker.pyworker、autoscaler.py自动扩容、console.html命令台产物、console-react/命令台源码、sub2api.py中转网关、websearch_mcp.py网页搜索 MCP、db.pySQLite/Postgres 双后端)。控制面本身零第三方依赖,只用 Python 标准库。cloud/console-react/:命令台源码。改页面只改src, 再npm run build:console生成cloud/console.html。deploy/:主机部署与运维脚本(deploy.py+host_deploy.sh, tar 与 git 直拉两种模式;deploy/README.md为准)。docs/:架构与专题文档(云端架构、自动记忆、网页搜索 MCP、 线程生命周期等)。cloud/README.md里仍写着“本地演示、 不是真实云服务”的旧诚实边界已过期,以本 README 顶部的 生产现状为准。config.example.toml:沙箱、审批、MCP、features 的配置示例。protocol/:codex app-server generate-json-schema原样导出的 协议 schema(v1 + v2),客户端的方法名和字段以它为准。examples/subagent-prompt.txt:让主 agent 派生子代理的 prompt 模板。examples/jobs.json:并行审查的 job 示例。work/job-a、work/job-b:并行示例用的两个只读样例目录。
快速开始
1. 先确认登录(这一步不在开源里)
在你自己的机器上先跑一次 codex login(ChatGPT 登录),
或配好带 API key 的 model provider。否则会出现下面实测里
的状况:thread 能建、turn 能发,但模型调用返回 401,
turn 以 failed 结束——这是登录问题,不是 app-server 问题。
2. 跑一回合
cd ~/workspace/codex-app-server
python3 client.py --prompt "修掉 README 里的错别字" \
--cwd /path/to/your/repo \
--sandbox workspace-write --approval-policy on-request
只看协议、不跑模型:
python3 client.py --list-threads
python3 client.py --thread-id <上次的 thread id> --prompt "继续"
3. 换传输方式
./start.sh --listen unix:///tmp/codex.sock
./start.sh --listen ws://127.0.0.1:8080
# 再用你自己的客户端连这个 socket / ws,而不是 stdio
4. 配置
把 config.example.toml 复制到 ~/.codex/config.toml 再改,
不要把登录信息、API key、你自己的 relay 地址写进本仓库。
沙箱三档:read-only / workspace-write / danger-full-access;
审批三档:untrusted / on-request / never。
开源照搬清单(全部来自官方 app-server)
| 能力 | 来源 | 说明 |
|---|---|---|
| Agent 循环 | codex-core | 推理 → 工具 → 结果 → 再推理,直到 turn 完成 |
| Thread 生命周期 | app-server | thread/start、resume、fork、archive、delete、list,事件历史持久化 |
| Turn 与流式事件 | app-server | turn/start、turn/steer、turn/interrupt、turn/completed,agent 文本/工具事件流式回推 |
| 沙箱 | codex-core | macOS Seatbelt、Linux Landlock/bubblewrap;本机 VM 的系统 codex 缺 host 和 bwrap,工具会 fail closed,已用 bin/ 配对三件套修复,见 TEST-REPORT.md“工具执行修复” |
| 审批 | app-server | 命令、改文件、权限、MCP 询问等请求回推给客户端裁决;也可配 approvalsReviewer=auto_review 让子代理先审 |
| 工具 | codex-core | shell、文件读写、搜索、改代码(apply_patch)、图片等 |
| MCP / skills / 插件 | app-server | thread 内按需加载,features list 可看本版开关 |
| AGENTS.md | codex-core | 全局、仓库根、子目录三层合并,thread 启动时自动读 |
| 上下文压缩 | codex-core | 长会话自动 compact,不用客户端自己截断 |
| exec-server 拆分 | codex-core | 推理(app-server)与执行(exec-server)可分开部署,见 codex exec-server --help |
| Goal / 多 agent 开关 | features | 本版 multi_agent 已是 stable,codex features list 可验证 |
子代理:两层,不要混
内置子代理(开源已给)
主 agent 在 prompt 明确要求时派生 worker / explorer 等子代理,
各自独立上下文、并行跑、主 agent 汇总。模板见
examples/subagent-prompt.txt,要点是:分工写清楚、要求分开
列结果、最后合并去重、只列有证据的项。
限制(重要):子代理继承父 thread 的沙箱和审批设置, 不是独立权限边界。“只读评审”是指令约束,不是技术隔离。 真要隔离,改代码的 job 必须走下面一层。
宿主编排(开源没给,自己写,已给示例)
run_parallel.py 就是这一层:宿主并行开多个 thread,
每个 thread 一个独立工作目录(生产上用 git worktree),
最后宿主收集结果再汇总。桌面版“多线程指挥台”的感觉
主要就是这一层,不是 app-server 本身。
python3 run_parallel.py --jobs examples/jobs.json
示例默认是只读审查(read-only + never),要并行改代码时: 每个 job 换成各自的 worktree、改成 workspace-write, 事后逐个 review diff 再合并,冲突仍需人工处理—— core 里没有自动解决冲突的调度器。
云端控制面(cloud/,生产已跑,不再是演示)
本地 host.py 只管一台机器上的任务;多用户、远程派发、
排队、配额、评审门要靠 cloud/。它不是 host.py 的包装,
而是控制面只负责“谁可以派、派给谁、状态在哪、结果通知谁”,
执行仍走 worker 调 host.py,agent 逻辑不重写。
python3 -m cloud.server # 默认 127.0.0.1:8765,生产经 nginx + TLS 对外
# 除 GET /health、/webhooks/*、/oauth/* 外,所有端点都要 X-API-Key 请求头
# 任务实时链路:GET /tasks/{id}/trace(快照)与 GET /tasks/{id}/trace/stream(SSE)
# 命令台:GET /console(每次现读 cloud/console.html,无需重启)
- 任务状态机:
queued → running → completed / failed / interrupted / merged / merge-failed;merged只由POST /tasks/{id}/merge同步状态,不执行合并本身(合并仍由 host.py merge 执行,成功后再调本接口;仅 completed 可改 merged,重复调用 409,越权 403)。 - worker 与扩容:worker 心跳注册(
POST /workers/heartbeat), autoscaler 按队列扩缩;2026-10-29 已修协调员槽位泄漏 (PR #118:协调员完成不清_running曾导致容量门静默拦单, 40 人轮与 18 人轮的“认领停滞”都是它)与缩容误杀协调员 (PR #117)。长 goal 任务异常退出不再一律判 failed, 无结论的非 0 退出记 interrupted,断点在盘上可goal-resume续跑。 - 权限、配额、成本:项目 RBAC、API key 只存 sha256 哈希 (明文只返回一次)、租户月度 USD 上限与建任务/分支/重试 准入、按任务与按项目/按日的成本(实际与估算分开)、 每 key 每分钟限流(环境变量可调,窗口与上限见部署 env, 不要把生产调的值当成代码默认值写进文档)。
- 记忆与搜索:跨线程记忆与项目 KB(按项目隔离)、自动记忆 (生产默认账号已开,worker 每轮读库)、网页搜索 MCP (Perplexity → Exa → Tavily → Brave → SearXNG → DuckDuckGo 自动回退;Docker 任务容器不继承 worker 环境,搜索 env 须经 worker 的 extra_env 透传,见 PR #114)。
- 继续、计划、followup、fork:继续消息已持久化进会话历史
(PR #126,此前失败任务只落任务记录、不进历史,网页重载后
看不到助手回复);建任务可带
plan(只读沙箱,不能与approval=full或 goal 同用);POST /tasks/{id}/followups(queued 排队不打断当前轮,direct 立即进链路且跑完当前轮后 优先于排队执行);POST /tasks/{id}/fork(仅终态任务可分支, 继承 executor/模型/强度/权限/计划,goal 字段置空)。 - 模型与网关:子任务默认模型/强度(
child_model/child_effort,PR #121);模型路由走 sub2api 中转网关, 断点命令的 model provider 须透传(PR #127 未合并,见顶部)。 - 部署:
deploy/deploy.py(tar 默认,--source git走主机 直拉 GitHub;产物唯一命名 + 三道 sha/commit 门 + 换目录 代替原地解包,health 须ok=true且backend=postgres且 commit 对上才算成功;主干推送不自动部署生产,见deploy/README.md)。部署前先确认无人同时在推,并发部署 会撞目录换位;部署完须核 4 个 unit 全 active。
宿主编排(host.py,无 GUI,对齐 ChatGPT / Codex 桌面版宿主)
ChatGPT / Codex 桌面版作为宿主,本质不是再写一个 agent,而是
管好这几件:线程、并行、隔离、委派、长任务、评审、自动化、
边界。host.py 就是这一层,一个 app-server 进程托管多个 thread,
后台读线程按 threadId 分发 turn/completed,所以多 thread 的
turn 可以真正并发(不是轮流排队)。
| ChatGPT / 桌面版宿主能力 | host.py | 状态 |
|---|---|---|
| 多 thread 并行 | host.py parallel --jobs …,一个 app-server 进程内多 thread 并发 |
已实测 |
| 一任务一 git worktree 隔离 | dispatch 自动 git worktree add -b host/<task>,非 git 仓库退化为独立目录拷贝 |
已实测 |
| diff 评审 | host.py review <task>:status + diff + untracked |
已实测 |
| 评审后合并 / 丢弃 | merge:先在 worktree 提交,再 --no-ff 合并进主仓,冲突自动 abort;discard:删 worktree 和分支 |
已实测(host-b 已合并进主仓) |
| 线程续跑 / fork / 归档 | run(resume + 新回合)、fork、archive、list |
已实测 |
| 宿主级子代理委派 | delegate:子 thread(默认 read-only)跑完,结果可 --report-to-parent 汇总回主 thread |
已实测 |
| 模型内子代理 | prompt 明确要求派生,见 examples/subagent-prompt.txt;与宿主委派是两层,别混 |
已实测(模板 + 协议) |
| 长任务 / 目标循环 | goal:同一 thread 多回合,每轮落盘检查点,中断后 goal-resume 从下一轮续跑(goal-met / goal-unmet 终局后不许续跑,须新建);--turn-timeout 0 不限时;回合超时下一轮提示写明是回合中断不是验收失败。两种验收形态:旧形态 --verify 验收命令通过即达标,到 --max-rounds 则 unmet;新形态(console 打勾 goal 模式,goal_mode=1)不设验收命令、不设轮次上限(--max-rounds 0),prompt 本身就是目标,模型在回合末尾单独一行输出“目标已达成”即宣告达标,否则一直运行,停靠取消或预算(max_daily_turns 耗尽时记 interrupted、可续跑) |
旧形态已实测:端到端、断点续跑、正常多轮反馈修复;新形态单测已过,真模型宣告达标尚未实弹 |
| 自动化 / 评审队列 | automations --add + run-due:到期的任务自动派发,结果进 host-state.json 评审队列 |
已实测 |
| 沙箱边界 | read-only thread 的写文件请求被拦下,文件确实没建出来 | 已实测 |
| 审批控制 | untrusted + deny 时 agent 明确收到审批拒绝,没拿到命令输出 | 已实测 |
| 模型内子代理 | 3 子代理并行检查并汇总,视角是真的不同,不是重复一句话 | 已实测 |
| 中断 / 续跑控制 | turn/interrupt 后状态为 interrupted;host.py interrupt --task 已接协议,直接中断不生效时写 <state_dir>/interrupt-requests/<task>.json,属主在等 turn 时每秒轮询到后用自己的 live 连接自中断(跨进程可靠路径);compact 需先 resume(host.py 的 compact 命令已包好这一步);超时回合也记 interrupted 且保留部分文本 |
单测已过(等待循环、请求路径、透传);真 server 跨进程中断尚未实弹,属主自中断逻辑与直接中断共享同一协议调用 |
| 长等待进度 | 等 turn 时每满 --progress-interval(默认 30 秒,0 关闭)往 stderr 打一行 turn progress: elapsed=…s 并往链路文件记一条 kind=progress(console 按 fallback 当 JSON 行展示);进度绝不走 stdout,不破坏 dispatch 的 JSON 与 goal 的结论行/用量行解析 |
单测已过(等待循环进度回调);真模型长等待下的进度尚未实弹 |
| 合并冲突处理 | 先合的进主仓,后合同一行的冲突 abort,主仓干净,冲突 worktree 留着人工解 | 已实测 |
| 自动化幂等 | 未到期的自动化 run-due 时不重复派发 | 已实测 |
| 宿主提供 MCP 工具 | 隔离 CODEX_HOME 注册 stdio MCP,agent 能发现并调用;注意 MCP 审批是 elicitation 协议,不是通用 decision 字段,host.py 已分流处理 | 已实测(修过一轮才通) |
| 模型切换 | 同一 thread 内换模型续跑,turn 的 model 字段随之改变 | 已实测 |
| 运行中改向 | turn/steer(expectedTurnId 校验)能把进行中的长任务改成短任务 |
已实测 |
| 3 并行 | 3 个 worktree 任务同时跑,全部完成且互不污染 | 已实测 |
| 未追踪文件评审 | review 时 diff 为空也要看 untracked 栏 | 已实测 |
| 失败不崩宿主 | 不存在的模型导致 task failed 并带上游 404 错误,host 不崩 | 已实测 |
| 生产级项目闭环 | 真实仓:并行功能开发 + 测试套件验收 + 评审修复闭环 + 合并链,主仓从 2 个测试涨到 17 个全部通过 | 已实测 |
| 目标循环反馈 | 验收失败时把命令和失败输出喂给下一轮,否则 agent 会原地不动 | 已实测(修过一轮才通) |
| 中断恢复 | dispatch/delegate/goal/run-due 先落盘再跑 turn,中断后可用 run 续跑 | 已实测(委派超时后修) |
| fork 隔离 | fork 建新 worktree + 拷工作区文件(跳过 .git)+ turn 级 sandboxPolicy 覆盖 read-only 重置 | 已实测(修过两轮才通) |
| 跨进程 state 竞态 | host-state.json 单文件,多 host.py 进程同时写会丢任务,同一进程内并行安全 | 已修:锁文件 + 保存时合并,实测双进程并发保存不丢 |
| 项目配置 | 每次都要传 model/repo/state 等参数 | 已修:host-config.json + init/config,CLI 优先于配置,密钥只走环境变量 |
| 定时自动化 | run-due 要手动跑,跑完没日志 | 已修:host-run.log 记录派发/跳过,automations --cron-line 生成 crontab 行,--install-cron 才装机 |
| 合并后自动验测 + 回滚 | merge 合完不验主仓测试,合坏了靠人发现 | 已修:merge 默认仓库有 tests/ 就跑 unittest,验测失败 reset 回合并前 HEAD,分支/worktree 保留;--no-verify 跳过,--verify 可指定命令 |
| 任务依赖 + 优先级 | parallel 是扁平的,没有依赖和优先级 | 已修:jobs 里 depends_on/priority,拓扑排序 + 按波次跑,依赖失败时依赖任务标 skipped 不跑;依赖任务从依赖分支建 worktree(有血缘关系),不是文件拷贝 |
| 失败自动重试 | 失败任务就停在 failed,不重试 | 已修:dispatch/parallel 里 --max-retries,失败后同 prompt 再跑,重试前查预算;默认 0,行为不变 |
| 评审评论回流 | 评审只看 diff,修要另起 run prompt,评论没地方钉 | 已修:review-comment --file --line --comment 把评论存进任务,review 时列出 open 评论,fix-review 按 open 评论逐条修,修完标 resolved |
| server 崩溃重连 | app-server 死了,host 就 BrokenPipe 失败 | 已修:Server.ensure_alive,死了重拉并 resume thread(thread 在 CODEX_HOME 里不丢);request 遇 BrokenPipe 重试一次;单测 kill 进程后重拉 + list 能通 |
| 成本预算 | 没有预算,模型跑多少全靠自觉 | 已修:配置 max_daily_turns,按 turn 数算(协议不暴露真实 token 数,这是诚实边界),超预算不派发/不重试;usage 记在 state 里,doctor 可看 |
| 失败通知 | 失败只有 state 里的 status,跑完没人知道 | 已修:失败时写 host-run.log + 跑配置的 notify_command(本地 shell,模板 {task} {status});failures 命令列失败任务,doctor 列失败/缺失 worktree/open 评论 |
| 多机器 state 冲突 | 锁只防同一台机器,多机器共享 state 文件会静默覆盖 | 部分修:state 加 version/last_writer/updated_at,保存时磁盘版本比加载时新就记 STATE-CONFLICT 日志并合并保留双方任务,不静默丢;真多机同步仍需中心服务(见下“没对齐”) |
| 合并时提交缓存文件 | merge 的 git add -A 会把 pycache/*.pyc 也提交进主仓,下次合并被 .pyc 改动挡住 | 已修:merge 前清 worktree 的 pycache/*.pyc,主仓的 .pyc 改动先还原;单测覆盖 pyc 不入库 |
| 守护进程 + 健康检查 + 自动恢复 | host.py 跑完就退出,不常驻;state 坏了静默当空 state;中断任务一直停在 running | 已修:daemon(双 fork 到 PID 1,循环 recover + run-due)、health(不跑模型,查二进制/配置/state/server/daemon)、restore-state(坏了从 .bak 恢复并留底)、recover(超过阈值的 running 标 interrupted,可用 run 续跑,不自动重跑模型回合) |
| GitHub PR / CI 一条线 | 本地 merge 完就完,没有 PR、没有 CI 门 | 已修(代码 + 本地可测部分):github-workflow 生成 .github/workflows/host-verify.yml(checkout + python + unittest,幂等);pr(push 分支 + gh pr create,成功后 pr_url/pr_number 写回任务记录);pr-checks(解析 gh pr checks,all_pass 仅全 pass 为真,空 checks 不算过);pr-merge(先查 checks,未全过拒绝且不调 merge,全过才 gh pr merge --merge 并标 merged,不动本地主分支)。gh 调用走 run_gh(),单测用假 gh。本仓的真实 PR 链路现已走通,见下“GitHub PR/CI”一节。 |
| 边界:cwd、沙箱、审批、模型 | dispatch / turn 参数里定,见 config.example.toml |
已实测 |
| 任务登记 | host-state.json:task -> thread/worktree/状态/diff;agent 状态本身仍在 CODEX_HOME,不另存第二份 |
已实测 |
没对齐的部分(诚实说,不是偷工):
- 云端派发、推送、GitHub 触发:本地 host 层仍没有,
run-due要靠你自己的 cron 定时调,才等于“定时自动化”;但云端控制面 层已经有了(见上文“云端控制面”):队列派发、webhook 入站、 PR/CI 门、推送订阅与分发。Slack 触发未对真实 Slack 服务器 实测,不要把它写成已验证。 - GUI 的 inline 评论、side-by-side diff、点按钮批准:无 GUI 版 本来就不要这层,评审就是 git diff 文本 + merge/discard 命令。
- Computer Use:另一条管线,不在宿主编排里。
- 并行改同一文件:worktree 能防互相踩,但两个分支都在文件末尾 追加时,合并第二支仍可能冲突,host 会 abort 等人工解冲突, 不会自动猜合并结果——这和桌面版一样,是正确做法,不是 bug。 2026-10-26 补了一层安全网:merge 冲突时先试 -X ours,解完 后逐行核对任务分支的新增行是否都在主仓里,缺一行就回滚 (gap2 实测拦下了一次 -X ours 丢 mul2 的情况);依赖任务 现在从依赖分支建 worktree,依赖先合、依赖任务后合时不再 重复加同一段。
- 多机器同步:本地 host-state 仍是单机文件(有 version 和 冲突日志,不会静默丢任务),真多机同步要走云端控制面 的中心数据库(Postgres),不在本地 host 的范围里; 同主机双进程模拟过的 HA 不等于第二台主机仍存活。
- 守护进程的边界:daemon 本身也活不过 VM 平台回收(和 微信桥 serve 一样,回收后要重拉),所以 VM 上更稳的定时 仍是 Muse cron 定时调 run-due/health,而不是只靠 daemon 或 crontab;daemon 适合常驻机器,VM 上是“会话内常驻 + 回收后重拉”的定位。
宿主快速开始(最小闭环)
下面全部是 host.py --help 核对过的真实子命令和参数,
不要凭印象编命令。先装检查,再走闭环;不要在示例里
直接派发真实模型任务来“试一下”——dispatch 一步就会跑模型。
cd ~/workspace/codex-app-server
./install.sh # 只检查 python3、git、codex,不写密钥、不跑模型
python3 host.py health # 不跑模型:查二进制、配置、state、server、daemon
python3 host.py init --repo /path/to/your/repo
# 上一步写 host-config.json(CLI > 配置 > 默认;key 绝不进配置文件,只走环境变量)
python3 host.py dispatch --name example-task --task "你要做的事" --repo /path/to/your/repo
# ^ 这一步跑模型:需要模型 key 已走环境变量提供,且会建 git worktree host/example-task
python3 host.py review --task example-task # 看 status + diff + untracked + open 评审评论
python3 host.py merge --task example-task # 默认仓库有 tests/ 就跑 unittest,验测失败自动回滚
# 合并不了就别硬合:python3 host.py discard --task example-task # 删 worktree 和分支
python3 host.py daemon --interval 300 # 双 fork 常驻:每轮 recover + run-due
python3 host.py daemon-status
python3 host.py daemon-stop
三条边界,闭环前先知道:
health不跑模型、不花额度,随时可跑;daemon-status/daemon-stop同样不跑模型。daemon在这台 VM 上活不过平台回收(和微信桥 serve 同理): 回收后要重拉;VM 上更稳的定时是 Muse cron 调health/run-due,而不是只靠 daemon 或 crontab。run-due到期会跑模型、会花额度:先用python3 host.py automations --add --name <名> --task "<任务>" --repo <仓> --interval 86400登记自动化,再确认模型 key 已走环境变量提供,才让定时去调python3 host.py run-due;未到期的自动化 run-due 时不重复派发。- 云端控制面另起一端(生产上 worker 会跑真实模型回合,
不再是“演示、不跑模型”):
python3 -m cloud.server(默认 127.0.0.1:8765), 除GET /health、/webhooks/*、/oauth/*外都要X-API-Key请求头。 任务实时链路:GET /tasks/{id}/trace(已结束任务的快照)与GET /tasks/{id}/trace/stream(运行中任务的 SSE:先 snapshot 再逐条 entry,终态发 done 关流;命令台详情的链路区用 fetch 流式读它, EventSource 不能带 X-API-Key 头故不用)。 新业务链路见上文“云端控制面”:plan、followups(queued/direct)、fork,以及继续消息持久化。
GitHub PR/CI:本仓已走通,旧“无远端、无登录”已过期
旧版这里写着“本仓没有 git remote、gh 未登录、真实 PR 没跑”——
那是 2026-10-25 左右的快照,现已过期。本仓现为
github.com/openqj/codex-app-server,PR → CI(host-verify)
→ --merge 合并 → deploy.py git 模式部署已是常规链路
(例如 #124 React 命令台、#125 Muse 风格、#126 继续消息持久化、
#128 一页式排版、#129 GitHub 风格、#130 自适应分栏,均经此链路上线生产)。
不要再拿旧段落去推断本仓的 PR 能力。
但要分清两条线:
- 本仓的发布链路:分支 → PR → CI 全过 → 合并进 main → git 模式部署到生产主机,部署须验 health 与 commit 一致。
- host.py 的 pr/pr-checks/pr-merge:那是给任意目标仓库用的
本地命令,仍要求目标仓有 remote 且运行机器上
gh已登录; 没有登录时它会把 gh 的错误原文打印出来,不编造 PR URL。 不要把 PAT 写进任何文件、state、日志或聊天记录;需要登录时 走 Secure Vault。 - 流程(远端和登录齐了之后):
host.py github-workflow生成 CI workflow(已生成的文件 要提交进仓库,GitHub Actions 才会跑它);- 任务 completed 后
host.py pr --task <名>:push 分支 + gh pr create,pr_url/pr_number 写回任务记录; host.py pr-checks --task <名>:看 CI 全过没有;host.py pr-merge --task <名>:全过才合并,合并由 GitHub 完成,不动本地主分支——和本地merge命令是两条 线,不要混用:本地 merge 是直接合进本地主仓,PR merge 是走 GitHub 的 PR 流程。
- 两条线不冲突的原因:本地 merge 用 merge_task()(自动验测
- 回滚),PR merge 用 pr-merge(checks 门 + GitHub 合并); 一个任务要么走本地 merge,要么走 PR,不要两边都合。
开源没给到的(别指望照搬)
- 模型本身、ChatGPT 登录、计费、Codex Cloud 云端沙箱。
- GitHub / Slack 等渠道的托管派发:云端层已有 webhook 入站、 推送订阅与自动 PR,但 Slack 触发未对真实服务器实测, 托管派发不等于 ChatGPT 托管服务。
- 桌面版的 inline 评论、side-by-side diff、点按钮批准: 命令台有评审队列与对话时间线,但评审仍是 git diff 文本 + merge/discard 命令,不是桌面版评审 UI。
- Computer Use(屏幕操作)是另一条管线,不在 CLI 中间层里;
本版 features 里虽然有
computer_use开关,但那不是 “复制 app-server 就自动得到一个桌面操作 agent”。
诚实边界(2026-10-29,更新 README 时仍成立的)
- 内层沙箱:Docker 容器嵌套 pidns 下 bwrap 的 fresh proc
mount 被内核拒绝(宿主级 + 带 userns 的 AppArmor profile
才能跑通 workspace-write),所以生产是 Docker 隔离 +
容器内 danger-full-access,不是双层沙箱;宿主级 workspace-write
下 agent 只能写自己 worktree,但
/tmp不隔离,写 /tmp 会落到 宿主 /tmp 上。 - 并行规模:18 人并行已探过,40 人并行未证明且已暂搁置; 需要 40 人规模时走串行分波次或拆小批次,不要把 18 人的通过 写成 40 人的通过。
- HA:多机 HA 0→4 的跨机部分是同主机双进程模拟验证, 真“杀一台主机仍存活”需要第二台 VPS;VM 会被平台定时回收, 不能作 HA 节点。
- 未合并的在途工作:G10 断点 provider(PR #127)只推了 PR, 未合并、未部署;不要在 README 里把它写成已上线。
- 密钥:API key、OAuth secret、中转 key 只走环境变量或 Secure Vault,绝不写进仓库、state、日志或聊天记录; key 明文只在签发时返回一次。
安全
- 常驻服务不要用
danger-full-access,也不要把--dangerously-bypass-approvals-and-sandbox写进启动脚本, 那是给外部已经沙箱化的环境准备的。 - 审批策略默认:自动化用
never+workspace-write或更严; 交互使用再用on-request,由客户端(人)逐条裁决。 client.py --approval auto会放行所有审批请求,只用在 你完全信任的仓库和一次性任务里,默认是 deny。- ws 传输对外暴露时务必配
--ws-auth,不要裸奔在公网。
本机实测记录(2026-10-25)
隔离 CODEX_HOME(不污染你真实的 ~/.codex)下跑:
python3 client.py --codex-home /tmp/codex-client-test \
--cwd work/job-a --sandbox read-only --approval-policy never \
--prompt "只用一句话回答:2+2等于几?不要调用任何工具。"
结果:
initialize成功,返回 codexHome / platformOs。thread/start成功,拿到 thread id,model 为默认模型。turn/start成功,收到turn/started、userMessage 事件。- 模型调用处失败:
401 Unauthorized: Missing bearer token, 随后turn/completed状态为 failed。 原因:隔离目录里没有任何登录/provider 密钥,且默认 provider 指向 api.openai.com。这是预期内的登录边界, 不是协议或客户端 bug。在你已登录的机器上同一条链路 会直接出结果,不需要改代码。
完整输出见 TEST-REPORT.md。