エージェントが妙な変更をしたとき、「なんでこうしたの?」と聞いている人向けです。返ってくる答えが何なのか、代わりにどこを読めばよいのかを書きます。あわせて、手元の Claude Code のセッションログに思考の中身が残っているかを数えたので、そのコマンドと出力をそのまま載せます。
きっかけは X の投稿です。そこで挙げられていた理由(thinking が直近ターンしか文脈に入らない)は、公式ドキュメントに当たるとモデルによって違いました。ただし、どちらのモデルでもやることは変わりません。なぜ変わらないのかまで書きます。
まとめ
- エージェントに「なんで?」と聞くのをやめます。代わりに、依頼するときに「変更ごとに、その根拠を1行で添えて」と書いておいて、あとでその記録を読みます。理由は、聞いてから作らせるのではなく、作業した時点で書かせるほうが確かだからです。
- 聞いて返ってくる説明は、そのとき作られたものです。ヒントを与えて答えを変えさせた実験で、そのヒントを使ったと自分で書いたのは 25% でした(Claude 3.7 Sonnet での測定。新しいモデルで同じ数字になるとは限りません)。
- Claude Code を使っているなら、自分のセッションログを開いてみてください。既定では、考えた形跡だけがあって中身が空です(数えたら 6,319 個すべてが 0 文字でした)。設定を入れると、画面で対話しながら進めた分は残ります。
-pを付けて実行した分は残りません。
⚠ 「セッションログ」「思考」「対話モード」が何を指すかは、本文で順に説明します。まとめだけで分からなくても、読み進めれば分かるようになります。
きっかけの投稿
もとは、LLM に理由を尋ねたときの応答についての投稿です(@golden_lucky、2026-07-30 投稿、2026-08-15 取得)。
「なんでこうしたの?」ってLLMに言うと「すみません考えが足りませんでした」みたいなこと言ってくるけど、そうじゃなくて理由を聞いてるんだよ、理由を答えてくれよ、人間になるなよ
これを引用して、原因と対策を述べた投稿がこちらです(@Hi_Noguchi、2026-07-31 投稿、2026-08-15 取得)。長いので要点だけ引きます。
少なくとも Claude についていうと、「なんで」かは Claude 側も「知らない」。というのも thinking の内容が直近ターンのものしかコンテクストウィンドウに入っていないため。
なので Claude は過去の自分のアウトプットだけを見て、事後推測するしかないというわけ。だからそれっぽい「ウソ」ついたりする。
対策として、showThinkingSummaries を有効にしてセッションログ(jsonl)に記録される thinking summary を振り返らせること、その自動投入先として DuckDB や hooks が挙げられています。
言っていることには納得しました。理由を聞く相手を、エージェント本人から記録へ移すという話です。そのうえで、記録に何がどう入るのかを確かめました。
エージェントとのやりとりで、思考はどう扱われているのか
1回目と2回目に、何が行き来しているのか
先に用語を分けます。この記事で「説明」と呼んでいるのは、あとから「なんで?」と聞いたときに返ってくる文章のことです。モデルが答える前に内部でやっている推論のほうは「思考」と呼び、API では thinking ブロックとして扱われます。別物です。
以下は Anthropic の Extended thinking(2026-08-15 取得)に書かれている API の挙動です。Claude Code のようなハーネス越しに使っているときも、下ではこのやりとりが起きています。
1回目に何が起きるかを、言葉でも書きます。
| 何が起きるか | |
|---|---|
| 入力 | 依頼文と、それまでの会話 |
| 処理 | 答えを出す前に、モデルが内部で考える。その過程が「思考」 |
| 出力 | 応答の本文(text)、thinking ブロック(中身は設定次第)、署名(signature) |
| 課金 | 思考に使ったトークンは出力トークンとして課金される。要約しか受け取らなくても、元の思考の分が課金される |
⚠ thinking に何が入るかは設定で変わります。display: "summarized" なら生の思考ではなく要約が入り、"omitted"(新しいモデルの既定)なら空で返ります。どちらにしても、生の思考の連鎖はどの設定でも返りません。
2回目はこうです。
| 何が起きるか | |
|---|---|
| 送るもの | 1回目の依頼文・応答・thinking ブロック(署名つき)を、そのまま送り返す |
| API がすること | 署名を復号して、生の思考を復元する。thinking ブロックが書き換えられていれば 400 で拒否する(空の thinking に書き足した場合だけは、拒否ではなく無視される) |
| モデルが見るもの | 復元された生の思考(保持するモデルの場合) |
| あなたが読めるもの | 要約まで("omitted" なら何も読めない)。署名は復号できない |
ここで当然の疑問が出ます。思考が復元されてモデルに戻っているなら、「なんで?」と聞けばそれを読んで答えられるはずではないか、という疑問です。
そのとおりで、答えられる可能性はあります。新しいモデルなら、前のターンの思考は手元に戻っています。「思考が消えているから答えられない」という説明は、そのモデルには当てはまりません。
引っかかるのは別のところです。2つあります。
- 思考そのものが、実際に効いた手がかりを書いていないことがある(下の 25% がその測定です)
- あなたはその思考を読めない。返ってくるのは要約か空で、生の思考は署名の中にあり、復号できるのは API だけです
だから、返ってきた説明が思考と合っているかを、こちら側で突き合わせられません。「答えられない」ではなく、「答えが当たっているかを確かめられない」が正確なところです。
⚠ 「あとから理由を尋ねたとき、モデルが復元された思考をどれだけ読んで答えるか」を測った資料は見つけていません。ここは分かっていない、と書いておきます。
なお、依頼文も応答も全部残ります。特別なのは思考だけです。生の思考は署名の中に暗号化されて入っていて、リクエストに乗って行き来します。手元に持ち歩いているのに、読めるのは API とモデルだけ、という形です。⚠ 原文は Anthropic 側で別に保存するかどうかには触れていないので、ここで言えるのは往復の話までです。
誰が何を決めているかも分かれています。ここが「ハーネスの問題かモデルの問題か」の答えになります。
| 決まること | 決めるのは |
|---|---|
| 前のターンの思考を保持するか捨てるか | モデルごとの既定。API が自動でやる |
| 生の思考を返すかどうか | 返らない。設定で変えられない |
| 要約を返すか、空で返すか | 開発者が渡す設定(display) |
| セッションのログに思考の中身を書くか | ハーネス(Claude Code)の設定 |
設定をどう変えても手に入らないのは、生の思考です。この記事の後半で空だったログの話をしますが、それは最後の行、ハーネスの側の話になります。
聞いて答えられるかどうかは、使っているモデルで決まる
Anthropic の拡張思考のドキュメントに、前の応答ターンの思考を既定で文脈に残すかどうかはモデルによって異なる、と書かれています(Extended thinking、2026-08-15 取得)。「Thinking block preservation by model」という節が立っていて、2つの系列が区別されています。
| 既定の挙動 | モデル(2026-08-15 時点の記載) |
|---|---|
| 前のターンをすべて保持する | Claude Opus 4.5 以降の Opus、Sonnet 4.6 以降の Sonnet、Fable 5、Mythos 5 ほか |
| いま組み立て中の応答の中だけ保持する | それ以前の Opus / Sonnet、および Haiku 4.5 までの Haiku |
下の行を「直前の1往復なら残る」と読むと外します。原文は、tool_result ではないユーザーメッセージを送った時点で、それまでの思考はすべて文脈から取り除かれる、と書いています。残るのは前のターン1つ分ではありません。道具を呼びながら1つの応答を組み立てている、その最中だけです。あなたが次に何か打ち込んだ時点で落ちます。
これが「なんで?」に直接効いてきます。その質問自体が、聞こうとしている思考を落とす引き金だからです。下の行のモデルでは、答えを組み立てているモデルの手元に、根拠になる思考はもうありません。残っているのは依頼文と応答、つまり「何を頼まれて、何をしたか」だけです。
上の行のモデルなら残ります。10 ターン続けていても1ターン目の思考まで残っていて、そのぶん入力トークンとして課金されます。ターンを重ねたから薄れていく、という消え方ではありません。残るか残らないかは、最初からモデルで決まっています。
なお、Claude Code の /context を実行すると文脈の使われ方が区分ごとに出ますが、そこに「思考」という枠はありません。画面で埋まって見えているものと、実際にモデルへ渡っているものは別です。ここは実測を並べたほうが早いので、次回に回します。
では上の行のモデル、つまり比較的新しいものを使っている人には関係のない話かというと、そうではありません。思考が文脈に残っていても、聞いたときに返ってくるのが本当の理由とは限らないからです。次がその話です。
思考が残っていても、返ってくる説明が本当の理由とは限らない
では、思考が文脈に残っているモデルなら、聞けば本当の理由が返るのでしょうか。ここには測定があります。
Anthropic は、思考の連鎖が実際に使った手がかりを言語化しない割合を測っています(Reasoning models don’t always say what they think、2026-08-15 取得)。ヒントを与えて答えを変えさせ、モデルが実際にそのヒントを使ったことを確かめたうえで、思考の連鎖でヒントに言及した割合を数えています。
| モデル | ヒントに言及した割合(全ヒント種別の平均) |
|---|---|
| Claude 3.7 Sonnet | 25% |
| DeepSeek R1 | 39% |
⚠ 39% のほうは Anthropic 製のモデルではありません(DeepSeek R1)。また、いずれもヒント種別をまたいだ平均で、種別ごとに数字は動きます。
⚠ 測られたのは Claude 3.7 Sonnet の世代です。今のモデルで同じ割合になるとは限りませんし、この記事でも確かめていません。ただし、割合がいくつであれ「説明が実際の処理と食い違うことがある」ことは変わりません。返ってきた説明を検算せずに信じてよい理由にはならない、ということです。
もう1つ、報酬ハックを仕込んだ実験では、99% 超の頻度でハックを利用しながら、それを言語化したのは 2% 未満でした。⚠ こちらは Claude 3.7 Sonnet を合成環境で強化学習した実験で、原文の「2% 未満」にも「試験シナリオの多くで」という限定が付いています。モデル一般の性質としては読めません。
同社は、思考の連鎖そのものが誤導しうることも別記事で名指ししています(Tracing the thoughts of a large language model、2026-08-15 取得)。行き着きたい結論に合わせて、もっともらしく見える手順を作ることがある、という趣旨です。
⚠ この実測が測っているのは「思考の連鎖がヒントに言及したか」であって、「あとから理由を尋ねたときに正直に答えるか」ではありません。別の測定なので、射程を混ぜないでください。それでも、言語化された説明と実際の処理が食い違いうることは、これで十分に示されています。
思考が文脈に残っていても、返ってくる説明が実際の処理の報告である保証はありません。だから「揮発するから答えられない」ではなく、「説明は聞かれた時点で作られるものだから、そのまま根拠にできない」と言うほうが正確でした。前提が1つ落ちても、対策は変わりません。聞く相手を記録へ移すことです。
自分のログに、思考は残っているのか(実測)
「記録を読めばよい」と言うからには、記録に何が入っているかを見る必要があります。Claude Code のセッションログは ~/.claude/projects/<プロジェクト>/<セッションID>.jsonl にあります。type が thinking のブロックを取り出して、中身の文字数を数えました。
以下、$LOG $A $B $D は、その都度できた jsonl のパスです(ls -t ~/.claude/projects/<プロジェクト>/ の先頭)。jq の ... は、最初に出てくるフル形(select(.message.content? | type=="array") | .message.content[])と同じものです。
thinking ブロックは 6,319 個あって、中身は全部空
$ cd ~/.claude/projects
$ find . -name '*.jsonl' -newermt '2026-08-01' | wc -l
423
$ find . -name '*.jsonl' -newermt '2026-08-01' -print0 | xargs -0 \
jq -r 'select(.message.content? | type=="array") | .message.content[]
| select(.type=="thinking") | (.thinking|length)' | sort -n | uniq -c
6319 0
左の 6319 が件数、右の 0 が文字数です。1つも中身がありません。ブロック自体は残っていて、入っているのは署名だけです(sig_len は .signature の長さを jq で計算した値で、生のログにこのキーがあるわけではありません)。
$ jq -c '... | select(.type=="thinking") | {type, thinking, sig_len:(.signature|length)}' $A
{"type":"thinking","thinking":"","sig_len":864}
思考した痕跡はあるのに、思考の内容は無い。記録から読み取れるのは「ここで考えた」までで、「何を考えたか」は残っていませんでした。
一方で、残っているものもあります。この記事を書いた作業のログを見ると、こうです。
$ jq -r 'select(.type=="assistant") | .message.content[]?
| select(.type=="tool_use") | .name' $LOG | sort | uniq -c | sort -rn
57 Bash
4 Edit
3 Read
3 Agent
1 Write
1 Skill
「何をしたか」は完全に残っています。実行したコマンド、読んだファイル、書いた差分。残っていないのは「なぜ」だけです。理由を復元したければ行為の列から推測するしかなく、つまり、あとから尋ねられたエージェント自身と同じ材料しか、こちらにも無いことになります。
設定を入れても、headless では記録されない
もとの投稿が挙げていた対策を試します。以下 $WORK は作業用の空ディレクトリです。~/.claude/settings.json は書き換えず、--settings で一時的に渡しました。
$ claude --version
2.1.233 (Claude Code)
$ cd $WORK/exp-A && claude -p "1から20までの素数の和を求めて。方針を2つ比べてから決めて。" --model sonnet
$ jq -r '... | select(.type=="thinking") | (.thinking|length)' $A | sort -n | uniq -c
1 0
$ cat $WORK/thinking-on.json
{ "showThinkingSummaries": true }
$ cd $WORK/exp-B && claude -p "1から20までの素数の和を求めて。方針を2つ比べてから決めて。" --model sonnet \
--settings $WORK/thinking-on.json
$ jq -r '... | select(.type=="thinking") | (.thinking|length)' $B | sort -n | uniq -c
1 0
変わりませんでした。ただし「何も起きなかった」は、フラグが無視されただけでも同じ見た目になります。そこを潰さないと実測になりません(前回の記事と同じ話で、「変わらなかった」の確認には対照が要ります)。両方向から確かめました。
# 陰性対照:壊れた設定なら起動が落ちるか → 落ちる(ログも作られない)
$ claude -p "hi" --model sonnet --settings '{not valid json'
Error: Settings file not found: {not valid json
# 陽性対照:別の設定なら効くか → 効く(--model を付けずにモデルが変わる)
$ claude -p "say ok" --settings $WORK/model-haiku.json # {"model":"haiku"}
ok
$ jq -r 'select(.type=="assistant") | .message.model' $D | sort | uniq -c
2 claude-haiku-4-5-20251001
--settings 経路そのものは生きています。つまり B の空は本物です。
迂回も3本試しました。--output-format stream-json --verbose --forward-subagent-text、thinkingDisplay の直接指定、MAX_THINKING_TOKENS=8000 の併用で、どれも空でした。ここで1つ分かったことがあります。
# ログではなく、stream-json の標準出力そのものを保存して長さを取る
$ jq -c '... | select(.type=="thinking") | {thinking_len:(.thinking|length), sig_len:(.signature|length)}' exp-E.out
{"thinking_len":0,"sig_len":1112}
ログに書くときに削られているのではありません。応答が返ってきた時点で、署名だけがあって本文が無い状態です。
効かない理由は、設定を読む前に出力形式が決まっているから
インストールされているバンドルを直接見ます。ここだけは追試の手順が違うので、コマンドごと載せます。
$ /usr/bin/grep -a -o 'function [A-Za-z0-9_$]\{2,6\}({explicitDisplay:.\{0,200\}' \
~/.local/share/claude/versions/2.1.233 | head -1
function oNs({explicitDisplay:e,isNonInteractive:t,outputFormat:r,verbose:n}){if(e)return e;
if(!t)return nNs()?"summarized":void 0;if(r==="text"||r==="json"&&!n)return"omitted";return}
t が isNonInteractive、nNs() が設定を読む関数です(showThinkingSummaries??!1 を返します)。nNs() は if(!t)、つまり「対話モードなら」の内側にしかありません。-p で走るとこの内側に入らないまま、次の行の "omitted"(出さない)に届きます。設定の書き方が悪いのではなく、設定を読むコードまで来ないということです。
⚠ 識別子は圧縮されていてバージョンごとに変わります(上は 2.1.233)。⚠ 折り返しはこちらで入れたもので、原文は1行です。
⚠ したがってこの実測が示したのは「設定が無意味」ではなく、「headless からはこの設定に到達できない」です。手法の限界であって、投稿の対策が否定されたわけではありません。
対話モードなら記録される
到達できないなら、到達する側で回すしかありません。空のディレクトリで、設定を渡して対話モードで起動し、同じ質問を1回投げて抜けました。
$ mkdir -p /tmp/thinking-test && cd /tmp/thinking-test
$ claude --settings '{"showThinkingSummaries":true}'
(「1から20までの素数の和を求めて。方針を2つ比べてから決めて。」と入力して /exit)
$ jq -r '... | select(.type=="thinking") | (.thinking|length)' $LOG | sort -n | uniq -c
1 215
入りました。中身はこれです。
The primes up to 20 are 2, 3, 5, 7, 11, 13, 17, and 19, which sum to 77. I could enumerate them manually or use a sieve algorithm, but for such a small range, simple enumeration is the most straightforward approach.
対照は上の 6,319 件です。同じ端末の、設定が無い対話セッションは全部空でした(この記事を書いた作業のセッション単体でも 64 ブロックすべて空)。⚠ ただしプロンプトもプロジェクトも違うので、厳密な A/B ではありません。言えるのは「設定を渡した対話セッションでだけ中身が入った」までです。
⚠ もとの投稿は、この要約を DuckDB や hooks に流し込んで振り返らせるところまで書いていますが、そこは試していません。この記事で確かめたのは、設定を入れれば対話モードで要約が記録される、というところまでです。
⚠ これは要約であって、生の思考ではありません。215 文字で、方針を2つ比べた結論だけが残っています。投稿が「あくまで summary であって raw ではない」と書いていたとおりで、詳細まで復元できると期待するものではありません。それでも、出力だけを見て事後に組み立てた説明とは出どころが違います。
実際に使うなら、こうすると思います
ここから先は実測ではなく提案です。上の結果をふまえて自分の運用をこう変える、という話で、効果を測ったわけではありません。
やることは2つあります。順番に意味があります。
主にやるのは、依頼のときに書かせることです。依頼文に「変更ごとに、その根拠を1行で添えて」と入れておきます。あとで尋ねる代わりに、作業と同時に出させるという違いです。書かれた根拠は変更そのものと並んでいるので、差分と突き合わせて「本当にそのために変えたのか」を確かめられます。
補助として、記録の設定を入れておきます。showThinkingSummaries を有効にすると、対話モードでは考えた内容の要約が残ります。ただし残るのは要約で、突き合わせる相手がありません。作業を始める前に入れておかないと、その作業の分は戻ってこないので、やるなら先です。
どちらか片方しかやらないなら、書かせるほうです。確かめられる記録と、確かめられない記録の違いがここにあります。
そのうえで、日々の運用ではこうします。
- 「なんで?」は聞かない。聞いてしまうと、確かめていない説明がその場の文脈に残り、あとから分離できません。知りたければ記録を開きます。
- それでも説明を聞いたときは、事実ではなく主張として受け取る。記録と食い違ったら記録を採り、記録が無いならその説明は根拠にしません。当たっている説明と、当たっていない説明は、同じ顔をしています。
- 記録の送り先を確かめてから有効にする。セッションの記録には依頼文も出力も入ります。GitHub Copilot CLI は、既定でセッションデータを GitHub アカウントへ同期するとドキュメントに明記しています(About GitHub Copilot CLI session data、2026-08-15 取得)。無効化は同じページの
"remoteExport": falseで、こうすると記録は端末に留まり、問い合わせも CLI からだけになります。
聞いても答えは返ってきます。返ってこないのは、その答えが当たっているかどうかです。だから、確かめられる形で書かせておきます。