はじめに:「エージェントってブラックボックスだな」と感じていたあなたへ
Claude Codeを毎日のように使っていると、ふと思うんですよね。
「これ、中身どうなってるんだ?」と。
ファイルを勝手に読んで、差分を見せて、コマンドまで走らせてくれる。便利すぎるがゆえに、仕組みがブラックボックスのまま。仕事で使う道具の中身がわからないのは、ちょっと気持ち悪い。かといって、平日の仕事終わりに分厚い論文を読み解く時間も気力もない……というのが、働きながら学ぶエンジニアのリアルだと思います。
そこで私が選んだのは「自分で小さく作って理解する」という挑戦でした。
このシリーズ(全8回)は、ローカルLLM(Ollama)をメインに、Claude APIにも切り替えられるAIエージェント「Code Maintenance Agent」をゼロから13ステップで作り上げた軌跡です。Claude Codeのミニマム版を、自分の手で動かしながら理解していきます。
この記事は、その全8回を1本のロードマップとして再構成した「総集編」です。各回の核心となる知見と、つまずきの乗り越え方を一気に俯瞰できる構成にしました。これから自作に挑戦したい人の地図として使ってもらえたら嬉しいです。
このシリーズと本記事は Claude(設計・構想・レビュー)と Claude Code(実装・記事生成)を相棒にして書いています。AIに作業を「丸投げ(代行)」するのではなく、自分の理解と時間を広げる「自己拡張」の相棒として使う、というスタンスで進めています。
このシリーズで目指したゴール
最初に、走りきった先にある完成形を共有します。これがモチベーションの源でした。
$ python src/agent.py --llm ollama --model llama3.1:8b
╔══════════════════════════════╗
║ Code Maintenance Agent ║
╚══════════════════════════════╝
> target/sample_app/main.py を読んで改善してください
[🔧 ツール実行] list_files
[🔧 ツール実行] read_file
📋 変更計画
1. open() を with 文に変更してリソースリークを防ぐ
2. enumerate を使って for ループを改善する
実行しますか? [y/N] : y
(差分確認 → 承認 → パッチ適用 → サマリー出力)
自然言語で指示すると、エージェントが自分でコードを探索し、改善計画を立て、人間の確認を取ってから変更を適用する。Claude Codeでおなじみのあの動きを、ローカルで無料で動かせるところまで持っていく――これがゴールでした。
なぜローカルLLM(Ollama)をメインにしたのか
仕事と学びを両立させるうえで、これは大事な選択でした。
- コスト:ローカルなので何度試しても無料。心置きなく失敗できる
- 速度:APIのネットワーク遅延がない
- 学習効果:手元で動かすことでLLMへの理解が深まる
- 比較:同じコードで Claude API に切り替え、挙動を比べられる
「気軽に何度も挑戦できる環境」が、学習の継続には何より効くと感じています。
全体ロードマップ:13ステップ × 4フェーズ
完成までの道のりは、4つのフェーズに分けて段階的に進めました。一気に作らず「1ステップごとに動作確認」が、挫折しないための鉄則です。
Phase A:土台(API呼び出し・REPL)
Step 01 Ollamaセットアップ+LLMクライアント抽象化
Step 02 システムプロンプト設定
Step 03 ターミナルREPLループ
↓
Phase B:ツール化(読み取り)
Step 04 tool_useの仕組みを理解する
Step 05 read_file / list_files 実装
Step 06 search_text 実装(grep相当)
Step 07 ツール自動ディスパッチループ
↓
Phase C:書き込み・実行
Step 08 show_diff 実装
Step 09 patch_file 実装+人間確認フロー
Step 10 run_command 実装(subprocess)
↓
Phase D:統合
Step 11 探索→計画→確認→実行の一連フロー
Step 12 変更サマリー出力
Step 13 デモ用サンプルコードで動作確認
| フェーズ | テーマ | 走りきって得た核心 |
|---|---|---|
| A | LLMクライアント・REPLループ | 抽象化で Ollama/Claude を切り替える |
| B | 読み取り系ツール・ディスパッチ | tool_use の仕組みとループ設計 |
| C | 書き込み系ツール・人間確認 | 安全な変更フローの設計 |
| D | 統合・サマリー・デモ | システムプロンプトで行動を制御する |
最終的に装備したツールは7本。「やり直しが効くか」を境界線に、役割をきれいに分けたのがポイントです。
| 種別 | ツール | 人間確認 |
|---|---|---|
| 読み取り(自動) |
read_file / list_files / search_text / show_diff
|
不要 |
| 書き込み(確認) |
patch_file / write_file
|
必要 |
| 実行(確認) | run_command |
必要 |
各回の要点総括
ここからは全8回を、ロードマップに沿って一気に振り返ります。細かいコードは各回の記事に譲り、「何がわかったか」「どこでつまずき、どう乗り越えたか」に絞ってまとめます。
A1:AIエージェントとは何か・設計思想
自律的にツールを使いながら、目標を達成するまでループし続けるLLMの応用
シリーズの出発点。エージェントの本質は 「ループ」と「ツール」 の2つだけ、と定義したのがこの回です。
設計の柱は3つに絞りました。
-
LLMを抽象化する:Ollama も Claude も同じ
chat()で呼べるようにする - 人間の確認を挟む:取り返しのつかない操作の前で必ず止まる
- 段階的に実装する:13ステップに分け、毎回動作確認する
「自律的に動く」と「人間がコントロールする」のバランスこそ設計の核心、という方針をここで固めました。
A2:ローカルLLMとClaude APIを切り替えるアーキテクチャ(Phase A)
いよいよ実装スタート。土台となる3点を作りました。
-
LLMクライアント抽象化レイヤー:
LLMClientBaseを定義し、戻り値を{content, tool_calls, stop_reason}に統一 - システムプロンプト設定:エージェントの行動原則を一か所で管理
-
REPLループ:会話履歴を
messagesリストに積み上げ、文脈を保つ
OllamaがOpenAI互換エンドポイントを提供しているおかげで、openai ライブラリの base_url を向けるだけで接続完了。この「同じインターフェースで両対応」という設計が、後々まで効いてきます。
つまずき → 乗り越え:Windowsの cp932 エンコーディングで日本語出力がエラーに。sys.stdout.reconfigure(encoding="utf-8") で解決しました。
📖 A2:ローカルLLM(Ollama)とClaude APIを切り替えるアーキテクチャ
A3:tool_useを理解してツールを作る(Phase B)
「ただの会話AI」を「ツールを使うエージェント」へ進化させた、シリーズ最大の山場のひとつ。
核心は tool_use サイクル です。LLMの stop_reason が "tool_use" のときツールを実行し、結果を messages に追加して再度LLMに渡す。これを while True で繰り返すだけ。シンプルですが、これこそがエージェントの心臓部です。
ツール追加を楽にする dispatch() と TOOL_REGISTRY の設計も導入し、「ツールが増えても呼び出し側は変わらない」拡張性を確保しました。
つまずき → 乗り越え:最初に使った qwen2.5-coder:7b がtool_use非対応で、JSONを平文返却していた事件。llama3.1:8b に切り替えて解決。「モデルがtool_useに対応しているか」の事前確認は必須だと痛感しました。
A4:ファイル探索エージェントを作る(Phase B)
読み取り3本(list_files / read_file / search_text)を組み合わせ、自然言語でコードベースを探索できるエージェントが完成。
| ツール | アナロジー |
|---|---|
list_files |
地図を広げる |
read_file |
地図の1点を拡大する |
search_text |
地図から目的地を探す |
「あのコードどこに書いたっけ?」を自然言語で解決できる体験は、地味ですが感動モノでした。EXCLUDE_DIRS や MAX_RESULTS=50 でLLMに渡す情報を小さく保つ工夫が、安定動作のコツです。
このフェーズのツールは全部「読み取り専用」。どれだけ動き回ってもファイルは変わらないので、安心して試せるのが嬉しいポイントでした。
A5:差分確認・人間承認フローの実装(Phase C)
ここから「読む」を超えて「書く」の領域へ。一番こだわった安全設計の回です。
==================================================
[変更確認] target/test_patch.py
--- a/target/test_patch.py
+++ b/target/test_patch.py
(差分表示)
==================================================
実行しますか? [y/N]:
採用した方針は 「取り消せない操作には必ず確認を挟む」。読み取りは自動、書き込みは人間が判断する――Claude Code自身と同じ思想です。
設計上の工夫が光ったのは2点。
-
show_diff(表示専用)とpatch_file(書き込み)を分離:確認フローを組みやすくする -
patch_fileは最初の1件だけ置換(.replace(..., 1)):意図しない箇所まで書き換わらない安全策
A6:コマンド実行ツールの実装と全7本ツール完成(Phase C)
最後のツール run_command を実装し、全7本が揃った回。subprocessで stdout / stderr / returncode をまとめてLLMに返します。
これで「変更したらテストを実行する」という開発の基本サイクルを、エージェントが自律的に回せるようになりました。
危険コマンドの簡易チェック(DANGEROUS_COMMANDS)も入れましたが、これは完全なサンドボックスではないと正直に位置づけています。主役はあくまで人間確認フロー。簡易チェックは「うっかり」への最低限の防壁、という割り切りです。
つまずき → 乗り越え:Windowsの dir 出力が cp932 で文字化け。encoding="utf-8", errors="replace" で「成否は正しく取れればOK」と許容しました。
A7:探索→計画→実行を統合してエージェントを完成させる(Phase D)
7本の部品を1本のフローに統合。「ファイルを探して→読んで→計画を提示して→確認を取って→変更する」が、ひとつの指示で自律的に動く最終形です。
最大の変更点は システムプロンプトの設計 でした。
- 行動フローを番号付きで明示(Phase 1探索 → Phase 2計画 → Phase 3実行):計画提示のスキップを防ぐ
-
tool_use形式を強制(「テキストでJSONを返さない」の一文):
llama3.1:8bのJSON平文返却を解消
つまずき → 乗り越え:終了条件がないと無限ループに陥る問題。MAX_TOOL_ITERATIONS = 20 で「ループは外から止める」を徹底しました。エージェント設計では終了条件の明示が必須です。
📖 A7:探索→計画→実行を統合してエージェントを完成させる
A8:完成・振り返り・v2への改善案
13ステップの集大成。llama3.1:8b の正直な評価がこの回の白眉です。
| 項目 | 評価 | ひとこと |
|---|---|---|
| tool_use の安定性 | ✅ 良好 | 適切なタイミングでツールを呼ぶ |
| 計画の質 | △ 概ね妥当 | 適用コードが曖昧になることも |
| 差分生成の正確さ | ✅ 良好 |
original_snippet の特定が正確 |
| 日本語応答の品質 | △ ムラあり | hallucination が混入することも |
一言でいえば「完全には使えないが、思ったより使える」。tool_useは安定する一方、自然言語の説明には精度のムラが残るため、プロダクション用途ではClaude APIへの切り替えが現実的、という着地でした。
v2への改善案(Git操作ツール、マルチファイル対応、高精度モデルへの切り替え等)と、セッションをまたいで文脈を維持する PROGRESS.md駆動開発 の知見も整理しています。
走りきって得た「3つの核心」
8回を振り返って、ロードマップ全体から抽出できる学びは3つに集約されます。
-
エージェントの本質は
while Trueループとツールのディスパッチ
複雑に見える動作も、分解すれば「LLMに次の手を聞く→ツールを実行→結果を返す→また聞く」の繰り返しに帰着します。 -
境界線は「やり直しが効くか」で引く
読み取りは自動、書き込み・実行は人間確認。REQUIRES_CONFIRMATIONセットで宣言的に管理することで、ツールが増えても確認ロジックは変わりません。 -
システムプロンプトはコードと同じくらい設計対象
行動フローの明示、tool_use形式の強制、カレントディレクトリの明示――プロンプトの一文がLLMの挙動を大きく左右しました。
そして何より、「作って理解する」アプローチの価値は 「なぜそう設計するのか」が体験として残ること にあります。MAX_TOOL_ITERATIONS で無限ループを止めたとき、JSON平文問題をプロンプトで解決したとき――どの判断も「動かしてみて必要だとわかった」という生の体験に紐づいています。
おわりに:自作がくれた「時間と心のゆとり」
正直に言うと、このシリーズを走りきって一番変わったのは、Claude Codeを触るときの「心のゆとり」でした。
以前は「便利だけど中身がわからない道具」だったものが、今は「だいたいこういう仕組みで動いているな」と想像しながら使えます。エラーが出ても、ツール呼び出しの挙動が変でも、慌てなくなりました。仕組みを知っているという安心感が、日々の開発のストレスを確実に減らしてくれます。
ローカルLLMをメインにしたおかげで、コストを気にせず何度でも挑戦できたのも大きい。失敗を恐れない環境が、限られた休日の時間でも学習を続けられた理由です。
AIを「作業の代行」として丸投げするのではなく、自分の理解と可能性を広げる「自己拡張」の相棒として使う。その手応えを、この自作の旅でしっかり掴めた気がします。
次のステップは Series B:Claude Codeを使いこなす。エージェントを自作した体験を土台に、開発ツールとしてのClaude Codeをさらに深掘りしていきます。
あなたも、まずは Phase A の小さな一歩から、自分だけのエージェントづくりに挑戦してみませんか?