Devin CLIから GPT-6 Astra (モデル名 gpt-6-astra-medium) を使っていて、奇妙なことに気づいた。手元のpromptは587字なのに、1呼び出しあたりのinput消費は27,079 tokenになる。差の約26.7k tokenは、こちらが書いていない文脈 — CLIが毎回先頭に乗せるシステム文・rules・skills一覧・MCPサーバー一覧だ。
しかも料金表には割引がある。devin models list を見ると、astraはinput $10/M tokenに対して cached input $1/M token (1/10)。公式docsにも "Staying on the same model across turns in a conversation enables caching, which significantly reduces input token costs." (同一会話で同じmodelを使い続けるとcachingが有効になり、input tokenコストが大幅に減る) とある。quotaのdocsも "requests to the same model leverage caching and reduce overall token usage" と案内している。
実際には効いていなかった。この記事は、効かない原因をSHA1比較で確定し、設定だけで割引を実効させる手順と、18ターンの実測 (定価$4.77相当 → 実課金$0.95) を示す。
読み終えた時、手元に残るもの:
- Astraのコストが割高に見える構造 (固定文脈 × 単価)
- cacheがhitしない原因の切り分け方 (マスクSHA1比較)
- 割引を実際に受けるworkaround (MCP 1サーバー化) と検証データ
- one-shot (
-p) とmulti-turnで結論が変わる運用指針
計測環境と方法を先に書く。Devin CLI v3000.6.14・Linux。input token数は ~/.local/share/devin/cli/sessions.db の num_tokens_preceding 実数、消費$は同CLI表示単価 (rate card) からの換算。この単価はCLI表示であってOpenAI API公開単価とは別レートである。
587字のpromptが27,079 tokenになる
まず現象の再現から。同一タスクを各2回ずつ実行したときの実測:
| タスク | 手元の入力 | input token実数 | 所要 | cost/呼 |
|---|---|---|---|---|
| 長文記事の起草 (28.7k tokenの素材を渡す) | 28.7k tok | 29,286 / 29,285 | 49.5s / 46.2s | ≈$0.43 |
| 短文の起草 (本番prompt) | 587字 | 27,079 / 27,080 | 11.2s / 14.7s | ≈$0.28 |
下の行が本題だ。入力587字でも27,079 token消える。全呼び出しに約26.7k tokenの固定文脈が乗っている (sessions.db実測)。
この固定文脈が単価と噛み合うと、こうなる。
| model | input単価 | 固定文脈26.7k分のコスト | Astra比 |
|---|---|---|---|
| GPT-6 Astra | $10/M | ≈$0.27/呼 | 1× |
| GPT-5.6 Sol | $1.2/M | ≈$0.032/呼 | 約1/10 |
| GPT-5.6 Luna | $0.2/M | ≈$0.005/呼 | 約1/50 |
同じ仕事をさせても、LLMのmodel選択だけでコスト差は1〜2桁に開く (同一長文タスクでの実測もSol $0.041-0.049・Luna $0.007-0.008で、表の比と整合する)。Astraの品質が長文の難物で価値が出ることは最後に書く。まず「cacheで安くならないのはなぜか」を確定する必要があった。
割引は実在する — なのにhitしない
devin models list にはcached単価の列が実在する。手元の表示 (2026-09-06時点の保存例を assets に置いた) では、全modelでinput単価の1/10だ。
gpt-6-astra-medium [1M context, $10 / 1M Input · $1 / 1M Cached input · $50 / 1M Output]
gpt-5-6-sol-medium [$1.2 / 1M Input · $0.12 / 1M Cached input · ...]
gpt-5-6-luna-medium [$0.2 / 1M Input · $0.02 / 1M Cached input · ...]
公式docsも同じ設計を記述する。冒頭に引いたとおり、cachingは「同一会話で同じmodelを連続して使う」ことで有効化される。つまり割引の受け取り方はsession内のmulti-turnのはずだった。
ところが、デフォルト設定のまま同一sessionで6ターン回しても、割引の痕跡がusageゲージに現れない。input計160.3k token = 定価換算$1.60で、cacheが効いていれば+2.5ptのはずのところ、実測は+12pt ≒ 定価 ($9.8pt予測に近い) 。cacheがhitしていない。原因を突き止めた。
切り分け: 毎ターン何が変わっているのか — SHA1を取る
provider側のprompt cacheのhit条件はprefix一致 (リクエスト先頭のbyte列が同じ) である。だから「毎ターン何が変わっているか」を機械的に特定すればいい。
方法: sessions.dbに保存された各ターンのsystem prompt nodeについて、(a) 生のまま (b) MCPサーバー一覧を名前順に並べ直したもの (マスク) — この2通りのSHA1を取って比較した。
結果 (astra・同一session・6ターン):
- 生SHA1: 6ターンすべて異なる (6種類)
- マスクSHA1: 全ターン完全一致 (
32dc9a434357)
差分の正体は、system prompt内のMCPサーバー一覧 (4台) の並び順だけだった。content内にtimestamp形式の文字列はゼロ (ナノ秒刻みの created_at はwrapperのmetadata側にあり、provider送信対象外)。同じ内容が、毎ターン違う順序で再レンダリングされていた。
one-shot (-p) では、さらに別の箇所もシャッフルされる。別modelでの8呼びで、rules (2本のblob) の順序が呼び出し毎に2通りに分布することを確認した。skills一覧 (30,243字・80 skills) は22 sessionすべてでSHAが異なり、先頭に来るskillも毎回違う。つまり順序が壊れる場所は3つある:
- rules の並び (session生成時)
- skills 一覧の並び (session生成時)
- MCPサーバー一覧の並び (session生成時 と毎ターン)
ここでcacheの原理に還元すると、詰み方が見える。文脈の先頭 (システム情報→rules) は短く、最初のシャッフル地点は約200 tokenしかない。provider cacheの最小単位は1,024 token。prefixが揃っていても1ブロックにも届かず、hitは構造的に起きない (skills一覧が末尾に来るbest caseでも、部分hitは2割弱どまり)。
なぜ毎回変わるのか。devin skills list や devin rules list の表示順自体が実行毎に変わることから、内部のコレクションが順序を保証しない型で持たれていて、表示にも注入にもsortが掛かっていない、と観測できる (表示と注入で同じ順序の乱れが出る)。原因箇所の特定はここまでで十分で、対処は次のとおり入力側で行える。
workaround: MCPを1台に絞るとprefixが凍る
順序が揃わない3要素のうち、ユーザー側で制御できるのはMCPサーバー台数だけだ (skillsはbuiltin分が消せず、rulesを1本化してもskills/MCPが残る)。1台なら並び順の入れようがない:
devin mcp disable supabase --scope user
devin mcp disable serena --scope user
devin mcp disable playwright --scope user
# → 有効なMCPサーバーが1台になる (残すのは filesystem 等、注入が名前のみで最小のもの)
検証手順はこうだ。
- 1サーバー化して軽いprobeを3回打つ → system promptの再レンダリング3回が同一SHAになる
- astraで同一sessionに6ターン → 全system prefix nodeが全ターンbyte同一 (user/assistantの追記分だけ増える) = cache-hit条件を満たすリクエストが初めて作れた
- 戻すときは
devin mcp enable <name> --scope user(設定のbackupで復元を確認済み)
実測: 18ターンで定価$4.77相当が$0.95になる
1サーバー構成のまま、astraで6ターン+12ターン (echoタスク、input計317.7k token、rate card定価換算$4.77) を実行した。
usageゲージの実測: +5pt ≈ $0.95相当。
3つの仮説と突き合わせると、どれが当たっているか一目で分かる。
| 仮説 | 予測 | 実測 |
|---|---|---|
| 全ターン定価 ($10/M) | +29pt | ✗ |
| 全ターンcached ($1/M) | ≈+2pt | ✗ |
| 各sessionのturn1 = 定価、turn2以降 = cached (1/10) | +5.9pt | +5ptで一致 |
つまり課金構造は「turn1だけ定価 + turn2以降1/10」で矛盾なくfitした。結論を3つにまとめる:
- 割引は実在し、利用者にパススルーされる (課金不正ではない)
- ただしデフォルト設定では毎ターンのMCP再シャッフルがhit条件を潰している = 実質ほぼ常に定価
- 直し方は、MCPの1サーバー化だけ
cached単価が表示どおりに実適用されることは、別modelでも確かめた。手元のmodels listには1行だけ1/10でなく1/40のcached単価 ($0.25/M) を持つmodelがあり、こちらも26ターン実測 (+4.0pt) が$0.25/Mからの予測と一致した (1/10解釈なら+8.7ptになるので棄却)。単価表の表示は実課金に効いている。
運用指針: どこまで直る・直らない
| 運用形態 | cache | 理由 |
|---|---|---|
one-shot (devin -p) |
常に定価 | 毎回新規session — rules/skills/MCPが再シャッフルされる |
| multi-turn・デフォルト | ほぼ定価 | session内でもMCP一覧が毎ターン再シャッフルされる |
| multi-turn・1サーバー化 | turn2以降1/10 | prefixがbyte同一になる (本稿のworkaround) |
one-shot主体の運用なら、消費は「トークン実数 × 定価」の線形モデルで正確に予測できる (実測が定価線形に乗ることを110呼びで確認済み — 誤差は数%以内)。multi-turnで重い仕事を回すときだけ、1サーバー化がコスト削減として効く。
最後に、Astraをいつ使うか。手元の記録では、長文の難物 (お手本の構造守らせる記事化タスク) で品質はお手本水準を満たした一方、所要46-49秒かかった (同タスクのSolは15-18秒)。軽い起草なら品質はLuna/Sol級で飽和していて、コスト差 (1〜2桁) を払う意味がない。「長文の難物だけAstra・それ以外は安いmodel」+「重いmulti-turnは1サーバー化」の組み合わせが、今のところ割に合う使い方だと判断している。
この「固定文脈 × 単価」の構造が分かっていれば、利用枠の実金額を逆算するのにも使える。整数%表示のゲージから週次枠の$を点確定する手順 (連続flip法) は、前回の記事に書いた:
同じゲージを見る2つの視座として、合わせて読める。
限界 (実測できていないこと)
- 検証できたのはinput側の課金のみ。output単価 ($50/M表示) が表示どおりに課金されるかは、実験がすべて数tokenのecho応答で信号がなく、未計測である
- 単価はCLI表示のrate card (API公開単価とは別レート)・v3000.6.14時点の値
- 割引の観測はusageゲージ (整数%) の読み取りなので、fitの精度は±1pt程度
- skills構成やMCP台数が違う環境では、最初のシャッフル地点までの長さが変わり、best caseの部分hit率も変わる (構造 «順序が壊れるとhitしない» は同じ)
参考
- Devin docs — adaptive rate card (cachingの条件): https://docs.devin.ai/cli/adaptive
- Devin docs — quota (token単位の計測とcaching): https://docs.devin.ai/desktop/accounts/quota
- 前回 — サブスクAIの週次利用枠は整数%表示から確定できる (連続flip法): https://qiita.com/jinno_ai/items/8110c5e1b94010b5bc84
本稿の実測に使った生データ (起草出力4本・models list保存) は assets/qiita/astra-prompt-cache-record/ に置いた。SHA1比較とfit計算は本文中の手順どおり sessions.db から再現できる。