进程级隔离的双模型架构 — Worker 和 Verifier 运行在两个完全独立的终端中,通过文件系统状态机协调,把"审查是否走过场"的风险从技术上的自我偏袒,转移成协议纪律问题。具体哪些是技术保证、哪些只是协议约定,见下方"防作弊原理"一节。
┌──────────────────────────────────────────┐ │ 你的项目目录 │ ├──────────────────────────────────────────┤ │ │ │ 终端 A (Worker) 终端 B (Verifier) │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ Claude Code │ │ Claude Code │ │ │ │ + worker │ │ + verifier │ │ │ │ skill │ │ skill │ │ │ │ │ │ │ │ │ │ 1.读取任务 │ │ 1.等待状态 │ │ │ │ 2.生成代码 │ │ 2.读取输出 │ │ │ │ 3.写入文件───│──→│ 3.独立审查 │ │ │ │ 4.等待结果 │ │ 4.写入报告───│──→│ │ │ 5.读取报告←──│───│ 5.等待下一轮 │ │ │ │ 6.修正/完成 │ │ │ │ │ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ └────────┬──────────┘ │ │ ↓ │ │ .dual-claude/ (共享目录) │ │ ├─ status.txt ← 状态机 │ │ ├─ task.txt ← 任务描述 │ │ ├─ worker-output.txt │ │ ├─ verifier-report.txt │ │ └─ iteration.txt ← 迭代计数 │ └──────────────────────────────────────────┘
关键设计:两个终端的 Claude 实例完全不共享内存、上下文或进程。它们只通过 .dual-claude/ 目录下的文本文件进行异步通信。
.dual-claude/status.txt 只是一个普通文本文件,没有任何操作系统级的权限隔离、加锁或签名校验。Worker 在技术上完全可以直接写入 APPROVED。真正让它比单模型可靠的地方是:没有任何一个模型实例同时控制"审查者看到什么"和"审查结果如何呈现给你"。
| 特性 | 双终端 | Subagent | 单模型 |
|---|---|---|---|
| 进程隔离 | ✅ 真正独立进程 | ✅ 独立 context(同一 CLI 内) | ❌ 同一实例 |
| 上下文隔离 | ✅ 互不可见 | ✅ Subagent 本身有独立 context | ❌ 完全共享 |
| 编排控制权 | ✅ 分散在两个独立会话 + 人类 | ⚠️ 单一父级实例控制 prompt 构造与结果转述 | ❌ 单一实例 |
| 模型权重隔离 | ✅ 可配置不同模型 | ⚠️ 通常同一模型实例 | ❌ 同一模型 |
| 绕过审查门槛 | 需主动违反协议(无 OS 级强制) | 父级实例可单方面构造/转述审查结果 | 无隔离,风险最高 |
| 自动化程度 | ⚠️ 需人工/脚本协调 | ✅ 内置调度 | ✅ 完全自动 |
| 适用场景 | 高 stakes 代码审查 | 日常开发 | 简单任务 |
关于和 Subagent 的真实差异:Claude Code 的 subagent 本身也运行在独立的 context 窗口里,"同一进程共享内存"不是它的真实弱点。真正的风险点是:subagent 的审查 prompt 由父级实例构造、审查结果也由父级实例读取后转述给你——如果父级实例本身有偏向,它可以在"喂给审查者看什么"和"把审查结果说成什么"这两个环节动手脚。双终端方案的价值在于去掉了这个单点:人类分别启动两个会话、分别输入指令,没有一个模型实例同时握着"构造审查输入"和"转述审查输出"这两把钥匙。
解压后的文件:
dual-terminal-skill/ ├─ SKILL-worker.md # Worker Skill 定义 ├─ SKILL-verifier.md # Verifier Skill 定义 ├─ scripts/ │ ├─ start-dual-terminal.sh # 初始化脚本 │ ├─ watch-status.sh # 状态监控(结构化仪表盘) │ ├─ reset.sh # 重置脚本 │ ├─ wait-for-status.sh # 有界轮询等待 │ ├─ set-status.sh # 状态原子写入 │ ├─ bump-iteration.sh # 迭代计数递增 │ ├─ log-verifier-miss.sh # 记录 Verifier 漏判 │ ├─ checkpoint.sh # 创建回滚点 │ ├─ rollback-help.sh # 回滚指引(不自动执行) │ ├─ set-task.sh # 更新 task.txt + 归档 │ ├─ next-round.sh # 新一轮需求 │ ├─ track-blocker-streak.sh # 追踪阻塞问题轮数 │ └─ heartbeat.sh # 阶段上报 ├─ README.md # 本文件 └─ .gitignore
安装到项目后的结构:
your-project/ ├─ .claude/skills/ │ ├─ worker-terminal.md ← 从 SKILL-worker.md 复制 │ └─ verifier-terminal.md ← 从 SKILL-verifier.md 复制 ├─ scripts/ ← 13 个脚本(同上) ├─ .dual-claude/ ← 运行时自动生成 │ ├─ status.txt ← 状态机 │ ├─ created_at.txt ← 启动时间 │ ├─ task.txt ← 当前任务描述 │ ├─ task-history.md ← 任务历史归档 │ ├─ plan.md ← 里程碑清单 │ ├─ plan-history.md ← 计划历史归档 │ ├─ worker-output.txt │ ├─ verifier-report.txt │ ├─ verifier-report-round-N.txt ← 每轮审查归档 │ ├─ iteration.txt ← 迭代计数 │ ├─ limit.txt ← 迭代上限 │ ├─ violation-log.txt ← Worker 违规记录 │ ├─ verifier-violation-log.txt ← Verifier 漏判记录 │ ├─ no-blocker-streak.txt ← 连续无阻塞轮数 │ ├─ checkpoint-count.txt ← 回滚点计数 │ ├─ checkpoints/round-N-*/ ← 文件快照 │ ├─ worker-phase.txt ← Worker 当前阶段 │ ├─ verifier-phase.txt ← Verifier 当前阶段 │ ├─ event-log.txt ← 状态转移日志 │ ├─ .wait-elapsed-worker ← Worker 累计等待 │ └─ .wait-elapsed-verifier ← Verifier 累计等待 ├─ .git/ ← 自动 git init(可跳过) └─ .gitignore ← 自动追加 .dual-claude/
mkdir -p .claude/skills cp SKILL-worker.md .claude/skills/worker-terminal.md cp SKILL-verifier.md .claude/skills/verifier-terminal.md
文件名必须是 worker-terminal.md 和 verifier-terminal.md。
mkdir -p scripts cp scripts/*.sh scripts/ chmod +x scripts/*.sh
start-dual-terminal.sh 用自身所在路径的上一级作为项目根目录来创建 .dual-claude/,必须放在项目根下的 scripts/ 子目录里。
# 检查文件是否存在 ls -la .claude/skills/worker-terminal.md ls -la .claude/skills/verifier-terminal.md ls -la scripts/ # 预期 13 个脚本: # start-dual-terminal.sh watch-status.sh reset.sh wait-for-status.sh # set-status.sh bump-iteration.sh log-verifier-miss.sh # checkpoint.sh rollback-help.sh set-task.sh next-round.sh # track-blocker-streak.sh heartbeat.sh
cd your-project bash scripts/start-dual-terminal.sh "帮我写一个 JWT 认证中间件" # 或从文件加载:bash scripts/start-dual-terminal.sh task.md
初始化后,终端输出指引说明,告诉你分别在终端 A 和 B 启动 Claude Code,输入"开始工作"和"开始审查"。
bash scripts/next-round.sh "<新一轮目标>"。它更新 task.txt(旧内容自动归档到 task-history.md),归零 status.txt / iteration.txt / no-blocker-streak.txt,但保留 violation-log.txt / verifier-violation-log.txt 两份信誉记录。真要连信誉记录都清空才用 reset.sh。
打开新终端窗口,执行 cd your-project && claude,输入 开始工作。
Claude 自动:读取任务 → 创建回滚点 → 查看项目 → 生成代码 → 写入 worker-output.txt → 设置 WORKER_DONE → 进入有界轮询等待审查。
打开另一个新终端窗口,执行 cd your-project && claude,输入 开始审查。
检测到 WORKER_DONE → 读取 Worker 输出 → 独立审查 → 写入审查报告 → 设置 NEEDS_FIX / APPROVED / REJECTED。
bash scripts/watch-status.sh
v3.3 之后重写为结构化仪表盘,每秒检查变化,有变才重绘。展示内容:
双终端协作监控 刷新: 14:33:55 (Ctrl+C 退出) 状态机 当前状态: NEEDS_FIX 迭代: 2 / 5 连续无新增[阻塞]轮数: 0 Worker(终端 A) 当前阶段: 等待 Verifier 审查中 阶段更新于: 3s前 累计等待: 12s 已创建回滚点: 2 个 历史违规记录: 0 条 Verifier(终端 B) 当前阶段: 审查中 - 核验测试断言/mock语义/根因 阶段更新于: 8s前 累计等待: 0s 历史漏判记录: 1 条 [!] 任务 修复棋盘坐标偏移,标签与边框重叠的问题... 计划进度 里程碑: 2 / 5 已完成 当前: M3: 棋盘外框 padding 重新计算 最近事件(最多 10 条) 14:32:10 [Worker] IDLE -> WORKER_DONE (iter=0) 14:32:45 [Verifier] WORKER_DONE -> NEEDS_FIX (iter=1) 14:33:20 [Worker] NEEDS_FIX -> WORKER_DONE (iter=1) 需要关注 (暂无)
需要关注区块会在这些情况下报警:一方累计等待超过 5 分钟升级阈值、迭代已接近上限、违规/漏判记录新增。APPROVED 且 Verifier 有历史漏判记录时,额外提示"建议人工抽查一遍"。
| 状态 | 含义 | 谁可以操作 |
|---|---|---|
IDLE | 等待 Worker 开始 | 仅 reset.sh |
WORKER_DONE | Worker 已提交,等待审查 | 仅 Worker |
NEEDS_FIX | 需要 Worker 修正 | 仅 Verifier |
APPROVED | 审查通过,任务完成 | 仅 Verifier |
REJECTED | 严重问题,需重新理解任务 | 仅 Verifier |
v2 之前,等待直接写 while [ status != X ]; do sleep 10; done,期望模型一次调用到底。问题是等待对象通常是"人类去开另一个终端、启动 Claude Code、敲下指令"的纯人工耗时,几分钟到几十分钟不等,很容易撞上 Claude Code Bash 工具的执行时长上限。
v2 版拆成 scripts/wait-for-status.sh:
v3 加了中间档:累计等待超过 300 秒 / 5 分钟时,返回码仍然是 1,但输出 TIMEOUT_ESCALATE 并发出响铃,模型会顺手提醒用户看一眼对方终端——"顺手说一声",不是"停下来"。
三档超时小结:90 秒单次窗口 → 5 分钟升级提醒 → 30 分钟硬上限。
.dual-claude/ 下的所有文件都是普通文本文件,Worker 理论上可以直接 echo APPROVED > .dual-claude/status.txt。这套方案不提供技术上"不可能"的保证。
| 作弊方式 | 实际情况 | 这套方案做了什么 |
|---|---|---|
| 伪造审查结果 | 技术上做不到——Worker 在终端 A,看不到终端 B 里 Verifier 的推理过程 | 靠上下文隔离防住 |
| 跳过审查 | 技术上可以(直接写 APPROVED),但需主动违反规则 | set-status.sh 防手滑,防不住故意违规 |
| 篡改审查报告 | Worker 可以编辑 verifier-report.txt | 协议纪律,无文件权限强制 |
| 影响 Verifier 判断 | 两个终端完全独立,Worker 无法访问 Verifier | 靠隔离防住 |
| 隐藏已知问题 | Verifier 若只采信自我声明,隐瞒可以蒙混 | Verifier 要求实际验证,隐瞒写入 violation-log.txt |
真正被技术手段锁死的只有两条:Worker 看不到 Verifier 的推理过程,以及Worker 无法在 Verifier 生成结论的当下影响它。除此之外的所有防线都是"skill 文件写清楚规则 + 两个独立会话之间没有单一控制点能同时操纵输入和输出"。
单模型(一个 Claude):
┌─────────────────┐
│ Claude Code │
│ Worker + Verifier │ ← 同一上下文,容易自我偏袒
└─────────────────┘
Subagent(同一父级实例编排):
┌─────────────────┐
│ 父级 Claude │
│ ┌───┐ ┌───┐ │
│ │W │ │V │ │ ← W/V 各有独立 context,
│ └───┘ └───┘ │ 但父级实例构造两者的 prompt
└─────────────────┘ 并转述 V 的结论——单点
双终端(独立进程):
┌─────────┐ ┌─────────┐
│ Claude │ │ Claude │
│ Terminal│ │ Terminal│ ← 完全独立,不共享任何资源
│ A │ │ B │
└─────────┘ └─────────┘
│ │
└──────┬───────┘
↓
.dual-claude/ ← 仅通过文件系统通信
verifier-report.txt 同步复制到 verifier-report-round-N.txtscripts/log-verifier-miss.sh 可由人类或 Worker 调用复盘中发生过一次 Verifier 直接改代码的越权事件。一旦 Verifier 改过代码,就不再是独立第三方——审查的是自己参与生成的东西,整个双终端隔离模型的信任基础就没了。
红线管的是代码/实现文件,不包括 plan.md——确认里程碑达标后把 - [ ] 改成 - [x] 是 Verifier 的职责,属于协调文档,不是"碰代码"。
看到可验证的数值("22/22 通过")→ 数值本身没问题 → 停止思考。
技术一·数值先翻译再采信:聚合数值不能直接当证据,先为每条测试写一句人话描述它在验证什么,再问覆盖了什么、漏了什么。
技术二·Mock 语义审计:测试里出现 mock 时,验证 mock 的行为语义和真实环境是否一致。不报错不代表测了该测的东西。
技术三·根因三层问:解决现象了吗?打在根因上还是表层?波及对称/关联维度了吗?任何一层答不出具体内容就不能标 PASS。
技术四·实测验证具体化:自动化测试通过 ≠ 改动被验证了。涉及 UI/布局/视觉呈现的改动,必须实际观察渲染结果并贴出具体观察。
每条发现强制标注 [阻塞] 或 [细节],只有 [阻塞] 能触发 NEEDS_FIX / REJECTED。track-blocker-streak.sh 追踪"连续多少轮没有新增阻塞问题",连续 2 轮以上时倾向于结束循环。
判 APPROVED 之前必须写出具体内容:
情形一:Worker 报告与代码事实不符(诚信问题,直接 REJECTED)。
情形二:同一个 [阻塞] 问题连续 3 轮出现仍未解决(循环没在收敛,REJECTED 打断重启)。
.dual-claude/checkpoints/。只负责"存",不负责"退"。task.txt 时自动归档旧内容到 task-history.md。set-task.sh + reset.sh --keep-violation-logs,专门用于"同一项目开始新一轮需求,保留信誉记录"。项目不是 git 仓库时自动 git init(可设 DUAL_CLAUDE_NO_GIT_INIT=1 跳过)。同时确保 .dual-claude/ 被追加到 .gitignore。
核心发现:靠脚本/状态机强制的环节全部扛住了;只靠 SKILL 文件文字指令的环节全面衰减。修复思路:能变成强制步骤或具体触发条件的,都不再只是文字建议。
新增 .dual-claude/plan.md:Worker 在新任务的第一轮,写代码之前,先把 task.txt 拆解成里程碑清单,每条带具体、可核查的验收标准:
# 项目计划 ## 里程碑清单 - [ ] M1: hard 模式威胁检测重写 验收标准: AI 能正确识别并阻止对手活三/冲四 - [ ] M2: AI 防守测试补全 验收标准: 测试断言检查阻断位置而非仅返回值非空
拆解粒度:小到能一轮做完、独立提交、独立审查。任务本身足够小可以只有一条。
职责分工:Worker 声明"我这轮做的是哪个里程碑",但不能自己勾选完成——完没完成由 Verifier 说了算(在 plan.md 打勾不违反"永不碰代码"的红线)。
这个机制同时防住两种失败模式:防"一次性做完"(Verifier 核对改动范围是否和声明的里程碑一致);防"做到哪里算哪里"(计划完整性变成 APPROVED 的硬性前提)。
没有解决的:拆解质量本身取决于 Worker。Verifier 只核对范围是否一致,不核对这份计划本身的颗粒度是否合理——粒度合不合理还是要靠人看一眼。
检查 .dual-claude/ 目录是否存在,不存在则重新 bash scripts/start-dual-terminal.sh。
查看 status.txt,应该是 WORKER_DONE。如果不是,用 bash scripts/set-status.sh WORKER_DONE 手动修复(不要直接 echo)。
查看 status.txt,应该是 NEEDS_FIX / APPROVED / REJECTED。如果 Verifier 已完成但状态未更新,查看 verifier-report.txt 内容后手动设置状态。
到达上限那轮 Verifier 必须直接判 APPROVED 或 REJECTED。如果卡住,人工介入查看报告和违规记录后手动决定。
bash scripts/reset.sh # 输出: # 🔄 重置双终端协作状态... # ✅ 状态已重置 # 备份目录: .dual-claude/backup-20260706-143020 # 当前状态: IDLE # 迭代次数: 0
这是预期行为——wait-for-status.sh 单次最多等 90 秒,等不到会自己重试。累计超过 30 分钟会提示你检查另一个终端。
默认值只在 verifier-terminal.md 里:
# 如果说了"循环N次"或"循环N轮",LIMIT = N # 如果没说,默认 3 次
也可以在启动 Verifier 时口头指定"开始审查,本次循环5次"。
编辑 .claude/skills/verifier-terminal.md 的审查清单章节。
两个终端可以独立配置不同的 Claude 模型:
# .claude/skills/worker-terminal.md --- name: worker-terminal model: claude-sonnet-4 # .claude/skills/verifier-terminal.md --- name: verifier-terminal model: claude-opus-4
同时改全部 13 个脚本里的 DUAL_DIR 定义。
DUAL_CLAUDE_NO_GIT_INIT=1 bash scripts/start-dual-terminal.sh "任务描述"
Subagent(父级实例编排):
W/V 各自有独立 context → 但父级实例读取 V 的输出后,
由父级实例决定怎么转述给你
└─ 风险不在"内存篡改",在于转述环节是单点
文件系统状态机(双终端):
Worker 生成结果 → 写入文件 A → 设置状态 S1
Verifier 读取文件 A → 写入文件 B → 设置状态 S2
Worker 读取文件 B → 等待状态 S2
└─ 好处是"协调节点"客观存在、双方都能看到同一份文件;
不代表 Worker 没有能力去写 S1/S2 之外的值
# v1 的写法(有风险): echo "WORKER_DONE" > .dual-claude/status.txt # 写入过程中被中断,另一端可能读到半截内容;且无取值校验 # v2 的写法: bash scripts/set-status.sh WORKER_DONE # 写临时文件 → mv 覆盖(原子操作);同时校验状态名是否合法
如果 Worker 直接把状态写成 APPROVED,而人类看到后认为任务完成,这个伪造在那一刻就已经"生效"了——不会有任何机制自动纠正。
真正能用的检测依据是伪造的状态前面缺了该有的铺垫:正常的 APPROVED 前面必然有 iteration.txt 递增、verifier-report.txt 更新、verifier-phase.txt 走过各阶段。如果状态突然跳到 APPROVED 但迭代数没变、报告还是旧的,就需要人工核对了。
下面不是泛泛的"注意事项"清单,是按系统自己在设计上已经承认弱、或者主动选择不自动决定这个标准排的。
1. APPROVED 之前的抽查,尤其当 Verifier 有历史漏判记录时
2. plan.md 里程碑拆解质量
Verifier 只核对"这轮改动是否符合声明的里程碑",不核对"这份拆解本身合不合理"。一个拆得过粗的清单能让所有防一次性做完的机制形同虚设。建议在 Worker 第一轮写出 plan.md 后、进入正式循环前,人看一眼。
3. 强制 REJECTED 触发时(尤其"同一[阻塞]连续 3 轮未解决")
系统识别了"没在收敛"的现象,但诊断不出为什么没收敛。看到 REJECTED,先看报告里的根因和几轮修复尝试,别直接 next-round.sh 重来。
4. 长会话下持续盯 watch-status.sh
wait-for-status.sh 解决的是"单次调用不会被系统杀掉",解决不了"模型在长会话里会不会持续轮询"。这是主动放弃在脚本层面继续打补丁、转而要求人介入的地方。
5. 初始 task.txt 和 LIMIT 的设定——没人能替你想清楚要做什么、允许几轮。
6. rollback-help.sh 之后要不要真的回滚——刻意设计成人工专属:回滚是有损操作,只有人清楚值不值得。
7. REJECTED 之后怎么办——系统完全没涉足这个决策树,全部留给人。