@yag_ays

自作AI AgentでClaude CodeのPlan Mode相当を実装する

Claude Codeを効果的に使うためのベストプラクティスとして、Plan Modeで最初に計画を立てることの重要性がよく挙げられます。いきなりコードを書き始めるのではなく、まず何をするかを整理してから実装に進むことで、手戻りを減らし精度の高い成果物を得られるというものです。

このPlan Modeの仕組みを自作のAI Agentにも取り入れたいと感じたので、Claude Agent SDKを使って再現してみました。この記事では、Claude CodeのPlan Modeが内部的に何をしているかを解説し、それを自作AI Agentで再現する実装について紹介します。

Claude CodeのPlan Modeとは

Claude CodeのPlan Modeは、実装に着手する前に計画を立てるフェーズを設けるための仕組みです。ユーザーがPlan Modeを有効にすると、Claudeはコードの読み取りと計画の策定だけを行い、ファイルの編集やコマンドの実行は一切行いません。

これらの実装や内部構造は公式には公開されていませんが、Armin Ronacherの記事 What is Plan Mode? に詳しく書かれています。彼の分析によると、Plan Modeの本質は「カスタムプロンプトとシステムリマインダー」であり、技術的に複雑なことはしていないとのことです。

ここからは、このブログの内容に沿って解説します。内部的には、以下の3つの要素で成り立っています。

要素 1. システムリマインダーによる制約

各ユーザーメッセージにシステムリマインダーが自動的に付加されます。このリマインダーには「Plan mode is active. The user indicated that they do not want you to execute yet — you MUST NOT make any edits」という強い制約が含まれており、「This supercedes any other instructions you have received」と他のすべての指示を上書きすることが明記されています。つまり、仮にユーザーが「このファイルを編集して」と言っても、Plan Mode中はそれが無視されます。

要素 2. 構造化されたワークフロー

4フェーズの段階的なワークフローがプロンプトとして埋め込まれています。

  1. Initial Understanding — コードの読解とユーザーへの質問
  2. Design — 実装アプローチの設計
  3. Review — 計画とユーザー意図との整合性チェック
  4. Final Plan — 計画ファイルへの書き込み

各フェーズでExplore AgentやPlan AgentといったSub Agentを並列起動する指示も含まれており、効率的な情報収集と計画策定が行われます。

要素 3. ExitPlanModeツールによる承認

Claudeは計画をファイルに書き込んだ後、ExitPlanModeツールを呼び出すことでユーザーに承認を求めます。このツールは計画内容をパラメータとして受け取るのではなく、計画ファイルから自動的に読み取る仕組みになっています。これにより、計画が完成したタイミングで必ずユーザーの承認を挟む流れが実現されています。

自作AI Agent向けの実装方針

Claude CodeのPlan Modeを自作AI Agentで再現するにあたって、以下の方針で実装しました。

  1. plan.mdに計画とユーザへの質問を書き込む
    • Claudeにplan.mdというファイルへ計画を書かせる
    • 質問がある場合は構造化されたフォーマットで質問セクションを書き込む
  2. 不足している情報を主体的に検討してユーザに質問を投げる
    • 推測で埋めるのではなく、不明点を明示的に質問として提示させる
  3. 最後にすべてをまとめて承認を取る
    • 質問がすべて解消されたら最終計画を書き出し、ユーザーの承認を得てから実装フェーズに進む

Claude CodeにはExitPlanModeやAskUserQuestionといった専用ツールがありますが、今回の実装ではこれらを使わず、plan.mdの構造化されたフォーマットをプログラム側でパースすることで同等の機能を実現しています。

実装

サンプルとなる実装は、以下のリポジトリで公開しています。

https://github.com/yagays/ai-agent-plan-mode-example

実装にはClaude Agent SDKのPython SDKを利用しています。全体の流れを、以下の4つの要素に分けて解説します。

システムプロンプト

プランを検討するAgentのシステムプロンプトです。ポイントは、Claudeに「ソフトウェアアーキテクトであり、計画立案の専門家」というロールを与えつつ、plan.mdへの書き込みフォーマットを厳密に指定している点です。

def build_system_prompt(plan_file: str) -> str:
    return f"""\
あなたはソフトウェアアーキテクトであり、計画立案の専門家です。
ユーザーのタスクを分析し、実装計画を作成してください。

## 計画の管理方法
計画は `{plan_file}` に直接書き込んでください(Writeツール使用)。

## 最重要ルール:不足情報の主体的な特定
計画を立てる前に、要件の曖昧さや不足情報を**必ず**チェックしてください:
...
不足情報がある場合は、**推測で埋めずに `{plan_file}` に質問セクションを書いてください。**
"""

特に「推測で埋めずに質問セクションを書く」というルールが重要です。これにより、Claudeは不明点があれば必ず質問として構造化して出力するようになります。

質問のフォーマットは以下のように指定しています。

