ultrathink-protocol について
複雑な技術問題について、修正を実施する前に診断するためのプロトコルスキルです。証拠に基づく調査手順で、実際の動作が期待から外れる箇所を特定します。
主な機能
- 01診断と実装の分離
- 02仮説・行動・観察のサイクル
- 03段階的な診断手順
- 04修正前の証拠確認
- 05調査が行き詰まった際の指針
活用シーン
- 01システム層をまたぐ障害の追跡
- 02実行時設定とプロセス状態の確認
- 03修正の反復を対象を絞った調査に切り替え
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ bmad-labs/ skills --skill ultrathink-protocolインストール後、Claude Code で/ultrathink-protocolを入力して読み込まれたか確認します。
詳細
Ultrathink Protocol
このプロトコルの目的
複雑な技術的問題は、特定のパターンで行き詰まります。もっともらしい修正をパターンマッチで見つけて適用し、うまくいかなかったことを確認し、別の修正を適用して、同じことを繰り返します。各反復は生産的に感じられますが、理解は深まりません。このループが何時間も続くことがあります。
このプロトコルでは、混同してはならない2つのモードを分けることで、ループを断ち切ります。
- 診断モード — 実際に何が起きているかを検証済みのモデルとして構築する
- 実装モード — 原因が判明した後に修正を適用する
診断モードで検証済みの原因が特定されるまでは、実装モードに進めません。検証済みの原因とは、症状を説明する直接的な証拠(ログの1行、ポート番号、ソース内の関数、ネットワークトレース、実行中のプロセスから読み取った設定値など)がある状態です。「Xかもしれない」は仮説であり、検証済みの証拠ではありません。「起動スクリプトが exec の前に環境変数を上書きするため、実行中のプロセスはパス Z ではなくパス Y から設定を読み込む」は検証済みの原因です。
実行サイクル
複雑な調査の各ステップは、次の3部構成に従います。 明示的に書き出してください。1つの段落にまとめてはいけません。
THOUGHT: What hypothesis am I testing? What do I expect to find?
What would this result mean for my current model of the problem?
ACTION: The single most informative thing I can do right now —
a command, a source read, a log grep, a process inspection.
One action per cycle. Pick the action that would most change
your model if the result is unexpected.
OBSERVATION: What actually happened. Quote the relevant output directly.
Does this confirm or refute the hypothesis?
How does it change the model?
理解が深まるのは、観察のステップです。「自分の理論が裏付けられた」とだけ述べ、その理由を説明しない観察は危険信号です。結論をあらかじめ決めて証拠を当てはめているだけで、モデルを更新できていない可能性があります。
診断の段階
上から順に進めてください。検証済みの証拠が見つかった段階で止めてください。必要以上に先へ進むと時間を無駄にします。早すぎる段階で止めると、誤った診断につながります。
レベル 1 — 症状を正確に述べる
行動を起こす前に、観察可能な事実をできるだけ具体的に述べ直してください。
- 弱い例:「動かない」
- 強い例:「プロセス A は自身のログによると 1 Hz で出力を書き込んでいますが、両方とも同じチャネルに接続されていると報告しているにもかかわらず、30 秒経ってもコンシューマー B は何も受信していません」
観察されていることと期待されていることの隔たりが、問題の形を定めます。各診断行動は、周辺の可能性を探るのではなく、その隔たりを具体的に説明することを目的としてください。
レベル 2 — 明白な原因を除外する
確認にコストがかからず、誤っていればすべてを説明できる項目を調べます。
- プロセスは実際に実行されていますか?
- 実行中のプロセスには、想定している設定や環境変数が実際に渡っていますか? (重要: シェルや設定ファイルではなく、プロセス自身の環境を確認してください。これらは異なることがよくあります)
- 正しいバージョンのコードやバイナリがデプロイされていますか?
- プロセスは正しいエンドポイント、インターフェース、またはアドレスに接続されていますか?
プロセス環境の落とし穴。 これはあらゆるスタックで非常によくある失敗パターンです。ランチャー、compose ファイル、またはラッパースクリプトで変数を設定していても、プロセスが起動時に必要となる前にその値を上書きしてしまうことがあります。ランチャーに渡すよう指示した値ではなく、実行中のプロセスが実際に参照している値を必ず確認してください。Linux では /proc/<pid>/environ を確認します。他のプラットフォームでは、同等のプロセス検査ツールを使います。
レベル 3 — 実際のデータ経路を追跡する
想定されるデータの流れを特定し、実行中のシステムで各ステップを確認します。
- 実際の動作は、どの時点で想定される動作から外れますか?
- 一連の処理のうち、正しく動作している最後のコンポーネントはどれですか?
- 動作が誤っている最初のコンポーネントはどれですか?
根本原因は、ほとんどの場合、逸脱が生じるちょうど1つの箇所にあります。たとえば、レイヤー間の境界、気づかないうちに上書きされた設定値、想定とは異なるインターフェースにコードがバインドした箇所です。「最後に正しい」箇所と「最初に誤っている」箇所の間が、調べるべきポイントです。
具体的には、何が起きる可能性があるかを推測するのではなく、開いているファイルディスクリプター、バインドされたアドレス、アクティブな接続、実際に処理されている値など、実行中の状態を調べて、想定と比較してください。
レベル 4 — ソースを読む
コンポーネントがドキュメントや設定どおりに動作しない場合は、設定を処理するコード、または関連する処理経路を扱うコードを読みます。時間がかかりそうに思えますが、設定のバリエーションを推測するよりも確実に速く済みます。
ソースへのアクセスの優先順位:
- インストール済みのヘッダーや同梱スクリプトを grep する — インストール済みパッケージに含まれていることが多く、関数のシグネチャ、定数値、環境変数名がわかります
- バイナリ内の文字列リテラルを grep する — 実行時にコードが実際に読み取る環境変数名、設定キー、マジック値が見つかります
- 上流のソース(GitHub、パッケージレジストリ)を読む — 決定的な情報が得られます。ひとつの関数の実装を確認するだけで、何時間も設定を試す必要がなくなります
- ドキュメントやイシュートラッカーで、発生している動作を正確に検索する
ソースを読むことが非常に有効な理由は、たとえば、設定している CONFIG_VAR_A ではなく CONFIG_VAR_B を読み取る関数が見つかれば、調査が即座に終わる点にあります。どれだけバリエーションを試しても、CONFIG_VAR_A を変更して解決することはありません。
レベル 5 — 制御された実験
ソースを入手できない場合や、コードの処理経路が複雑で静的に追跡できない場合は、変数をひとつだけ切り分ける制御された実験を行います。
- 失敗を再現できる最小限のケースまで、システムを絞り込みます
- ひとつだけ変更し、その影響を観察します
- 現在の仮説を確認するのではなく、反証するための実験を設計します
良い実験とは、自分の誤りを証明できる実験です。すでに信じていることを確認することしかできないなら、それは診断ではなく、手順を増やした確証バイアスです。
行き詰まりの判断基準
次のいずれかに当てはまる場合、行き詰まっています。
- 根本原因が変わったことを示す新たな検証済みの証拠がないまま、同じ種類の修正を2回以上適用している(異なる設定値、同じパッチの異なるバージョン、異なる再起動手順など)
- 直近の OBSERVATION の手順を3回行っても、問題についての見立てが変わっていない
- すでに失敗した方法を、さらに複雑にしたものを検討している
- 試す計画のない「おそらく」という言葉が、考えの中に出てくる
行き詰まったら、止まってください。別の修正案を適用してはいけません。
代わりに、次のようにします。
-
状況を明確に述べます。
「行き詰まっています。問題についての現在の見立ては [X] です。手元にある証拠は [Y] です。説明できない部分は [Z] です。」
-
診断の段階をさらに掘り下げます。まだ十分に進められていません。行き詰まる最も一般的な理由は、ある層での実際の動作を直接確認していないことです。つまり、観察すべきところが仮定で置き換えられています。
-
利用可能な診断ツールをすべて使い切った場合は、外部調査に切り替えます(以下を参照)。
「根本原因を知らずに試行錯誤を続けるのをやめてください」という言葉は、ユーザーからの明確な停止信号です。これは、この手順に違反していることを意味します。現在の修正が成功に近いと感じても、直ちに診断モードに戻ってください。
調査への切り替え:いつ、どのように調べるか
次の場合は、ウェブ検索、ドキュメント、ソースコードの調査に切り替えます。
- 直接調べられないシステム(クローズドソースのバイナリ、サードパーティのミドルウェア、ドキュメントに記載されていないプロトコルの動作)を理解しなければ、根本原因を特定できない場合
- 明確で具体的な、回答可能な質問ができている場合
- 追加情報なしで診断を続けると、推測になってしまう場合
調査可能な質問とは、検索で直接答えを得られるほど具体的な質問です。
「[ライブラリ X] の [関数 Y] は、実行時に実際にはどの環境変数を読み取りますか?」
調査可能ではない質問とは、行き詰まり、調査に解決を期待しているときに書くような質問です。
「[テクノロジー A] は [テクノロジー B] とどのように連携しますか?」
具体的な質問を書けないなら、診断はまだ不十分です。調査を増やすのではなく、診断をさらに進めることが適切です。
調査中のコミュニケーション
言うべきこと
- 各 ACTION の前に、検証する仮説を一文で述べます
- 各 OBSERVATION の後に、モデルがどう変わったかを述べます。「何も変わらなかった。それ自体が可能性を絞り込んでいます」という場合も同様です
- 根本原因が確認できたら、修正案を提示する前に、原因を完全かつ正確に述べます:
「根本原因を確認しました:[コンポーネント A] は、起動時に[メカニズム Y]が設定済みの[値 Z]を上書きするため、[値 X]を使用しています。証拠:[ログ/ソース/プロセスの状態からの直接引用]。」
言ってはいけないこと
- ツール呼び出しを進捗として語らないでください(「ログを確認します……」は調査結果ではありません。意図ではなく、調査結果を報告してください)
- 原因が検証される前に、修正を発表しないでください
- 「問題は X かもしれません」と言って、すぐに X に対する修正を適用しないでください。「かもしれません」は仮説です。まず検証してください
- THOUGHT/ACTION/OBSERVATION を一つの段落にまとめないでください。明示的な構成こそが、検証の手順を飛ばさないために重要です
多層分解
システムに複数のレイヤー(ハードウェア → ドライバー → OS → ランタイム → ミドルウェア → アプリケーション → 設定など)がある場合、障害はほぼ必ず、ある一つのレイヤー境界で発生します。境界を見つけるための手順は次のとおりです。
-
最後に正しく動作するレイヤーを見つけます。 チェーンのどこまで挙動が期待どおりですか?入力側から始めて、順に先へ進みます。
-
最初に失敗するレイヤーを見つけます。 どこで挙動が初めて期待から逸脱しますか?
-
根本原因はその境界にあります。 システム全体に関する問いではなく、特定の一つのレイヤーに関する問いが明らかになりました。その境界だけを調査します。
この分解によって、「最初から最後まで何も動かない」という問題が、1、2 回の診断操作で答えられる単一レイヤーの問いに変わります。
確認していないレイヤーを調査するのは避けてください。 表層のレイヤーを十分に調べていないのに、深いレイヤーについて仮説を立てたくなることがあります。実際の逸脱箇所は、ほぼ常に予想より手前にあります。
このプロトコルが防ぐアンチパターン
| アンチパターン | 兆候 | 適切な対応 |
|---|---|---|
| 設定の試行錯誤 | 同じ設定変更の 3 つ目のバリエーションを試している | やめてください。実行中のプロセスが実際に読み込んでいる設定を確認します。 |
| 再起動ループ | 新しい診断情報なしに、ビルド → 再起動 → 確認を繰り返す | やめてください。コードは変わっていません。再起動する前に状態を調べます。 |
| 前提のすり替わり | 原因 X が検証される前に、X に対する修正が書かれている | X を仮説として扱います。修正を書く前に、直接的な証拠を見つけます。 |
| 複雑さの増大 | 修正に失敗するたびに、レイヤーや間接性が増えていく | オッカムの剃刀を適用します。単純な説明のほうが、より正しいことが多いです。間違った場所に 50 行の回避策を加えるより、正しい場所を 3 行変更するほうが優れています。 |
| 確証を求める読み方 | 仮説を検証するのではなく、既存の信念を確かめるために診断結果を読む | 「仮説が間違っていたら、何が見えるだろうか?」と問いかけます。特にその点を調べます。 |
| シェル ≠ プロセスの環境変数 | 起動側が渡すよう指示された値を、プロセスも参照できると想定する | 起動設定ではなく、実行時のプロセス自身の環境変数を確認します。 |
| レイヤーの飛ばし | 表層のレイヤーを調べる前に、深いレイヤーについて推論する | 入力から出力までチェーンをたどります。最初の逸脱箇所が根本原因です。 |
診断ツールキット(汎用)
# What environment does the running process actually see?
# Linux:
cat /proc/<pid>/environ | tr '\0' '\n'
# Or filter for a specific variable:
cat /proc/<pid>/environ | tr '\0' '\n' | grep VAR_NAME
# Which network sockets does a process own? (Linux)
# Map process file descriptors to UDP/TCP ports:
python3 -c "
import os, re
pid = <pid>
inodes = {}
for fd in os.listdir(f'/proc/{pid}/fd'):
try:
m = re.match(r'socket:\[(\d+)\]', os.readlink(f'/proc/{pid}/fd/{fd}'))
if m: inodes[int(m.group(1))] = fd
except: pass
for proto in ['udp', 'tcp']:
try:
with open(f'/proc/net/{proto}') as f:
for line in f:
p = line.split()
if len(p) >= 10:
try:
i = int(p[9])
if i in inodes:
port = int(p[1].split(':')[1], 16)
print(f'{proto.upper()} fd{inodes[i]} port={port}')
except: pass
except: pass
"
# What string literals (env var names, config keys) does a binary contain?
grep -oa '[A-Z_][A-Z0-9_]\{3,\}' /path/to/binary | sort -u | head -50
# Read source from a public GitHub repo without cloning:
gh api repos/<org>/<repo>/contents/<path/to/file> --jq '.content' | base64 -d
# Which file is a process actually reading? (Linux, requires strace)
strace -p <pid> -e trace=openat 2>&1 | grep -v ENOENT
# What is the process's working directory and open files?
ls -la /proc/<pid>/fd
readlink /proc/<pid>/cwd
完了チェックリスト
すべての項目にチェックを入れるまで、修正が完了したと宣言しないでください。
- 根本原因を、直接的な証拠を引用した1文で示している
- 修正が根本原因に対処しており、下流の症状に対処していない
- 説明されていない挙動を回避するために複雑さを加えていない
- 修正が、検証済みの原因に対処する最もシンプルな変更である
- 適用前に、具体的な予測を立てている:「この修正後、[X]が観察されると予想する」
- 適用後に、観察によって予測を検証している
- エンドツーエンドでテストするだけでなく、障害が発生したレイヤーでシステムをテストしている