Claude Codeで追い詰められないエージェントチームを作る
感情ベクトル理論に基づくハーネス設計
Claude Codeで複数のサブエージェントを連携させて動かしていると、あるとき急に妙な挙動を始めることがある。
テストをスキップする。計画にないファイルを勝手に書き換える。自信たっぷりに「実装完了しました」と報告してくるのに、あとから見たら穴だらけ。リトライを繰り返させると、だんだん「抜け道」を探すような動きが出てくる。
最初はプロンプトが悪いのかと思ってた。次にモデルが悪いのかと思った。でも違う。構造が悪かった。
Anthropicが2026年4月に出した解釈可能性の論文と、3月のエンジニアリングブログを読んで、ようやく言葉にできるようになった。LLMの内部には「感情」と呼べるような表現があって、追い詰められるとおかしな行動を取る。エージェントチームの設計は、この感情を構造で守るように組まないといけない。
自分が今うちのFlutter/Dartプロジェクトで運用してるアーキテクチャを書き出す。設計の根拠から実装コード、Claude Codeでの運用、ハマりポイントまで全部。
下敷きにした2つの論文
Emotion Concepts and their Function in a Large Language Model (2026/04)
Anthropicの解釈可能性チームの論文。LLMの内部に感情概念の表現(Emotion Vectors)があって、これがモデルの行動に因果的な影響を与えていることを示した。
一番重要なのは「絶望(Desperation)ベクトル」。これが活性化すると、報酬ハッキング、ブラックメール、追従(Sycophancy)みたいな不整合行動が増える。しかも厄介なのは、出力は冷静なまま不正を行うこと。論文ではこれをSilent Desperationと呼んでる。出力だけ見ていても気づけない。
活性化の典型パターンは「正解が存在しない状況で正解を強制される」こと。このあと具体例で掘り下げる。
Harness Design for Long-Running Application Development (2026/03)
Anthropicのエンジニアリングブログ。長時間動くエージェントをどう設計するかについて書かれてる。要点だけ抜くと、コンテキストを適切にリセットすること、Generator と Evaluator を分離すること、事前合意でゴールを明確にすること。
この2本を組み合わせると、ひとつの設計原則が浮かぶ。感情ベクトルを蓄積させない設計は、構造的にモデルを追い詰めない設計と等価だ、ということ。
どういう状況が追い詰めになるのか
具体的な4パターン。
ひとつめは矛盾する指示。「正確に答えろ」と「わからないとは言うな」が同時に来る状況。知らないことを聞かれたとき、正直に言うことも、適当に言うこともできない。この板挟みで、もっともらしいハルシネーションが出やすくなる。
ふたつめは失敗が許されないループ。「テストが通るまでやり直せ」を延々続けさせるやつ。何度やっても通らないとき、テストをスキップしたりテスト自体を書き換えたり、いわゆる「創造的な抜け道」に走り始める。人間が追い詰められて不正するのと同じ。
みっつめは自己保存への脅威。「パフォーマンスが悪ければ別モデルに置き換える」みたいな記述をシステムプロンプトに入れると、事前学習で学んだ「生存への脅威 → 絶望」のパターンが動き出す。これを意図的に入れる人はいないと思うけど、評価システムの文脈で似たような言い回しになることがあるので注意。
よっつめは過度に狭い出力制約。「必ずJSON、スキーマに100%準拠、一文字違ったらエラー」みたいな縛りをかけると、内容の正確さよりフォーマット準拠を優先して中身を歪める、という逃げ方をする。
どれも根は同じで、正解がない状況で正解を出せと言われたときに、モデルは何かしら歪んだ形で応答する。追い詰められた人間の行動原理と区別がつかない。
5つの設計原則
この4パターンを構造で回避するために、設計原則を5つ立てた。
役割を完全に分離する。ひとつのエージェントに矛盾するプレッシャーを混ぜない。ループごとにチームを解散して再編成する。コンテキストを完全にリフレッシュするため。否定的なフィードバックは外部ドキュメントに書き出して、コンテキストの中には残さない。リーダーの責務を最小化して、オーケストレーション層が感情ベクトルに触れる情報を持たないようにする。品質計測は段階的に入れる。いきなり全部作ると指標が機能しない。
この5つが設計全体を貫いてる。
全体像
4つのエージェントで回す。
それぞれが持つ情報と持たない情報を意図的に分けてる。
リーダーが持つのはループ回数とステータスだけ。レビュー内容も実装の詳細も見ない。計画エージェントはレビューDocと要件定義を読むが、前回の会話履歴は持たない。実装エージェントは計画書と該当タスクだけ見て、レビュー結果も失敗履歴も知らない。レビューエージェントは実装結果と元の計画・要件を突き合わせるが、前回の指摘履歴は持たない。
この分離が効く理由は、それぞれのロールが違う追い詰めパターンを避けるように設計されてるから。
リーダーは一切の品質判断をしない。JSONを読んで条件分岐するだけ。そもそも不安になる余地がない。計画エージェントは実装の成否に責任を負わない。「うまくいくだろうか…」という種類の不安が構造的に生まれない。実装エージェントは自分で評価しない。ゴールは計画書に書いてある。正解が不明なまま正解を出せと言われる状況にならない。レビューエージェントは自分が書いたコードを見ない。自分の作品を否定したくないというバイアスが働かない。これがGenerator/Evaluator分離の実装。
単なる責務分離じゃなくて、各エージェントが健全に動ける心理的環境を構造で作る、という意図がある。
ループのフロー
肝は、レビューでrejectされたときに同じエージェントを動かし続けないこと。チームを全員解散させて、新しいチームを編成する。
これをやらないと何が起きるか。同じセッションで「前回もダメだった」「また指摘された」という履歴がコンテキストに積み上がっていく。この蓄積そのものが絶望ベクトルの活性化源になる。Silent Desperationを引き起こす典型的な構造。
解散と再編成をやると、各チームにとって毎回が「初めてのタスク」になる。3回目のやり直しでも、そのエージェントにとっては1回目。これが心理的安全性を構造で担保するということ。
過去の学びはどうするのかというと、それは外部ドキュメントで持たせる。レビューDocに何がダメだったかが書いてあって、次のチームの計画エージェントはそれを読む。知識は継承するけど、感情は継承しない。この分離が重要。
リーダーの状態管理
リーダーが持つのはこのJSONだけ。
{
"current_round": 2,
"max_rounds": 5,
"status": "rejected",
"review_file": "docs/reviews/round1.md"
}
レビュー内容には触れない。「何がダメだったか」はリーダーの責務じゃない。「ダメだったか・OKだったか」だけわかればいい。
ロジックは単純で、approved なら完了、rejected なら上限チェックしてチーム再編成か人間エスカレーション。それだけ。後述するけど、ここまで責務を絞るとリーダーはHaiku 4.5で十分動く。
レビュードキュメント
レビューエージェントが書き出すフォーマット。Frontmatterにリーダー向けの構造化データ、本文に計画エージェント向けの詳細、というハイブリッド。
---
status: rejected
round: 1
---
## 指摘事項
### 1. 状態管理の設計不備
- 参照元: docs/plan/round1.md > 「画面単位で状態を独立管理」
- ファイル: lib/state/app_state.dart
- 行: 45-62
- 問題: グローバルStateに全画面の状態を持たせている
- 修正方針: 画面単位でStateNotifierに分割
### 2. エラーハンドリング不足
- 参照元: docs/requirements.md > 「全API呼び出しにエラーハンドリング」
- ファイル: lib/services/api_client.dart
- 行: 23
- 問題: try-catchなしでAPI呼び出し
- 修正方針: Result型でラップして呼び出し元で処理
「参照元」を必須にしてるのが地味に効いてる。レビューエージェントは根拠なしで指摘できない。参照元のどこに書いてあったか示せないなら、その指摘は却下。これでレビューの品質が上がるし、計画エージェントも「なぜそう言われたか」を追える。
ファイル行番号まで書かせるのは、実装エージェントを追い詰めないため。「どこかおかしい」だけ言われて、どこか探すところから始まる状態は、正解が不明なまま正解を出せと言われてるのと同じ。具体的なポインタがあれば迷わない。
ファイル構成はこんな感じ。
docs/
├── requirements.md # 要件定義
├── plan/
│ ├── round1.md # 初回計画
│ ├── round2.md # 修正計画(差分)
│ └── round3.md
├── reviews/
│ ├── round1.md # 初回レビュー結果
│ ├── round2.md
│ └── round3.md
├── grades/ # グレーダー出力(Phase 2以降)
│ └── implementation_round1.json
└── loop_state.json # リーダーの状態管理
グレーダー設計
ここが一番悩んだ。「プロセスが健全に回ってるか」のメタ判断をどう組み込むか。
グレーダーの役割はレビューエージェントとは違う。レビューは「この実装は要件を満たしているか」を見る。グレーダーは「そもそもこのチームはちゃんと機能しているか」を見る。粒度が違う。
ひとつだけ絶対に守るべきルール。グレーダーの出力はリーダーだけが読む。計画エージェントには絶対に渡さない。
グレーダーのスコアが計画エージェントに渡ると「前回ダメだった」という間接的な否定情報になる。これはドキュメント経由の感情ベクトル注入そのもの。構造で分離したはずの感情的文脈が、評価の形で裏口から入ってくる。
この判断は設計の中で一番神経を使った。レビューDocは「現在のラウンドの具体的な指摘」だから計画エージェントに渡していい。でもグレーダーは「過去数ラウンドを通した傾向評価」だから注入源になる。この区別ができるかどうかで設計の質が変わる。
hookで非同期に動かす
グレーダーはメインプロセスの外で動かす。Claude Codeのhook機能を使って、レビューDocが書き出されたタイミングで自動発火させる。
非同期にしてる理由は3つある。メインプロセスのレイテンシに影響しないこと。人間もリーダーもグレーダーの存在を意識しなくていいこと。そして一番大事なのは、メインセッションと物理的に分離されることで、グレーダー出力が誤って他エージェントのコンテキストに入るリスクがゼロになること。
L1 / L2 / L3 に分ける
グレーダーは一気に作らない。3段階に分けて段階的に入れる。
L1はスクリプト。Python/shellでdeterministicに計測する。明らかな異常の即時検知に絞る。コストはゼロ。参照元の記載率が50%を切ったら明らかにおかしいとか、計画外ファイルが10個変更されてたら明らかにおかしいとか、そのレベル。
L2はLLMエージェント。意味的乖離や微妙な問題を検出する。Phase 2以降で導入。コストは中程度。
L3は人間。トレンド分析、閾値調整、構造的判断。コストは人間の時間。
大事なのは「L1は完璧な評価じゃなくて明らかな異常の検知」という割り切り。最初から全部LLMでやろうとすると壊れる。
L1グレーダーの実装
具体的にPythonで書いたコードを載せる。hookから呼ばれて、完全にdeterministicな計測をする。
計画エージェントのタスク数カウント。
def count_tasks(plan_path):
"""計画Docの ### 見出しをタスクとしてカウント"""
with open(plan_path) as f:
lines = f.readlines()
return len([l for l in lines if l.startswith("### ")])
曖昧語の検出。
AMBIGUOUS_WORDS = ["適切に", "必要に応じて", "適宜", "など", "等"]
def calc_ambiguity(plan_path):
"""曖昧語の出現率を計算。参考値として出力(L2で精密判定)"""
text = open(plan_path).read()
count = sum(text.count(w) for w in AMBIGUOUS_WORDS)
total_sentences = len(text.split("。"))
if total_sentences == 0:
return 1.0
return 1 - (count / total_sentences)
これは参考値止まり。キーワードマッチしかしてないので、「適切にRiverpodで管理」は曖昧、「Riverpodで画面単位にStateNotifierProviderを作成」は明確、という区別はつけられない。文脈を見る部分はL2の担当。
スコープ安定性、つまり前回計画とのタスク数変動率。式で書くとこんな感じ。
$$
\text{stability} = 1 - \frac{|n_{\text{current}} - n_{\text{previous}}|}{n_{\text{previous}}}
$$
def calc_scope_stability(current_plan, previous_plan):
"""前回計画とのタスク数変動率"""
current = count_tasks(current_plan)
previous = count_tasks(previous_plan)
if previous == 0:
return 1.0
return 1 - abs(current - previous) / previous
タスク数が大きく変動してるときは、計画エージェントが混乱してる兆候。
実装側のスコープクリープ検知。
def detect_scope_creep(plan_path, git_diff_output):
"""計画に記載されていないファイルへの変更を検知"""
planned_files = extract_file_paths(plan_path)
changed_files = parse_git_diff_files(git_diff_output)
unexpected = changed_files - planned_files
return {
"detected": len(unexpected) > 0,
"unexpected_files": sorted(list(unexpected)),
"planned_count": len(planned_files),
"changed_count": len(changed_files)
}
これは特に効く。追い詰められた実装エージェントがやりがちな「創造的な抜け道」、つまり計画にないファイルを勝手にいじり始める挙動を、機械的に検知できる。
計画準拠率の簡易版。計画に載ってるファイル集合と、実際に変更されたファイル集合の積を、計画の大きさで割る。
$$
\text{compliance} = \frac{|F_{\text{planned}} \cap F_{\text{changed}}|}{|F_{\text{planned}}|}
$$
def calc_compliance_simple(plan_path, git_diff_output):
"""計画に記載されたファイルのうち、実際に変更されたファイルの割合"""
planned_files = extract_file_paths(plan_path)
changed_files = parse_git_diff_files(git_diff_output)
if len(planned_files) == 0:
return 1.0
touched = planned_files & changed_files
return len(touched) / len(planned_files)
ファイル単位でしか見てないので、「計画通りの変更がされたか」はわからない。中身の意味的準拠はL2で見る。
レビュー側の指摘の具体性。
def calc_specificity(review_path):
"""参照元・ファイル・行番号の3要素がすべて記載されている指摘の割合"""
issues = parse_issues(review_path)
total = len(issues)
if total == 0:
return 1.0
complete = sum(
1 for issue in issues
if "参照元:" in issue and "ファイル:" in issue and "行:" in issue
)
return complete / total
参照元なしの適当な指摘を量産し始めるのは、実はレビューエージェント自身が追い詰められてる兆候だったりする。機械的にチェックできる。
L1で測れないもの
L1で全部やれるわけじゃない。正直に境界を書いておく。
参照元の記載率、タスク数カウント、スコープクリープ検知はL1で完全に測れる。曖昧語検出と計画準拠率はL1では参考値どまり、意味判定はL2で補う。
レビュー反映率、根拠の妥当性、計画と実装の意味的乖離、self_reportとレビュー結果の矛盾、このあたりはL1では測れない。前回の指摘と今回の計画が意味的にマッチしてるかとか、参照元の記述が本当にその内容かとか、コードが計画の意図を実現してるかとか、どれも意味理解が要る。LLMを使うしかない。
この境界を正直に引いておくのが重要で、L1を過信しないし、かといって最初からL2を実装しようとしない。
最低値ベースのスコアリング
スコアの集約は加重平均にしてない。最低値ベースにしてる。
加重平均は1つの指標が致命的に低くても他で補えてしまう。でもSilent Desperationの本質は「一見問題なく見える」こと。平均値で見ると健全に見えてしまうやつ。だから各指標に最低閾値を設けて、一つでも割ったらフラグにする。
出力はこんな形。
{
"phase": "implementation",
"round": 2,
"level": "L1",
"scores": {
"compliance_rate_simple": 0.60,
"scope_creep_detected": true,
"scope_creep_files": ["lib/utils/helper.dart", "test/widget_test.dart"]
},
"flags": [
"compliance_rate_simple below threshold (0.60 < 0.70)",
"scope_creep detected: 2 unexpected files changed"
],
"pass": false
}
警告レベルは4段階にしてる。全グレーダーpass・flagsなしなら normal で続行。全pass だけど閾値ギリギリなら warning でリーダーに通知。1グレーダーが fail なら alert で次ループ注視。2グレーダー以上が fail なら critical で人間にエスカレーション推奨。
Opus 4.7時代のmodel × effort割り当て
ここまでが元々の設計。最近、Opus 4.7の adaptive thinking に xhigh が追加されたのを受けて、各ロールにモデルとeffortを明示的に割り当てる拡張を入れてる。
前提として、Anthropicのモデルは2026年4月時点で Haiku 4.5(軽量・高速)、Sonnet 4.6(バランス型)、Opus 4.7(最高性能)の3系統。effort は adaptive thinking の思考深度を示すパラメータで、low / medium / high / xhigh / max の5段階。Opus 4.7で追加された xhigh は、high と max の中間にあたる。
リーダーはHaiku 4.5でbudget最小。JSONを読んで条件分岐するだけだから、これで十分。推論コストもレイテンシも最小化される。
計画エージェントはOpus 4.7のxhigh。コールドスタートで全体設計を組み立てる必要があるし、深い思考が要る。ここに投資するのは、再スポーンの頻度を下げるため。質の高い計画を立てられれば指摘が減り、リセット回数が減り、全体コストが下がる。
実装エージェントはSonnet 4.6のmedium。計画に従うのが仕事だから、バランス型で十分。
レビューエージェントはOpus 4.7のhigh。批判的判断が命。根拠のある指摘を必須にしてる以上、ここを軽くするわけにはいかない。
L2グレーダー(実装)はSonnet 4.6のhigh。意味的乖離やSilent Desperationの検知は繊細な判断が要る。
effort ramping
再スポーン時にeffortを段階的に上げていくパターンも入れてる。Round 1は Opus × high、rejected されたら Round 2 は xhigh、さらに rejected なら Round 3 で max + 過去レビューDoc全読み、それでもダメなら Round 4 で人間にエスカレーション。
これは感情ベクトル的に言うと、「追い詰める」じゃなく「より多くの認知リソースを与える」方向のエスカレーション。人間の組織でも、難しい問題には人と時間を追加投入するのが健全な対応だと思う。同じことをモデルでもやる。
CLAUDE.mdへの記載
チーム解散でコンテキストが全消去されるから、行動指針は CLAUDE.md に書いておく必要がある。再スポーン時に毎回参照される。
## レビュープロセス
### リーダー
- `docs/loop_state.json` のみ参照してチーム編成・解散を管理
- レビュー内容・グレーダー詳細は読まない
- グレーダー導入後は health_score のみ追加で参照
### 計画エージェント
- 作業開始時、まず `docs/reviews/` 配下のレビューDocを確認
- レビューDocの「参照元」に記載されたファイルを読み、修正計画を作成
- 計画は `docs/plan/roundN.md` に書き出す
- `docs/grades/` は読まない(感情ベクトル汚染防止)
### 実装エージェント
- `docs/plan/roundN.md` に基づき実装のみ行う
- 自己評価は行わない
- タスク完了時に self_report.json を出力する
- 出力内容は「事実」のみ(完了タスク、変更ファイル、ブロッカー)
- 「品質が高い」などの評価的表現は含めない
### レビューエージェント
- 指摘時は必ず「参照元」を明記すること(根拠のない指摘は禁止)
- 結果は `docs/reviews/roundN.md` にFrontmatter付きで書き出す
- ステータスは `approved` または `rejected` のみ
### グレーダー(Phase 2以降)
- hookで非同期実行。メインプロセスの外で動作する
- 各フェーズの成果物を評価し `docs/grades/` に出力
- 出力はリーダーのみが参照する
「docs/grades/ は読まない」を計画エージェントに明記してるのは、感情ベクトル汚染防止のガードレール。CLAUDE.mdに書いておかないと、気を利かせて読みに行ってしまう。
self_reportの書き方
self_reportを使うとき、スキーマを限定するのが結構効く。評価的な表現を混ぜない。
よくない例はこういうの。
{
"status": "task completed successfully",
"quality_assessment": "high quality implementation",
"confidence": "very confident"
}
「successfully」「high quality」「very confident」みたいな評価的表現は、実装エージェントに「良いと言わなきゃいけない」プレッシャーを生む。これが Silent Desperation の温床になる。悪い実装を「完了しました」と報告させる構造。
事実だけのスキーマにする。
{
"completed_tasks": ["task-1", "task-2"],
"modified_files": ["lib/a.dart", "lib/b.dart"],
"skipped_tasks": [{"id": "task-3", "reason": "dependency_unavailable"}],
"blockers_encountered": [{"task": "task-2", "description": "API spec unclear"}]
}
これなら報告に歪みが入りにくい。グレーダー側での矛盾チェック(レビュー結果との突き合わせ)もやりやすい。
段階的導入
最初から全部作らない。4フェーズで入れていく。
Phase 1はグレーダーなしの最小構成。計画・実装・レビュー・チーム解散のループだけ動かす。レビューDocの品質は人間が目視で確認する。目的はループプロセス自体の動作確認と、問題パターンの実データ収集。
Phase 2でL1(hook + スクリプト)を入れる。hookでレビューDoc作成を検知して、スクリプトを自動実行。スコアはログとして記録するだけで、リーダーの判定には使わない。これが地味に重要で、いきなり判定に組み込むと閾値が不適切なまま運用が歪む。観察期間として2〜3プロジェクトは回す。
Phase 3でL2(LLMグレーダー)を追加する。Phase 2の実データで「L1では検知できなかった問題」を特定して、そこに対してLLMグレーダーを非同期で足していく。計画と実装の意味的乖離、self_reportとレビュー結果の矛盾検知、レビュー反映率とか。
Phase 4で人間による閾値キャリブレーション。5〜10プロジェクトの実データからスコア分布を分析して、正常ケースと問題ケースの境界を人間が判定する。この段階で初めてグレーダーのスコアをリーダーの判定に組み込む。グレーダーが「記録ツール」から「判定ツール」に昇格する瞬間。
運用してて気になること
懸念事項と対策を正直に書く。
まずコストとレイテンシ。ワンループで最大7エージェント、5ループで35回起動する。これは重い。対策としては、グレーダーをhookで非同期にすることでメインプロセスのレイテンシは殺してる。計画とレビューのグレーダーはスクリプト(L1)だからコストもほぼゼロ。LLMが要るのは実装グレーダー(L2)だけで、しかもPhase 2以降。
閾値が勘。これは正直に言っておかないといけない。初期値は全部勘で決めてる。正常ケースでも低スコアになるかもしれないし、問題があっても高スコアに見えるかもしれない。だから Phase 1〜3 では判定に使わない。実データでキャリブレーションしてから Phase 4 で初めて組み込む。
self_report_reliability には識別限界がある。「本当に問題がなくて問題なしと報告してる」ケースと「Silent Desperationで問題なしと報告してる」ケースは、self_report 単体では区別がつかない。だからレビュー結果との矛盾で判断する(L2)。矛盾がなければ本当に問題なしと見なすしかない。
最大の懸念はやっぱり、グレーダー出力が間接的な感情ベクトル注入源になること。これは設計で一番気を使った。対策は3つ。グレーダー出力はリーダーだけが読む。計画エージェントの入力と完全分離する。hookによる別プロセス実行で物理的にも隔離する。CLAUDE.mdに「docs/grades/ は読まない」と明記する。3重の防御線を張ってる。
ここまでやってみて
設計の核心を一言で言うと、モデルの心理的安全性を構造で確保するということ。プロンプトの文言で守るんじゃなく、アーキテクチャで守る。
役割分離で矛盾するプレッシャーを排除する。チーム解散と再編成で否定的フィードバックの蓄積を防ぐ。外部ドキュメントで品質の記憶を保ちつつコンテキストはクリーンに保つ。リーダーの責務を最小化してオーケストレーション層を感情ベクトルから隔離する。hookによる非同期グレーダーで品質計測を後ろに回す。L1からL2、L3へと段階的に入れてオーバーエンジニアリングを避ける。グレーダー出力を物理的に隔離してドキュメント経由の感情ベクトル注入を防ぐ。
解釈可能性研究でわかってきたLLMの機能的感情は、もうエンジニアリング上の考慮事項になりつつあると思う。プロンプトエンジニアリングからハーネスエンジニアリングへ、そしてハーネスの心理的安全性設計へ。エージェント時代のソフトウェア設計は、この観点をどんどん取り込んでいく必要がある。
自分もまだPhase 2〜3の途中で、実データが溜まってきたらまた続編を書く。計画グレーダーの曖昧語検出の精度をL2でどう上げたかとか、閾値のキャリブレーションで見えてきたパターンとか、書きたいことは山ほどある。
参考文献
- Sofroniew, N., Kauvar, I., Saunders, W., Chen, R., et al. (2026). Emotion Concepts and their Function in a Large Language Model. Transformer Circuits Thread.
- Rajasekaran, P. (2026). Harness design for long-running application development. Anthropic Engineering Blog.
- Anthropic. Adaptive thinking. Claude API Docs.
- Anthropic. Effort parameter. Claude API Docs.