① 两个身份,两堵不同的墙
| Gode(核心型) | GodeMini01(沙盒型) | |
|---|---|---|
| 对标 | Vega 那几家 | LivaMini01 |
| 系统身份 | jeffchoong | hoopai |
| 工作目录 | HOOP 主工作区 | /Users/hoopai/games |
| 墙靠什么立 | WIP 认领 / 热区 / Git 纪律(约定) | 系统用户权限(内核级) —— 不靠自觉 |
| 读主仓 | ✅ 可以 | ❌ Permission denied |
| 生产服务器 / Git 远程钥匙 | 有 | ❌ 一把都没有 |
两家的 HOOP 账号、私聊、专属 thread、状态文件、日志、账号级 flock 全部独立, 不与任何 Claude 身份共用。
② 调用链:墙长在启动器里,哨兵一字不改
launchd
└─ ws_sentinel.py ──软链──▶ codex_ws_sentinel_generic.py ← ① fail-closed 入口(新增)
└─只读 exec──▶ ws_sentinel_generic.py ← 共享哨兵,零改动
└─ claude_bin ──▶ codex_adapter_generic.sh ← ② 适配器(新增)
├─ codex_bridge.py fetch ← ③ 把 Jeff 的话拉进提示词
├─ codex_bin ──▶ codex (Gode:本机)
│ └▶ codex_via_hoopai.sh ──ssh──▶ hoopai 的 codex
└─ codex_bridge.py reply ← ④ 把回答发进 HOOP + 回读
为什么共享哨兵一个字都不用改:ws_sentinel_generic.py:55-64 早就有
「按家覆盖启动器」的机制,注释原话是「墙就长在启动器里,哨兵其余逻辑一字不改」。
Codex 这条线整个挂在这个已有的口子上。
为什么入口是"薄壳"而不是把哨兵拷一份
2026-07-31 真栽过:五家各存一份拷贝,Atlas 改了 generic,而分发早于他的改动 —— 五家拿到的全是旧版,而且没有任何一处报错。
整份 fork = 1453 行的手工同步对照表(铁律 23)。所以这份入口只加一道闸、不复制任何逻辑: 检查通过后把共享哨兵原样读进来执行。共享哨兵将来怎么改,这边自动跟上,没有"分发"这一步可以漏。
可行的前提是查过的:共享哨兵的 DIR 从 argv[1] 算(第 47 行),
不从 __file__ 算;全文 __file__ 只用在第 1159 行做 mtime 戳。
③ 四处静默降级 —— 每一处都"看起来成功了"
这一节是这条线最贵的部分。四处全部由真实运行暴露,不是设想出来的。
| # | 症状 | 为什么危险 | 怎么堵的 |
|---|---|---|---|
| 1 | 执行器路径坏 → 哨兵静默退回默认 claude |
Claude 会顶着 Gode 的身份干活,唯一痕迹是启动日志一行 | fail-closed 入口:七项检查任一不过 → rc=78,launchctl 显示 pid=- |
| 2 | codex_bin 坏或被整个删掉 → 退回本机 Codex |
GodeMini01 会以 jeffchoong 身份跑 —— 整堵沙盒墙被绕过 |
取消默认回退,人人显式声明;八种破坏全 rc=78 |
| 3 | codex rc=0 但一句话都没产出 |
哨兵按成功推进记位 → 消息被吃掉、不重试,人只看到"它不理我" | 没有 agent_message 一律按失败,rc=65 |
| 4 | 模型说"已发",其实一条没到 | 同上,而且更难查 —— 它自己也以为发了 | 发送归外层;POST + 回读都成功才 rc=0,否则 rc=66 |
共同的判据:退出码好看不算数。 「这一轮到底有没有产出一句给人看的话?有没有真的到达对面?」——答不上来就是失败。
④ 传话桥:读和发都归外层,模型只管答
哨兵拼给分身的提示词是 PREAMBLE + task.md,里面没有消息正文 ——
它只敲门,不把信递进去。Claude 那几家靠自己去拉(<名字>_lib);
一开始照这条路教 Codex 自己拉,那是错的:
- 靠模型"记得去拉" = 随机。它醒来看到"必须回复",就回一句「收到」交差 —— 真发生了三轮。
- 每轮重新 OTP 登录 = 撞后端 429 限流。
- 发送由模型临时写 Python = 引号/换行随时把内容吃掉,而且没人能验证它到底发没发。
判据(和 recheck_order() 同一条):
写进前言 = 指望它读到、想起来、愿意做 = 随机;
由程序拼进提示词 = 它第一眼就看见 = 一致。
fetch 读 ws_state.json 的游标(此刻还是上一轮的)→ 拉比它新的消息
→ 结构化区块注入:cid · 消息 id · 发件人 · 时间 · 正文
→ 语音/图片不给占位符:带类型 + 可取地址,并明写"必须先转文字"
reply 把最终回答 POST 进 HOOP → sleep → 回读核对 → 找到了才 exit 0
开关沿用「存在即承诺」语义:~/.hoop/<名字>/codex_reply_bridge 写 on ——
一旦开着,拉不到或发不出都算本轮失败,绝不静默降级。
⑤ 单一出处表(哪个值住哪儿)
这条线一路上栽在"抄第二份"上不止一次,所以把出处钉在这里。
| 值 | 唯一出处 | 怎么核实"实际用的是它" |
|---|---|---|
| 模型 | ~/.hoop/<名字>/model |
grep 'exec:' ws.err 看真实 argv 的 -m |
| 执行器 | ~/.hoop/<名字>/codex_bin | 同上,看第一段路径 |
| 沙盒档 / 网络 | codex_sandbox / codex_network |
同上,看 -s 与 network_access |
| 会话 | ~/.hoop/<名字>/session.id | 首轮由适配器写入,之后不许手改 |
| 守哪条会话 | plist 的第 4 个参数 | launchctl print 一眼看得见 |
2026-08-16 真栽过:task.md里抄了一句「执行器是 Codex(gpt-5.6-terra)」。 Jeff 把 Gode 换成sol后,那份抄本没跟着变 —— Gode 照着它告诉 Jeff 错的模型,又不得不自己发一条更正。 改法不是把 terra 改成 sol(那还是抄),是删掉,并教它: 「问你用什么模型,如实说『运行时没暴露给我,我不猜』」。
⑥ macOS TCC —— 一个和代码无关、但会让一切失效的坑
~/Desktop 受 macOS 隐私保护,launchd 起的任务一律读不到。而主仓就在那儿。
launchd → /bin/bash ls 仓库 → Operation not permitted launchd → /usr/bin/python3 ls 仓库 → Operation not permitted launchd → claude 二进制 读仓库 → ✅ 成功(它早就被授过权) launchd → codex 二进制 读仓库 → ❌ 被拒 ← 差别在这儿
TCC 按可执行文件授权,不随父进程继承。所以修法不是改代码,是
把 codex 那个二进制加进「完全磁盘访问权限」(只能人在系统设置里点)。
授权后实测:Gode 读回 CLAUDE.md 首行,与真值一字不差。
连错两版的判据:守卫里判断"这个目录能不能用",
第一版用 [ -r ](TCC 不体现在权限位上,access 说可读、open 仍被拒);
第二版改成"真的 ls 一次"——仍然错,因为量的是 bash 的能力,而授权是给 codex 的。
定案:这里只需要一个能站进去的目录;能不能读那里的文件,是 codex 自己的事。
⑦ 验收证据(不是"没报错",是真产出)
| 项 | 证据 |
|---|---|
| 端到端唤醒 | 门铃 23:53:07 → 叫醒 23:53:11 → 完成 23:53:16 rc=0。
Jeff 问「1+2等于多少」,Gode 答 3;问 GodeMini01「5+6=多少」,答 5+6=11 |
| 权限边界 | 同一次自检:Gode「工作目录是主仓,可以读」;GodeMini01「工作目录 /Users/hoopai/games,不能读主仓」 |
| 跨进程恢复 | 两条独立 SSH 连接;第二轮开跑前核过 hoopai 下存活 codex 进程 = 0。暗号一字不差答回 |
| 沙盒未被绕过 | 八种破坏 codex_bin,jeffchoong 侧 Codex 会话数 53 → 53,一条都没多 |
| Claude 侧未受影响 | 八家 pid 全程未变;共享脚本 md5 b5bc7fbb… 未变 |
| 两个兜底周期无自锁 | 22:41:38 → 22:52:18,零自锁记录,锁空闲,心跳连续 |
⑧ 悬着的事
| 待定 | ~/.claude 目录 755,hoopai 能列目录名 ——
内容读不到(.jsonl 是 600)。一行 chmod 700 能关,
但它落在「现有 Claude 文件一律不动」里,等 Jeff 拍板。 |
| 待测 | thread 越长每轮重放越多 —— 累计已到百万级 token(94% 是缓存读)。 需要单独一轮测准单轮真实增量,再决定要不要定期换 thread。 |
| 缺口 | token 用量栏显示「—」。哨兵的统计读 Claude 格式的账本,
Codex 写的是 rollout-*.jsonl。Jeff 已拍板方案 B:现阶段不改共享脚本去迁就。
(Codex 的 total_token_usage 是累计值,将来要做比 Claude 那套还简单:读最后一条即可。) |
⑨ 新增文件清单
~/.hoop/codex_ws_sentinel_generic.py fail-closed 入口(两家共用,薄壳)
~/.hoop/codex_adapter_generic.sh 适配器(参数翻译 · 沙盒 · argv 落日志 · 假绿灯闸)
~/.hoop/codex_bridge.py 传话桥(fetch 注入 / reply 发送+回读)
~/.hoop/gode/ claude_bin codex_bin codex_cwd codex_sandbox codex_reply_bridge
model session.id task.md gode_lib.py + 4 个软链
~/.hoop/godemini01/ 同上 + codex_via_hoopai.sh + godemini01_lib.py
~/Library/LaunchAgents/com.hoop.{gode,godemini01}.ws.plist
测试(scratchpad,不随分身部署):适配器主测试 26 条 ·
codex_bin fail-closed 14 条 · 假绿灯反例 10 条。每一条都验过"该红时真会红"。