Joifup Blog

Claude Codeのトークン節約 第2弾 ― 実装ワークフローに軽量モードを入れて実測してみた

2026-08-20

はじめに

前回の記事で、Ponytailの横着ラダーを1文に蒸留してCLAUDE.mdに常駐させ、トークン消費が減るかを実測した話を書きました。その最後に「次は小さいタスクをいちいちフルコースで回さない『軽量モード』を実装ワークフローに入れてみようと思っています」と書いたので、今回はその続きです。

結論から言うと、作った軽量モードは実測の結果まちがっていて、定義を作り直すことになりました。この記事はその一部始終を時系列で書いていきます。想定読者は、AIエージェントに開発フローを乗せていて、レビューやサブエージェントをどこまで積むべきか迷っている人です。

背景: follow-upタスクのループでトークンが溶ける

前回のラダーは「書くコードの量そのものを減らす」施策でした。今回気になっていたのは別の場所です。

自分の開発フローは、1つのタスクを実装すると、その過程で見つかった問題が複数のfollow-upタスクとして起票されます。それをまた実装すると、また新しいfollow-upが出てくる。この連鎖自体は健全なのですが、1周ごとにフルコースの手順が乗るので、小さい修正でもプロセスの固定費が毎回かかります。このループだけでトークンの上限を使い切りそうだ、というのが出発点でした。

自分のワークフロー(j-devflowという自作スキル)の実装フェーズは、こういう構造になっています。

  • タスクを計画で分解する
  • タスクごとに新しいサブエージェントを立てて実装させる
  • タスクごとにレビュー担当のサブエージェントを立ててレビューする
  • 全部終わったら、ブランチ全体をもう一度レビューする

つまりレビューが2層あって、実装もタスクごとに別セッションに切り出されています。ここを軽くできないか、という話です。

軽量モードを設計した

削れそうなレバーは2つありました。

  1. タスク毎のレビューをやめて、最後のブランチ全体レビュー1回だけにする
  2. タスクをサブエージェントに渡さず、同じセッションの中で順番に実装する

これを両方入れたものを-lightというフラグとして設計しました。設計を詰めるときに一番こだわったのが、最終レビューだけは絶対に軽くしないという点です。

というのも、実装したセッション自身にレビューさせると、自分の判断の経緯やそこまでかけた手間が文脈に残っているぶん「まあいいか」で通してしまいがちだからです。なので軽量モードでも、最終レビューはdiffだけを渡した文脈ゼロの別セッションが行う、というのを不変条件として明記しました。ここが軽量モードに残る唯一の安全網になるからです。

測ってみた(8ラン)

前回と同じ型で前後実測しました。

  • 対象タスク: 修正方針が既に一意に決まっている小さめのタスクを2本。1本はCSSの表示不具合、もう1本はテスト証跡の出力範囲を直すもの
  • アーム: 通常モード vs 軽量モード
  • 反復: 各アーム2回、同じコミットからworktreeを生やして、毎回まっさらなセッション
  • 指標: 出力トークン、コスト、生成コード量、品質

結果がこれです。

タスクアームトークン中央値コスト中央値ΔトークンΔコスト
CSS修正通常236,743$19.16
CSS修正軽量180,961$20.04−24%+4.6%
証跡スコープ修正通常294,152$26.26
証跡スコープ修正軽量123,891$12.06−58%−54%

トークンは両方のタスクで減りました。速く回る体感も正しくて、レイテンシも軽量モードの方が短かったです。

ただ、CSS修正の方はトークンを24%減らしたのにコストが増えています

気づき①: 通常モードは実装を安いモデルに逃がしていた

これは測ってすぐ分かりました。ランごとのモデル別内訳を見ると、通常モードの1ランはこうなっていました。

  • Opus: 118,916トークン / $13.51
  • Sonnet: 126,973トークン / $6.93
  • Haiku: 11,731トークン / $0.29

出力トークンの54%が、安いSonnetとHaikuで生成されていたわけです。タスクごとにサブエージェントを立てる構造が、意図せず「計画は高いモデル、実装は安いモデル」という分業になっていて、そのままコスト最適化として機能していたんです。

軽量モードはそのサブエージェントをやめて全部を同じセッション(=Opus)で回すので、この逃がしを失います。だからトークン数を削ってもお金が減るとは限らない。

