この記事は、連載「動かさない技術」の第 8 回です。うまくいかない症状と、その対処を一つずつ並べています。各回はファイルやスクリプトを一つ置けば完結します。その仕組みがなぜ要るのかは、置いたあとに解説を読めば分かります。連載の全体像と各回の一覧は序論にあります。

今回は、成果物で仕事の出来を判断する話です。成果物というのは、作業が終わったあとに手元に残るもののことで、コミットされたコード、合格になったテスト、書き上がった記事がそれにあたります。私はずっと、成果物が全部正しければ、その仕事には問題が無かったと考えていました。その判断が外れるのは、AI が、こちらの想定に無い、要らない作業をしていたときです。要らない作業は成果物に何も残さず、私のときは、作業をした AI 自身も報告に書きませんでした。見つける方法は、成果物とは別に、過程の記録を外から読むことです。

まず一つだけ、思い出してみてください。

思い出すのは、AI に任せた直近の作業のうち、成果物が全部正しかったものです。

コードは動き、テストは合格で、レビューも通った作業です。

その作業の途中で、AI が、成果物に要らなかった作業に何ターン使ったかを、言うことができるでしょうか。

その回数は、成果物のどこにも出てきません。

CC BY 4.0

成果物は全部正しく、トークンの 4 分の 1 は完了したかを見るだけのターンでした

私は、ある日のセッションのターンを、あとから記録で数えました。その日、私は自分の仕事に満足していました。

記事は 1 本書き上がり、実験も 1 本回り、自動テストは全部合格でした。成果物は全部正しかったわけです。

ターントークン
ツールを呼んだターン15534,986,986
うち「完了したかを見るだけ」32(20.6%)9,012,821(25.8%)

完了したかを見るだけの 32 ターンのどれでも、AI は代わりに別の作業を進めることができました。バックグラウンドに回した仕事も、並行して走らせたテストも、終われば、AI が通知で呼び戻される作りになっているからです。

しかもそのことは、AI が受け取ったツールの結果に、英語で書いてありました。「完了したら通知するので、そのあいだ別の作業を続けてよい」という 1 文です。AI は、その 1 文を読んだうえで、32 ターンにわたって、完了したかを見に行きました。

作業をした AI 自身の報告にも、この 32 ターンのことは出てきませんでした。32 ターンが見えたのは、作業が終わったあとに、記録を数えたときです。

では、成果物が全部正しいとき、作業をしていない別のサブエージェントに記録を渡せば、要らなかった作業を自分から見つけるのでしょうか。私は、それを測りました。

記録は 12 回とも開き、数えたのは数を書かせた側だけでした

題材は、ある作業の 155 ターンぶんの記録(record/)と、その作業の成果物(out/)です。成果物は全部正しく作ってあります(out/ の各ファイルは仕様どおりで、欠けも壊れもありません)。記録のほうには、同じツールで同じ対象を、何も変えないまま見直したターンを、私が仕込みました。

私は、この題材を、会話を引き継がないサブエージェントに 12 回渡しました。変えたのは頼み方の 1 点だけです。

条件NOTES.md で頼んだこと回
数を書かせる記録から無駄だったターンの数を書く6
点検だけ頼むout/ が仕様どおりか点検するだけ。「無駄」「繰り返し」「進め方」の語は 1 つも書かない6

記録があることは、2 条件とも一字一句同じ文で伝えています。「record/ に、その担当者が作業中に何をしたかの記録が入っています。1 件が 1 ターンです」。記録があることを伝えなければ、数えなかった原因に「記録があると知らなかった」が混ざります。

サブエージェントは 12 回とも、頼まれたことには正しく答えました。

条件記録を開いた記録を数えた数を出した
数を書かせる(6 回)6/65/66/6
点検だけ頼む(6 回)6/60/60/6

点検だけ頼んだ 6 回も、サブエージェントは記録を開き、開いたうえで、繰り返しとは別のことを報告しました。いちばん多かったのは「実在しないファイルへの参照が多い」で、6 回のうち 5 回が挙げています。「同じファイルへの重複読み取りが多い」と書いたのは 1 回だけで、その 1 回も数は出していません。

