はじめに
僕は今、中小企業の執行役員をしながら、「AI業務OS」を自分で作っている。
やっていることはシンプルだ。毎朝のルーティン業務 ── クライアントごとのKPIチェック、Chatworkのやり取りの要約、アクションアイテムの管理 ── を、AIエージェントに全自動で実行させて、Discordに配信する仕組み。
これが動けば、朝起きたらDiscordにクライアント5社分のKPIレポートが並んでいて、「今日やるべきこと」が整理されている状態になる。コンサルタントとしての意思決定だけに集中できる。
そう思って、Claude Codeの claude -p(プログラマブルなCLI実行モード)を使ったVPS自動化に着手した。
そして2日間、泥沼にハマった。
※ この記事に出てくるコードは全てAI(Claude Code)が書いたもの。自分が書いたのは「こうしたい」という日本語の指示だけ。コードの中身は正直よく分かっていないが、何が起きたかは説明できる。
最初の設計: 「全部AIに任せよう」
最初の設計はこうだった。
VPSのcronが毎朝8:15に起動して、シェルスクリプトが claude -p を呼ぶ。claude -p は:
- クライアントの設定ファイル(YAML)を読む
- Google Sheets MCPでスプレッドシートからKPIデータを取得する
- 前日比・前週比を計算する
- レポートを生成する
- Discord Webhookに投稿する
1つのコマンドで全部やってくれる。完璧だ。
最初のテスト。クライアントA(KPI 5個、スプレッドシート1つ)。
成功した。120秒でレポートが生成されてDiscordに投稿された。
「よし、これでいける」と思った。
クライアントBで壊れた
次のクライアント。クライアントB。
このクライアントのKPI設定はこうなっている:
- EC KPI: 12個(GA4のスプレッドシート。列はA〜BZ列まで散らばっている)
- META広告: 6個(別のスプレッドシート)
- Google広告: 4個(さらに別のスプレッドシート)
- 合計: 22個のKPI、3つのスプレッドシート
claude -p に投げた。
300秒経過。タイムアウト。出力ゼロ。
「タイムアウトが短いのか」と思って600秒に延長。
600秒経過。タイムアウト。出力ゼロ。
10分待って、何も起きない。
泥沼の始まり: プロンプト調整の無限ループ
ここから2日間の泥沼が始まった。
試行1: プロンプトを軽量化
「プロンプトが長すぎるのでは?」
日本語の詳細なプロンプト(80行)を英語の1文に圧縮した。
→ クライアントAでは動いた。クライアントBではタイムアウト。
試行2: データ取得とレポート生成を分離
「1回の claude -p に全部やらせるから重い。2ステップに分けよう」
- Step A:
claude -pでスプレッドシートからデータ取得だけ → ファイル保存 - Step B:
claude -pでファイルを読んでレポート生成だけ → ファイル保存
→ Step Aでタイムアウト。
試行3: 取得範囲を絞る
「A1:BZ700 が広すぎるのでは?」
列をグループ化して、必要な列だけ取得するようにPythonで前処理を書いた。
→ MPC呼び出し回数が14回になって、それでもタイムアウト。
試行4: 列をさらにグループ化
隣接する列をまとめて、4回のMPC呼び出しに削減。
→ データ量が多すぎてタイムアウト。
試行5: A列で最終行を特定してから直近20行だけ取得
→ claude -p が「A列を取得→最終行を計算→rangeを組み立て」の思考ステップに時間を食って、タイムアウト。
試行6: 行番号を手書きで明示
ここで気づいた。
1. spreadsheetId: 1Sv..., range: "日付のみ!A47:F61"
2. spreadsheetId: 1Sv..., range: "日付のみ!AX47:AY61"
3. spreadsheetId: 12p..., range: "data!A1:Z20"
4. spreadsheetId: 10X..., range: "data_asset_group!A1:Z20"
行番号まで確定したrangeを4つ並べたら、120秒で成功した。
問題の本質が見えた
成功と失敗の差は明確だった。
成功するパターン: 「この4つのrangeをそのまま取得しろ」 → 即座にAPI呼び出し → 120秒で完了
失敗するパターン: 「YAMLを読んで、どのスプレッドシートのどの列をどの範囲で取るか考えて、取得して」 → 思考ループ → タイムアウト
claude -p は「考える」タスクが遅い。
YAMLをパースして、22個のKPIの列番号を整理して、3つのスプレッドシートへのアクセス計画を立てて……という「計画を立てる」部分に時間を食う。実際のAPI呼び出し自体は数秒で終わるのに、その前段の思考で数分かかる。
しかも行番号を手書きするには、事前にスプレッドシートにアクセスして最終行を知る必要がある。それ自体が claude -p のタスクになり、また思考ステップが増える。鶏と卵。
さらに発覚した問題: stdout が消える
並行して、もう1つの問題が発覚していた。
VPSで claude -p を実行すると、標準出力が空になることがある。
# これは空
timeout 120 claude -p "..." > /tmp/result.txt
# これも空
timeout 120 claude -p "..." | tee /tmp/result.txt
# これは取れる
RESULT=$(timeout 120 claude -p "...")
同じコマンドなのに、出力先によって結果が変わる。
おそらくSSH越しのtty問題。claude -p がターミナルの接続状態によって出力の振る舞いを変えている。
対策として「ファイルに書け」とプロンプトに指示したが、ファイルに書く指示も無視されることがある。 同じプロンプトで昨日は書いたのに今日は書かない。
3/18 朝: 全滅
2日間の調整を経て、cronが走った。
✅ 成功: 0件 / ⏭️ スキップ: 1件 / ❌ 失敗: 5件
❌ EC/クライアントC: レポート未生成
❌ EC/クライアントA: レポート未生成
❌ EC/クライアントD: スプシ情報なし
❌ EC/クライアントB: スプシ情報なし
❌ IG/クライアントE: レポート未生成
全滅。
ログを見ると、3クライアントはデータ取得には成功していた(15KB〜40KBのデータファイルが生成されていた)。しかしレポート生成の claude -p がファイルを作らなかった。
「ファイルに書け」と指示しているのに、書かない。昨日のテストでは同じプロンプトで書いたのに。
決断: claude -p をやめる
オーナー(僕自身)からの言葉:
「もうずっと対応してて全然修正されないんだけど、どうすればいい?治すことできるの?」
正直に答えた。
「claude -p をやめる。Pythonで全部やる。」
Python版の設計: AIが得意な部分だけAIに任せる
新しい設計はこうだ。
kpi-monitor/src/
├── main.py # オーケストレーター
├── config.py # kpi_config.yaml パーサー
├── sheets_client.py # Google Sheets API 直接呼び出し
├── data_fetcher.py # 3パターンのデータ取得
├── kpi_calculator.py # KPI計算(deterministic)
├── report_generator.py # テンプレートベースのレポート
└── discord_poster.py # Discord Embed 投稿
核心の設計判断:
1. Google Sheets API を Python から直接叩く
VPSには既にGoogle Sheets MCP用のOAuth認証情報(tokens.json に refresh_token、gcp-oauth.keys.json に client_id/client_secret)が保存されていた。
これを google.oauth2.credentials.Credentials に渡すだけで、MCPサーバーを経由せずにGoogle Sheets APIを直接叩ける。
creds = Credentials(
token=tokens.get("access_token"),
refresh_token=tokens["refresh_token"],
client_id=keys["installed"]["client_id"],
client_secret=keys["installed"]["client_secret"],
token_uri="https://oauth2.googleapis.com/token",
)
service = build("sheets", "v4", credentials=creds)
「MCPサーバー経由でしかアクセスできない」は思い込みだった。
2. LLM はレポート生成に一切使わない
KPI計算(前日比、前週比、スパークライン、アラート判定)は全てPythonの算術。
レポートのフォーマットはテンプレートベース。セクション(エグゼクティブサマリ、KPIサマリー、広告、月間累計、アラート、7日トレンド、所見)を順番に組み立てるだけ。
「所見」欄もルールベース。アラートの内容に応じて定型文を出す。
if r.week_change_pct < -30:
insights.append(f"{r.name}が前週比{change}と大幅低下。原因調査を推奨")
将来的にAnthropic APIで所見を高度化する余地は残してあるが、今は不要。テンプレートで十分。
3. kpi_config.yaml の3パターン全対応
クライアントによってスプレッドシートの構造が違う:
- クライアントA: 列がアルファベット指定(
column: "B") - クライアントC: 列がヘッダー名指定(
column: "消化金額") - 広告KPI: 各KPIに個別の
spreadsheet_id
全パターンを data_fetcher.py で吸収。claude -p にYAMLを読ませて判断させていたのを、Pythonのロジックで確定的に処理する。
結果: 12秒
12:42:59 Google Sheets API: connected
12:42:59 Clients: 5 found
12:43:13 クライアントC: Discord: OK ← 14秒
12:43:27 クライアントA: Discord: OK ← 13秒
12:43:33 クライアントD: Discord: OK ← 5秒
12:43:46 クライアントB: Discord: OK ← 12秒 ★
12:43:48 クライアントE: Discord: OK ← 2秒
12:43:49 === Done: success=5, fail=0 ===
全5クライアント成功。全体50秒。
クライアントB。22個のKPI、3つのスプレッドシート。claude -p では600秒(10分)でもタイムアウトしていたクライアント。
12秒。
Before / After
| claude -p 方式 | Python 方式 | |
|---|---|---|
| クライアントB(22 KPI) | 600秒タイムアウト | 12秒 |
| 全5クライアント | 0〜2件成功(ガチャ) | 5件全成功(毎回同じ) |
| 所要時間 | 10〜30分 | 50秒 |
| 成功率 | 不安定 | 100% |
学んだこと
1. AIエージェントに「考えさせる」タスクは不安定
claude -p に「YAMLを読んで判断してデータを取得して計算してレポートを書いて」と全部任せると、毎回違う結果になる。
同じプロンプトで昨日は動いたのに今日は動かない。クライアントBでは動かないが、クライアントAでは動く。この「たまに動く」が一番厄介で、プロンプト調整の泥沼に引きずり込まれる。
2. AIの使いどころは「判断」であって「実行」ではない
データ取得、計算、フォーマット整形。これらは全部コードで書くべきだった。
AIに任せるべきは「この数値を見てどう解釈するか」「クライアントに何を提案すべきか」という判断の部分だけ。
今回のPython版では所見欄をルールベースにしたが、将来的にここだけAntropic APIを使う設計にしてある。データの取得・計算・整形はdeterministicなコード、解釈だけAI。これが正しい分離。
3. 「不安定なものは使わない」という判断を早期にすべきだった
2日間で試した対策:
- プロンプト軽量化
- 2ステップ分離
- 列グループ化
- 行数削減
- 英語プロンプト
- ファイル書き込み方式
全部「claude -p をなんとか動かそう」というアプローチ。根本的に不安定なものをパッチで直そうとしていた。
最初から「claude -p は信頼できない。Pythonで書こう」と判断していれば、2日間の泥沼は避けられた。
4. 50倍の高速化は「AIを使わない」ことで達成された
皮肉だが、AIエージェントがAIの使用をやめたことで劇的に改善した。
AI業務OSの中核は「全部AIにやらせる」ことではない。
「AIが得意な部分だけAIにやらせ、コードで確実にできる部分はコードで書く」 こと。
現在の構成
毎朝 8:15(VPS cron)
↓
Python スクリプト(50秒で全処理完了)
├── Google Sheets API でデータ取得(MCP OAuth 再利用)
├── Python でKPI計算(前日比/前週比/アラート/スパークライン)
├── テンプレートでレポート生成
└── Discord Embed で配信(色分け: 🚨赤 / ⚠️黄 / 正常=緑)
朝起きたらDiscordに5社分のKPIレポートが並んでいる。毎日、確実に、50秒で。
おわりに
「AIエージェントに全部やらせたい」という欲求は強い。Claude Codeは信じられないくらい賢くて、対話的に使えば何でもできる。
でも、cronで毎朝自動実行するような「確実性が必要なタスク」には向かなかった。
AIエージェントは優秀な「相談相手」であり「判断者」だが、「実行者」としては不安定だった。実行はコードに任せて、AIには判断だけ任せる。この切り分けが、AI業務OSを本当に「動くもの」にする鍵だった。
600秒のタイムアウトが、12秒になった。
そのために必要だったのは、プロンプトの改善ではなく、Pythonスクリプト200行だった。