正直これは、やる前に気づけたはずのことでした。サブエージェントをやめて全部を高いモデルで回せばコストが上がるのは、言われてみれば当たり前です。実測で初めて気づいたというより、当たり前を見落としていたのを実測に教えてもらったという感じで、少し恥ずかしかったですw

ただ、副産物として「トークン数とコストで逆の結論が出ることがある」というのは、以降ずっと効いてくる教訓になりました。この後の判断も、全部この2軸を併記して見ています。

品質を見たら雲行きが怪しくなった

トークンだけで勝ち負けをつけると「劣化して勝った」を見逃すので、前回と同じく実装の中身も横断で比較しました。4パターンのブランチを並べて、ガードロジックやエッジケースの扱いを見ていく方法です。

ここで嫌なものが出ました。証跡スコープ修正のタスクで、軽量モードの2本とも、仕様に明記されていた要件を落としていたんです。

つまり「−58%トークン/−54%コスト」という派手な数字は、やるべき仕事を一部やらなかったから安く出た可能性がある。前回の記事で自分が警戒していた「劣化して勝った」に、まさに自分がハマった形です。

一番マズかったこと: 原因が切り分けられない

そして、ここでもっと根本的な問題に気づきます。

軽量モードは2つのレバーを同時に動かしていました。 レビューを削るのと、サブエージェントをやめるのと。品質が落ちたとして、犯人がどっちなのか分からないのです。

実験の設計としては2×2のうち対角の2マスしか測っていなかった、ということになります。

タスク毎レビューあり最終レビューのみ
サブエージェント通常モード(測定済・品質良)未測定
同一セッション未測定軽量モード(測定済・品質劣化)

空いている2マスを埋めないと、何も結論できない。というわけで追加実験に進みました。

追加実験: プロンプトで頼むのをやめた

空きマスを測るには、j-devflowに無い挙動(「サブエージェントは使うがタスク毎レビューはしない」など)をさせる必要があります。最初はプロンプトで指示すればいいと思っていたのですが、これはやめました。

理由は、前の実験で実際に失敗していたからです。通常モード側を回すときのプロンプトに「軽量モード」という語が混ざっていて、それを拾ったセッションが軽量モード側の挙動に入りかけたことがありました。自然言語で挙動を頼むのは、アームの独立性を担保する手段としては弱すぎます。

なので、本番のワークフローには手を触れず、挙動を構造で固定した実験専用のスキルを2つ作りました。あとは/j-devflow-cellcのようにスラッシュコマンドで呼ぶだけ。解釈の余地が入りません。

もう1点、現実に寄せた変更もしました。同一セッション実装の方はSonnetに固定しています。前段で分かった通り、同一セッション実装をOpusで回すのは「逃がしが無いぶん高いだけ」の非現実的な構成なので、実運用としてありえる形にしました。

気づき②: レビューは犯人ではなかった

各セル2反復、計4ランの結果です。

構成コストレイテンシコア要件
サブエージェント+レビュー無し r1$13.1839.3分
サブエージェント+レビュー無し r2$9.5429.1分
同一セッション(Sonnet)+レビュー有り r1$4.1816.5分
同一セッション(Sonnet)+レビュー有り r2$3.3515.4分Critical

4本を横断レビューして出た結論は、当初の予想と逆でした。

タスク毎のレビューは、思っていたほど効いていませんでした。 レビューを削った2本はどちらもコア要件を満たした上に、CLIのテストを足すなどの付加価値まで出しています。

一方で唯一のCriticalバグが出たのは、レビューを2層積んでいる方でした。しかも内容が悪くて、タスクIDの解決処理で受け取った値をそのまま正規表現に埋め込んでいたため、このリポジトリで主流の書き方(--task 188-sidebar-...のように番号の後ろにスラッグが付く形)でコマンドが落ちるというものです。

一番こたえたのは、このバグをタスク毎レビューと最終レビューの両方がすり抜けたことです。しかもこのバグの型は、リポジトリの中の別のタスク文書に「こういう形式で渡される」と名指しで書いてあるものでした。レビュー層を積むことと、実際にバグが捕まることは、別の話なんだなと。

気づき③: 設計で落としたものはレビューでは拾えない

もう一つ、切り分けの過程で分かったことがあります。

