AIエージェントの性能はモデルでなくハーネスで決まる
Claude Code で /daily-review を実行すると、結果が出るまで、体感で30〜40秒ほど待っていました。/toeic も /secretary --morning も同じです。最初は「LLM を使っているのだから、こういうものだろう」と思っていました。でも、違いました。遅いのは LLM ではなく、私が書いたスキルのほうでした。
当時19本あった自作スキルを並べて診断してみると、共通のパターンが見えてきました。Dailyノートを30ファイル、LLM に1件ずつ読ませていた。同じ数値を何度も再読み込みして「整合性チェック」と称していた。「並行して読み込む」と書いてあるのに、実際には1件ずつ順番に読まれていた。
問題はモデルではありません。ハーネス — エージェントを動かす実装の足回り — のほうでした。
この記事は、その足回りを作り直した記録です。かかった時間はおよそ30分。しかも手を動かしたのはほとんど Claude Code で、私は方針を決めて結果を確認しただけです。19本のうち手を入れたのは8本ほど、変更したファイルは新しく2本と既存の十数本でした。体感では、おおよそ4〜6割は速くなった実感があります。やったことは新しい技術ではなく、「LLM に任せすぎていた仕事を、シェルと Python に戻しただけ」と言ったほうが正確です。
1. 何が遅かったのか — 19スキル診断
まず Claude Code に、当時あった自作スキル19本すべての SKILL.md(スキルの定義ファイル)を読み返してもらい、ボトルネックを6つに分類しました。
ボトルネック 影響スキル 推定遅延 Dailyノート逐次読み込み(最大30件) health-tracker, study-tracker, weekly, daily-review 30〜90秒/回 冗長なサニティチェック daily-review, weekly, monthly, quarterly, health-tracker 15〜30秒/回 TOEIC自己検証10項目+tracker三重読み込み toeic 10〜20秒/回 Gmail下書き逐次作成 weekly, gtd-helper, inbox-sort, monthly 3〜10秒/N件 並列読み込みの未実施(指示はあるが実効なし) weekly, monthly, secretary 5〜15秒/回 サブエージェント未活用(データ抽出にOpusフル投入) 全般 コスト過大
特にひどかったのが health-tracker でした。30日分のDailyノートを読むのに、for i in $(seq 0 $((DAYS-1))) というループで、LLM に1件ずつ読ませていたのです。30回の読み込み呼び出しと、そのたびに走る LLM の解析処理。1ファイルあたり数百ミリ秒として、30件でざっと10〜30秒。同じことをシェルスクリプトで書くと、実測で0.64秒でした(詳しくは後述します)。
冗長な「サニティチェック」も見過ごせませんでした。daily-review は本文を生成したあとに、わざわざ「整合性チェックのため再読み込み」する手順を持っていました。けれど、そこで見つかる問題はたいてい LLM の推論ミスで、ファイルを読み直しても直りません。事後の再検証は、ほとんどの場合、ただのパフォーマンス税になっていました。
2. LLMに30ファイル読ませない — bash一括抽出
いちばん効いた改善は、Dailyノートの定量データ抽出を、まるごとシェルスクリプトに切り出したことでした。
Dailyノートのフォーマットは、毎日ほぼ同じ形に整っています。
- 体重(kg):72.8
- 体脂肪率(%):20.0
- Deep Sleep(時間):1h26m
- 脳トレ:497/1000
📊 今日の完了タスク (36件)
- TOEIC Part 5: 7/10 (70%) 5分 — ...
この形なら、正規表現で十分パースできます。LLM に読ませる必要は、まったくありませんでした。
そこで ~/.claude/skills/shared/extract_daily_data.sh を用意しました。
#!/bin/bash
# 抜粋: メイン処理
echo "{"
echo " \"records\": ["
first=1
while IFS= read -r d; do
rec=$(extract_one "$d") # PythonでDailyを1件パース→JSON
[[ $first -eq 1 ]] && first=0 || echo ","
echo -n " $rec"
done < <(date_list "$START_DATE" "$END_DATE")
echo " ]"
echo "}"
extract_one の中身は Python で、Dailyノートの Markdown から体重・体脂肪率・Deep Sleep・起床時刻・体調スコア・脳トレ・完了タスク数・TOEIC記録を JSON として返します。
効果は数字で出ました。
期間 旧方式(LLM逐次・概算) bash抽出(実測) 速度比 単日 〜2秒 約0.05秒 約40倍 14日 〜15秒 0.30秒 約50倍 30日 〜30秒 0.64秒 約45倍
スキル側の修正は、データ読み込みのステップを次のように置き換えるだけでした。
- # 旧: 30日分を逐次読み込み
- for i in $(seq 0 $((DAYS-1))); do
- obsidian daily:read date=$(date -v -${i}d +%Y-%m-%d)
- done
+ # 新: bashスクリプトで一括抽出
+ ~/.claude/skills/shared/extract_daily_data.sh --days "$DAYS"
ひとつだけ、設計上の工夫を入れました。フォールバックを残したことです。extract_daily_data.sh が失敗したとき(戻り値が0以外のとき)だけ、従来どおり LLM で個別に読み込みます。確定的な処理を優先しつつ、新しいフォーマットの記入が混じったときの保険にもなります。
この一手で、health-tracker・study-tracker・weekly・daily-review の4スキルが、それぞれ体感で30〜60秒ほど短くなりました。
ここまで読むと、何か難しいことをやってのけた人に見えるかもしれません。けれど、コードを自分で書いたわけでも、処理の動きを設計したわけでもありません。Claude Code が立てた計画を承認し、出てきた結果を確認しただけです。手を動かしたのは、ほとんど Claude Code のほうでした。ですから、コードが読めなくても — 読めたほうがいいのは確かですが — 同じことはできます。
3. 検証・並列・モデル選択の設計判断
bash抽出だけでも体感はかなり変わりましたが、残りの遅延も一つずつ潰していきました。考え方は3つです。
3-1. やめたこと — 事後サニティチェック
daily-review・weekly・monthly・health-tracker・study-tracker が持っていた「Step 4.5(または 3.5)」の再読み込みチェックは、すべて廃止しました。
旧版はこういう流れでした。
Step 4: レビュー本文を生成
Step 4.5: 生成した本文が、抽出データと整合しているか「再読込して」検算
Step 5: ファイルへ書き込み
代わりに、本文を生成している最中に、インラインのルールを3行だけ守らせるようにしました。
### Step 4.5: インラインバリデーション(v2: ファイル再読込なし)
- 数値は bash抽出JSONを単一ソースとし、Daily本文からの再パースを禁止する
- 平均・合計はJSON配列から1回計算してから本文に埋め込む
- 出力前に明らかな桁ずれ(体重100kg超、Deep Sleep 8h超など)があれば再確認
教訓: 「整合性チェック」は、事後に読み直しても直りません。読み直して直るなら、最初の読み込みのほうが悪いのです。検証は、生成時の制約として埋め込むほうが、コストも品質も両立します。
3-2. 明示化したこと — 並列読み込み
weekly の Step 2 には、もともと「以下のファイルを並行して読み込む」と書いてありました。ところが実際には、LLM が順番に1件ずつ読んでいたのです。「並行」と書いてあっても、ツール呼び出しのレベルで並列にしなければ、意味がありません。
- 旧: 「並行して読み込む」(実効性なし)
+ 新: 「同一メッセージ内の並列ツール呼出として発行する。
1つのメッセージに複数のReadを並べる。逐次に発行すると順番に実行される。」
同じ修正を monthly の Step 2 と secretary にも入れました。Gmail下書きの作成も同様で、「1タスク=1下書き」とは書いてあったのに、「N件作るなら N個の作成呼び出しを1メッセージに並べる」というルールが明文化されていませんでした。これも新しい項として書き足しました。
3-3. 分けたこと — モデル選択
処理タイプごとに使うモデルを分ける方針を、1枚の表にまとめました。
処理タイプ 使うもの 定量データ抽出 シェルスクリプト(LLM不要) Vault横断検索 軽量・高速モデル ウェブ検索・要約 中位モデル 問題生成・レポート生成など品質が要るもの 最上位モデル(メインスレッド)
サブエージェントを呼ぶときは、軽い仕事には軽いモデルを明示的に指定する、という指針です。メインが最上位モデルで、サブまで最上位モデルにすると、コストが二重にかかります。
教訓: 「とにかく最強のモデル」は、安全策のようでいて、実は速度もコストも悪くします。処理の種類で分けたほうが、結果として体感も財布も軽くなります。
ちなみに、この「処理の種類でモデルを分ける」という発想は、everything-claude-code という Claude Code 向けのプラグイン(スキルやエージェント、フック、ルールをひとまとめにした設定集)の考え方を参考にしています。計画立案のような作業の根幹をなす部分は高度なモデルに、検索のような軽い処理は速くて軽いモデルに振り分ける、という方針です。
4. 失敗・気づき・残った課題
すべてが順調だったわけではありません。
並列化しすぎて競合が起きた。最初、secretary で「6ファイルの並列読み込み」と「bash抽出」を同時に走らせたら、スクリプトが結果を返す前に LLM が JSON を参照しようとする挙動が、まれに出ました。bash の呼び出しは同一メッセージ内に置けば順序が保証されますが、「bash の結果を待ってから次へ進む」という書き方を明示しないと、待たずに先走ります。SKILL.md に「bash抽出 → JSON取得 → そのあと並列読み込み」と順序を明記して回避しました。
安全装置との相性。私の環境では、ファイル編集のたびに「このファイルを参照している全ファイル」と「データの構造」を事前に提示させるゲートが効いています。安全装置としては有用なのですが、十数ファイルをまとめて直す作業では、その応答時間がそれなりに積もります。一括変更のときだけ一時的に緩める仕組みがあってもいい、と感じました。
残った課題もあります。
gtd-helper の OmniFocus 連携は、osascript プロセスの起動だけで、体感で3〜5秒ほどかかります。これは bash 層ではなく macOS 側の制約で、手が出せませんでした。
note-writer の「段落ごとに確認を取るループ」は、あえて残しています。速度より品質を取る判断です(この記事も、まさにそのループで書いています)。
inbox-sort のファイル読み込みは、いまも N件の並列読み込みのままです。0件のときに早く切り上げる工夫は入れましたが、件数があるときは並列で読んで問題ありません。
その後、この抽出結果を SQLite にためる層をさらに足し、いまは日々の集計をデータベースから引いています(bash抽出は、そのひとつ下の確実な足場として残っています)。
完璧な改善というより、「ハーネスのうち、変えていい部分を変えた」というのが正確な感覚です。
5. 「自分専用ツール」を育てる人へ
この記事は自分専用スキルの話ですが、教訓はほかの自作ツールづくりにも当てはまると思います。3つだけ持ち帰ってください。
1. LLM に任せている仕事のうち、確定的なものはシェルや Python に戻す
ファイルのパース、日付の計算、集計 — こういう「答えが一意に決まる処理」を LLM にやらせると、体感速度が落ちます。LLM の強みは「曖昧な入力を、意味のある出力に変える」こと。確定的な処理は、その強みを使っていません。
2. 「並列に動く」と「並列と書いてある」は違う
Claude Code で並列に動かしたいなら、「同一メッセージ内に複数のツール呼び出しを並べる」という具体的な書き方を指定しないと、順番に実行されます。指示書に「並行して」と書くだけでは効きません。
3. 検証は事後ではなく、生成中に埋め込む
「念のため読み直して整合性チェック」は、たいてい時間の無駄になっています。生成しているその場の制約(インラインのルール)にしたほうが、速くて確実です。
最初の一歩としておすすめなのは、自分がいちばん頻繁に呼ぶスキルを1本だけ選んで、その定義ファイルを読み返してみることです。「LLM に任せている確定的な処理」が、きっと見つかります。あとはそれを Claude Code に頼んで bash へ切り出すだけ。体感は、たぶん変わります。
結び
スキルを書くのも、動かし続けるのも、結局は道具を育てる行為です。「とにかく動く」段階から「速く・安く・正確に動く」段階へ移るタイミングは、必ずやってきます。そのとき見直すべきは、モデルではなく、自分が書いたハーネスのほうです。
これからも、道具を育てていく過程で気づいたことを、少しずつ書いていきます。
#ClaudeCode #Obsidian #AIエージェント #ハーネス設計 #LLM #自動化 #個人開発 #生産性向上
·····························
🤖 執筆補助: Claude Code(+ ChatGPT 校閲) / ✍️ 主張・体験: 筆者 / 🖼️ ヘッダー: GPT Image 2