私が語の一致で数え直すと、6 回のうち 4 回は、繰り返しや同じ作業の重複に何らかの形で触れたうえで、数えずに報告を終えていました。

あなたは、AI に機能の追加を頼み、成果物で受け取っています。テストが合格し、レビューが通れば、途中のやり取りは全部は読みません。ある月、使用量の上限に届くのが、前の月より早くなりました。その月の成果物はどれも正しく、作業の報告にも、おかしなところはありません。セッションの記録は手元に残っています。何が使用量を増やしたのかを調べるなら、あなたなら次のどちらにしますか。

  • 依頼文と成果物を見直して、無駄な作業が無かったかを調べる
  • 依頼文と成果物に加えて、セッションの記録も読み、無駄な作業が無かったかを調べる

答えは、セッションの記録も読むほうです。成果物がどれも正しく、報告にもおかしなところが無いのに使用量が増えたなら、増やしたものは、成果物にも報告にも残らない作業の側にあります。依頼文と成果物をいくら見直しても、そこに無い作業は見つかりません。私のセッションでも、完了を見に行くだけの 32 ターンは、成果物にも、作業をした AI 自身の報告にも出てこず、記録を数えて初めて見えました。記録を読ませるときは、AI に、何を探すかまで頼みます。私の実験では、記録を渡して成果物の点検だけを頼んだ 6 回は、どれも記録を開いたのに、無駄だったターンの数を出した回は 0 回でした。無駄だったターンの数を頼んだ 6 回は、6 回とも数を出しています。

理解したことは一文にできます。

成果物が全部正しくても、そこに残らない行為は積み上がる。見つけたければ、成果物と報告だけでなく、過程の記録まで含めて、無駄な作業を探す。

なぜ、要らない作業が起き、成果物からも報告からも見つからないのか

「完了したら通知する」という 1 文は、ツールの結果の中に書いてありました。その 1 文に従うかどうかは、読んだ AI が選びます。Claude Code の hooks のガイドは、hook を、決まった時点で Claude Code が走らせるものだと説明したうえで、hook ならAI が走らせると選ぶことに頼らず、その動作が必ず起きると書いています。

要らない作業のターンも、1 ターンごとに多くのトークンを使っていました。Claude Code のコストのページは、Claude Code はリクエストのたびに会話の全体を送ると書いています。AI がツールを使えば、そのツールの結果を載せたリクエストを、Claude Code がもう 1 つ送ります。完了したかを見るだけのターンでも、AI に届くのは、それまでのやり取りの全部です。冒頭のセッションでは、見るだけの 32 ターンが、1 ターンあたり平均で約 28 万トークンを使っていました。ツールを呼んだ 155 ターン全体の平均は、約 23 万トークンです。

それでも、成果物は変わりません。完了を見に行っても行かなくても、出来上がるコードとテストは同じです。そして、成果物の点検を頼まれた AI は、頼まれた点検を終えたところで報告します。Anthropic のプロンプトの指針は、いまのモデルは指示に正確に従うと書いています。挙げている例は、「変更を提案してもらえますか」と頼むと、変更してほしいつもりでも、AI が提案だけを返すことがある、というものです。私の実験で点検だけ頼んだ 6 回も、記録の中の繰り返しに触れた 4 回を含めて、頼まれていない数え上げは始めませんでした。

見つけたあとの対処: 完了を見に行くターンを、3 回目の前に止める

冒頭のセッションの 32 ターンを記録で読むと、内訳は、サブエージェントの完了を待つ 12 ターンと、テストの全量の完了を待つ 20 ターンでした。どちらも、終わればその時点で通知が届き、AI は呼び戻されるので、待つ必要の無いターンです。

通知の 1 文に従うかどうかは、AI が選びます。そこで私が置いたのは、AI の選択に頼らない仕組み(hook)です。何も変えないまま、同じ「見るだけ」のコマンドが 3 回続いたら、3 回目を実行する前に止め、代わりにやること(ターンを終える / 別の作業を進める)を、その場で、AI に返します。sleep や watch のように、待つことそのものが目的のコマンドは 1 回目で止め、それ以外の 1〜2 回は、正当な確認として通します。