## 確認事項
以下の点について確認が必要です:

### Q1. 使用するデータベースはどれですか?
- 背景: スキーマ設計に影響するため
- [ ] PostgreSQL
- [ ] SQLite
- [ ] MySQL

### Q2. 認証方式の要件を教えてください
- 背景: ミドルウェアの選定に影響するため

選択肢がある質問は- [ ]で列挙し、選択肢がない質問は自由記述として扱います。このフォーマットにすることで、プログラム側で機械的にパースできます。

マルチターンループ

Plan Modeの核となるマルチターンループの処理です。Claude Agent SDKのquery()とセッション再開(resume)を使い、依頼→質問→回答→計画更新のループを実現しています。

async def run_plan_phase(task, cwd, model):
    for turn in range(1, max_turns + 1):
        # query()でClaudeを呼び出し、resumeで会話を継続
        async for message in query(prompt=current_prompt, options=options):
            session_id = message.session_id

        # plan.mdをパースして質問があればユーザに聞く
        questions = parse_questions(plan_content)
        if questions:
            current_prompt = format_answers(questions, await ask_questions(questions))
        else:
            # 質問がなければ承認を求めて終了
            if await ask_approval():
                return plan_content

allowed_toolsで使用可能なツールをRead、Glob、Grep、Writeに限定しています。これによりClaude CodeのPlan Modeと同様に、コードの読み取りと計画ファイルへの書き込みだけが許可されます。なお、

Claude Agent SDKにはplanというパーミッションモードが用意されていますが、これはツールの実行を一切許可しないモードです。今回の実装ではplan.mdへの書き込みにWriteツールが必要なため、planモードは使わずにallowed_toolsで制御しています。

セッション再開(resume)を使うことで、Claudeは過去のやり取りを踏まえてplan.mdを更新できます。ユーザーの回答を受け取るたびに新しいプロンプトとして渡し、計画を洗練していく流れです。

質問のパース

plan.mdから質問を抽出する処理です。「## 確認事項」セクション内の「### Q{番号}. {質問文}」を正規表現で検出し、背景情報と選択肢を構造化データとして取得します。

def parse_questions(plan_content):
    # "## 確認事項" セクションを抽出
    section = re.search(r"## 確認事項\n(.*?)(?=\n## |\Z)", plan_content, re.DOTALL)
    # "### Q1. 質問文" のブロックごとに分割し、背景・選択肢をパース
    for block in re.split(r"(?=### Q\d+\.)", section.group(1)):
        header = re.match(r"### Q(\d+)\.\s*(.+)", block)
        choices = re.findall(r"- \[ \]\s*(.+)", block)
        ...

質問がなくなった(= ## 確認事項セクションが存在しない)場合は計画が完成したと判断し、承認フローに進みます。これがClaude CodeのExitPlanModeに相当する仕組みです。

ユーザーインタラクション

ユーザーへの質問にはquestionaryライブラリを利用しています。選択肢がある質問では矢印キーで選択するUIを表示し、選択肢がない質問では自由記述の入力を受け付けます。以下は実行時の出力例です。

Q1. 天気予報のデータソースはどのAPIを使いますか?
   背景: 天気予報APIによって、取得できる情報や必要な認証(APIキー)が異なります。

? Q1の回答を選んでください: (Use arrow keys)
 » Open-Meteo API(無料・APIキー不要・世界中の天気を取得可能)
   OpenWeatherMap API(無料枠あり・APIキー必要・人気が高い)
   気象庁API(非公式・APIキー不要・日本国内のみ)
   その他(自由記述)

計画完成後の承認フローでは「承認する / 修正をリクエスト / キャンセル」の3択を提示します。修正をリクエストした場合は、フィードバックをClaudeに渡して計画を更新するループに戻ります。

動作例

試しに「天気予報を表示するPythonコード書きたい」というざっくりとした依頼を投げて、より詳細な実行計画に落とす処理をさせてみました。

Plan Modeの動作例

この動画では一部思考中の待ち時間をカットしています。実際にはもう少し返答までに時間がかかります。

まとめ

今回はClaude CodeのPlan Mode相当のものを、Claude Agent SDKを使った自作AI Agentで再現してみました。実際に実装してみると、質問の生成はLLMにより作成されるため、それをハンドリングするだけで、特に難しいことはやっていないということがわかります。Plan Modeの仕組み自体はプロンプトエンジニアリングとファイルベースの状態管理という枯れた技術の組み合わせです。Claude CodeのAskUserQuestionやExitPlanModeといった専用ツールに相当する機能も、出力フォーマットの構造化とプロンプトの工夫によってシンプルに実現できました。

あくまで全体の処理の流れとI/Oを規定しているだけなので、筋の良い計画をつくれるかどうかは、LLMの思考能力に依拠しているといえます。ユーザーがいかに明確な完成像を持ってコンテクストを与えられるかが、Agentの計画の質を左右する大きな要因になるでしょう。