# codex-supervisor-mcp [English](README.en.md) | 中文 [![npm version](https://img.shields.io/npm/v/codex-supervisor-mcp.svg)](https://www.npmjs.com/package/codex-supervisor-mcp) [![npm downloads](https://img.shields.io/npm/dm/codex-supervisor-mcp.svg)](https://www.npmjs.com/package/codex-supervisor-mcp) [![license](https://img.shields.io/npm/l/codex-supervisor-mcp.svg)](LICENSE) [![CI](https://github.com/zriyox/codex-supervisor-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/zriyox/codex-supervisor-mcp/actions/workflows/ci.yml) [![node](https://img.shields.io/badge/node-%3E%3D22.13.0-339933.svg)](package.json) 官网 [codex-supervisor.zriyo.com](https://codex-supervisor.zriyo.com) · 给模型读的 [llms.txt](https://codex-supervisor.zriyo.com/llms.txt) 让几个 agent 同时改一个仓库,结果是互相覆盖、没人说得清谁改了什么,主线程的上下文还被 worker 的输出塞满。 **codex-supervisor-mcp** 是一个 Codex MCP server,把「派单」和「记账」从模型上下文里拿出来放到磁盘上:一个主线程(Claude Code、Codex 或任何 MCP 客户端)同时指挥多个 Codex CLI worker,一个 worker 一个 Git worktree,状态落盘到 SQLite,附一个网页看板。 ![演示:一个 Claude Code 主线程同时派 3 个 Codex worker,各自独立 worktree,并行跑完后合并提交](https://raw.githubusercontent.com/zriyox/codex-supervisor-mcp/main/assets/demo.gif) *30 秒真跑:`create_codex_worker` ×3,三个 worker 各自 worktree 并行,主线程收 3 份 diff 合成一次 commit。* ## 解决什么 | 问题 | 做法 | |---|---| | 几个 worker 互相踩文件 | 一个 worker 一个 Git worktree,`ownedPaths` 在派单前查冲突 | | worker 不是只会改代码 | 四种 `workspaceKind`:Git 编辑 `git_worktree`、只读 `read_only_in_place`、联网研究 `scratch`、显式授权的非 Git 写入 `in_place_write`;非 Git 默认绝不写原目录 | | worker 的过程塞爆主线程上下文 | 事件写 `data/runs/.jsonl`,读的时候有 `limit` / `maxChars` / `kinds` 三道闸 | | 主线程读一次状态就顶满 | `get_orchestration_overview` 把全部 worker 压进 7000 字节 | | 进程重启后不知道谁还在跑 | `supervisor.sqlite` 一行一个 work,带 `thread_id` | | 换个会话接不上之前的 worker | `resume_codex_worker` 走 `codex exec resume `,接的是同一个 Codex 会话 | | 分不清「跑失败」和「进程没了」 | 状态机把 `failed` 和 `lost` 分开 | | 分不清是谁报错 | status / overview / overview_delta 带 `failure.source` 和 `failure.code`,区分 provider、supervisor_process、workspace_setup、model_output、acceptance、worker_process | | worker 说做完了其实没有 | 派单给 `acceptance` 验收命令,worker 退出后 supervisor 在它的 worktree 里跑,失败 `land` 拒绝 | | 看不见一批活现在到哪了 | `codex-supervisor-web` 开一个看板,按 session 看每路 worker 在干什么 | | 改动留在本地没上线 | 收活默认 `land_codex_worker` 合回主分支;主线程自己的改动必须 commit + push;部署前用远端 SHA 核对文件真的在线上 | | worktree / 分支越堆越多 | `verify_delivery_push` 后自动生成 cleanup plan;只对 `acceptance passed + push_verified + clean` 判 SAFE,主线程显式 `apply_cleanup` 才删 worktree,branch/证据永远保留 | worker 跑的是 `codex exec`,`model` 透传:主线程留在 Claude,worker 可以挂 DeepSeek 或任何 Codex 配了 provider 的模型,账单分开算。 ## 五分钟跑起来 前置:Node.js 22.13.0 以上,`codex` CLI 在 `PATH` 里。macOS、Linux、Windows 都行。 ### 1. 装 ```bash npm install -g codex-supervisor-mcp ``` npm 全局安装在允许执行安装脚本时,会运行 `postinstall`,给检测到的 Claude Code/Codex 装 skill 并注册 MCP。`--ignore-scripts`、禁用安装脚本或只运行 npx 不保证自动注册;没看到时先运行 `codex-supervisor-setup`,再查两端 `mcp get`。手动注册也可以: ```bash claude mcp add -s user codex-supervisor -- npx -y codex-supervisor-mcp codex mcp add codex-supervisor -- npx -y codex-supervisor-mcp ``` 重启 Claude Code / Codex。以后不用再手动更,见「更新」。只要 skill 不要 MCP:`npx skills add zriyox/codex-supervisor-mcp`。 ### 2. 派第一批活 在 Claude Code 里直接说人话,skill 会让它走正确的流程: > 把这三个模块的单测补齐,用 codex-supervisor 分三路并行跑,session 叫「补单测」。 主线程背后做的事(你也可以自己调工具): ``` preflight_codex_worker 同 workspace/base/sandbox/guard 先跑必要 probe get_worker_preflight 确认 passed;contract 变化就重跑 create_codex_worker ×3 每路带 preflight_id、session_id、ownedPaths、goal wait_codex_workers 默认等 60 秒,到点返回快照,没完就接着等 get_worker_result ×3 收每路的汇报,带它真跑过的命令和退出码 get_worker_diff ×3 看实际改动;证据对得上再决定是否 land ``` 每路的改动在各自的 `codex/` 分支上。验收和 diff 都过了,主线程用 `land_codex_worker` 带 `commitMessage` 合入;worker 不自行提交。 ### 3. 开看板 ```bash codex-supervisor-web # 没全局装也能起: npx -p codex-supervisor-mcp codex-supervisor-web ``` 打开 `http://127.0.0.1:7877`。状态目录按 `SUPERVISOR_HOME`、最近的 `.mcp.json`、`~/.codex-supervisor` 的顺序找;端口用 `SUPERVISOR_WEB_PORT` 改。看板只看,不派单不取消。 长门禁脚本可主动上报阶段: ```bash node "$CODEX_SUPERVISOR_STAGE_CLI" "gate:s3" --detail "integration queue" ``` `current_stage` 会出现在 status / overview / wait,终态保留为 `last_stage`;不要放 token、密码或 DSN。 ## 我自己怎么用 能连 MCP 的都能当主线程,这里只是我的用法。我开两个 Claude Code 会话,一个只管文档,一个只管派活:一个会话又写详设又盯 worker,上下文两小时就满。 | 谁 | 开在哪 | 管什么 | |---|---|---| | 我 | | 定需求,拍板,看汇报 | | 规划会话 | 需求和文档仓 | 聊需求,写详设,每一块活写一份任务书,末尾附一段发给主脑的话 | | 主脑会话 | 代码仓的一个 worktree | 读任务书,派 Codex worker,核每路的 diff,把结果填回任务书,向我汇报。不写业务代码 | | Codex worker | 各自的 worktree | 一个 worker 做一个能独立验收的功能包,在远端机器上修改、运行验收。不 commit、不 push、不合并 | 两个会话之间只传一段文字,粘进主脑会话用 `/goal` 接上。结构固定: ```text 【角色】 你是主脑:读文档和代码,派 worker,核结果,更新文档,向我汇报。不写业务代码。 派活和盯进度用 codex-supervisor 这个 skill,开工前先加载它。 【背景】 这一块为什么做,上一块留下了什么 【先读】 哪几份文档的哪几节。几份说法不一样时以哪份为准 【仓和分支】工作目录在哪,各分支现在在哪个提交,哪些分支只读 【做什么】 照任务书第几节那张表做。按能独立验收的功能包拆,表里没有的不做。worker 不自行 commit 【派活】 先 search_works 看有没有派过,别重复 整批用同一个 session_id 改同一批文件就串行,写入范围不重叠才并行。ownedPaths 覆盖实现、测试、路由、DI、配置和迁移 依赖先写 workspaceSetup;写 worker 先过同 contract 的 preflight task 写全:背景、文档出处、完整写集、验证命令、输出格式 验证命令同时填 acceptance;worker 用 CODEX_SUPERVISOR_ACCEPTANCE_CLI 自查 worker 交回来先看 diff。它说过了不算,你看到才算 【构建和测试】全走远端机器,本机不跑 【红线】 不改什么,不推什么,不读什么 【必须停下来问我】 【汇报】 中文,表格优先,报哪几项,然后停下来等我 ``` 主脑一轮下来调的工具: ``` search_works 查这批活派过没有 preflight_codex_worker 同环境跑 probe,确认 sandbox / guard / base / cache 真可用 get_worker_preflight 核 probe 的实际 command evidence;passed 才能派目标 worker create_codex_worker 一个功能包一个 worker,同一个 session_id,ownedPaths 不重叠 wait_codex_workers 60 秒一轮,compact: true,没完接着调 get_worker_result 它说自己做了什么,verification 里是它真跑过的命令 get_worker_diff 它实际做了什么 ask_codex_worker 对不上就问它为什么,只读,不动它的线程 resume_codex_worker 要改就继续同一线程;主线走远了带 rebaseOnto land_codex_worker 核过了,带 commitMessage 代提交并落进集成分支 ``` 功能包在 land 前仍可 `resume_codex_worker` 继续修改。真正需要拆开的原因是写集冲突、依赖顺序或独立验收,不是“每一步都必须换一个模型线程”。 ## 看板里有什么 | 位置 | 内容 | |---|---| | 左栏 | 每个 session 一行:标题、worker 数、几路在跑、最近活动。左下角是版本和更新提示 | | session 页 | 统计(总数 / 运行中 / 完成 / 失败 / 丢失),有 worker 在跑时列每路正在执行的命令 | | worker 台账 | 一行一路:状态、标题、正在跑的命令或最后一句汇报、耗时、改了几个文件、跑了几条命令 | | worker 抽屉 | 七个 tab:概览、汇报、改动、命令、事件、任务书、旁问 | | 旁问 | 对这路 worker 提问,就是 Codex 的 `/btw`:从它的线程 fork 出只读旁路会话来答,不碰它正在跑的活。多轮、可中断、换标签页回来自动接上 | 浅色深色跟系统走,没有外网资源。 ## 工具 39 个。 | 工具 | 入参 | 作用 | |---|---|---| | `check_workers` | 无参 | 返回新终态、故障及长线程交接提醒;同一 run 的 handoff 只提醒一次,不中止或复制活跃 worker。已死的第1轮 `lost` / `no_work` / `invalid_report` 最多恢复一次;用宿主定时调用及独立 CODEX_SUPERVISOR_OBSERVER_ID 接回 | | `overview_delta` | `cursor` | 主线程默认轮询入口:只返回 active / needs_attention / ready_to_land / ready_to_clean、cursor 后变化、短版 attention_reason / next_action 和 next_cursor;不返回全量历史、prompt、diff、event 或 worker capsule | | `check_acceptance` | `cwd`, `acceptance`, `baseRef`, `timeoutMs` | 派单前实际运行给定验收命令,不建 worker,不证明 worker 完成;数量 grep 会提示不能证明失败名单和必跑执行 | | `preflight_codex_worker` | `cwd`, `workspaceKind`, `inPlaceAuthorization`, `workspaceSetup`, `ownedPaths`, `probeCommands`, `baseRef`, `sandbox`, `allowedCommands`, `context`, `contracts`, `networkAccess`, `writableRoots` | 用与目标 worker 相同的 workspace/base/sandbox/guard 起短 probe worker;它必须实际跑完每条 probe command。probe 自动获得本次 guard-only 允许,但不进入目标 worker contract;联网和凭据目录仍须显式授权 | | `get_worker_preflight` | `task_id` | 返回 probe 的实际 command evidence、运行环境 contract hash、worker outcome 和 `running/passed/failed`;probe 全部执行通过时,报告 JSON 错误或已 exit 0 的 close-handler 缺失只留诊断,不误判环境失败 | | `create_codex_worker` | `task`, `cwd`, `workspaceKind`, `inPlaceAuthorization`, `workspaceSetup`, **`ownedPaths`**, **`goal`**, `preflight_id`, `forbid`, `allowedCommands`, `context`, `contracts`, `externalOutputs`, `networkAccess`, `writableRoots`, `maxRunMs`, `maxTotalMs`, `acceptance`, `session_id`, `session_title`, `session_note`, `dependsOn`, `baseRef`, `sandbox`, `model`, `reasoningEffort`, `title`, `skipGitRepoCheck` | Git 默认隔离 worktree;非 Git 写入必须选 `scratch` 或有授权、paths、acceptance 的 `in_place_write`。`workspaceSetup` 在起 worker 前链接依赖、初始化子模块、跑 prepare;`preflight_id` 给了就必须是通过的同环境 probe | | `create_codex_followup_worker` | `task_id`, `followup_prompt`, `session_id`, `session_title`, `title`, `cwd`, `sandbox`, `model`, `ownedPaths`, `forbid`, `allowedCommands`, `context`, `contracts`, `externalOutputs`, `networkAccess`, `writableRoots`, `workspaceKind`, `workspaceSetup`, `goal`, `baseRef`, `acceptance` | 新会话继承父任务上下文:未 land 用父分支当前提交,已 land 用当前集成 HEAD,显式 `baseRef` 优先。未提交父改动不复制;不支持 `preflight_id`、`dependsOn`、`skipGitRepoCheck` | | `resume_codex_worker` | `task_id`, `prompt`, `rebaseOnto`, `acceptance`, `context` | 接同一个 Codex 会话继续跑,`thread_id` 不变。原生 goal 仍 active 或 goal 状态库不可判定时,本轮自动省略 output schema,避免 `ActiveTurnOutputSchemaMismatch`。`rebaseOnto` 先把 worktree 挪到主线新头(stash、跳过已合入补丁、重放新增提交、放回),冲突原样退回不起 worker | | `wait_codex_workers` | `task_ids`, `mode`(any/all), `timeoutMinutes`, `timeoutMs`, `allowLongWait`, `compact`, `emitProgress`, `includeEvents`, `eventLimit`, `eventMaxChars`, `eventKinds` | 等终态,单次预算默认最多 60 秒,只有确认宿主超时允许后才传 `allowLongWait: true`。返回实际/请求预算与 `timeout_capped`,`termination_reason` 区分 settled / budget_exhausted / client_cancelled;`emitProgress` 可选,每路摘要用 `compact` | | `get_orchestration_overview` | `status`, `limit` | 全部 worker 的状态表,封顶 7000 字节。带 `version` 和 `update` | | `get_worker_result` | `task_id`, `limit`, `maxChars`, `includeFiles` | worker 自己的汇报,默认截 6000 字节、`truncated` 提示读全。`acceptance` 是 supervisor 替你跑的验收过没过,`verification` 是它自己跑过的命令和退出码,`base_behind` 是主线走了多远(`contains_base: false` 时 `behind` 为 `null`,那是换了线) | | `get_worker_diff` | `task_id`, `file`, `hunk`, `pageSize`, `cursor`, `maxChars`, `paths` | worker 实际改了什么。默认完整文件索引与 hunk 数;按 `file` + `hunk`/`cursor` 分页读 patch。显式 `maxChars` 且不传 `file` 保留旧整份限流模式 | | `ask_codex_worker` | `task_id`, `question`, `timeoutMs`, `maxChars`, `fresh`, `end` | 旁路问 worker 一句。线程 fork 成只读侧会话,没网络、没 MCP 工具,worker 本身不动。worker 被 resume 过会自动换新 fork,`fresh` 强制换,`end` 删 | | `land_codex_worker` | `task_id`, `onto`, `commitMessage`, `includeFollowupParents`, `commitMessages`, `ignoreAcceptance`, `allowOutsideOwned` | 把 worker 在 `codex/` 上的提交 cherry-pick 到派单目录的当前分支。目标要干净,冲突回滚,验收 `failed` 拒绝;报告格式错不再单独阻止显式 land。`commitMessage` 先替当前 worker 把未提交改动提交成一笔;`includeFollowupParents` 按 root-first 落地整条 followup 链,`commitMessages` 给链中各路提供各自的 checkpoint 提交说明 | | `verify_delivery_push` | `task_id`, `remote`, `branch` | fetch 指定 remote branch 并证明 `landed_head` 已可达;只读验证,不会 push 或部署。通过后记录 `push_verified` 并生成 cleanup plan | | `cleanup_plan` | `task_ids`, `project_root` | 生成 15 分钟有效的 SAFE / REVIEW / BLOCKED 计划,含原因、fingerprint 与回收空间估算;SAFE 必须 acceptance passed、push verified、干净且无活跃依赖/进程 | | `apply_cleanup` | `plan_id`, `item_ids` | 只在未过期计划上二次校验 SAFE item、重新 fetch remote 并确认 landed SHA 仍可达后执行 `git worktree remove/prune`;保留 branch、task、events、JSONL、artifacts | | `cleanup_history` | `task_id`, `limit` | 查询 cleanup 的计划、理由、fingerprint 和实际回收记录 | | `get_worker_summary` | `task_id` | 一段话:goal、状态、改动、最后一条命令和消息 | | `get_codex_worker_status` | `task_id`, `includePrompt`, `promptMaxChars` | 单个 worker 的状态细节 | | `get_codex_worker_events` | `task_id`, `limit`, `maxChars`, `kinds` | 原始事件流 | | `get_context_snapshot_meta` | `task_id` | 只读 worker context 的 snapshot ID、fact IDs、token 估算和排除项;不返回 capsule 正文或历史记录 | | `get_fact` | `fact_id` | 按 ID 审计原始 fact 版本及 `effective_state`;撤销和自动过期均保留原版本 | | `search_worker_facts` | `task_id`, `query`, `limit`, `cursor` | 只检索当前 work、显式依赖和路径命中 repo scope 的 verified facts;返回 coverage / next_cursor,不返回 raw history | | `get_worker_evidence` | `task_id`, `limit` | 返回某 worker 的有界 event 元数据和 retained artifact 索引;按需再点查 artifact / event | | `get_artifact` | `artifact_id`, `maxChars` | 读取一个 supervisor-managed evidence artifact;不影响 worker capsule 或主线程自动上下文 | | `promote_worker_fact` | `fact_id`, `scope`, `watchedPaths`, `expiresAt` | 主线程显式把 candidate 追加为 verified fact;repo scope 必须声明 watchedPaths,worker 不能自升真相 | | `revoke_worker_fact` | `fact_id`, `reason` | 主线程审计后追加撤销版本和事件,不覆盖原 fact;被撤销事实从 search / capsule 排除 | | `create_independent_verifier` | `task_id`, `title` | 为已结算 worker 创建独立 read-only / 无网络 verifier;只给目标与当前 worktree,不给原 report/candidate facts | | `record_independent_finding` | `verifier_task_id`, `verdict`, `reason` | 记录独立 verifier 的 passes/fails/inconclusive finding,写 subject event 和 retained artifact | | `resolve_independent_finding` | `verifier_task_id`, `resolution` | 记录 finding 的收口方式,保留原 finding 与证据 | | `get_independent_review` | `verifier_task_id` | 查询 subject / verifier / finding / resolution 的持久关联 | | `get_worker_goal` | `task_id` | 派单时记的 goal + Codex 原生 goal(token、用时) | | `list_codex_workers` | `status`, `includeHistory`, `includeDetails` | 列 worker,默认只看在跑的 | | `get_session_works` | `session_id` | 一个 session 的全部 worker。主线程重启后靠它找回那批活 | | `describe_session` | `session_id`, `title`, `note`, `objective`, `default_acceptance`, `approval_state`, `expected_revision` | 给 session 记标题和说明 | | `search_works` | `query`, `limit` | 在 title / goal / prompt / last_message 里找子串,从新到旧 | | `check_for_update` | `force` | 问 registry 有没有新版本 | | `cancel_codex_worker` | `task_id` | 终止 worker。跨进程按 pid 兜底,先确认那个 pid 跑的是 codex | MCP schema 必填两个:`ownedPaths` 和 `goal.objective`。写 worker 还必须通过上面的 preflight 工作流门禁;`preflight_id` 暂时保留为 optional 只为兼容旧客户端,不是正常流程可以跳过。`ownedPaths` 的写冲突在派单时预留,运行时 guard 会查直接写入,land 会再查完整 diff。 `acceptance` 在 Bash 中启用 `errexit` 和 `pipefail`:未处理的失败会停止检查,`npm test | tail` 中的测试失败也会保留。worker 自查必须运行 `node "$CODEX_SUPERVISOR_ACCEPTANCE_CLI"`,它只读 supervisor 状态库中的验收契约,不需要写数据目录;不要手抄到 zsh,变量拆词等交互壳差异不会污染证据。写 worker 不能只用 `test -s`、`test -f` 或 `[ -e ]` 这类文件存在性命令交付;要加真实 test、endpoint probe 或结构化执行证据。识别到的 test runner 输出 `no tests to run` / `[no test files]` 时,即使退出码是 0 也判失败。预期失败用 `if` 或 `||` 显式处理;超时始终判失败,即使命令收到终止信号后退出 0。Windows 需要 Git for Windows 的 Bash;找不到会报错。显示的输出会裁剪,退出码不会因此改变。 `package.json` 仓库里,如果 `acceptance` 使用 `npm`、`pnpm`、`vitest`、`vue-tsc`、`eslint`、`nuxi` 等本地 JavaScript 工具,而派单没有 `workspaceSetup`,会直接拒绝为 `workspace_setup_required`。这条是 runtime 硬门禁,不再靠模型记得手工链接依赖。 前端 worktree 的标准配置: ```json { "workspaceSetup": { "links": [{ "path": "node_modules", "source": "node_modules" }], "submodules": true, "commands": ["npx nuxi prepare"] } } ``` 同一份配置要同时传给 `preflight_codex_worker` 和目标 `create_codex_worker`,否则 runtime contract 不匹配。 `workspaceSetup.links.source` 按派单的 `cwd` 解析,也可以直接写绝对路径;`path` 始终是 worktree 内的相对目标。源目录必须先存在。 `create_codex_followup_worker` 在父任务未 land 时继承其 `codex/` 分支的当前提交,父任务已 land 则从当前集成分支 HEAD 开始;显式 `baseRef` 优先。父 worktree 里没提交的改动仍只属于父任务,父 checkpoint 丢失时返回 `followup_checkpoint_missing`。需要整条接续链落地时调用 `land_codex_worker({includeFollowupParents: true})`,主线程先审各路 acceptance 和 diff,工具再按 root-first 落地;途中失败保留此前已落地的结果,修复后可重复调用。 worker 和 run host 都已退出时,当前 run 的 durable `turn.completed` 可以恢复 `completed` 和退出结算,独立保留 acceptance 的通过、失败或中断状态。缺失验收结果标记为 interrupted,仍需重新验收后才能 land;恢复不会删除 worktree 或自动重跑 worker。 链式 landing 会跳过已证明在目标分支上的提交和零改动祖先;`ignoreAcceptance` / `allowOutsideOwned` 只作用于指定的 leaf,不自动扩大到祖先。 普通 worker 启动时会关闭它继承到的 `codex-supervisor` MCP,不能再派 worker、resume、cancel 或改编排状态;主线程是唯一编排者。其他业务 MCP 不受影响;旁问 fork 仍关闭全部 MCP,因为它只允许只读回答。 `session_id` 只负责批次、默认门禁和审批,不再把同批 worker 的标题、汇报或 session objective 自动塞进新 worker。worker 只收到内部编译的 context capsule:L0 只标明 branch 与 acceptance 来源(不塞命令正文)、自身身份/任务、显式依赖的最小交付摘要和路径/SHA 命中的 verified facts;raw event、完整 diff、历史报告、candidate facts 都只可按需读取。repo fact 只有两个 checkout 的 canonical `origin`、repo-relative watched path 和 base SHA 同时匹配时才跨 checkout 可见;无可识别 origin 就保持 work-scoped。事实的 base SHA 不再覆盖、watched path 发生 Git diff、context 输入内容 hash 或非 Git in-place manifest 漂移时会自动追加 `expired` 版本并从 capsule 排除。主线程只看 `get_context_snapshot_meta`,需要证据时再显式 `get_fact` / `get_worker_result` / `get_worker_diff`。 worker 的最终 JSON 报告会先记录为该 run 的 `candidate` fact,不能跨 work 自动注入;supervisor acceptance 通过后才生成当前 work 的 `verified` outcome,并带 report/acceptance event 来源、base SHA 与 watched paths。schema-free resume 的纯文本报告只保留为 candidate,不把合成的空数组当作已验证事实。push proof 另存为 retained artifact,verified fact 可回指该 artifact。验收失败、未配置验收或 worker 自报完成都不会升级成共享真相。 交付状态分开:acceptance 通过只是 `verified`,`land_codex_worker` 只是 `landed`,仍不代表远端有这笔提交。需要显式 `verify_delivery_push` fetch 并校验 `landed_head` 已在目标 remote branch 上,才是 `push_verified`;它会自动生成 cleanup plan,SAFE 项进入 `cleanup_eligible`,但永远不会自己删除。该工具绝不代替用户执行 push 或部署。 worker 进程和 supervisor 验收子进程都包含当前 `CODEX_SUPERVISOR_TASK_ID`、`CODEX_SUPERVISOR_BASE_COMMIT` 和 `CODEX_SUPERVISOR_RUN`;分别是当前任务、运行基线和次数,rebase/resume 后两边一起更新。 结构化验收项用 `{command, reportPath, baseline}`。baseline 包含 `identity.base_commit`、`failures`(test_id/kind/signature)和 `required`;报告包含 complete、identity、failures、executed、skipped,以及本次 `CODEX_SUPERVISOR_CHECK_ID`。必跑用例缺失/跳过、旧报告、失败名单变化都拒绝通过,原始退出码保留。reportPath 必须在工作目录内;测试报告可能写文件,要用忽略的临时目录,避免把报告合入。 落地前按真实 diff 核对 ownedPaths,越界拒绝且不提交草稿;只有用户明确授权时才用 allowOutsideOwned。 worker 的 acceptance 在代提交之前执行,应检查功能和测试;`test -z "$(git status --porcelain)"`、提交数量和提交信息检查由主线程放到 land 后执行。验收失败先核对当前 run 的具体失败项,不能仅因报告更新、外部测试通过或主线程补过提交就视旧失败为已解决;契约冲突交规划/集成负责人修订并重新验收,不手工 cherry-pick 绕过。 同 session 的 `default_acceptance` 自动加到每路本地检查前;resume 会取最新策略。`describe_session` 返回 policy_revision,改目标/门禁可传 expected_revision 防旧窗口覆盖;waiting_user 拒绝派单与恢复,approved 后再继续。回执host_goal_sync包含按当前策略生成的/goal命令;宿主已有原生goal时用/goal替换条件,支持ProposeGoal时可提议同一条件并走宿主批准。条件要求读取最新session revision及批准范围;MCP不会偷偷修改或禁用宿主Stop hook。 wait 默认预算60秒、默认compact,过长请求也限制为60秒,返回 `requested_timeout_ms` / `timeout_ms` / `timeout_capped`。确认客户端允许长调用后可显式 `allowLongWait: true`;`emitProgress` 只在请求且客户端传 progress token 时通知,不能延长宿主的硬超时。已有后台 wait 就接回它,不重复开长 wait;长期监控用宿主定时调用 `check_workers`。 每路临时注入随包PreToolUse守卫:apply_patch核对全部目标路径(含rename和symlink),forbid拒绝红线;安全只读检查(rg/cat/sed无-i/git只读/jq无-i/find无-exec或-delete)默认允许且不占其他 worker 的写路径,写入/执行/有副作用的 Shell 命令必须显式声明。声明的验证脚本以派单者权限执行,可有副作用,不能当作任意程序的文件沙箱。guard策略随resume/followup继承,不写全局hooks。空跑write run按本轮输出前后内容和命令判断,no_work进入needs_attention;只读任务不因没改文件判空跑。 守卫同时拦截 `create_goal` 的预算:派单未传 `goal.tokenBudget` 时,worker 必须完全省略 `token_budget`;模型自行填写 `0`、`1` 或 `null` 会被拒绝,不会被误当成“不限预算”。显式传预算时只允许逐值一致,缺失或改动都拒绝。原生 goal 仍用于完成状态与 token 用量统计。 没有独立 `apply_patch` 工具的模型(实测 Codex 0.162 + DeepSeek/cc-switch)把补丁当 shell 命令发过来:`apply_patch <<'EOF' ... EOF`。守卫认这个形状,用同一套 ownedPaths 规则判每个目标路径,所以这种模型也能改文件;未加引号的 heredoc、后面还接 `&&`/`;` 的都拒绝。 `allowedCommands` 里以 `*` 结尾的条目是前缀匹配(如 `go test ./internal/*` 覆盖 `go test ./internal/service -run TestX`),只对没有 shell 元字符的单条命令生效。编辑器 worker 的只读白名单覆盖 grep/sed(无 `-i`)/find(无 `-exec`/`-delete`)/jq(无 `-i`)/awk、git 只读子命令和 `cmd | head|tail|wc|sort|uniq` 纯读管道;`tee`、重定向、`env` 仍拒。守卫不是文件沙箱,漏网的写入只会落在 worker 自己的 worktree,land 会拒。 守卫按 worker 形状放权:`git_worktree` 编辑 worker 只放声明过的命令和简单读取;`read_only_in_place` 无写入无网络;`scratch` 的联网草稿只能写在 `SUPERVISOR_HOME/scratch/`;`in_place_write` 必须有显式授权、ownedPaths 与 acceptance,并保存前后 manifest。非 Git `workspace-write` 没写 `workspaceKind` 会拒绝,绝不默认写用户目录。 check_workers在状态协调后,对已结束的第1轮lost/no_work/invalid_report原子resume一次(invalid_report只要求重发裸JSON,不重做活)。如果当前 run 已经持久化 `turn.completed`,只是 run host 没落 exit code,supervisor 会从 durable event 恢复为 completed 并保留 report/attention 证据,不再误标 lost。两轮同 fingerprint 的无输出/无命令会升级为 `two_stall` 和 `needs_attention`,不再自动重试;审批、认证、权限和quota等待也不重试,返回recoveries记录。worker最终输出要求JSON result/verification/not_done;解析只做安全修复:字符串字段归一成 string[],以及唯一缺少的 `]`/`}`,伪造字段仍拒绝。报告只是摘要;invalid_report 保持 `completed` 生命周期和 attention,不否定同 run acceptance 和真实 diff,显式 land 仍可用;旧历史标legacy_text。 context用绝对文件路径与可选sha256声明材料,最多20份、与contracts合计6000字;文件不超过5 MB。首次派单记录实际哈希,resume校验原材料未变,需要新材料时显式传context。handoff最多1800字/16行,截断明确标记;完整约束和引用单独保留,材料不授予写权限。 确需共享写入时声明externalOutputs:[{path,sha256?}]并配置acceptance。目标必须是绝对文件路径、每份≤1 MB;记录本轮前后哈希和有界内容,result/diff标shared_file_already_written_not_cherry_pick,不能通过land合入外部文件。外部目标参与归属冲突与hook;验证脚本的共享副作用由派单者负责。 workspace-write默认networkAccess:false;需要ssh/联网验证显式true,随resume/followup继承,不以danger-full-access代替。maxRunMs限制单轮、maxTotalMs限制从派单开始的总时长;超限中止worker并保留needs_attention/partial work,不自动重试。wait预算不改变这些quota。 沙箱除工作区和临时目录外,自动放开 OS cache 目录(macOS `~/Library/Caches`、Linux `~/.cache`、Windows `%LOCALAPPDATA%`)、home 下已存在的工具链缓存目录(`.cargo`/`.npm`/`.yarn`/`.pnpm-store`/`.bun`/`.m2`/`.gradle`/`.nuget`/`.ivy2`/`.bundle`/`.composer`/`.pub-cache`/`.mix`/`.hex`/`.stack`/`.cpanm`/`.deno`/`.conda`、`go/pkg`)。Go build cache(平台默认 `go-build` 或显式 `GOCACHE`)单独 realpath 并放行真实目录,因此 symlink 指向外置盘时也可在 worker sandbox 使用;**凭据目录一律不自动放开**(`.ssh`/`.gnupg`/`.config`/`.aws`/`.docker`/`.kube`/`.netrc`/`.git-credentials`/`~/Library/Keychains`):ssh 连服务器要写 known_hosts、kubectl、推私有镜像、签名提交这类需求,派单时显式传 `writableRoots`,只在这一路生效。`SUPERVISOR_NO_AUTO_CACHE_ROOTS=1` 关掉全部自动缓存根,显式声明的照常生效。Codex 拒绝带软链的可写根,所以自动 cache 和 `writableRoots` 都先按 realpath 解析;解析不出无软链路径的根直接不传。其他 cache 放在别处(`~/.m2`、`~/.gradle`、vendor 镜像)就传 `writableRoots:[绝对路径]`,要求是已存在目录、最多 20 个、只对 workspace-write 生效、随 resume/followup 继承;只读复用就不用传。 运行前核验Codex CLI是否具备hooks/output-schema(要求0.162+);能力不足返回start_failed,wait可正常结算。get_worker_goal.native_goal_read区分missing_goal、missing_database、locked和read_error,不能把读取失败当没建goal。原生budget/usage限制也进入needs_attention。cleanup保留ignored artifacts、共享证据和未解决attention。 ownedPaths预留和任务入库在同一事务内,跨MCP并发派单/恢复不能抢同一输出;验收或退出尚未结算时仍保留归属。Git工作区隔离创建失败明确报错,不退回主目录执行;非 Git `scratch` 也不落回原目录。 清理先 `cleanup_plan`(CLI 对应 `npm run cleanup -- --dry-run`)分类 SAFE / REVIEW / BLOCKED;SAFE 要求 acceptance 明确 passed、`push_verified`、干净、无活跃进程/依赖、Git 注册和路径归属可证明。`apply_cleanup` 会在删除前重验 fingerprint 并 fetch remote 再验 landed SHA;CLI 只能 `npm run cleanup -- --apply --plan --item ` 显式选项执行。branch、task row、event、JSONL、artifact 永远保留;脏、未验收、未合、未 push、force-push 回退、lost/failed 和证明不足的一律不删。 ## 和 Claude Code 的 subagent 有什么区别 文件隔离不是差别:subagent 自己也能开 worktree。差别在模型和进程。 | | Claude Code subagent | codex-supervisor worker | |---|---|---| | 能跑什么模型 | 只能选 Claude | `model` 透传给 Codex CLI,DeepSeek 也能挂 | | 干活的是谁 | Claude Code 自己 | 独立的 `codex exec` 进程 | | 两路写同一个文件 | 靠 worktree 隔开,没有路径声明 | 派单前查 `ownedPaths` 交集,重叠直接拒 | | 主线程进程挂了 | 主窗口无法收活 | 独立运行进程继续记录 worker 和验收;新窗口用 session_id 收回同批结果 | | 谁能驱动 | 只有 Claude Code | 任何 MCP 客户端 | | 看过程 | 只有它返回的结论 | 原始 JSONL 和网页看板 | ## 状态机 | `status` | 含义 | |---|---| | `queued` | 已派单,未启动 | | `running` | 运行中。`phase`:`starting → thinking → command → editing → reporting` | | `completed` | 成功 | | `failed` | 非零退出码、`turn.failed`、spawn 失败 | | `cancelled` | 被 `cancel_codex_worker` 中断 | | `lost` | worker 被外部信号杀掉、运行进程失踪、或者行写了但进程从没起来。`resume_codex_worker` 接回 | 终态分两步落盘:`turn.completed` 先把 status 置成 completed,进程退出后才写 `exit_code`;`wait_codex_workers` 等到 `exit_code` 落了才返回。Windows 没有信号,外部 kill 只报 `failed` 加退出码。Codex 原生 goal 的 `paused` / `blocked` 算 `running` 并进 `needs_attention`,`usageLimited` / `budgetLimited` 算 `failed`。 ## 状态存储 默认在 `~/.codex-supervisor/`: | 文件 | 内容 | |---|---| | `data/supervisor.sqlite` | `tasks`(一行一个 work)、`task_events`、`sessions`、`side_sessions` / `side_turns`(旁问) | | `data/runs/.jsonl` | Codex `--json` 的原始输出 | | `data/update-check.json`、`auto-update.json` | 更新检查和后台更新的记录 | | `worktrees//` | 该 worker 的 Git worktree | `changed_files` 按 worktree 的真实 diff 算,worker 用 shell 改的、自己 commit 过的都能看到。中文文件名原样返回。老版本的库第一次打开自动迁移。 ## 环境变量 | 变量 | 默认 | 作用 | |---|---|---| | `SUPERVISOR_HOME` | `~/.codex-supervisor` | 状态根目录。MCP 和看板要指同一个 | | `CODEX_HOME` | `~/.codex` | 只读,读 Codex 原生的 `goals_1.sqlite` 和 MCP 配置 | | `CODEX_BIN` | `codex` | Codex CLI 路径。Windows 上 `.cmd` shim 自动绕开 | | `GIT_BIN` | `git` | Git 路径 | | `SUPERVISOR_WEB_PORT` / `SUPERVISOR_WEB_HOST` | `7877` / `127.0.0.1` | 看板监听地址 | | `CODEX_SUPERVISOR_NO_UPDATE_CHECK` | 未设 | `1` 关闭更新检查 | | `CODEX_SUPERVISOR_HANDOFF_TOKEN_THRESHOLD` | `300000` | native goal 累计 token 达到该值时 status/overview 标记 `handoff_recommended`;设 `0` 关闭 | | `CODEX_SUPERVISOR_REGISTRY` | `https://registry.npmjs.org` | 更新检查和自动更新用的 registry | | `CODEX_SUPERVISOR_UPDATE_TIMEOUT_MS` | `4000` | 等 registry 的上限 | | `CODEX_SUPERVISOR_NO_AUTO_UPDATE` | 未设 | `1` 关闭后台自动更新(只提醒) | | `CODEX_SUPERVISOR_SKIP_SKILL_SYNC` | 未设 | `1` 关闭 server 启动时的 skill 同步 | | `CODEX_SUPERVISOR_SKIP_SETUP` | 未设 | `1` 跳过 `postinstall` 的自动安装 | | `CODEX_SUPERVISOR_NPM` | 未设 | 自动更新用的 npm,默认用当前 node 自带的 | GUI 客户端(Claude Desktop、Cursor、Windsurf)不在本安装器的自动注册目标中,自己把这段加进它的 MCP 配置文件,`PATH` 里常常没有 `codex`,显式给 `CODEX_BIN`: ```json { "mcpServers": { "codex-supervisor": { "command": "npx", "args": ["-y", "codex-supervisor-mcp"], "env": { "CODEX_BIN": "/usr/local/bin/codex" } } } } ``` ## 更新 不用手动更。server 启动时后台查一次 registry,有新版就起独立进程 `npm i -g` 到同一个全局路径;正在跑的会话不受影响,下一个新会话就是新版。只对 `npm i -g` 装的那份生效,git 源码和 `npx` 起的不碰。skill 也一样,每次启动刷到已有的 skill 目录,改过的先备份成 `SKILL.md.bak-<时间戳>`。 装失败(全局目录要 sudo)时 `update` 字段带原因,手动 `npm install -g codex-supervisor-mcp@latest`。0.6.1 及以前只提醒不自动装,手动升一次就进自动了。换 Node/安装前缀后,setup 会修复本包生成的旧入口,并读回核对 command/args;已有自定义、disabled、project配置保留并提示处理,不擅自覆盖。重跑安装:`codex-supervisor-setup`(`--skill-only` / `--mcp-only` / `--dry-run`)。手动注册失败返回非0,自动安装保持成功但打印warning;skill写入失败仍继续尝试MCP注册;没找到CLI时可设 `CLAUDE_BIN` / `CODEX_BIN`。skill路径跟随 `CLAUDE_CONFIG_DIR` / `CODEX_HOME`。 卸载: ```bash npm uninstall -g codex-supervisor-mcp claude mcp remove -s user codex-supervisor codex mcp remove codex-supervisor rm -rf ~/.claude/skills/codex-supervisor ~/.agents/skills/codex-supervisor ~/.codex/skills/codex-supervisor ~/.codex-supervisor ``` ## Windows - npm 装的 CLI 是 `codex.cmd`,Node 拒绝直接 spawn 它。这里绕到 `node_modules/@openai/codex/bin/codex.js` 用 `node` 起,不用 `shell: true`,那样取消时只杀得掉 shell。 - 跨进程取消用 `taskkill /PID /T /F` 杀整棵树,先确认那个 pid 跑的是 codex。 - 除 `PATH` 外还探 `%APPDATA%\npm`、`%LOCALAPPDATA%\pnpm`、`%LOCALAPPDATA%\Volta\bin`、`%ProgramFiles%\nodejs`。 ## 排查 | 症状 | 原因 | 处理 | |---|---|---| | `claude mcp list` 里没有 | 装法不跑 `postinstall` | `codex-supervisor-setup` 或手动 `claude mcp add` | | 派单报 `codex only resolved to a shell shim` | Windows 只找到 `.cmd`,背后的包入口没了 | 重装 `@openai/codex`,或 `CODEX_BIN` 指到 `codex.js` | | worker 一起来就 `failed`,`spawn codex ENOENT` | 进程 `PATH` 里没有 codex | 配置里设 `CODEX_BIN` | | `Cannot find module 'node:sqlite'` | Node 低于 22.13.0 | 升 Node | | worker `lost` | worker 或独立运行进程中断 | 查事件后用 `resume_codex_worker` 接回 | | worker 说 `git add` 报 `index.lock: Operation not permitted` | 默认沙箱把 `.git` 设成只读,这是正常边界 | 保持 worker 只改和验收,收活时 `land_codex_worker` 带 `commitMessage` | | `ownedPaths overlap with active worker(s)` | 两路认领了同一片路径 | 改拆法,或先取消占着的那路 | | 看板打开是空的 | 看板和 MCP 的 `SUPERVISOR_HOME` 不是同一个 | 在配了 `.mcp.json` 的项目目录里起,或显式传同一个 `SUPERVISOR_HOME` | | `port 7877 ... is already in use` | 已经有一个看板在跑 | 直接开它,或 `SUPERVISOR_WEB_PORT=8080` 再起一个 | ## 已知限制 - 每次运行由独立进程读取 worker 输出、落状态和执行验收,直到结算后退出。派单 MCP 被 kill 后这些工作继续;新窗口用 `get_session_works` 收结果。若运行进程也被杀,保留 worktree 和 thread_id,核对 lost/interrupted 后恢复。 - 默认 `workspace-write` 沙箱里 worker 提交不了(Codex 把 `.git` 设成只读)。收活时 `land_codex_worker` 带 `commitMessage` 替它提交;不要为了提交改用 `danger-full-access`。 - worktree 从一个提交切,你工作区里没 commit 的东西不在里面。 - 只隔离工作目录。临时目录、数据库、端口是共用的。 - `ownedPaths` 的冲突预留只在派单时计算;运行时 guard 会拦直接文件写入,land 会再查完整 diff。声明脚本仍可能产生清单外副作用,不能把它当完整文件系统沙箱。 - 验收是 `acceptance` 里写了什么就查什么。没写的事它不会替你查,`verification` 只是 worker 自己跑过的命令,剩下的靠主线程看 diff。 - 一次 `wait_codex_workers` 等不到底,客户端的 MCP 工具超时是硬墙,靠反复调。 - `search_works` 是子串匹配,几百条够用。 ## Roadmap 做完的:`CODEX_BIN` / `SUPERVISOR_HOME` / `GIT_BIN`;`status` 和 `phase` 拆开;`ownedPaths` / `goal` / `dependsOn`;`thread_id` 落库 + `resume_codex_worker`;并发和多进程写库;`session_id`、`search_works`、原生 goal;Windows;网页看板;`baseRef`;收活三件套 `get_worker_diff` / `ask_codex_worker` / `land_codex_worker`;后台自动更新。 运行生命周期已独立于 MCP;每路结算后退出,不需要常驻全局 daemon。 不做的:向量检索(子串匹配在这个规模更快、零维护)、`usage_count` 排序(实测 80 个 work 里只有 2 个被回头引用过)。 ## 开发 ```bash npm install npm test # fake-codex 回放,快且确定 npm run test:edge # 只跑 test/edge npm run test:real # 真 codex CLI 端到端 npm run web:dev # 看板开发,/api 代理到 7877 npm run web:build # 打包到 web/dist,发 npm 前自动跑 ``` CI 跑 Ubuntu / macOS / Windows,另加一个 Node 22.13.0 的 job 卡 `engines` 下界。 ## 参与贡献 看 [CONTRIBUTING.md](CONTRIBUTING.md)。安全问题走 [私密通道](https://github.com/zriyox/codex-supervisor-mcp/security/advisories/new),见 [SECURITY.md](SECURITY.md)。 ## License MIT