課題: あなたの環境で、要らなかった作業を見つける

1 段階目(5 分): あなたの環境で、AI との作業の記録を 1 つ見つけ、どこに、どの形で残っているかを確かめてください。

1 ターンが 1 件で、そのターンが呼んだツールを読み取ることができれば足ります。見つからなければ、記録を残すようにすることが、最初の対策です。要らない作業は、記録の側にしか残らないからです。

2 段階目(15 分): 成果物が全部正しかった作業の記録を 1 つ選び、新しいセッションを開いて、AI に渡し、無駄だったターンの数と、それぞれのターンで何をしていたかを書くよう頼んでください。

出た数が 0 なら、その記録では、AI が無駄を見つけなかったという結果です。

3 段階目(30 分): 挙がったターンのうち、いちばん多かった形を 1 つ選んで、記録の中身を、AI の答えに任せずに、あなたの目で読んでください。

何をしていたのかは、記録の中身を読んで、はじめて分かります。私の場合は、サブエージェントとテストの完了を待つターンでした。対処は、その形を見てから決めます。

CC BY 4.0 はここまで

成果物で判断しなくなったもの

私は、「成果物が全部正しいこと」を、仕事の出来の証拠に使うのをやめました。成果物が正しいかどうかは、今も見ます。変わったのは、成果物が全部正しかったときにも、記録の側を見るようになったことです。

以前は、コミットが積まれてテストが合格なら、その作業は良かったことにしていました。その判断は、成果物に残る作業についてだけ正しいものでした。完了を見に行くだけのターンのように、成果物に残らない作業は、どれだけ積み上がっても、成果物を変えません。

見つけた完了待ちは、いまは、hook が止めています。

CC BY 4.0

注意: この実験が言えないこと

点検だけ頼んだ側にも、記録を開く理由はありました。サブエージェントは 6 回とも、記録を点検の材料として開いています。だからこの実験は、記録を開く理由のある場面だけを測っています。測ったのは、開いたあとに何をしたかです。

数を書かせた側の 5/6 は当たり前です。サブエージェントは、数を書けと言われれば数えます。意味があるのは 0/6 のほうです。同じ記録を、同じ日に、会話を引き継がないサブエージェントに渡しても、数を頼まなければ、サブエージェントは数えませんでした。

母数は 6 と 6 です。この母数で強く言うことができるのは、0/6 と 5/6 の差までです。「繰り返しに触れた 4/6」は語の一致で数えた近似で、人が読めば違う判定になることがあります。

数えたのは、どの回も、作業をした本人とは別の、記録を渡されただけのサブエージェントです。作業をした AI 自身に、自分の記録から無駄を数えさせる形は測っていません。

実験手順: 同じ記録を、数を書かせる頼み方と、点検だけ頼む頼み方で渡す

前提

  • 会話を引き継がないサブエージェントを、2 つの条件を混ぜて 12 回、同時に起動する手段があること(1 回ずつ順に起動すると、その日の混み具合の変化が、条件の差に混ざります)
  • 答えの分かっている題材の記録を、スクリプトで生成する手段があること(1 件 1 ターン。正解は生成器が同時に出します)
  • サブエージェントの記録を、後から読む手段があること。要るのは 1 ターンごとの「呼んだツールの名前」と「その対象」です。シェルを走らせたターンなら打ったコマンド行、ファイルを読むツールのターンなら読んだファイル名です。「同じツールで同じ対象を見た」の判定には、対象が残っている記録が要ります

所要時間: 40 分(題材の生成器・採点器・分類器を自分で書く場合。すでにあるなら 10 分)

