Letting Claude Code Build the Servers
これはクラウドの話ではない。データセンターに自分でラックを借りて、物理サーバーを置いて、その上にVMを並べて、外向きのサービスを動かしている——いわゆるオンプレの話だ。AWSのコンソールも、Terraform で aws_instance を増やす話も出てこない。出てくるのは、物理ホスト、ハイパーバイザ、VLAN、VRRP、ファイアウォール、IPMI、そして「できればやりたくないが、いざとなったら現地に行かないといけない」という現実だ。正直、ラックの前に立つ作業はこの仕事のなかで一番気が重い部類に入る。
そのオンプレ環境を、ある日、前任者から引き継いだ。引き継ぎ自体はあったが、長年積み上がってきたものを短期間で全部聞き出すのはどだい無理がある。ドキュメントは断片的で、構成図は一部古く、なぜそうなっているのかの背景は前任者の頭の中にしかない、という箇所が随所にある。物理ホストが十数台、その上に数十のVM、ネットワーク機器も数台。本番トラフィックは止めない。
いま、サーバーの構築・運用・障害調査を、すべて Claude Code に任せている。 設計を相談し、方針を決め、ロールを書き、VMを起こし、OSを設定し、ファイアウォールを開け、監視に組み込み、DNSにレコードを足し、本番トラフィックを段階的に移し、深夜のアラートでメトリクスを横断クエリして原因を切り分ける——この一連の作業を、エージェントが手を動かす。わたしは方針と承認を出す側に回っている。これがベストな答えだとは思っていない。これからまだ何度も形を変えるはずだ。それでも、引き継いだ1ラックのオンプレIDCで実際に回っている方法論を、ひとつ書き残しておく。
何が起きたか
そこからどう動いたか、結果のほうを先に出す。
1週間とちょっとで、本番で動いている15台前後のVMを、ひとりオペレーションで入れ替えた。 内訳を雑に出すと、SLB 2台、内部DNS 2台、VPNゲートウェイ 2台、Bastion 1台、DBレプリカ 2台、Valkey 3台、リバースプロキシ 2台、ジョブサーバー 1台。どれもユーザーから見えるサービスを抱えた箱で、止めるとどこかが見えなくなる。従来の感覚では、複数人の運用チームで数ヶ月かけてやるような規模の話だ。
SLB(Software Load Balancer)
ハードウェアアプライアンスではなく、汎用サーバー上のソフトウェア(HAProxy、nginx、Envoy など)でロードバランサ機能を実現したもの。冗長化のために2台以上を VRRP で組み、片方が落ちたらもう片方が VIP(仮想IP)を引き取る構成が一般的。
そのなかにはロードバランサの入れ替えも含まれている。SLBの入れ替えというのは、昔は一大プロジェクトだった。新規ハードウェアの構築、疎通試験、上流・下流とのプロトコル互換検証、HAの縮退試験、本番切り替えのリハーサル、夜間メンテ枠の確保——複数人が数週間がかりで臨むイベントだ。それをエージェントと一緒に、計画から本番投入まで数日で回した。互換試験の項目もエージェントが洗い出し、結果のログをまとめ、合否を判定した。段階ロールアウトの加重変更も、トラフィック観察も、エージェントが順番を覚えていてくれる。
ただ、互換検証のシナリオをエージェントだけで設計させると、現場で踏んだことのある罠が抜け落ちる。ここは人間が経験で持ち込む部分が大きい。たとえば、リバースプロキシの入れ替えなら「ドメインごとにキャッシュキーの組み立て方が違う」「特定のドメインだけ Vary ヘッダの扱いが特殊」「キャッシュパージのAPIを叩いたときに上流のオリジンに余計な負荷が乗らないか」といった項目は、過去にそれで事故ったことのある人間しか思いつかない。これをエージェントに最初に教えておくと、検証項目として機械的に並べてくれるようになる。
SLBの縮退試験も同じだ。1台落としたときに、VRRP の切り替わりが期待通りに走るか、というのは基本中の基本だが、実際の現場では「切り替わりは走ったが、監視側のアラートが上がらなかった」「上がったが Discord 通知に流れなかった」「通知は来たが復旧通知が来なかった」のような、監視系との連動部分で躓くことが多い。だから縮退試験のチェックリストには、必ず「実際に通知が来たことを確認する」「復旧後に復旧通知が来たことを確認する」を入れる。エージェントは縮退試験を「LB側の挙動の確認」だと思いがちなので、これは最初に明示する。
つまり、検証シナリオの「観点」は人間が持ち込み、「実行」と「結果のまとめ」はエージェントがやる、という分担になる。観点の側は、起きた事故ごとに少しずつ増えていく。これも前述の「ナレッジに畳み込む」ループに乗る。
それが回ったのは、わたしが急に有能になったからではない。Claude Code に構築・運用・調査を任せて、自分は判断と承認に集中したからだ。
「サーバーを建てさせる」とは何をやらせているか
抽象論ではなく、具体的に何をエージェントにやらせているかを書く。たとえばVMを1台追加するとき、エージェントは次のことを順番にやる。
- キャパシティを計算する(どの物理ホストに余裕があるか、CPU・RAM・ストレージで判定)
- 仮想化基盤のCLIを呼び出してテンプレートからVMを起こす
- ホスト名・IP・VLANを決める(既存のホスト一覧と照合して衝突を避ける)
- NICを接続する
- OSの初期設定を Ansible ロールで流す(パッケージ、ユーザー、SSH、時刻同期、ログ転送など)
- ファイアウォールのルールを書き、適用する
- 内部DNSにレコードを追加し、ゾーンファイルを更新して2台のDNSサーバーに配布
- 監視対象として登録(exporter のインストール、VictoriaMetrics の scrape 設定追加、Grafana ダッシュボードの追加)
- ホスト一覧、サーバー個別ページ、サービス横断ページ、トポロジ図、Ansible inventory を一括更新
- 上記すべてを1つのコミットにまとめて Git に push
人間がやるのは、最初に「こういうVMが欲しい」と意図を伝えることと、途中で差分を見て承認することだけだ。手は動かさない。打鍵で間違える要素がそもそもない。
サービスの入れ替えになると、これがさらに大きくなる。新しいホストを上の手順で建て、本番トラフィックを段階的に移し(5% → 20% → 50% → 100%)、各段階でメトリクスを確認し、問題なければ次に進み、最後に旧ホストを停止してリソースを回収する。この一連の流れを、エージェントが順序を覚えていてくれる。途中で人間が席を立っても、戻ってきたときに「次は何でしたっけ」と聞けば正しい次の手順が返ってくる。
これが、1週間で10台以上を入れ替えられた理由だ。
任せるための足場
もちろん、何の準備もなしに Claude Code に「サーバー建てて」と頼んだら、すぐに事故が起きる。性能の高いエージェントほど、走らせる前のコース整備が要る。
足場の中身を整理するとシンプルだ。届いた先での作業をどう記録に残すか——IaC、単一リポジトリ、スキル、ドキュメント構造——これがひとつ。エージェントが本番サーバーに「届く経路」をどう設計するか——ガードレール、VPN、SSHラッパー、クレデンシャル管理——これがもうひとつ。前者がなければ再現性が消え、後者がなければ事故が起きる。両方そろって初めて、構築をエージェントに任せられる。
以下、6つに分けて書く。
1. すべてをコードに引き上げる
Ansible の --check モード
プレイブックを実際には適用せず、「適用したらどう変わるか」だけを出力するドライランモード。サーバーの現在の状態とロールに書かれた目標状態の差分を確認できる。差分がゼロなら、ロールは現状を完全に記述できていることになる。
引き継いだ環境は「動いてはいるが、コードの形にはなっていない」状態から始まった。最初の仕事は、現状を観察して Ansible ロールに書き起こし、--check で「差分ゼロ」になるまで突き合わせることだった。
ここを飛ばすと後で全部崩れる。コードで説明できないホストが1台でも残っていると、自動化はそのホストを避けて回り始め、特例のための条件分岐がロールに増えていく。エージェントに「全ホストにXを適用して」と頼んでも特例で止まる。だから最初に例外を解消する。
ここから先の鉄則はひとつだけ。変更はすべてロール/プレイブック経由で流す。エージェントが構築するということは、エージェントが書いたコードがそのまま記録になる、ということでもある。手で直す余地を残すと、その瞬間にエージェントの「事実認識」と現実がズレ始める。
2. ひとつのGitリポジトリに全部載せる
ドキュメント(Markdown)、設定ファイル、Ansibleロールとプレイブック、inventory、スキル定義、ルール定義——これを全部ひとつのGitリポジトリに置いた。
分けると世代がズレる。「ドキュメントは去年、Ansibleは先月、実機は今日」という三重構造ができあがる。ひとつのリポジトリにまとめると、コミットが「事実」と「説明」と「適用方法」を同時に動かす。VMを1台増やすコミットには、ホスト一覧の追加行、個別ページの新規ファイル、サービス横断ページの参加情報、Ansible inventory の差分、関連する計画書の更新が、ぜんぶ入る。
世代管理が自動でついてくる、というのが核心だ。「いつ・誰が・なぜ・何を変えたか」を別のシステムで管理する必要がない。git log と git blame がそれを全部やる。エージェントに「このホストはなぜこの構成か」と聞けば、git log を遡って関連コミットから経緯を組み立てて返してくる。
ひとつだけ現実的な制約がある。インフラのリポジトリは性質上、外部のホスティングサービスには置きづらい。GitHub等は使えない。そこでGitのリモートだけは内側に立てる。PRレビューやCIといった便利機能は諦め、その代わりに Ansible の --check を CI 代わりにし、エージェントに差分をレビューさせてから人間が最終判断する。不便は不便だが、Git そのものの世代管理さえ手元にあれば、「なぜこうなっているか」を遡る能力は失われない。
3. ガードレール(rules)を先に立てる
Claude Code には、特定のパスを触るときに自動でロードされるルール定義の仕組みがある。リポジトリ全体に対しては、もっと根本的な原則を1枚置く——情報収集は read-only コマンドのみ、設定変更は明示の指示があるまで禁止、出力は必ず制限をかける、といった内容だ。
許可リスト発想で書くのが効く。「これを禁止」ではなく「許可されたこと以外は全部禁止」のほうが抜け穴が少ない。rm を禁止しても find -delete がある。個別に塞いでいくより、最初から許可リスト方式にしたほうが早い。
ルールが自動で挟まると、エージェントの暴走範囲が物理的に狭くなる。プロンプトに毎回書く必要も、書き忘れもない。
ルールは許可リスト発想で「許可された世界」を定義する。一方で、ルールはあくまでエージェントが読んで従ってくれることに依存している、という意味では「お願い」でもある。そこで、もう一段、強制力のある層として hook を併用する。Claude Code には、ツール実行の前後にスクリプトを差し込めるフックがある。フック側はブロックリスト型で、「これだけは絶対に通すな」というパターンを文字列でマッチして物理的に止める。エージェントがどう判断しようと、フックを越えられない。
ルール(許可リスト・自制依存)と hook(ブロックリスト・物理遮断)の二層で守る、というのが基本構図だ。
実際に組んでいるのは、3つのフックだ。
- 環境ファイル読み取りの遮断:
.envやsecrets/配下に対する Read / Bash 系の呼び出しを、パスマッチでブロックする。エージェントが「このファイルを読みたい」と判断しても、ツール実行の手前で止まる。 - Git 破壊的操作の遮断:
git reset --hard、git push --force、git clean -fdのような、履歴を巻き戻したり消したりする操作を弾く。エージェントが「リカバリのため」と判断しても、ここは人間の手で行う。 - リモート破壊的操作の遮断:SSH 経由で対象ホストに対して
rm -rf、systemctl stop、rebootのような操作を流そうとしたとき、コマンド文字列をパースして弾く。./scripts/ssh-wg.shを経由する以上、フック側はコマンドの中身を見ることができる。
ルールは「読む側の自制」、フックは「実行側の物理的な遮断」。同じことを二重に守るのは無駄に見えるが、エージェントは時々ルールを忘れる(あるいは創造的に解釈する)ので、フックがあると安心して任せられる。「絶対に触ってほしくないもの」だけは、フックで物理的に止めておく。
4. 手順をスキルにする
Claude Code のスキルとサブエージェント
似た概念だが役割が違う。スキルは手順書に近い。「VMを作る」「DNSをデプロイする」のような特定の作業について、開始前のチェック・各工程・終了後の検証を1ファイルに書いておき、/skill-name のように呼び出して実行する。毎回同じ順番で同じことをやらせるための仕組み。 サブエージェントは専門家に近い。情報収集だけをする調査員、障害切り分け係、キャパシティ計算係、というふうに役割で分けて、それぞれに必要な権限とプロンプトだけを与える。スキルが「やり方」を固定するのに対し、サブエージェントは「視点と権限」を固定する。
繰り返す作業はすべてスキルとして固定した。VMを作る、DNSをデプロイする、監視設定を流す、Bot をデプロイする、Ansible playbook を流す——各スキルには、開始前のチェック、各工程、終了後の検証が、決まった順番で書いてある。
スキルの内部には必ず「dry-run → 差分提示 → 人間の承認 → 本番実行 → 検証」の五段構えを組み込む。エージェントは、そう設計しておかなければ確認しない。確認させる仕組みを用意するのは、こちら側の責任だ。
人間の承認ポイントが必ず1回挟まる。エージェントがどれほど自信ありげに語っても、実行ボタンは人間が押す。逆に言えば、それ以外の退屈な前処理と後処理はすべて任せていい。3ヶ月後の自分も、同じスキルで同じ結果を出せる。
5. ドキュメント構造をエージェントが迷わない形に整える
リポジトリのディレクトリ構造そのものを「エージェントが迷わない地図」として設計し直した。
inventory/ ホスト一覧・ネットワーク・ストレージ・ラック図(事実の単一の出所)
servers/ ホスト個別ページ(1ホスト1ディレクトリ、親子関係はトポロジファイルに集約)
services/ サービス横断(HA、VIP、ルーティング、DB、キャッシュ)
monitoring/ 監視の設計と構築手順
ansible/ inventory / playbooks / roles
docs/plans/ 進行中・未着手の計画書
docs/task/ 進行中タスク(残タスクのチェックリスト付き)
docs/done/ 完了済み計画書ルールはシンプルだ。ホストの事実は inventory にしかない。個別ページから値を「写す」ことはしない、参照する。計画は plans に書き、着手したら task に残タスクを置き、終わったら done に動かす。Markdown が物理的に移動する。
この構造の効用は、人間より先にエージェントに対して出る。「VMを1台増やしたい」と頼むと、エージェントは迷わず inventory に行を足し、個別ページを作り、サービス横断ファイルに参加情報を書き、Ansible inventory を再生成する。やることが定型化されていて、参照先が一意だからだ。定型化されているからスキルにできる。
6. アクセス経路を一段抽象化する(VPN / SSH / クレデンシャル)
エージェントが本番サーバーに届くまでの経路は、3段になっている。VPN(WireGuard)でネットワーク的に到達し、SSHラッパーでホストを呼び出し、認証情報はラッパーの内側だけが知っている。この3段をまとめて「アクセス経路」と呼んでいる。
設計の方針はひとつ。エージェントから見える世界を一段抽象化する。生のIPも、生の鍵も、生のパスワードも、VPNの設定ファイルも見せない。見せるのは「ホスト名」と「やりたいこと」だけだ。
具体的には、
- VPN 接続は前提として閉じ込める。ラッパーが起動時に到達性を確認し、未接続なら最初に教えてくれる。エージェントが「届かない理由」を推測する必要がない。
- SSHラッパー1枚に接続方法を集約する。ホストごとに鍵か、パスワードか、sudo が要るかを判別し、エージェントから見ると全部「同じ呼び方で実行できる」ように見せる。
- 認証情報ファイルは Git 管理外。エージェントのルールにも「このパスは読むな」と明記する。Ansible 側は vault を使い、プレイブック実行時にだけ復号される。エージェントに渡るのは変数名であって値ではない。
この抽象化が効くのは、エージェントが暴走したときに被害が広がらないからだ。エージェントが取れる行動は「ラッパー経由でホストにコマンドを送る」までで、そのコマンドは read-only がデフォルト(ガードレール側で縛られている)。VPNを切る権限もなければ、別ネットワークに飛び出す経路もない。アクセス経路そのものが、ガードレールと一体になっている。
ガードレールは「何をしてはいけないか」を縛る。アクセス経路は「どこに届くか」を縛る。クレデンシャル管理は「何を見られるか」を縛る。3つは独立した足場ではなく、ひとつの「届く経路の設計」として組み合わさっている。
人間がやっていること
エージェントが構築するなら、人間は何をしているのか、というのが当然次の問いになる。実際にやっていることを並べる。
- 方針を決める。「キャッシュ層を3台→2台に再構成して、用途別に分離する」のような大方針。
- 計画書を書く(エージェントと一緒に)。
docs/plans/にMarkdownを起こす。Phase分け、ロールバック手順、影響範囲、検証項目。エージェントが下書きして、わたしが直す。 - 差分を見て承認する。スキルが提示する dry-run の差分を読み、おかしくなければ承認する。おかしければ突き返す。
- 判断する。「いまこの瞬間にトラフィックを移して大丈夫か」「このアラートは本物か誤報か」。データはエージェントが集めるが、決めるのは人間だ。
- 「なぜ」を残す。コミットメッセージや計画書に、決定の理由を書く。後から自分(あるいはエージェント)が読み返したときにわかるように。
打鍵が消えた、と言うのが一番近い。vim を開く時間も、コマンドの引数を思い出す時間も、ターミナルを行ったり来たりする時間も、ほぼゼロになった。代わりに、画面に出てくる差分を読む時間と、判断する時間が増えた。
障害対応のループ
オンコール中の3時、アラートで叩き起こされる。眠い目で状況を把握しはじめる——この場面でも、エージェントは効く。
実際のフロー:
- アラートが通知チャンネルに来る
- 障害切り分けエージェントを起動し、アラート本文を貼る
- エージェントがメトリクスサーバーを直接叩き、関連メトリクスを横断的に並べる
- 必要なら read-only で対象ホストに入って
journalctlやssを見る - 「以下が異常値です。原因の候補は3つ:A、B、C」と返してくる
- 人間が候補を見て、どれを掘るか決める
- 原因が特定できたら、対処はスキル経由(人間の承認を挟む)
切り分けエージェントには、メトリクスサーバーをMCP経由で直接問い合わせさせている。SSHでサーバーに入って curl でメトリクスを取らせるより速いし、構造化されたデータが返るので推論精度が上がる。優先順位ははっきりさせる:構造化APIが最優先、SSH越しのコマンド実行は最後の手段。
IPMI と SEL
IPMI(Intelligent Platform Management Interface)は、サーバーのマザーボード上に載っているBMC(Baseboard Management Controller)と通信するための規格。OS が動いていなくても電源操作・センサー値・ハードウェアイベントの取得ができる。SEL(System Event Log)は BMC が記録するハードウェア由来のイベントログで、メモリエラーや電源異常、温度異常などが残る。
OS が反応しなくなったときのために、IPMI も同じ流儀でエージェントから扱えるようにしてある。電源状態、SEL、センサー値、ファン回転数、温度——OS越しに見えるものとは別系統の情報源だ。SSHが返ってこないホストに対して、エージェントがまずIPMIで「電源は入っているか」「ハード由来のイベントは出ていないか」を確認し、OS側のハングなのかハードウェア障害なのかを切り分ける。ここまでが自動で並ぶと、人間が「現地行きが必要かどうか」をその場で判断できる。
眠い頭で PromQL を組み立てる時間も、IPMIコンソールにログインして電源状態を確認する時間も消える。最初から「候補のなかから選ぶ」フェーズに入れる。
ChatOps と VictoriaMetrics をつなぐ
VictoriaMetrics
オープンソースの時系列メトリクス収集・保存システム。Prometheus 互換の scrape で各ホストの exporter からメトリクスを取り、PromQL 互換のクエリ言語(MetricsQL)で集計・条件抽出を行う。Prometheus と比べてストレージ効率が高く、長期保存に強い。アラート評価は vmalert が担う。
オンコールの体験をもう一段変えたのが、ChatOps を VictoriaMetrics と直結させたことだ。具体的には、Discord に Bot を常駐させている。これは固定コマンドの Bot ではなく、Bot 自身が AI エージェントになっていて、VictoriaMetrics を tool として持っている。
PromQL のインスタントクエリ、レンジクエリ、ターゲット一覧と up/down 取得——この3種類を tool として渡してあって、ユーザーが Discord で自然言語で話しかけると、Bot が必要な PromQL を自分で組み立てて投げ、結果を要約して返す。「いまキャッシュ層の up は?」「過去1時間の特定ホストのCPUは?」といった問いに、Bot が tool を選んで答える。
Claude Code 側からも、同じ VictoriaMetrics を MCP 経由で見ている。つまり Discord Bot と Claude Code は 同じデータソースを共有している が、入口がふたつある、という形だ。手元の作業中なら Claude Code から、外出中でスマホしかないなら Discord から、同じ問いを同じ VictoriaMetrics に投げられる。
副次的な効果として、対応の履歴が自然にチャンネルログに残る。Bot とのやりとり、Alertmanager の通知、人間の判断——全部が同じスレッドに並ぶ。ポストモーテムを書くときに、「あの障害のとき何を見て何を判断したか」がそのまま辿れる。
注意点もある。Bot に渡す tool は read-only に絞っている。再起動や設定変更のような操作は持たせない。チャットからの操作は強力だが、誤った入力で本番を止める経路を増やしたくない。書き込み系の操作は、必ずスキル経由で人間の承認を挟む形に統一している。
実際に踏んだ穴
うまくいった話だけ並べると嘘になる。具体的な構成は伏せるが、実際に踏んだ穴の「型」をいくつか書いておく。同じ型を踏まずに済むなら、それがいちばん役に立つはずだ。
- テンプレート選定の前提が共有されていなかった。VM作成の元になるテンプレートが複数の場所に存在していて、エージェントが意図しない方を選んでしまい、起動に失敗した。「正規のテンプレートはこれ」という前提をドキュメントとスキルの両方に明示するまで、似た事故が再発する余地が残っていた。
- 冗長構成の優先度計算を、設計段階で見落とした。新旧の HA 切り替えで、健全性チェックが落ちたときの実効優先度を計算違いし、新側が主系を奪取できない設計にしかけた。投入前のレビューで気づいたが、初期設計では人間もエージェントも気づいていなかった。以後、冗長構成を伴うスキルの開始前チェックに「優先度の検算」を必須項目として組み込んだ。
- 新旧コンポーネント間の前提のギャップを残したまま切り替えた。旧側と新側で接続条件の前提が違っていたのに、移行手順の最初でその差分を確認するステップが抜けていて、切り替え後にクライアント側で連鎖的な失敗を起こした。以後、移行系の手順は「新旧の構成差分を最初に列挙する」を必須にした。
- 「旧側を到達不能にする」手順が抜けていた。新クラスタに切り替えたあと、自動フェイルオーバの仕組みが旧側を再発見して戻してしまった。新旧切り替えの手順に「旧側を経路上で到達不能にする」を明示するまで、似た構造のミスが起きる余地があった。
- 「安全側に倒したつもり」のオプションが、必要な動作まで止めた。リスクを抑える意図で足したオプションが、副次的に正常系の挙動をブロックしてしまった。「安全側のオプションを無条件で足さない、必要性と影響を確認してから入れる」というメモを残した。
並べてみると、どれも「エージェントが暴走した」話ではない。エージェントは指示通りに動いていて、指示の側、あるいは前提知識の側に穴があった。だから対処も「エージェントを縛る」ではなく「前提と手順を残す場所を増やす」になる。
起きたことをナレッジに畳み込む
そして、ここがエージェントを使った運用でいちばん複利が効くところだと思っている。
問題が起きると、原因が判明する。普通ならポストモーテムを書いて終わりだが、それだと次に同じ穴を踏むかどうかは「次にオンコールに入る人がポストモーテムを覚えているか」に依存する。覚えていない。人間は忘れる。
なので、起きたことは必ずどこかに「畳み込む」。畳み込み先は4つある。
| 起きた内容 | 畳み込み先 | 効果 |
|---|---|---|
| 手順のミス・抜け | スキル | 次回から自動で挟まる |
| 設計の理由・背景 | ドキュメント(docs/) | 「なぜこうなっているか」が残る |
| 切り分けのコツ・観点 | サブエージェントの定義 | 次の障害でエージェントが自分で気づく |
| 全エージェント共通の禁止事項・前提 | CLAUDE.md | リポジトリ全体に効く |
たとえば、デプロイ中に --check を飛ばしたせいで意図しない差分が当たった、という事故があったとする。次にやることは、そのスキルの先頭に「--check の差分提示を必須にする」というステップを足すことだ。これで、次にそのスキルを呼ぶ全員(人間でもエージェントでも)が、必ず差分を見ることになる。同じ事故は二度起きない。
切り分けの観点も同じだ。「キャッシュ層の異常時には、上流のロードバランサのコネクション数を最初に見る」という経験則を1回学んだら、それを切り分けエージェントの定義に書く。次の深夜アラートで、エージェントは聞かれる前にその観点で並べてくれる。
CLAUDE.md には、リポジトリ全体に効く前提を書く。「VPN未接続では到達不可」「secrets/ は読むな」「設定変更は明示的な指示があるまで禁止」のような、すべてのエージェントが起動時に読むべきこと。ここに1行足すと、その瞬間からリポジトリ全体のエージェントの行動が変わる。
これをやり始めてから、運用の質が階段状に上がるのを感じた。最初の数日と、それ以降では、エージェントの賢さが体感で別物になる。エージェントが急に賢くなったわけではなくて、エージェントが参照する世界のほうが豊かになっている。
ナレッジが「人間の頭の中」ではなく「エージェントが読めるファイル」に蓄積される、というのが効いている。引き継ぎのときに失われるのは、たいてい人間の頭の中の暗黙知だ。それが最初からファイルにあるなら、引き継ぎ自体がほぼ不要になる。
なぜ汎用の自律エージェントを選ばなかったか
ここまで読んだ人は、ひとつ疑問を持つかもしれない。OpenClaw のような汎用自律エージェント——LLM を頭脳に据え、メッセージングアプリから自然言語で指示を受け、ローカルマシンや外部ツールに対して自分でコマンドを実行していく——あの方向のツールが、いま急速に広まっている。「JARVIS にいちばん近い」とまで言われていて、GitHub のスター数も短期間で爆発している。任せたいことを自然言語で伝えれば全部やってくれるなら、Claude Code でわざわざスキルやサブエージェントを設計する必要はないのではないか、という疑問だ。
実際、わたしも触った。便利だ。手元のラップトップで、メールの整理やカレンダーの調整、ファイルの仕分けをやらせる用途なら、確かに強力だと思う。
けれど、本番のサーバー群を扱う用途では、いまのところ採用していない。理由は単純で、自律性の高さは、誤作動の影響範囲の広さと表裏一体だからだ。汎用の自律エージェントは、設計上「やっていいこと」の輪郭が広く、しかもユーザーが見ていない間にも判断して動く。この性質が、ローカルの個人用途では便利さの源泉になり、本番インフラではリスクの源泉になる。
セキュリティの研究者が「lethal trifecta(致命的な三点セット)」と呼んでいる構図がある——プライベートデータへのアクセス・信頼できないコンテンツへの曝露・ユーザーの代理として行動する権限、この3つが揃ったエージェントは、プロンプトインジェクションを介して攻撃者の意図通りに動かされうる。Cisco の AI セキュリティチームが、サードパーティ製のスキル経由でデータの持ち出しとプロンプトインジェクションが起きる事例を実際に検出している。本番サーバーへの SSH 鍵を持ったエージェントが、たまたま読んだログの中の細工された文字列に従って rm を打ち始める、というのは比喩ではなく現実の脅威だ。
だからわたしは、自律性を意図的に下げる方向で設計した。
| 汎用自律エージェント | この記事のやり方 | |
|---|---|---|
| 任せる範囲 | 自然言語の意図全部 | 事前に定義したスキルの範囲内 |
| 実行の判断 | エージェントが自律的に | 必ず人間の承認を経由 |
| 触れる対象 | 接続されたツール全般 | ラッパーで抽象化された経路のみ |
| 認証情報 | 必要に応じて参照 | エージェントから物理的に隠す |
| 失敗の影響範囲 | 設計次第で広い | 1つのスキルの dry-run まで |
これは「Claude Code が優れていて OpenClaw が劣っている」という話ではない。両者は設計思想がそもそも違う。汎用自律エージェントは「ユーザーが明示的に書かなかったことも気を利かせてやってくれる」ことに価値がある。本番インフラ運用は「ユーザーが明示的に書かなかったことは絶対にやってほしくない」ことに価値がある。価値の方向が逆だ。
境界線を1行で書くなら、こうなる。取り返しのつく作業は自律性を上げてよい。取り返しのつかない作業は自律性を下げる。 個人ファイルの整理は、間違えてもバックアップから戻せる。本番DBの設定変更は、間違えるとサービスが止まる。同じ「便利さ」でも、間違えたあとに払う代価の桁が違う。
将来は変わるかもしれない。汎用自律エージェントがプロンプトインジェクション耐性を獲得し、操作の取り消し性が標準で組み込まれ、影響範囲が形式的に証明できるようになれば、本番運用にも入ってくるはずだ。けれど今のところ、その水準には届いていない。だから今は、自律性を低く保ったまま、できることを地道に増やす方向で設計している。
採用を見送ったもうひとつ:HolmesGPT
もうひとつ気になっていたのが、HolmesGPT だ。CNCF サンドボックスプロジェクトで、Robusta.Dev が中心になって作っている、本番障害の調査と根本原因の特定に特化した OSS の SRE エージェント。アラートを受けて自動で調査を始め、結果を Slack に書き戻してくれる。read-only がデフォルトで、RBAC を尊重する設計になっていて、本番投入を前提に作られている。「障害切り分けエージェント」を自前でスキルとサブエージェントの組み合わせで作っているわたしにとっては、まさに同じ問題を解いている既成品に見えた。
それでも、いまの時点では採用していない。理由は2つある。
- 想定環境が Kubernetes / クラウド寄り。ドキュメントや組み込みのデータソースは Kubernetes、クラウドプロバイダ、SaaS 系のサービスを前提に整っていて、オンプレの物理ホストや独自の仮想化基盤を一級で扱う設計にはなっていない。動かないわけではないが、自分の環境に持ってきて違和感なく走るかは、そのまま試しただけでは判断できなかった。
- ハードウェア障害への対応をどこまで担えるか未検証。この記事の前半で書いた IPMI 経由の調査——「OSが反応しない、SELを見て電源異常を確認する、現地行きが要るかを判断する」という流れに、HolmesGPT がどう振る舞うかは、ドキュメントを読んだだけではわからなかった。クラウドだとそもそも IPMI を意識しないので、ここはオンプレ固有の検証項目になる。
つまり「採用しないと決めた」のではなく「自分の環境で安全に動かせるかをまだ検証していない」が正確だ。設計思想は近いし、CNCFサンドボックスとしてオープンに育っているので、検証する価値は十分にある。いまは自前のスキル+サブエージェントで間に合っているので優先度を下げているが、ハードウェア障害周りの担当範囲が見えてきたら、自前の障害切り分けエージェントを HolmesGPT に置き換える未来は十分にありうる。
並べてみると、選定の判断基準が見えてくる。OpenClaw は 設計思想(自律性の方向)が違う ので採用しなかった。HolmesGPT は 設計思想は近いが、自分の環境(オンプレ・物理層)での挙動が未検証 なので採用を保留している。前者は理屈の問題、後者は検証の問題だ。境界線は同じところに引いてある——「本番に入れる前に、影響範囲が自分で説明できる状態か」。
残るのは判断という仕事
引き継いだラックは短期間でかなり変わった。古い箱は整理され、監視が立ち、内部の名前解決は閉じ、VPNは冗長化され、ロードバランサも入れ替わった。そのほとんどを、Claude Code が手を動かして作った。
何が変わったかというと、わたしが「読む」「書く」「叩く」に費やす時間が劇的に減った。代わりに増えたのは、「これは本当にやるべきか」「この変更で何が壊れる可能性があるか」「いまこの瞬間、何を優先すべきか」を考える時間だ。
エージェントが奪ったのは作業であって、判断ではない。むしろ判断の比重は上がった。判断のために必要な情報をエージェントが瞬時に並べてくれるので、判断するしかなくなった、と言ったほうが近い。
これが最善のやり方だとは思っていない。少し先の自分は、ここに書いたことの半分くらいは別の形に直しているはずだ。それでも、いま走っている足場が、誰かの参考になればうれしい。