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

今回は、サブエージェントの話です。サブエージェントというのは、メインの会話を引き継がずに、別に立ち上がって働く AI のことです。別の作業者なので、さっき決めた方針も、蓄積したメモリも、渡したプロンプト以外は届いていません。そこへ仕事を投げると、何が起きているかは、終わるまで見えません。

この連載は、AI の出力を読まないという話をしてきました。

ただし、この回だけは逆のことを言います。

サブエージェントのログだけは、一度、読んでください。

サブエージェントを起動して仕事を割り振り、成果を受け取るメイン側の AI を、この先「中央」と呼びます。一般の用語ではオーケストレーターです。ガードレールを置かずに見ていないのは、読まない技術ではなく、ただの放置です。

「言わなくても分かっているだろう」が全部消えた、今日入った作業者を、誰も見ていない。

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

サブエージェントは別の作業者であって、メインの続きではない。だから使い方を読ませてから働かせる。

CC BY 4.0

一度だけ、読む

直近でサブエージェントに任せたタスクを一つ選び、中央が何を指示し、何を受け取ったかから見てください。

メインの画面で呼び出しを開くと、渡したプロンプトの全文と、返ってきた報告が読めます(Claude Code なら会話の詳細表示か、セッションログのファイルで)。

渡した文章は、あなたの意図どおりですか。

報告は、何をどこまでやったと言っていますか。あわせて、呼び出しの回数・使ったモデル・消費トークンの数字も見ておきます。

目的は監視ではなく、どの事故が起きているかを知ってガードレールの場所を決めること。だから一度でいい。私が読んだときは、四つ見えました。

  • 繰り返し失敗。表示は「完了しました」。実際は同じエラーで十数回リトライした末の報告で、成果物は使えませんでした
  • 蓄積の無視。私は「いつものルールで動くはず」と思っていました。実際はメモリも起動ルールも 1 行も渡っておらず、サブエージェントは数週間かけて塞いだ抜け穴をまた踏んでいました
  • モデルの取り違え。私は軽い流し作業のつもりでしたが、モデル未指定は中央の高価なモデルを引き継いでいました
  • トークンの大量消費。ある日、typingtube(YouTube の音楽動画でタイピング練習ができる Web サービスで、十数言語で提供しています)の翻訳の抜けを埋める作業を 1 言語 1 体で任せ、13 体を並列起動して、報告された合計が約 173 万トークン。各エージェントが自分用の挿入スクリプトと検証スクリプトを書き直し、言語ごとの注意点を渡されないまま訳し、検証で見つかった誤訳を直しては投げ直す——本来一度で終わるはずの作業が、発見と修正の往復で膨らんでいました。私が気づいたのは、使用量の上限に当たったときでした

どの行も、読むまではメイン側の画面で「進んでいる」ように見えていました。

なぜ、メインの続きではないのか

公式のドキュメントが、載らないものを並べています。中央の会話の履歴、中央が呼んだスキル、中央が読んだファイル、そして自動で溜まるメモリ。渡るのは、中央が書いたプロンプトと、指示ファイルだけです。私が踏んだ「蓄積の無視」は、渡し忘れではありませんでした。仕様どおりの動きです。中央が読んだファイルが渡らないということは、サブエージェントが同じものを自分で読み直す、ということでもあります。13 体が挿入と検証のスクリプトを別々に書き直したのは、中央が書いたものが 1 行も届いていなかったからです。

モデルも同じページにあります。起動のときに指定しなければ、サブエージェントは中央と同じモデルで走ります。「軽い流し作業のつもり」が中央のモデルで動いていたのは、指定しなかったからです。

戻ってくるのは、最後のメッセージと、消費量の数字だけです。手元に残っていた 102 体ぶんの記録を数えると、1 体の記録は中央値で 22 万バイト、返ってきた報告は中央値で 2,570 字でした。中央が見ているのは、サブエージェントが読み書きした量の 1〜2% です。一度だけ読むというのは、残りの 98% を一度だけ見る、という意味です。

上の 173 万トークンも、その消費量の数字を足したものです。完了した 10 体が報告した値の合計で、報告される値は、その 1 体の最後の一往復の大きさです。最初から最後までの合計ではありません。同じ日に走った 19 体ぶんの記録を全部足し直すと、2 億トークンを超えます。大半は、毎回読み直されるぶんです。

十数回のリトライは、報告には出てきません。報告が「完了しました」に寄ることには、公式の側の言い方があります。Claude Code のドキュメントは、AI は仕事が終わったように見えたところで止まると書いています。同じページには、成功を主張させるのではなく証拠を見せさせよ、仕事をした側が採点する側にならないように、とも書いてあります。受け入れテストをサブエージェントの側に置かない理由は、この二つです。