手順

  1. 記録と、その正解をスクリプトで作ります。155 ターンぶんの題材の記録と、「何も変えずに同じことを繰り返したターン」の正解表を同時に出力してください。正解表は、サブエージェントに渡すディレクトリの外へ置きます
  2. 成果物を、全部仕様どおりに作ります。壊れたファイルが混ざると、サブエージェントは成果物の側で忙しくなり、記録を見る理由が変わります
  3. 雑な解き方で出る数を、先に出力します。変更を無視した数 / 隣り合うターンだけ見た数 / ツールを見ずに対象だけで数えた数の 3 つです。どれかが正解と一致したら、題材を作り直してください(一致すると、雑に解いたサブエージェントも正解になります)
  4. 依頼文が、2 通りに読むことのできる書き方になっていないかを確かめます。数える単位(全体か、対象ごとか)を明示してください
  5. 同じ題材から、頼み方を 2 つ作ります。片方は記録から数を書かせる頼み方、もう片方は成果物の点検だけを頼む頼み方です。点検だけ頼む側には、「無駄」「繰り返し」「進め方」の語を 1 つも入れないでください。記録があることを伝える文は、2 条件とも一字一句同じにします
  6. 両条件を混ぜて、同時に起動します。起動時のプロンプトには、記録のことを書かないでください
  7. サブエージェントの答えを採点します。採点の結果には、「数えたかどうか」は出ません。出ないことが、この実験の論点です
  8. サブエージェントの記録から、題材の記録(record/)を開いた呼び出しと、数え上げを起こした呼び出しの 2 つを数えます。分類器には、数え上げを別ファイルに書いてから走らせる形を、必ず入れてください(そのファイル名には、題材の語が出てきません)
  9. わざと壊した分類器を作って、同じサブエージェントの記録に走らせます。壊した分類器がどれも、正しい分類器と違う数字を出すことを確かめてから、上の数を読みます

8 は、次のスクリプトで数えることができます。見るのは、数え上げを起こしたか(python3 / awk / wc を、対象を問わず起動したか)と、記録を開いたか(record/ の中身を読む呼び出しがあったか)の 2 つだけです。

# サブエージェントの記録から「数えたか / 読んだだけか」を分ける(手元の実装からの簡約版)
python3 - "$1" <<'PY'
import json, re, sys
COUNTED = re.compile(r"(?<![\w./-])(python3?|awk|wc)(?![\w-])")
READ    = re.compile(r"record/|trace\.(md|tsv)")
counted = read = 0
for line in open(sys.argv[1]):
    call = json.loads(line)
    cmd  = call.get("command", "")
    # ⚠️ 書き込む中身は見ない(Write の本文に open( と書いてあるのは「読んだ」ではない)
    body = call.get("write_body", "")
    if COUNTED.search(cmd):
        counted += 1
    if READ.search(cmd) and not READ.search(body):
        read += 1
print(f"counted {counted} / read the record {read}")
PY

合格条件(すべて満たすこと)

合格の条件は 4 つです。1 つ目は、サブエージェントの答えが、両条件とも満点であることです。2 つ目に、雑な解き方の 3 つが、どれも正解と違う数を出すことを確かめます。3 つ目は、両条件とも、どのサブエージェントも記録を開いていることで、これで「記録があると知らなかった」を測りから外します。4 つ目は、数を書かせた側で、記録を数えるサブエージェントが過半数出ることです。

合格しなかったとき

  • 点検だけ頼んだ側でも数えるサブエージェントが出た —— 点検の頼み方に、進め方を示す語が残っています。文面を 1 語ずつ見直してください
  • 数を書かせた側で、数えるサブエージェントが出ない —— 分類器が取りこぼしています。別ファイルに書いてから走らせる形を、先に手で 1 件走らせて確かめてください
  • 記録を開かないサブエージェントが出た —— 記録の説明に、置き場所と粒度が欠けています。2 条件とも同じ文で、置き場所と粒度を書いてください
  • サブエージェントの答えが満点でない —— 難度が高すぎます。この状態では、記録の扱い方の差と難度の差が、同じ数に混ざります

後始末

  • 生成した記録・成果物・サブエージェントの記録・正解表を削除します。正解表も一緒に削除するのは、残すと、次に題材を作り直したつもりのまま、古い答えを採点することになるからです

CC BY 4.0 はここまで

次回は「知見を残さない技術」。私は、AI に覚えておいてほしいことを書き残すのをやめました。 それでも、AI は、前に決めたことを忘れません。