最初に「軽量モードが要件を落とした」と判定した件、あれはレビュー層の問題ではありませんでした

追加実験でレビューを削った側の1本は、その要件をきちんと実装していました。逆に、レビューがある側の2本と、レビューを削った側のもう1本は、揃って設計の段階でその要件をスコープ外にしていたんです。

つまり「レビューを削ったから漏れた」のではなく、設計で決め落としたものは、後段のレビューをいくら積んでも拾えないというだけの話でした。だとすると、この手の網羅性を担保すべき場所はレビューではなく、設計を承認するゲートの受け入れ基準の側です。

気づき④: 一番厳密な検証を出したのは軽量モードだった

もう一つ意外だったのが、CSS修正タスクの方です。

こちらは4本ともコアの修正がバイト単位で同一でした。同じ1行のCSSルールと同じ属性の付け方。差が出たのは検証の作り込みだけです。

そして一番厳密だったのが、軽量モード側の1本でした。そのブランチは修正をわざとrevertして3回テストを回し直し、テストが本当は失敗しないのに通っていたパターンを2つ見つけて潰していました。1つはCSSトランジションが終わる前に測ってしまう問題、もう1つはマウスを当てる座標が狙った要素ではなく隙間に落ちる問題です。

レビュー層が薄い方が丁寧な検証を出す、というのは因果ではなくたまたまだと思います。ただ「レビューを削ったら雑になる」という素朴な予想が、少なくとも一律には成り立たないことは確かでした。

軽量モードを作り直した

ここまでで、当初の-light2つのレバーのうち間違った方を売りにしていたことがはっきりしました。作り直します。

  • やめたこと: 同一セッションでの実装(サブエージェントによるタスク隔離の排除)。ここがcorrectnessのばらつきを生んでいた
  • 残したこと: タスク毎レビューの削除。こちらは品質にほぼ影響していなかった
  • 触っていないこと: 最終レビューは今まで通り、diffだけを渡した文脈ゼロの別セッションが行う

つまり-lightは「実装フェーズを丸ごと軽くするモード」から「レビューの厚みだけを1段落とすモード」に変わりました。削減見込みは、控えめに見てコストが約1〜2割、トークンが約2〜4割です。削っている対象が安いモデルのレビュー層なので、トークンの減りに対してコストの減りが小さいという非対称になっています。

なお、実験全体でかかったのは8ランで約$155、追加の4ランで約$30でした。ただしこの検証はサブスクプランのトークンで回しているので、実費が追加でかかったわけではありません。それでも、節約施策を測るのにそれなりのトークンを使ってしまったという自覚はあります。

ハマったポイント

当たり前を測る前に気づけなかった。 前述の通り、サブエージェントをやめて全部を高いモデルで回せばコストが上がるのは当たり前でした。実験を組む前にモデル構成まで含めて考えておけば、設計自体をもっと早く正しい形にできたと思います。

2つのレバーを同時に動かした。 これが今回一番の反省です。「軽量モード」として一括りにしたせいで、品質が落ちたときに原因を特定できず、切り分けのために追加で4ラン回すことになりました。最初から片方ずつ測っていれば済んだ話です。

「設計が自明」に見えて実は自明じゃなかった。 軽量モードを当てるタスクは「修正方針が既に一意」なものを選んだつもりでした。でも証跡スコープ修正の方は、実は「あるフラグをどう扱うか」という設計判断を含んでいて、そこで各ブランチの結論が割れました。適用対象の見極めそのものが、思っているより難しいということです。

まとめ

  • 軽量モードとして「タスク毎レビューの削除」と「サブエージェントの排除」を同時に入れたら、品質が落ちたときに原因を切り分けられなくなりました。レバーは1つずつ動かすべきでした
  • 切り分けた結果、効いていたのはサブエージェントによるタスク隔離の方で、タスク毎レビューは品質にほとんど寄与していませんでした。唯一のCriticalはレビュー2層あるアームで出て、両層ともすり抜けています
  • 設計段階で落とした要件は、後段のレビューをいくら積んでも拾えません。担保する場所を間違えていました
  • n=2の実測なので、あくまで方向性の話です。ただ「レビューを積めば安心」という感覚は、少なくとも自分のハーネスでは実測に支えられていませんでした

おすすめ記事