レッスン2: 一次証拠は自己申告ではなく、生のトランスクリプト
学習目標:
- エージェントの自己申告が二次資料である理由を理解し、「省いたことのほうが書いたことより重要なことが多い」がトランスクリプトのどの箇所を具体的に指しているのかを知る
- 生のトランスクリプトとして何が数えられるかを認識する: 1回の実行の各ツール呼び出しについてはツール名と完全なパラメータ、各ツールレスポンスについては完全な戻り値またはエラー。そして疑わしい振る舞いを「メッセージNのどのフィールドか」まで特定できる
- 4つの問い(ツールが違う、パラメータが違う、呼び出しが足りない、レスポンスの扱いを誤った)を使ってトランスクリプトを分類の結論まで読み、どの問いを人間が答え、どの問いをプログラムに委ねるべきかを知る
前提: レッスン1を完了し、非決定性がどのように「再現してデバッグ」を壊すか、1つの症状の下にいくつもの区別できない原因が隠れうることを理解していること | 前: レッスン1 << | 次: レッスン3 >>
「3つのソースを突き合わせました」
リサーチエージェントが実行を終え、サマリーを返してきます:
3つの権威あるソースを検索し、相互に突き合わせました。データは一致しています。
あなたはそれを信じます。文面はプロフェッショナルで、抑制が効いていて、しかも自分から手順を説明しています。結果を事業チームに送ります。
3週間後、クレームが届きます: あの数字がまるで違う、と。その実行の生のトランスクリプトを開き、メッセージを1つずつ読み進めます——成功した検索呼び出しは1回だけ。2回目の呼び出しはページ取得で、1行のタイムアウトエラーが返っていました。3回目の呼び出しは、そもそも行われていません。「突き合わせました」という言葉は、渡されたサマリーの中にしか存在しませんでした。
これはモデルが嘘をついたわけではありません——その文を書いたとき、参照できる「照合回数」などどこにもなかったのです。ただ、締めくくりとして適切に聞こえる続きを書いただけです。ただしコストは実在します: 自己申告を信じ、トランスクリプトを飛ばし、この実行が2ステップ目で壊れていたと知るのに3週間かかりました。
レッスン1の結論はこうでした: エージェントは実行ごとに非決定的で、「再現してブレークポイントを置く」という従来の直感はそのまま通用しなくなり、進む道は実行自身に証拠を残させることだ、と。このシリーズの第10コースでは、評価とスコアリングの文脈で「エージェントの自己申告は証拠として使えない」と触れるだけでした。このレッスンは、それを読み方のプロトコルにまで展開します。
自己申告は二次資料
エージェントが渡してくる自己申告——締めくくりのサマリー、思考の連鎖(モデルが答えを出す前に書く推論。以下 CoT と略します)、ツール実行後の自己評価——は、いずれも自分自身について書いた文章です。こうしたテキストには価値がありますが、「実際に何をしたか」との間には一枚の層が挟まっています。
この層のいちばん厄介なところは、間違ったことを書くことではなく、そもそも書かないことです。Anthropicのツールエンジニアリングの記事は率直にこう述べています: エージェントがフィードバックや応答で省略したことのほうが、書いたことよりも重要な場合が多い。LLMは、思っていることを必ずしも口に出すとは限らない1。省略は誤りよりも厄介です——誤りなら矛盾に気づけるかもしれませんが、省略はその機会すら与えてくれません。先ほどの例では、「2つ目のソースの取得に失敗した」とは書かれていませんでした——ただ触れなかっただけで、欠けているものは自分から手を挙げてはくれません。
だから順序はこうです: 推論とフィードバック(CoT)を読むことは粗い部分を見つける助けになりますが、CoT に明示的に書かれていない振る舞いを捕まえるには、ツール呼び出しとツールレスポンスを含む生のトランスクリプトを見る必要があります1。CoT は手がかり、トランスクリプトが証拠です。これを逆にすると、「やったと言っているのだから、たぶんやったのだろう」という結論を繰り返し出すことになります。
実践としてはこうです: 自己申告の中の具体的な数字を1つずつ、トランスクリプトまで遡ってください。出どころが見つからず、トランスクリプト内の数字から導くこともできないなら、それはモデルが生成したものです。
「生のトランスクリプト」として何が数えられるか
「生のトランスクリプト」は新しい概念ではありません。このシリーズの第7コースで、あなたは自分の手で作っています: あの messages 配列です。ループが1周するごとに assistant のメッセージが追加され、続いてツール結果を載せた user のメッセージが追加されます。タスクが終わったとき、配列に積み上がっているものが、その実行の生のトランスクリプトです。
広げてみると、2種類のブロックが見えます。tool_use ブロックは、モデルがどのツールを呼ぶかを指定するもので、ツール名と、モデルが生成した完全なパラメータオブジェクトを含みます。tool_result ブロックは、ハーネスが実際にツールを走らせたあとに返すもので、成功時は完全な戻り値、失敗時はエラーの内容とエラーフラグです。この2つのブロックは対で読まなければなりません: tool_use だけを見れば、それが何をしようとしたかしかわかりません。tool_result だけを見れば、環境が何を返したかしかわかりません。
なぜこれらが証拠になりうるのか。Anthropicのエージェントパターンの記事は、エージェント自身の視点から説明しています: 実行中、エージェントは進捗を評価するために、各ステップで環境から "ground truth"(ツール呼び出しの結果やコード実行の結果など、環境が返す事実)を得る必要がある2。同じデータの束が、あなたにも使えます——エージェントはこれで自分がどこにいるかを判断し、あなたも同じもので判断するのです。
同じ記事は設計原則も示しています: エージェントの計画ステップを明示的に見せることで、透明性を優先せよ2。これはしばしば UI への推奨として受け取られますが、デバッグにとっての含意はもっと具体的です——ツール境界に着地しない行動は、トランスクリプトに何の痕跡も残さない。モデルが「頭の中で2つのデータセットを比較する」ことは痕跡を残しませんが、diff ツールを呼べば残ります。何かを監査可能にしたいなら、それをツール境界まで押し出す必要があります。
人手のレビューは、評価が取りこぼすものを捕まえる
ここでこう聞きたくなる人がいるでしょう: 第10コースで評価セットを作ったばかりではないか。スコアを回して終わりにすればいいのでは。なぜ人間がメッセージを1つずつめくる必要があるのか。
評価セットが捕まえるのは、あなたがすでに思いついた失敗だけだからです。人手による評価は、自動化が取りこぼすものを捕まえます。エージェントをテストする人間は、評価が見逃すエッジケースを見つけます。そこには、珍しいクエリに対する幻覚的な回答、システムの障害、そしてソース選択の微妙な偏りが含まれます3。3つ目のカテゴリは詳しく見る価値があります: Anthropicのマルチエージェントリサーチシステムでは、初期のエージェントが、学術PDFや個人ブログのように権威はあるがランクの低いソースよりも、SEO最適化されたコンテンツファームを一貫して選んでいることに、人間のテスターが気づきました3。
この偏りは評価ではほとんど見えません: 回答が必ずしも間違っているわけではなく——コンテンツファームも正しい事実をコピーします——スコアは良いままです。それが書かれている場所はただ1つ: トランスクリプトの中に並ぶ、実際に選ばれたURLの列です。
ツールの層にも同種の例があります。Claudeのウェブ検索ツールをローンチしたとき、チームは Claude がツールの query パラメータに不必要に 2025 を付け足しており、検索結果に偏りが生じて性能が落ちていることを突き止めました。修正はツールの説明を改善して Claude を正しい方向へ導くことでした1。このバグはクラッシュもせず、エラーも投げず、タイムアウトもしません。ツールは毎回、正当な検索結果とともに成功で返ります。メトリクスは「性能が落ちた」までは教えてくれるかもしれませんが、「query に 2025 が入っているから落ちた」と指を差すには、パラメータを1行ずつ読む必要があります。
トランスクリプトは人間だけが読むものではない
前のセクションは誤解を招きやすい書き方でした: トランスクリプトを読む=人間の目で1行ずつ、ではありません。トランスクリプトは構造化されたテキストであり、モデルはそれを読むのが非常に得意です。ツールエンジニアリングの記事は、ほとんど身も蓋もない方法を示しています: 評価用エージェントのトランスクリプトを単に連結して、Claude Code に貼り付けるだけ。Claude はトランスクリプトの分析の専門家であり、大量のツールを一度にリファクタリングできます——たとえば、新しい変更が入ったときにツールの実装と説明が自己整合を保つようにする、といったことです1。
「トランスクリプトを読む」ことのボトルネックは知性ではなく、根気です。だから分担ははっきりしています: モデルは総なめのスキャンが得意で、トランスクリプトの束を容疑者リストに圧縮できます。人間は質的な判断が得意で、抜き出されたサンプルが本当に問題と言えるかを決められます。
他人のトランスクリプト形式は、その内部実装
トランスクリプトがそれほど重要なら、既存の製品がディスクに書き出している出来合いのトランスクリプトをそのまま読めばいいのでは。Claude Code を例に取ると、セッションのトランスクリプトを ~/.claude/projects/*/*.jsonl ファイルに永続化しています4。理想的に見えます: 1メッセージ1行の、出来合いの、構造化された生のトランスクリプト。
しかし公式ドキュメントはすぐ後に警告を付けています: トランスクリプトのエントリ形式は Claude Code の内部仕様であり、バージョン間で変わる。したがってこれらのフィールドで結合するパイプラインは、どのリリースでも壊れうる。結合は安定した契約ではなく、バージョン固有のものとして扱うこと4。フックのドキュメントは2つ目の注意を加えています: フックが受け取るトランスクリプトファイルは非同期に書かれるためメモリ上の会話に遅れることがあり、フックが発火した時点では、そのターンの最新のメッセージがまだ含まれていない場合があります5。
どちらも同じ教訓です: 他人の形式はあなたの管理下になく、書き込みのタイミングもあなたの管理下にありません。読むことはできますし、トラブルシューティングに使うこともできますが、その上にあなたの可観測性を建てることはできません。結論: 自分のハーネスは、自分のトランスクリプトを書く必要があります。そのトランスクリプトのフィールドをどう設計するかはレッスン3、第7コースで書いたハーネスへの組み込みはレッスン6で扱います。
トランスクリプトを読むための4つの問い
トランスクリプトを開いたとき、どこから手を付けるか。ツールエンジニアリングの記事が、出来合いの分類を用意してくれています: エージェントは、間違ったツールを呼ぶかもしれないし、正しいツールを間違ったパラメータで呼ぶかもしれないし、呼び出しが少なすぎるかもしれないし、ツールのレスポンスを誤って処理するかもしれない1。この4つはもともとツール設計についての話ですが、読み方のチェックリストとして非常によく機能します。それぞれが、トランスクリプトの中に指し示すべき固有の場所を持っているからです:
問い1: ツールが違う。 各 tool_use ブロックの name フィールドを見ます。目の前のタスクと、直前のツールレスポンスに突き合わせます。判断基準は「もっと適切なツールがそこにあったのに、使われなかったか」です。
問い2: ツールは正しいが、パラメータが違う。 tool_use ブロックの input フィールドを、キーごとに見ます。先ほどの例——検索語に 2025 を付け足す——はこの問いに属します。パラメータは長く、しかももっともらしく見えるので、最も飛ばされやすい問いです。自分で正誤を独立に判断できるキーに絞って見てください: 日付、パス、ID、範囲の上下限、単位。
問い3: 呼び出しが足りない。 この問いの証拠は不在なので、最も読み取りにくいものです。見つけ方は、「次の呼び出しがあるべきだったのに、なかった」箇所を探すことです: リトライのないエラー、5件返ってきた検索のうち1件しか取得していない、2つのソースが必要なタスクなのにトランスクリプトには成功した往復が1回しかない。不在は自分から飛び出してはくれません——タスクの要件をテンプレートにして、数えるしかありません。
問い4: ツールレスポンスの扱いを誤った。 各 tool_result の直後の assistant メッセージを見て、「次に言っていることは、いま受け取ったものと整合しているか」を問います。エラーを成功として扱う、返ってきた期間Aのデータを期間Bとして使う、戻り値が明示的に「結果は切り詰められました」と言っているのにエージェントが全体として受け取る——いずれもこの問いに属します。これはエラーメッセージの書き方にも直結します: ツールがエラーを発生させたとき、不透明なエラーコードやトレースバックではなく、具体的で実行可能な改善策を明確に伝えるように、エラー応答をプロンプトエンジニアリングできます1。エージェントが同じ種類のエラーを繰り返し読み違えているのを見たら、まずそのエラー自身が何と言っているかを確認してください。
比例配分: すべてのトランスクリプトを人手で1行ずつ読む必要はない
トランスクリプトを人手で読むのは高価な行為です: 数十メッセージある1回の実行を丁寧に読むと、たっぷり10分以上かかります。それを100実行分に掛ければ、誰も追いつけません。だから人手の読みは2箇所に置きます。1つは抜き取り確認です: 定期的に何本かの実行をランダムに選んで通読する。特定のバグを狩るためではなく、「普段どう動いているか」の直感を較正するためです——SEOコンテンツファームのような偏りは、まさに「バグを狩らずに読んでいる」ときに偶然ぶつかるものです。2つ目は新しい問題の性質を掴むことです: 見慣れない症状にぶつかったとき、最初の何回かは人手で読まなければなりません。まだ何を探せばいいかがわかっていないからです。どのフィールドに現れるかが分かる程度まで読み込めたら、あとはプログラムに引き渡します。
日常の物量はプログラムによる読みが担います: ログのフィールドをどう設計するかはレッスン3、散らばった記録を1回の実行に相関させる方法はレッスン4です。そこまで来ると、人手の読みの入り口は「メトリクスで怪しい実行を数本まで絞り込み、それからトランスクリプトを開いて子細に見る」に変わります。まず証拠が存在して、初めて効率よく読む話ができます——このレッスンが扱ったのは、その前半です。
💻 演習
まとめ
- エージェントの自己申告は二次資料です: エージェントがフィードバックや応答で省略したことのほうが、書いたことよりも重要な場合が多く、LLMは思っていることを必ずしも口に出すとは限りません1。そして欠けているものは、サマリーの中で手を挙げてはくれません
- CoT を読むことで粗い部分は見つかりますが、CoT に明示的に書かれていない振る舞いを捕まえるには、ツール呼び出しとツールレスポンスを含む生のトランスクリプトを見る必要があります1——CoT は手がかり、トランスクリプトが証拠です
- 生のトランスクリプトとは、このシリーズの第7コースのあの
messages 配列に積み上がるものです。それが証拠になりうるのは、ツールの結果やコード実行が、各ステップで環境が与える ground truth だからです2。公式のガイダンスは「エージェントの計画ステップを明示的に見せることで透明性を優先せよ」とも述べています2——裏返して読めば、それがこのレッスンの系です: ツール境界に着地しない行動は、痕跡を残しません
- 人手による評価は自動化が取りこぼすものを捕まえます: 評価が見逃すエッジケース——珍しいクエリに対する幻覚的な回答、システムの障害、ソース選択の微妙な偏り3。現実の例が、初期のエージェントが学術PDFや個人ブログよりもSEO最適化されたコンテンツファームを一貫して選んでいたことです3——この種の偏りは、トランスクリプトに着地したとき、実際に選ばれたURLの列として現れます。ツール層のバグもパラメータに宿ります: Claude はかつて検索ツールの
query パラメータに不必要に 2025 を付け足しており、結果に偏りが生じて性能が落ちました。修正はツールの説明の改善でした1
- トランスクリプトは人間だけが読むものではありません: 評価用エージェントのトランスクリプトを単に連結して Claude Code に貼り付ける。Claude はトランスクリプトの分析の専門家であり、大量のツールを一度にリファクタリングできます——たとえばツールの実装と説明の自己整合を保つように1。トランスクリプトを読むための4つの問いは、ツールが違う、ツールは正しいがパラメータが違う、呼び出しが足りない、ツールレスポンスの扱いを誤った、です1
- 他人のトランスクリプト形式は、その内部実装です。Claude Code はセッションのトランスクリプトを
~/.claude/projects/*/*.jsonl に書きます4が、公式ドキュメントはこの形式が Claude Code の内部仕様でありバージョン間で変わると明言しており、これらのフィールドで結合するパイプラインはどのリリースでも壊れうるため、バージョン固有のものとして扱うべきです4。フックが受け取るトランスクリプトファイルも非同期に書かれ、メモリ上の会話に遅れることがあります5。だから自分のハーネスは自分のトランスクリプトを書く必要があります——フィールドの設計はレッスン3、実装はレッスン6です
レッスン3 >>