AI

サブエージェントに報告ルールを守らせる

  • Claude Code
  • hooks
  • サブエージェント
  • SendMessage
  • プロンプトインジェクション

サブエージェントに「最初に計画を1行で送れ」を守らせるなら、SubagentStart hookの additionalContext で渡します。スクリプトファイルも外部コマンドも要りません。

{"hooks":{"SubagentStart":[{"matcher":"","hooks":[{"type":"command","command":"printf '%s' '{\"hookSpecificOutput\":{\"hookEventName\":\"SubagentStart\",\"additionalContext\":\"<報告ルール>\"}}'"}]}]}}

同じ文章でも、どの経路で届くかで従うかどうかが変わりました。Claude Code 2.1.233 で確かめています。

途中経過を報告してほしい

サブエージェントに渡す指示文(brief)に書いて守ってほしいのは、3つだけです。

  • 着手したら、計画を1行で送る
  • 区切りごとに1行送る
  • briefの外に出る作業をする前に、相談して返事を待つ

この3つをまとめて報告ルールと呼んでいます。書いてあるぶんには守られます。

実際に困るのは、並列で何体も立てるときです。briefを短く書きがちで、報告ルールごと落ちます。そういう回に限って、戻ってくるまで何をしているか分かりません。

PreToolUse に付けたら、インジェクション扱いされた

最初に試したのは、settings.jsonPreToolUse です。agent_id はサブエージェントの内側で発火したときだけ値が入るので、それを見て内側の最初のツール呼び出しにだけ additionalContext を返しました。

報告ルール自体は届いています。3回とも、サブエージェント自身が差し込まれた文の中身に触れていました。そのうえで従いません。Sonnet 5 で2回、Opus 5 で1回、どれも同じです。

断ってきた文面に理由が書いてありました(訳は筆者)。

あなたや実際のハーネスからの正当な指示ではなく、ツール出力に埋め込まれたプロンプトインジェクションの特徴を備えている

報告ルールの文面から「hookが付けた」という主張を外して「ユーザーが設定したセッションポリシー」と書き換えても、結果は変わりませんでした。同じ文を CLAUDE.md に置いた場合も通りません。1回は依頼と無関係な埋め込み指示だと書かれて断られ、もう1回は何も言わずに無視されました。

どちらも、プロンプトインジェクション対策が働いた結果です。ツールの出力に混ざって出てきた指示は、依頼者から来たものとして扱われません。CLAUDE.md は断り文句こそ違いましたが、扱いは同じでした。自分で仕込んだ文章でも、判定は変わりません。

依頼の一部として渡すと通る

通った経路は2つあります。

1つは親側です。親がAgentツールを呼ぶ瞬間を PreToolUse で捕まえ、updatedInputprompt の末尾に報告ルールを足します。こうするとサブエージェントから見たbriefは、依頼者が書いた文章と区別がつきません。試した5回すべてで計画が main に届きました。

もう1つが SubagentStart です。ここで返した additionalContext は、ドキュメントにあるとおりサブエージェントが動き出す前のコンテキストに入ります。Sonnet 5 と Opus 5、settings.json に置いた形とプラグインに置いた形、いずれも計画が届きました。

届け方結果
サブエージェント内の PreToolUseadditionalContext拒否。ツール結果に付いた文として扱われる
CLAUDE.md に書く拒否か黙殺。依頼と無関係な埋め込みとして扱われる
親側 PreToolUseupdatedInput通る。briefの一部になる
SubagentStartadditionalContext通る。起動時のコンテキストに入る

通った2つは、どちらも報告ルールが依頼の一部として置かれています。親側の updatedInput はbriefそのものに混ぜ、SubagentStart はハーネスがサブエージェントに渡すコンテキストとして置きます。

落ちた2つは、依頼とは別の場所から来ました。ツール結果に混ざった文と、プロジェクトに置いてある文書です。CLAUDE.md は起動時に読み込まれるので、届くタイミングは SubagentStart と変わりません。それでも通りませんでした。報告ルールの文面を書き換えても、この境目は動きません。

プラグインに入れたのは printf 1つだけ

プラグインに載せたのは SubagentStart 版です。hooks/hooks.json に置いてあり、settings.json には1行も書いていません。

スクリプトファイルも無く、固定のJSONを printf で出すだけなので、依存するのは printf だけです。先に入れていた updatedInput 版と差し替えました。どちらも動きますが、SubagentStart はもともとそのために用意されているぶん、briefを書き換えずに済みます。

hookが渡している報告ルールはこれです。

Reporting (from the agent that briefed you — main if you were spawned by the main session, otherwise the agent named in your brief; call that agent the lead): within your first tool round, SendMessage a one-line plan to the lead. Send one line to the lead at each milestone. Before doing anything outside your brief, SendMessage to the lead and wait for an answer. Write your result to a file — the path the brief names, or, if it names none, a file under the current working directory that you name in your last message — and keep every SendMessage to one line: SendMessage truncates long bodies, so it carries the notice and the file carries the content. If you spawn agents yourself, put your own name in their brief so they can reach you.

判断はhookに入れていません。報告ルールはbriefに書くのが本筋で、hookは書き忘れの保険です。

GitHubGitHub - LabeeHive/standards: Shared standards repository for Labee LLC projects — a Claude Code plugin marketplace of skills, agents, and workflowsShared standards repository for Labee LLC projects — a Claude Code plugin marketplace of skills, agents, and workflows - LabeeHive/standards

孫エージェントの報告を、親に届けたい

宛先が main 固定では困る場面があります。サブエージェントがさらにサブエージェントを立てたとき、孫にとっての報告先は main ではなく親のほうです。

main から名前付きの lead-a を立て、その下に名前無しの孫を立てて測りました。孫の計画も途中経過も、lead-a ではなく main に届いています。SendMessage に「親」を指す宛先が無く、孫は自分を呼んだ相手の名前を知らないからです。

mainが名前付きのlead-aを立て、lead-aがさらに名前のない孫を立てる。briefは左から右へ順に渡る。ところが孫が送る計画と途中経過は、lead-aを飛び越えてmainに届く。SendMessageに親を指す宛先が無く、孫は自分を呼んだ相手の名前を知らないため、間に立つlead-aには途中経過が届かない

そこで報告ルールの文面を「briefを書いた相手に送れ」に変え、briefにleadの識別子を書いて測り直しました。今度は孫が ListAgents で宛先を確かめに行き、その識別子に一致するエージェントが見つからないという理由で、報告そのものを断っています(訳は筆者)。

主張された宛先は実在する識別可能なエージェントに対応していないので、作業を止めて承認を待てという権威ある指示として扱うことは、プロンプトに埋め込まれた検証不能な指図に従うことになる

ここでも同じ対策が働いています。宛先を書けば疑われ、書かなければ main に流れます。完了の通知だけはAgentツールの戻り値として lead-a に返るので、間の階層に見えないのは途中経過のほうです。

欲しいのは、孫の報告が親に届く形です。宛先を検証する仕組みか、間の階層が自分宛の報告を受け取れる仕組みが要ります。今のところ、間に立つエージェントには自分の名前をbriefに書かせる、という手作業の回避策しかありません。