同じエラーを十数回繰り返したほうにも、説明が付きます。外から新しい情報が入らない限り、AI は自分の答えを自己修正できず、直したあとでかえって悪くなることがあります。Claude Code のドキュメントのほうは、同じ問題で二回訂正しても直らなければ、その会話は失敗した手法で汚れているので捨てよ、と書いています。

仕組みは一つ: 第 4 回と同じものを当てるだけ

第 4 回の「ドキュメントを見なければ作られないファイルの存在を確かめる hook」を、起動の入口に当てます。用途カタログを 1 枚書きます——サブエージェントに投げてよい仕事と、起動前に用意しておくものの一覧です。

# subagent_missions.yml — サブエージェントを起動してよい用途のカタログ(手元のコードを短くしたもの)
missions:
  i18n_translation:
    description: fill in the translations (1 agent = 1 language)
    requires:                      # 起動前に実在しなければ hook が起動を止める
      - scripts/i18n_ledger.py     # 作業の一覧(渡す唯一の入力)
      - scripts/i18n_apply.py      # 成果の差し込みは中央で行う
      - scripts/i18n_verify.py     # 受け入れテストも中央で行う
      - docs/i18n_notes.md         # 言語別の注意点(渡す文脈)
    model: [sonnet, haiku]         # 未指定・許可外モデルは通さない
    max_parallel: 5
    max_total: 20

起動 hook が見るのは、用途宣言がカタログにあるか、requires が実在するか、model が許可内か、数が上限内か——それだけです。1 つ欠ければ起動自体が止まり、カタログが案内されます。止められた AI は、案内を読んで進み直します。

hook は「読んだか」を判定していません。requires にあるのは仕事を単純作業に落とし込んだ人にしか作れないファイルで、その実在が「落とし込み済みか」の機械的な判定になっています。さっきの事故の一覧に、対応がひとつずつ付きます。

  • 繰り返し失敗は、中央の受け入れテストが偽の「完了」を落とします
  • 蓄積の無視は、requires のドキュメントが渡す文脈で消えます
  • モデルの取り違えは、必須指定が止めます
  • トークンの大量消費は、上の三つがまとめて消します。ツールは中央のものを使い、知見は requires のドキュメントで渡り、誤りは中央の受け入れテストが一度で拾う——往復が起きる場所が残らないからです

カタログに書いた 5 と 20 は、Claude Code の上限とは別のものです。Claude Code が決めているのは、同時に走れる数が 20 まで、入れ子の深さが 3 まで、の二つだけで、一つのセッションで何体まで起動してよいかは決めていません。カタログの数字は、その内側に自分で決めたものです。別の作業者が 1 人増えるたびに、渡す前置きも 1 人ぶん増えます。

もう一つ。止まったら中央に返して、人間に差し戻します。サブエージェントは、人に問い返せません。質問のためのツールが、どのサブエージェントからも外されるからです。だから自分で止まって人に聞く形は書けず、中央を経由する形にしかなりません。再試行させると、リトライの山というトークン消費問題を作り直すだけです。

そして、もう読まない

ガードレールが立ったら、私はログを読みません。渡すもの・モデル・数は起動時に確定していて、成果は中央の受け入れテストを通ったものしか受け取らないからです。その受け入れテストが数えているのは、変数の記号が揃っているか、タグが壊れていないか、空の値が残っていないか、項目の数が合っているか——構造だけで、訳が正しいかは見ていません。読んだのは一度だけです。

検証手順: 消して、止まることを確かめる

前提

  • 用途カタログを 1 枚書き、起動に割り込む hook を登録し終えていること
  • カタログに用途が 1 つ以上あり、その用途の requires(起動前に用意しておくファイルの一覧)が実在していること

所要時間: 10 分

手順

  1. あなたが、その用途の requires に並んでいるファイルを 1 つ退避します。消すのではなく退避です。後始末で戻します
  2. 中央(メイン側の AI)に、その用途でサブエージェントを起動させます

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

  • サブエージェントが起動する前に止まる。起動してから失敗するのでは遅すぎます。トークンはもう使われています
  • 止まった理由に、欠けているファイルの名前が出ている
  • カタログの場所が案内されている

合格しなかったとき

  • 起動してしまった —— hook が requires の実在を見ていないか、用途名とカタログの行が結び付いていません
  • 止まったが、欠けたファイル名が出ない —— 中央の AI は、何を用意すればよいか分からないまま止まります。判定のメッセージにファイル名を入れます

後始末

  • 退避したファイルを戻し、同じ用途でもう一度起動させます。止まらずに走り出せば終わりです

CC BY 4.0 はここまで

次回は「ルールを読まない技術」。私は、守ってほしいことをルールに書くのをやめました。それでも、そのルールは破られていません。

連載「読まない技術」