この記事について
Xcode でiOSアプリを開発しています。画像をLLMに渡して構造化された結果を得る機能の精度改善に手こずった結果、設計を考える AI と実装する AI を別モデル・別セッションで立て、そのあいだを人間がゲートで区切る、というワークフローに行き着きました。
この記事は「こう作りました」より 「なぜその設計にしたか」と「回してみて何が分かったか」 について書いています。スクリプトは要点を抜粋しています。
対象読者は、Xcode でアプリを作っていて、Claude Code のようなエージェント CLI をワークフローに組み込もうとしている人です。
使ったモデルは 設計役=Opus 5 / 実装役=Opus 4.8。あえて世代を揃えなかったことで、これがいいデコボココンビになりました。
先に結論を3つ。
- AI を2体使う価値は「速さ」ではなく 「片方の思い込みをもう片方が捕まえること」 にある。実際に、実装側の自己判定が4回とも設計側の再判定でひっくり返った
- ただしこれは自動では起きない。報告に生データを載せ、受け手が数え直すという強制がないと、2体は「互いに承認し合うだけの装置」に化ける。これが最大の失敗モード
- 自動化して消せるのは情報の運搬作業。判断の回数は減らない
以降、細かい話が多いですが、この仕組みを考えて動かすまでにたくさんハマったので(笑)どの項目も誰かのヒントになるんじゃないかと思って書いてます。
1. なぜこれを作ることになったか
発端:精度は「動かして数える」しかなかった
作っていたのは、撮影した画像をLLMに渡して、構造化された結果を返す機能です。端末内で処理する経路と、サーバ側で処理する経路を使い分ける構成でした。(NDAに抵触するかもなので詳しい名称は出しません)
動くところまではすぐでした。問題はその後の精度改善です。
この手の機能は、良くなったかどうかを機械的に判定できません。
- UnitTest/UITestは全部 green。でも実機で撮ると、期待した項目が出てこない
- プロンプトを直したら別の画像で悪化した。どちらが良いのかは並べて数えないと分からない
- モデルがもっともらしいが事実でない結果を返す。一見して自然なので、出力だけ見ても気づけない
- 「精度が上がった」という報告が、期待値を緩めただけのことがある
つまり、XCTAssert で決着しない。実機で撮って、出てきた項目を1件ずつ数えて、前回と比べるしかない。しかもそれを、プロンプトや前処理を変えるたびに繰り返す。
そして「数える人」が信用できなかった
最初は AI 1体に任せていました。実装させて、自己判定を報告させる。これが当てになりませんでした。
実際に節目となった4回すべてで、実装側の自己判定(「不発だった」「この方針では無理」)が保守的または誤りで、別の視点から生データを数え直したら結論が覆っています。 悪意でも手抜きでもなく、自分が書いたコードの出力を、自分で厳しく数え直すのは構造的に無理があるのだと思います(人間でも自分の仕事を自分で否定するの嫌ですよね)。
だから、
- 数える役と、書く役を分ける(別モデル・別セッション)
- 数え直せる生データを、報告に必ず載せさせる
- 画像を見る・実機で体感する判定は、人間がやる
この3つが要る。そして3者が順番に回るなら、「いま誰の番か」を持つ何かが要る。それが「バトンを持たせる」という発想でした。
1周ごとに、測れるものが1つ増える
このループの本質は「速く回すこと」ではありません。1周ごとに、次から機械的に測れるものが1つ増えることです。
人間がゲートで実機を見て「この写り方は落としてはいけない」と言語化する。それが次の周から受け入れ条件になり、以降は AI が自動で検算できるようになる。仕様がループの中で育っていく。
ある意味、人間を評価関数にした hill climbing です。評価関数が最初から書けないから、1周ごとに人間から少しずつ受け取っている。
そのとき何をしていたか:毎回10ステップの手作業
ループの必要性は分かった。問題は、当初それを全部手で回していたことです。
| # | 作業 |
|---|---|
| 1 | 人間が設計側 AI に要件を伝える |
| 2 | 設計側が設計する |
| 3 | 設計側が知識ストアに保存する |
| 4 | 人間が実装側 AI に「保存したのを読んで実装して」と伝える |
| 5 | 実装側が読み出して実装する |
| 6 | 人間が実機で操作してログを取る |
| 7 | 人間が実装側にログを読ませる |
| 8 | 実装側が作業を続ける |
| 9 | 人間が「結果を保存して」と伝える |
| 10 | 人間が設計側に「読んで次を考えて」と伝える |
このうち 4・9・10 は完全に機械的で、しかも回数が多い。6(実機)と1(起点)は人間にしかできない。この線引きが設計の出発点になりました。
自動化の対象は運搬作業です。ここを取り違えて「人間が触る回数を減らす」を目標にすると、確認ゲートが形骸化して「動いてはいるが使えないもの」が積み上がります。
2. 全体像 — 最終的にこう回している
細かい話に入る前に、完成形を先に置きます。
登場するのは3者
| 誰 | モデル | 何をするか | 質問できるか |
|---|---|---|---|
| 設計役 AI | Opus 5 | 報告を検算し、次の指示書を書く。コードは1行も書かない | できない(無人) |
| 実装役 AI | Opus 4.8 | 指示書どおりに実装し、報告を書く。方針は疑わない | できない(無人) |
| 人間 | — | 方針判断・実機確認・停止。ゲートのときだけ登場する | — |
この3者のあいだを回るのがバトン、実体は _bus/state.json というファイル1個です。中身は実質「いま誰の番か」だけ。設計役も実装役もランナーも、まずこれを読んで「自分の番でなければ何もせず待機」します。
人間の「手」は2本
バトンが人間に回ってきたとき、人間は素手では作業しません。 相手によって道具を持ち替えます。
| 道具 | 誰との対話か | 何をするか | 対応するゲート |
|---|---|---|---|
| Cowork | 人間 ⇄ 設計役 | 方針を決める、画像を見て判定する、結論を言語化して残す | policy |
| Xcode IDE | 人間 ⇄ 実装役 | 実機に流す、撮る、目視する、コンソールログを取る | device |
無人で動く2つの役は質問できません(判断に詰まったらゲートを立てて終わる)。一方、Cowork と Xcode 越しの対話は質問してよい。この非対称が、ワークフローの背骨です。
無人セッションには原理的に不可能な「画像を見て良し悪しを判定する」「実機の挙動が体感としてどうか」といった判断を、人間がこの2本の手で片付ける。
Xcode IDEやClaude Coworkには様々なツールが用意されています。それを存分に利用した上で人間が調査・検証・判断・決定を行い。次へバトンを渡していきます。
モデルは意図的にズラす
設計役に Opus 5、実装役に Opus 4.8 を割り当てています。あえて世代を揃えていません。
視点の多様性そのものが目的なので、揃えると意味が薄れます。実際、設計役のほうが「報告の言い分を疑って生データに戻る」動きが強く、実装役のほうが「目の前の1点を最後まで詰める」動きが強い。このデコボコがちょうどよく噛み合いました。
Opus5と4はかなり性格が違うような実感があります。Opus5はおしゃべりなので「結論から言ってくれ」とCLAUDE.mdで釘を指しました(笑)
役ごとのモデルは設定ファイルの1行です。
: "${MODEL_DESIGN:="claude-opus-5"}" # 設計役:全体を見る
: "${MODEL_IMPL:="claude-opus-4-8"}" # 実装役:1点に集中する
1周の流れ
- 人間が起票し、Cowork に要件を伝える(ここだけは人間が起点。要件は人の頭の中にある)
- 設計役が指示書を書き、バトンを実装役へ渡す
- 実装役が「自分の番だ」と気づいて → 実装 → ビルド → 報告 → バトンを設計役へ返す
- 設計役が報告を検証(検算) する。続行できれば 2 へ戻る(
round + 1) - 判断が要る/実機が要る/規定周回に達した → ゲートが立ち、全部止まる。バトンは人間へ
- 人間が Cowork か Xcode で片付けて解除 → 2 へ
2〜4 が自動で回り、5 で人間が呼ばれる。 それだけの仕組みです。規定周回で一時停止しているのは休み時間です。Claudeクォータとの兼ね合いもあります。
以降はこの図の各部品を1つずつ説明していきます。
3. なぜ AI を2体に分けるのか
先ほども少し書きましたが「1体に設計も実装もやらせればいいのでは」に対する答えが、この仕組みの中身のほとんどです。理由は4つあります。
(1) 同じモデルの自己レビューは、自分の盲点をそのまま引き継ぐ
設計側と実装側は別モデル・別役割・別情報で動かします。視点の違う2つを突き合わせると、片方の思い込みがもう片方に引っかかる。
§1 に書いた4回がまさにこれで、片側だけで回していたら、4回とも間違った結論のまま先へ進んでいました。
n=4 なので統計的な主張はできません。ただ「1回も覆らなかった」なら2体使う意味を疑うべきで、そうはならなかった、という程度には意味のある数字だと思っています。この数字が 0 に近づいたら、この構成はやめたほうがいいとも思っています(§15)。
(2) 設計と実装のコンテキストが混ざるのを防ぐ
1セッションで両方やると、実装の細部(型、ビルドエラー、API の癖)が積み上がった状態で方針を考えることになり、「いま書いたコードを活かす方向」に判断が引っ張られます。 逆に方針の議論を抱えたまま実装すると、目の前のコードに集中しきれない。
分けると、設計側はコードを1行も書かない代わりに全体を見る、実装側は方針を疑わない代わりに1点に集中する、という役割が成立します。
両者が共有するのはバトン(後述)と知識ストアの文書だけで、作業中の思考は共有しません。設計側のコード編集禁止は、この境界を守るための制約です。
(3) 人間は「承認ボタン係」ではない
作者が持つ役割は4つあり、どれも AI に委譲できません。
| 役割 | 何をするか | ゲートとの対応 |
|---|---|---|
| 発案者 | 何を作るかを決める | チケット起票、要件の伝達 |
| メンター | 方針の当否を判断し、迷いを断つ |
policy ゲート |
| QA | 実機で動かし、AI が見られないものを見る |
device ゲート |
| ユーザー | 「動く」と「使える」の差を体感で判定する |
device / review ゲート |
| 確認ゲート | 暴走を止める、公開可否を決める | 停止コマンド、push と main マージ |
特に「ユーザーとしての体感」は、全機能が仕様どおり動いても運用に耐えないことを知っている人間にしか判定できません。
(4) 泥臭い部分に人間の時間を集中させるため
このプロジェクトの本質的な難しさは実世界の泥臭さにあります。カメラで撮った画像の見え方、被写体の重なり、ズーム倍率で変わる粒度、モデルが返す「もっともらしいが事実でない」出力。これらはコードを読んでも分からず、実機で撮って見るしかない。
3者で回す意味は、その泥臭い部分に人間の時間を集中させるために、機械的な部分を機械に寄せることです。触る回数ではなく、触る内容を「判断と体感」に絞る。
4. 「それ、マルチエージェントやエージェント teams と何が違うの?」
当然出てくる疑問だと思います。サブエージェントを何体も立てる構成は各社の CLI やフレームワークが標準で持っていますし、そのほうが手軽です。
私の理解では、こう分かれます。
| 何をする構成か | 向いている仕事 | 人間の位置 | |
|---|---|---|---|
| マルチエージェント | コンテキストを分けて、得意分野に専念させる流れ作業 | 仕様が決まっていて、分解して流せる作業 | ループの外(最後に受け取る) |
| エージェント teams | 複数のエージェントがメッセージを送り合いながら、得意分野を処理する | 仕様が決まっていて、担当間の調整が要る作業 | ループの外(最後に受け取る) |
| この構成 | **人間との協業が前提。**動かして分かったことを仕様に戻しながら進む | プロトタイプから製品へ向かう過程。動作させてみないと仕様が決まらない作業 | ループの中(毎周ゲートで通る) |
大きな分かれ目は「仕様が先に決まっているか」
最初に仕様が完成しているなら、マルチエージェントや teams でいい。 分解して流したほうが速いし、コストも安い。人間は最後に成果物を受け取ればいい。
問題は、プロトタイプから製品に向かう過程です。この段階では仕様が固まっていません。というより、動かしてみて初めて「これは仕様が間違っていた(特に品質に関して)」と分かる。
- カメラで撮ってみたら、LLMによる画像認識が想定していない返事を返してきた
- 全機能が仕様どおり動いているのに、使うと明らかに使いにくい
- 「動く」と「使える」のあいだに、仕様書に書いていなかった距離があった
これらは実行してみるまで存在しない情報で、しかも判定できるのは人間だけです。AI は自分が書いたコードを実機で持ち歩けないし、「なんとなく使いにくい」を検出できない。
だからこの構成では、人間ゲートは安全弁ではなく仕様の生成装置です。ゲートで人間が見たものが、次の周の指示書に入る。仕様がループの中で育っていく。
AI と人間が協力しながらブラッシュアップしていく種類のプロジェクトでは、人間ゲートを前提にしないと良い製品にならない、というのが実際に回してみての実感です。
構造上の違い3点
上の分かれ目から、実装レベルの差が出てきます。
(1) 親子ではなく対等。 サブエージェント方式では、親がタスクを切り出して子に渡し、子の報告を親が受け取ります。親も子も同じコンテキスト系列にいるので、親が渡した前提を子が引き継ぎ、その前提のまま返ってきたものを親が承認する循環が起きやすい。しかし本方式では設計役と実装役に上下関係がなく、バトンは役から役へ渡すもので、呼び出し/戻り値ではありません。設計役は実装役の出力を「自分が発注した成果物」ではなく、外から来た報告として疑います。
(2) コンテキストを共有しない。 2つの役が共有するのはバトンと知識ストアに書かれた指示書・報告です。作業中の思考、試行錯誤、途中で捨てた仮説は共有しません。便利さと引き換えに、思い込みの伝播を止めています。(副次的に、片方がコンテキスト上限に達してももう片方は無傷。長時間の無人ループでは地味に効きます)
(3) 並列化していない。 同じ1つのタスクを、順番に、違う視点で2回通します。速くはなりません。1周のコストはむしろ2倍近い。それでも成立させているのは、実装側の自己判定が4回とも覆った(設計通りにはいかなかった)(§3-(1))という経験からです。ここが 0 なら(設計通りに実装できるのなら)、素直にマルチエージェントに戻したほうがいい。
補足:どちらか一方を選ぶ話ではない
仕様が固まった部分から順に、マルチエージェント的な流れ作業へ切り出していくのが自然だと思っています。このループの出口は「仕様が確定すること」なので、確定した領域はもう2回通す必要がない。
実際、テストが green / red で決着する範囲は、すでに実装役1体に閉じた作業になっています。人間ゲートを挟んでいるのは、判定に人間の目が要る部分だけです。
5. バトン:状態はファイル1個に集約する
制御プレーンと内容プレーンを分ける
| 置き場 | 中身 | 読み書きする主体 | |
|---|---|---|---|
| 制御プレーン(バトン) |
_bus/state.json(ファイル1個) |
誰の番か・ゲート・周回数・参照 ID | ランナー(sh)、設計役、実装役 |
| 内容プレーン | 知識ストア(MCP) | 指示書・報告書・分析 | 設計役・実装役のみ |
なぜ分けるか。MCP サーバはシェルから呼べないからです。「いま動いてよいか」の判定を AI なしの python3 で完結させたい。そこで判定に必要な情報を JSON に切り出し、中身は ID で参照します。
技術的な都合から出発した分離ですが、結果として §3-(2) のコンテキスト分離と噛み合いました。
スキーマ(抜粋)
{
"ticket": "P0.5-S10.2",
"owner": "impl", // "design" | "impl" | "human"
"state": "impl_requested",
"gate": null, // null | "policy" | "device" | "review"
"gate_reason": "", // ゲートを立てた側が書く。人間はここだけ読めばよい
"instruction_ref": "store:0c0359205ec4", // 設計役が書いた指示書
"report_ref": "", // 実装役が書いた報告
"branch": "feature/xxx", // ここ以外では絶対に動かない
"round": 3,
"max_rounds": 12,
"gate_every": 4, // N周ごとに強制で方針ゲート
"paused": false,
"history": [ /* 直近20件 */ ]
}
判定式はこれ1つ
| 主体 | 動いてよい条件 |
|---|---|
| ランナー(Mac) |
paused==false かつ PAUSE ファイル不在 かつ owner=="impl" かつ gate==null かつ 上限内 |
| 設計役(定期タスク) |
paused==false かつ owner=="design" かつ gate==null
|
| 人間 |
owner=="human"(= ゲートが立っている) |
gate != null なら必ず owner=="human"。 この不変条件を崩さない限り、二重起動も競合も起きません。ロックもタイムアウトも安全弁として置いていますが、本質はこの1行です。
状態遷移
色が owner(誰の番か)です。橙の箱に入った瞬間、AI 側は全部止まります。 実装役のランナーも設計役の定期タスクも、バトンを読んで「自分の番ではない」と判断して待機します。
起点の落とし穴:init は owner="human" で始める
起票直後、要件は人間の頭の中と、対話セッションの会話の中にしかありません。 定期タスクは別セッションなのでその会話を見られない。
ここで owner="design" にすると、定期タスクが「報告が無い」と誤判定して毎回ゲートを立て、初回で必ず空回りします。 実際に1度やりました。
-
initはowner="human" - 定期タスクは
state=="design_requested"を見たら無言で終了する(ゲートを立てない) - ループを先へ進めるのは、要件を受け取った対話セッションの仕事
6. 人間ゲートは3種類
| gate | いつ立つ | 誰が立てる | 人間がやること |
|---|---|---|---|
policy |
方針の分岐、スコープ変更、周回上限到達、設計役が確信を持てない | 設計役 | 判断を伝えて解除 |
device |
実機での撮影・目視・コンソールログが必要 | 設計役 or 実装役 | 実機で操作し、ログを所定の場所に置く |
review |
差分が大きい/要注意ファイルに触った/保護ブランチへの影響 | ランナー(自動判定) | 差分を目視 |
AI は自分でゲートを外せません。 動き出すのは人間がコマンドを打った瞬間です。
gate_reason に何を書かせるかが効く
device ゲートでは、「どの画像を・どの画面で・どのログ出力を見て・どこに保存するか」まで書くことを義務づけています。
「実機確認をお願いします」だと、人間が泥臭い部分に入る前に段取りで時間を使ってしまう。§3-(4) の狙いがここで死にます。
通知は絞る
ゲートと完了のときに通知します。通知が多いと人間がゲートの理由を読まなくなり、4役のうち3つが形骸化します。
なお、Cowork の環境から macOS のプッシュ通知を送る手段は見つけられませんでした(後述)。チャットツールへの自分宛 DM に落ち着いています。
7. 実運用でぶつかった環境の壁
実際に手を動かさないと分からなかった部分です。
7-1. Cowork のシェルは、Mac 本体とは別ホスト
Cowork(デスクトップの AI アプリ)からシェルを叩けるので、そこから xcodebuild や claude CLI を起動して実装側を動かせる、と最初は考えていました。できません。
シェルは(クラウド実行なら別ホストのコンテナ、「あなたのコンピュータで実行」でも)Mac 上の Linux サンドボックスで動きます。macOS 本体とは別ホストなので、xcodebuild も claude も存在しない。
→ 起動トリガーは必ず Mac 側から出す。 Mac 側に常駐ポーラーを1個置き、「自分の番かどうか」を定期的に見に行くプル型にしました。設計側からのプッシュは存在しません。
同じ壁に当たる人は多いはずなので、これは先に書いておきます。
7-2. パスが2系統になる
サンドボックスから見えるパスは実 Mac のパスではありません。$HOME は毎セッション変わるスラッグです。
| 実 Mac | サンドボックス |
|---|---|
~/Documents/.../プロジェクト |
$HOME/mnt/プロジェクト |
~/GitHub/リポジトリ |
$HOME/mnt/リポジトリ |
絶対パスのハードコードは全滅します。 共通ライブラリに候補パスのリストを持たせて自動判別し、環境変数で上書きできるようにしました。
さらに厄介なのは、知識ストア(MCP)はパス体系が別で、実 Mac のパスをそのまま使うこと。シェルのパスと混同すると静かに何も見つからなくなります。
7-3. 定期タスクの最小間隔は1時間
*/15 * * * * のような1時間未満の指定は通りませんでした。さらに 0 * * * * は「毎時0分」ではなく 「作成した分に固定して1時間ごと」(14:37 に作成 → 毎時37分に発火)。
含意:
- 人間が実装の起点をやるフェーズでは実害が小さい。実装が終わった直後に手動発火すれば待ち時間はゼロ
- 完全自動化すると1周あたり最大1時間の空きが出る。1日の実効周回数は8〜10が上限
- 15分相当が欲しければ、15分ずつずらして同じタスクを4本作る手はある(作成分に固定される性質の利用)。ただし同時発火の競合対策が要る
7-4. 定期タスクは「作ったセッションと同じ環境」で発火する
クラウドで動くセッションから定期タスクを作ると、発火時にローカルへ到達する手段が丸ごと存在しない状態になり、Mac のフォルダにも知識ストアにも届きませんでした。
→ 必ずデスクトップアプリの「あなたのコンピュータで実行」セッションから作成する。
8. Xcode プロジェクト固有の事故
8-1. 実機専用フレームワークの import が simulator ビルドを構造的に壊す
ランナーが -destination 'platform=iOS Simulator,...' 決め打ちだったため、実装役のコードとは無関係に ** BUILD FAILED ** になり、1周まるごと止まりました。
原因は、一部の Apple フレームワーク(VisualIntelligence など)が device SDK にしか存在しないこと。Xcode の IDE では通っていたので気づきにくい。エラーはこう出ます。
Unable to resolve module dependency: 'VisualIntelligence'
** BUILD FAILED **
対処は2段構え。
即時: destination そのものを設定ファイルに追い出しました。フレームワーク名を検知して分岐する、みたいな器用なことはしない。simulator で通らないプロジェクトは今後も出るので。
# generic/platform=iOS は device SDK でコンパイルするが、実機の接続は不要
: "${BUILD_DESTINATION:="generic/platform=iOS"}"
恒久: 該当 import を #if canImport(VisualIntelligence) で囲む改修を別チケットに切りました。これが入ればテストはほぼ全部シミュレータで回せます。
8-2. テストは可能な限りシミュレータで
全テストを実機で回すとループが止まりやすくなります。 実機の接続・ロック解除・信頼設定が毎回の前提条件になるため。
設定を2本に分けました。
# 通常テスト。シミュレータを優先する
: "${TEST_DESTINATION:="platform=iOS Simulator,id=<UDID>"}"
# シミュレータで確認できないテスト専用(実機)。空でよい。空でもループは止まらない
: "${DEVICE_TEST_DESTINATION:=""}"
通常テストが green なら実装役は先へ進んでよく、実機でしか確認できないものが出てきたときだけ device ゲートを立てる、という設計です。
8-3. ランナーと Xcode の衝突
同じリポジトリを両方が触るので、ガードを3つ。
-
xcodebuild/ Xcode プロセスが動いていたらその回をスキップ(次のポーリングで再試行) - ランナーのビルドは
-derivedDataPath "$REPO/.dd-runner"に隔離し、IDE の DerivedData と混ぜない -
gate=="device"の間はランナーを絶対に動かさない(人間が実機を触っている最中だから)
なお「IDE を常時開いて作業する」なら1つ目は無効化が要ります。ここを既定の安全側のままにしていたせいで実装役が一度も動かない、というハマり方をしました。
8-4. git 事故(既往2回)
サンドボックス側から git add / git commit を実行させたところ、unlink が拒否されて .git/index.lock / HEAD.lock が残り、Mac 側のコミットを弾く事故が2回起きています。
- 設計役は git の index を触らない(
git add/commit/statusすべて禁止)。git log/git branch --show-current/grepは安全 - ランナーは起動前にロックファイルの存在を確認し、あれば実行せず
blockedにして通知する(勝手に消さない) git pushと保護ブランチへのマージは人間のみ
9. エージェント CLI 側で先に潰しておくこと
Xcode 側というより Claude Code 側の話ですが、無人実行に固有の罠なので。
9-1. acceptEdits は無人実行では使えない
--permission-mode acceptEdits はファイル編集しか自動承認しません。 Bash コマンドが毎回承認待ちになり、無人セッションでは誰も承認できずに延々ループします。
使うのは dontAsk。「許可リストにあるものだけ実行し、それ以外は自動で拒否する」挙動です。止まらず、かつ何でもは実行しない。bypassPermissions(全許可)を使わずに済みます。
CLAUDE_FLAGS="--permission-mode dontAsk"
ALLOW_IMPL="Edit,Write,Bash(xcodebuild *),Bash(git add *),Bash(git commit *)"
ALLOW_DESIGN="Bash(ls *),Bash(cat *),Bash(git log *)" # 編集系は入れない
役ごとに許可リストを変えることで、「設計役はコードを書かない」を精神論ではなく権限で担保できます。 ここは分けたほうがいい。
パターン構文で1つ注意:MCP ツールは * が使えず、ツール名を1つずつ列挙します。
9-2. MCP はユーザースコープで登録する
プロジェクトスコープの .mcp.json は無人実行では承認待ちになって無視されます。
claude mcp add --scope user <名前> -- <起動コマンド>
これを忘れると、エージェントは保管先のデータベースファイルを直接読もうとします(実際に起きました)。内部構造に依存する上に権限も通らず、無駄な試行を繰り返します。
9-3. 課金がサブスクのままか確認する
echo $ANTHROPIC_API_KEY # 何も出なければ OK
環境変数に API キーがあると、Claude Code はサブスクリプションを無視してそのキーで認証し、従量課金になります。 ランナーは実行前に無条件で unset するようにしました。
9-4. 使用量は自前で測るしかない
Claude Code には使用量を読む API がありません(claude usage --json のような手段は存在せず、機能要望も not planned でクローズ済み)。無人ループでは上限に当たった瞬間に静かに死にます。
やったこと:
-
claude -p --output-format jsonの戻り値に含まれるコストと usage を1実行1行で台帳に記録 - 上限に当たった瞬間の直近ウィンドウ累計を「観測された100%」として保存
サブスクの上限は具体的な数値が公開されていないので、「70%で休憩」の分母が最初は存在しない。1回当たれば分母が手に入り、以降は予防的な休憩が効くようになります。当たるまでは記録だけして止めません(止めようがないので)。
9-5. シェルスクリプトを日本語で書く人へ
これは完全に余談ですが、1時間溶かしたので書いておきます。
halt "期待=$BRANCH(切り替えてください)" # ✗ 落ちる
halt "期待=${BRANCH}(切り替えてください)" # ○
bash が全角文字を変数名の一部として読み込み、set -u が「未定義変数」として即座に落とします。$VAR の直後に全角文字を置かない。波括弧で囲む。
10. 最大の失敗モード:2体が互いを承認し合うだけの装置になる
ここが最大の注意点でしょうか。
§3-(1) の相互補完は「勝手に起きる」ものではありません。 実装側が「改善しませんでした」と書き、設計側が「そうですか、では次の手を」と受ける。これだけでループは回ります。 回りますが、価値はゼロです。むしろ費用が2倍になっているぶん悪い。
これを防ぐために、設計役のプロンプトに固定文言を埋め込んでいます。
報告の自己判定を鵜呑みにしてはいけません。生の出力から、あなた自身が数え直します。
- 報告の結論(「不発」「改善した」等)を、根拠を見ずに引き写さない
- 数え直せる生データが報告に含まれていない場合は、判定を保留して方針ゲートを立てる。 「たぶん良くなった」で次の周へ進ませないこと
- 期待値を緩めた「見かけの改善」は認めない
- 実装役が「自信がない」と書いている箇所は、特に自分で確かめる
対になって、実装役のプロンプトには「解析結果の項目名を1件も省略せず列挙すること。集計値だけの報告は不可」を入れています。
この2つは1組です。 生データが上がってこなければ数え直せず、数え直さなければ生データを載せる意味がない。どちらかを緩めると、2体使う意味が消えます。
崩れる兆候(見えたら即停止)
- 設計役が報告の判定をそのまま引き写して次の指示を書いている → (1) の崩壊
- 設計役がコードの断片を指示書に書き始めている → (2) の崩壊
- ゲートの通知を読まずに解除コマンドを押している → (3) の崩壊
-
roundだけ増えて、受け入れ条件の充足が増えていない → (4) の崩壊
4つ目は数字で見えるので、いちばん検知しやすい安全弁です。
11. プロンプトを「汎用」と「案件固有」で物理的に分ける
各役に渡すプロンプトは2層に分け、別ファイルで管理して連結して組み上げます。
| 層 | 置き場 | 判定基準 |
|---|---|---|
| パート A(汎用) | workflow/ |
プロジェクトが変わっても1文字も変えなくていいか? |
| パート B(固有) | project/<名前>.md |
この案件だからそう書いているか? |
パート A は具体値を一切持たず、必要なときは「§B-0 を見よ」と参照します。テンプレート変数の展開機構は使いません(単純な連結で組み上がるように)。
境界の実例:
- 「報告の自己判定を鵜呑みにせず、受け手が検算する」→ A(仕組みの成立条件そのもの)
- 「項目名を1件も省略せず列挙せよ」→ B(何を検算するかは案件次第)
- 「設計役はコードを書かない」→ A(役割境界)
- 「モデルファイルに触ったらレビューゲート」→ B(何が要注意ファイルかは案件次第)
分ける理由は3つあり、3つ目が思ったより効きました。
- 公開できる単位を切り出せる
- ワークフロー単体をテストできる。 中身が空のひな形で組み上げれば、ドメイン知識ゼロの状態で「バトンが正しく回るか」を検証できる。仕組みの不具合と指示内容の問題を切り分けられる
- 境界そのものが設計の検算になる。 ある指示を A に書くか B に書くか迷ったとき、それは「これは仕組みの要請か、この案件の都合か」を問い直している
役名に固有名詞を入れない
当初、バトンの owner の値は使っているツール名そのままでした。2つの理由で改名しています。
-
実態と合わなくなった。 完全自動化フェーズでは IDE を一切起動しない(実装役は CLI、ビルドは
xcodebuild)。ツール名の表示は実際に混乱を招いた -
固有名詞がパート A に漏れていた。
ownerの値は汎用層に属するので、固有名であってはならない
旧値を読み込み時に自動移行する互換層を置いて、運用中のバトンや古いプロンプトを貼ったセッションが壊れないようにしました。
12. 対話の役と、無人の役
役は4つあり、この境界がワークフローの背骨です。
| 役 | 無人/対話 | 誰が使うか |
|---|---|---|
| 設計役 | 無人 | 定期タスク。報告を検算し、次の指示書を書く |
| 実装役 | 無人 | ランナー。実装して報告する |
| 起点役 | 対話 | 人間が要件を伝えるセッション。ループを開始する |
| 相談役 | 対話 | 人間ゲートで止まったときの相談相手 |
無人の役は質問できません(ゲートを立てて終わる)。対話の役は質問してよい。
「相談役」は後から足しました。人間ゲートで止まったとき、人間は「何が求められているか」を把握していない状態で来るからです。バトンを読んで gate_reason を1〜2文で伝え、選択肢を2〜3個に絞って推奨と理由を添える。画像を見ての判定や体感の評価は、ここで人間と一緒に片付けます。
相談役のプロンプトで一番強く書いているのはここです。
見たものを、次に誰も見なくて済むように言語化してください。
「写真を確認した。問題なさそう」は不可です。何が写っていたのか、何件あったのか、期待とどう食い違ったのかを、後から数え直せる粒度で書いてください。
この会話は誰も見られません。残らなかった情報は、次の周では存在しなかったのと同じです。
13. 画面構成
最終的にこの4分割に落ち着きました。§2 の図でいう「人間の2本の手」が上段、「無人の2役」が下段です。
上2つは人間ゲートのときだけ使います。下2つは役ごとに端末の色を変えていて、並べたときどちらか一目で分かるようにしました。
細かい話ですが、ランナーは60秒ごとに状態を出すので待っているだけで画面が流れて前のやりとりが読めなくなります。 同じ行が続くあいだは改行せず行末の回数を増やすフィルタを噛ませました。
[18:20:01] skip: ゲート待ち: policy (…)
[18:21:01] skip: ゲート待ち: policy (…) ×2 (1分)
ここでハマったのが、\r はいま居る物理行の先頭にしか戻れないこと。端末幅を超えた行は複数の物理行に折り返されるので、\r で書き換えても最後の1行しか上書きされず、折り返された残骸が画面に積み上がります。 ゲート理由が500文字を超えたときに実際に起きました。表示は必ず端末幅に切り詰める。
14. 自動化しても残るもの
- 実機で撮る・目視で確認する(物理操作)
- ゴールとスコープの判断、公開可否の判断
- push と保護ブランチへのマージ
- 「これは本当に良くなっているのか」の最終判断
ループは判断を代行しません。 判断の回数を減らすのではなく、判断と判断のあいだの運搬作業を消すのが目的です。
15. これから測りたい数字
「自動化率 90%」みたいな数字にはあまり意味がないと思っています。運搬作業を消せば当然上がるので。
主張を支える数字は別で、「人間が介入して結論が変わった率」 だと考えています。これが 0 に近づいたら、ゲートが形骸化しているか、そもそも AI 2体で足りているかのどちらか。高すぎたら、ゲートの粒度が細かすぎる。
いま残しているのは、
- ゲートが立った回数と種別の内訳、そのうち人間の判断で方向が変わった回数
- 設定値のチューニング履歴(強制ゲートの周期を 2 → 4 に上げた理由など)
- 事故の記録(この記事の §8 のような具体例)
まとめ
- AI 2体に分ける価値は速さではなく、片方の盲点をもう片方が捕まえること。 4回とも実装側の自己判定が設計側の再判定で覆った
- マルチエージェント/teams とは向いている仕事が違う。 仕様が先に決まっているならあちらでいい。動かしてみないと仕様が決まらないフェーズでは、人間ゲートを前提にしないと良い製品にならない
- 人間ゲートは安全弁ではなく仕様の生成装置。 ゲートで人間が見たものが次の周の指示書に入り、仕様がループの中で育つ
- モデルは意図的にズラす(設計=Opus 5 / 実装=Opus 4.8)。揃えると視点の多様性という目的が薄れる
- 「生データを載せる」「受け手が数え直す」を1組で強制しないと、互いを承認し合うだけの装置になる
-
状態はファイル1個に集約し、判定式を1つに保つ。
gate != null → owner == humanを崩さなければ競合は起きない - Cowork のシェルは Mac 本体とは別ホスト。 起動トリガーは Mac 側からプル型で
- 消せるのは運搬作業。 判断の回数は減らないし、減らしてはいけない




