2024 年の夏、運営している壁紙アプリの 1 つで「画像生成プロンプトを少しだけ最適化したつもりの変更」を本番に直接出した翌朝、レビュー欄が荒れていたことがあります。問題はプロンプトそのものではなく、テンプレートの条件分岐を 1 行書き換えたことでフォールバック経路が動かなくなっていたことでした。AdMob のセッション単価は半日でいつもの 6 割まで落ち、復旧までの数時間で本来得られたはずの売上は数万円単位で消えました。「全量切り替え」の怖さを、AI エージェントを組み込んだ機能ほど痛感する瞬間はありません。
エージェントは決定論的なコードと違って、入力分布が変わると挙動が静かにズレます。回帰テストが通っていても、本番のユーザー入力の長尾で破綻していることに後から気づきます。だからこそ「段階的に出す」「悪化したら速やかに戻す」を仕組み化しておく必要があります。ここで Antigravity をハブにして、カナリアデプロイ+ Burn-Rate SLO ベースの自動ロールバックを、個人開発でも維持できる粒度で組み立てる方法を書いていきます。アート活動と並走しながら 2014 年からアプリ開発を続け、累計 5,000 万ダウンロードを超える運用を続けてきた感覚として、「壊れたあとに気づくのではなく、悪化の兆候の段階で自動で巻き戻す」設計に切り替えた瞬間からデプロイの心理的負荷が劇的に変わりました。
Dolice Labs の運営も2014年からの個人開発と地続きで、ここで扱う題材は実際の現場で検証してから記事に起こしています。
カナリアと「普通の A/B テスト」を分けて考える
A/B テストは「どちらの方が良いか」を測定する仕組みで、両方とも『十分まとも』であることが前提です。一方カナリアは「新版が壊れていないか」を確認する仕組みで、悪い側を早期に検出して全量公開を止めることが目的です。AI エージェントの場合、評価指標は単なる成功率ではなく、応答品質・コスト・レイテンシ・安全性の 4 軸を同時に見る必要があります。
私が個人開発の現場で採用している境界線は次の通りです。
カナリア: 「壊れていない」ことを保証するフェーズ。トラフィック割合は 1% → 10% → 50% → 100% と段階的に上げ、各段階で Burn-Rate が閾値以下である必要がある
A/B テスト: カナリア通過後の「より良いか」を測定するフェーズ。十分なサンプル数が貯まるまで両系統を並行運用する
両者を混同すると、A/B テストの「効果を見たい」という気持ちが優先され、明らかに壊れた変更を見逃すリスクが上がります。先に「壊れていない確認」、後に「効果検証」という順序を SKILL.md レベルで固定するのが、結局のところ一番事故が減るやり方でした。
SLI / SLO / エラーバジェット / Burn-Rate を AI エージェント向けに整理する
カナリアの判定基準は、感覚や雰囲気ではなく数値で固定しておく必要があります。AI エージェントを運用するときに必須となる 4 つの定義を整理します。
SLI(Service Level Indicator) : 計測する指標。AI エージェントでは「成功率(タスク完遂率)」「ハルシネーション検出率」「P95 レイテンシ」「コスト/リクエスト」を最低でも 4 系統測ります
SLO(Service Level Objective) : SLI に対する目標値。例: 「成功率 99.0%、P95 レイテンシ 8,000ms 以下、コスト ≤ $0.012 / リクエスト」など
エラーバジェット : 一定期間内に許容される失敗の総量。例: 月次 SLO 99% なら、エラーバジェットは月の総リクエストの 1.0%
Burn-Rate : 一定の時間窓でエラーバジェットがどれくらいの速度で消費されているか。例: 1 時間で月次バジェットの 1% を使ったら Burn-Rate = 7.2(月単位に正規化)
カナリアでは Burn-Rate を「カナリア区間のトラフィックに対して」計算します。全体に対する Burn-Rate ではないことに注意してください。カナリアが 5% のトラフィックを受け持っている場合、その 5% の中での失敗率を見ます。これを混同すると、「全体としては安定して見えるが、新版に当たったユーザーの 30% が失敗している」状況を見逃します。
私が運用している壁紙系のアプリでは、AI 画像生成エージェントに対して次の SLO を設定しています。
成功率(妥当な画像が返ってくる率): 98.5% 以上
P95 レイテンシ: 12,000ms 以下
セッションあたり生成コスト: $0.06 以下
不適切画像検出率(安全フィルタを通過した不適切コンテンツの推定割合): 0.1% 以下
数字は事業のリスク許容度から逆算します。月間アクティブユーザー数が大きいアプリほど、わずか 0.1% の悪化でも被害規模が大きくなるため、SLO を保守的に設定しておく必要があります。
カナリアトラフィック分割を Antigravity で実装する
トラフィック分割は、エッジ(Cloudflare Workers)かエントリポイント(Next.js / Hono のミドルウェア)で実装します。重要なのは「同じユーザーが常に同じバージョンに振り分けられる」スティッキネスです。リクエストごとにランダムに振ると、ユーザーが交互に新旧版に当たり、体験が支離滅裂になります。
Antigravity の Agent モードで以下の設計を依頼すると、ハッシュベースの安定振り分けが自動生成されます。実際に使っているコード例を貼ります。
// src/lib/canary-router.ts
// AI エージェントのバージョンをユーザーIDのハッシュで決定的に振り分ける
// 同一ユーザーは常に同じバージョンに振られる(スティッキー)
import { createHash } from "node:crypto" ;
export type AgentVersion = "stable" | "canary" ;
export interface CanaryConfig {
canaryPercent : number ; // 0-100 のカナリア割合
forceFlag ?: AgentVersion ; // 緊急時の強制指定(自動ロールバック発火時に使う)
}
export function selectAgentVersion (
userId : string ,
config : CanaryConfig
) : AgentVersion {
// 緊急ロールバック中なら強制的に stable に倒す
if (config.forceFlag === "stable" ) return "stable" ;
if (config.canaryPercent <= 0 ) return "stable" ;
if (config.canaryPercent >= 100 ) return "canary" ;
// userId をハッシュして 0-99 の整数に変換
// ハッシュ関数は安定したものを使うこと(Math.random は禁止)
const h = createHash ( "sha256" ). update (userId). digest ();
const bucket = h. readUInt16BE ( 0 ) % 100 ;
return bucket < config.canaryPercent ? "canary" : "stable" ;
}
// 期待する動作:
// canaryPercent=10 なら、約10%のユーザーが安定的に canary に振られる
// 同一userIdは常に同じバージョンに振られる(セッション中ブレない)
「同一ユーザーが常に同じバージョンに振られる」という性質は、Math.random() ベースの実装では絶対に得られません。Antigravity のコード生成に頼るときも、Agent から出てきたコードが乱数ベースになっていたら必ず指摘して書き換えさせる必要があります。ここを誤ると、後の Burn-Rate 計測が「ユーザー単位」で見えなくなり、根本的な事故調査が困難になります。
カナリアの割合は、Cloudflare の KV あるいは Durable Object に保管しておき、ロールバック時に Antigravity Agent から即座に書き換えられるようにします。デプロイのたびにコード変更で割合を上げ下げするのではなく、設定値の変更で動かせるようにしておくのが大切です。
// src/lib/canary-config.ts
// カナリア設定をCloudflare KVから読み込み、エッジで即時反映する
export async function getCanaryConfig (
env : { CANARY_KV : KVNamespace }
) : Promise < CanaryConfig > {
const raw = await env. CANARY_KV . get ( "agent:image-gen:canary" );
if ( ! raw) {
// フェイルセーフ: KV接続失敗時は安全側(stable 100%)に倒す
return { canaryPercent: 0 };
}
try {
return JSON . parse (raw) as CanaryConfig ;
} catch {
return { canaryPercent: 0 };
}
}
// 期待する動作:
// KV書き換えから約60秒以内(Cloudflareのキャッシュ無効化挙動に依存)で
// 全エッジに反映される
「KV 読み込み失敗時は安全側に倒す」というポイントは、エッジで発生しうる障害を考えると必須です。ここを throw で落としたり、ランダムに振り分ける実装にすると、KV 障害時にカナリアが暴走します。
メトリクス収集パイプラインを Agent タスクとして組み込む
カナリアの判定には、リアルタイムに近い粒度でメトリクスを収集する必要があります。私の運用環境では、エージェントの実行ログを Cloudflare Workers Analytics Engine と BigQuery の 2 系統に流し、判定用は Analytics Engine の数十秒粒度のクエリで回しています。
// src/lib/agent-telemetry.ts
// エージェント実行の結果を AnalyticsEngine に送る
// バージョン別に成功率・レイテンシ・コストを分けて集計可能にする
export interface AgentExecutionResult {
version : AgentVersion ;
userId : string ;
success : boolean ;
latencyMs : number ;
costUsd : number ;
errorCode ?: string ;
}
export function recordAgentExecution (
env : { TELEMETRY : AnalyticsEngineDataset },
result : AgentExecutionResult
) : void {
env. TELEMETRY . writeDataPoint ({
indexes: [result.version], // バージョン別に高速集計するためのインデックス
blobs: [
result.userId,
result.success ? "success" : "failure" ,
result.errorCode ?? "" ,
],
doubles: [result.latencyMs, result.costUsd],
});
}
// 期待する動作:
// SELECT _sample_interval, blob2, AVG(double1), AVG(double2)
// FROM telemetry WHERE index1 = 'canary' AND timestamp > NOW() - INTERVAL 5 MINUTE
// で 5 分間の canary 系統の平均レイテンシ・平均コストを取得できる
Antigravity Agent に依頼するときのコツは、「エージェント実行のメトリクスを記録する関数を 1 つに集約して、すべての呼び出し経路から必ず通るようにしてください」と明示することです。これを言わないと、エージェントごとにバラバラの記録方法が散らばり、後で集計するときに整合性が取れません。
Burn-Rate 計算と多重ウィンドウアラート
カナリアの判定で核となるのが Burn-Rate の計算です。Google の SRE 本で紹介されている「多重ウィンドウ・多重 Burn-Rate」アラートを AI エージェント向けに簡略化したものを使います。
// src/lib/burn-rate.ts
// カナリア系統の Burn-Rate を計算する
// 短期(5分)と長期(1時間)の両方で閾値を超えたときにアラート発火
export interface SLOTarget {
windowDays : number ; // 例: 30(月次SLO)
successRateTarget : number ; // 例: 0.985(98.5%)
}
export interface MetricsWindow {
totalRequests : number ;
failedRequests : number ;
windowSeconds : number ;
}
/**
* 指定ウィンドウでのBurn-Rateを計算する
* Burn-Rate = (実測失敗率 / 許容失敗率) を 月単位に正規化
* 値が1なら、月のエラーバジェットをちょうど1ヶ月で使い切るペース
* 値が10なら、月のバジェットを3日で使い切るペース(危険)
*/
export function computeBurnRate (
metrics : MetricsWindow ,
slo : SLOTarget
) : number {
if (metrics.totalRequests === 0 ) return 0 ;
const observedErrorRate = metrics.failedRequests / metrics.totalRequests;
const allowedErrorRate = 1 - slo.successRateTarget;
if (allowedErrorRate <= 0 ) return Number.POSITIVE_INFINITY;
return observedErrorRate / allowedErrorRate;
}
/**
* 短期と長期の両方で閾値超過したらロールバック判定
* Page-level alert(SRE本)の閾値: 短期5分で14.4, 長期1時間で6
*/
export function shouldRollback (
short : MetricsWindow , // 5分窓
long : MetricsWindow , // 1時間窓
slo : SLOTarget
) : boolean {
const shortBR = computeBurnRate (short, slo);
const longBR = computeBurnRate (long, slo);
return shortBR > 14.4 && longBR > 6.0 ;
}
// 期待する動作:
// SLO 99% で、直近5分で2%失敗・直近1時間で1.5%失敗していると
// shortBR=14.4超 && longBR=6超 となり true を返す(即時ロールバック判定)
「短期と長期の両方で同時に超過したときだけ」という AND 条件は重要です。短期だけの判定だと一時的なスパイクで誤発火し、長期だけの判定だと反応が遅すぎます。AND にすることで「短期で異常 + 既にバジェットの相当割合を消費している」という二重の確証を取ります。
なお、上記の 14.4 と 6.0 は Google SRE 本の標準的な値ですが、AI エージェントの場合は SLO が比較的緩い(99% など)ことが多いので、自分の運用に合わせて微調整します。私は壁紙アプリの画像生成エージェントで 12.0 と 4.5 まで下げて、より敏感に反応させています。早めに止まる方が、復旧コストよりも安いという判断です。
自動ロールバックトリガーの実装
Burn-Rate 判定の結果を受けて、実際にカナリアトラフィックを 0% に戻す処理を組みます。ここは Antigravity の Background Agent タスクで定期的に回すのが運用上ラクです。
// src/agents/canary-watchdog.ts
// Antigravity Background Agent として5分ごとに実行
// カナリアの Burn-Rate を監視し、危険水域なら自動ロールバックする
import { queryAnalyticsEngine } from "./telemetry-query" ;
import { computeBurnRate, shouldRollback } from "../lib/burn-rate" ;
import { setCanaryConfig } from "../lib/canary-config" ;
const SLO = { windowDays: 30 , successRateTarget: 0.985 };
export async function canaryWatchdogTick ( env : Env ) : Promise < void > {
const short = await queryAnalyticsEngine (env, "canary" , 300 ); // 5分窓
const long = await queryAnalyticsEngine (env, "canary" , 3600 ); // 1時間窓
// サンプル数が少なすぎる場合は判定保留(誤発火を防ぐ)
if (short.totalRequests < 50 || long.totalRequests < 500 ) {
console. log ( `[watchdog] insufficient samples, skipping` );
return ;
}
if ( shouldRollback (short, long, SLO )) {
const shortBR = computeBurnRate (short, SLO );
const longBR = computeBurnRate (long, SLO );
console. error (
`[watchdog] ROLLBACK triggered: shortBR=${ shortBR . toFixed ( 1 ) } longBR=${ longBR . toFixed ( 1 ) }`
);
// カナリア割合を 0% に倒し、強制的に stable に戻す
await setCanaryConfig (env, {
canaryPercent: 0 ,
forceFlag: "stable" ,
});
// 通知(Slack/メール/Discordなど運用に合わせて)
await notifyRollback (env, { shortBR, longBR });
}
}
// 期待する動作:
// 5分ごとに実行され、Burn-Rate閾値を超えたら canary を即停止
// 50リクエスト未満は判定スキップ(統計的有意性を担保)
「サンプル数が少なすぎる場合は判定を保留する」という分岐は、深夜帯のような低トラフィック帯で誤発火を防ぐ意味で必須です。アプリ系の事業だと深夜は通常時の 10% 程度までトラフィックが落ちるので、ここを忘れるとロールバックが嵐のように発動して逆に運用が破綻します。
Antigravity の Background Agent としてデプロイする際は、agent.config.json で cron: "*/5 * * * *" を指定して 5 分ごとに実行させます。Agent モードであれば、canaryWatchdogTick を呼び出す Worker をスキャフォールドする一連の作業を、自然言語の指示で一気に生成できます。
デプロイの「ステータスマシン」を明示的にコードで管理する
カナリアデプロイは状態遷移そのものです。私の手元では次の 6 状態を type で固定し、許容されない遷移は型エラーになるようにしています。
// src/lib/deploy-state.ts
// カナリアデプロイの状態を有限状態機械として表現する
// 不正な状態遷移をコンパイル時に防ぐ
export type DeployState =
| { kind : "idle" }
| { kind : "canary" ; percent : 1 | 10 | 50 ; startedAt : number }
| { kind : "promoting" ; from : number ; to : 100 ; startedAt : number }
| { kind : "live" ; deployedAt : number }
| { kind : "rolling-back" ; reason : string ; startedAt : number }
| { kind : "rolled-back" ; reason : string ; rolledBackAt : number };
export type DeployEvent =
| { type : "start" ; initialPercent : 1 }
| { type : "advance" ; nextPercent : 10 | 50 }
| { type : "promote" }
| { type : "complete" }
| { type : "rollback" ; reason : string }
| { type : "rollback-complete" };
export function transition (
state : DeployState ,
event : DeployEvent
) : DeployState | null {
// idle → canary(1%)
if (state.kind === "idle" && event.type === "start" ) {
return { kind: "canary" , percent: 1 , startedAt: Date. now () };
}
// canary(N%) → canary(N+%)
if (state.kind === "canary" && event.type === "advance" ) {
return { kind: "canary" , percent: event.nextPercent, startedAt: Date. now () };
}
// canary(50%) → promoting(50→100)
if (state.kind === "canary" && state.percent === 50 && event.type === "promote" ) {
return { kind: "promoting" , from: 50 , to: 100 , startedAt: Date. now () };
}
// promoting → live
if (state.kind === "promoting" && event.type === "complete" ) {
return { kind: "live" , deployedAt: Date. now () };
}
// any → rolling-back(緊急時はどの状態からでも戻せる)
if (event.type === "rollback" ) {
return { kind: "rolling-back" , reason: event.reason, startedAt: Date. now () };
}
// rolling-back → rolled-back
if (state.kind === "rolling-back" && event.type === "rollback-complete" ) {
return {
kind: "rolled-back" ,
reason: state.reason,
rolledBackAt: Date. now (),
};
}
// 上記以外の遷移は許可しない
return null ;
}
// 期待する動作:
// transition({kind:"idle"}, {type:"start", initialPercent:1})
// => {kind:"canary", percent:1, startedAt:1715... }
// transition({kind:"canary", percent:1, ...}, {type:"promote"})
// => null (50%を経由していないのでpromote不可)
状態を型で固定すると、Antigravity Agent にコードを書かせるときに「許可されていない遷移は型エラーになるから安心」と言えます。逆に文字列の status フィールド 1 つで管理していると、Agent は気軽に新しい状態名を作ってしまい、整合性が静かに崩れていきます。
実際の運用では、canary(1%) → canary(10%) → canary(50%) → promoting → live の各遷移に最低 30 分の観察期間を入れます。短すぎると Burn-Rate の計測サンプルが集まりません。私は壁紙アプリでは 30 分、決済が絡む癒し系アプリでは 2 時間置いています。事故の影響度に応じて長さを変えるのが原則です。
よくある落とし穴と回避策
実際に運用してきて何度か踏んだ罠を書いておきます。読者が同じ穴に落ちる確率を少しでも下げられたら嬉しいです。
乱数ベースの振り分けで失敗する : 既に書きましたが、Math.random() ベースだと「同一ユーザーが交互に新旧版を体験する」現象が起き、Burn-Rate の意味が崩れます。必ずユーザーID(または IP のハッシュ)を使ったスティッキー振り分けにしてください
キャッシュ層で旧版のレスポンスを返してしまう : エッジキャッシュや CDN が canary レスポンスを stable のキャッシュに混入させると、判定が壊れます。Vary ヘッダにバージョン識別子を入れるか、URL パスにバージョンを含める必要があります
メトリクスの粒度が粗すぎる : 1 時間粒度だと反応が遅すぎます。最低でも 1 分粒度、できれば 30 秒粒度で集計できる仕組みにしておきます。Cloudflare Analytics Engine か Datadog の Live Tail が個人開発でも手が届く選択肢です
エラーバジェットを「全体」で見てしまう : カナリア区間の判定は、必ずカナリア系統内のエラー率で見ます。全体のエラー率で見ると、95% が stable で動いている間は値が薄まり、カナリアの異常が見えなくなります
ロールバック後のクリーンアップを忘れる : カナリアを 0% に戻したあと、KV や Durable Object の forceFlag を解除しないままだと、次のデプロイで「canaryPercent を上げても全部 stable に倒される」というハマり方をします。rolled-back 状態に入ったタイミングで forceFlag を消す処理を、状態遷移と連動させてください
Background Agent の冪等性が崩れている : Watchdog Agent が同じ判定を 2 回連続で実行すると、2 回目で別の意思決定をする可能性があります。最後の判定時刻を KV に保存し、判定間隔が短すぎる場合はスキップする実装を入れておきます
シャドーモードを併用するパターン を採るとさらに安全度が上がります。シャドーは本番トラフィックをコピーして新版に流すだけで、ユーザーへの返答は stable から返す方式です。カナリアの 1% 段階の前にシャドーを 1 週間挟むと、レスポンスの差分を観察できる時間が確保できて精神的にも余裕が出ます。
ロールバックは「使う前」にテストしておく
一度も発動させたことのないロールバック機構は、ロールバック機構ではなく約束の紙切れです。Burn-Rate ウォッチドッグを初めて信用する瞬間が本番事故の最中であってはなりません。
私が本番でカナリアを信用する前に必ずやっている演習が 2 種類あります。
1 つ目は合成的な失敗注入です。フィーチャーフラグで 3% の人為的失敗を仕込んだカナリア版をデプロイし(カナリア系統だけ 30 リクエストに 1 回エラーを返すラッパーを通します)、ウォッチドッグが 1 周期以内にカナリアを 0% に戻すかを確認します。リアリティが目的ではなく、メトリクス → Burn-Rate 計算 → KV 書き換え → エッジ伝搬の配線が「最後まで」つながっていることを確かめるのが目的です。
// src/lib/synthetic-failure.ts
// カナリア演習用の人為的エラー注入
// 本番では FAILURE_RATE_KV が未設定のため発動しない設計
export function maybeInjectFailure (
version : AgentVersion ,
env : { FAILURE_RATE_KV : KVNamespace }
) : Promise < void > {
if (version !== "canary" ) return Promise . resolve ();
return env. FAILURE_RATE_KV . get ( "drill:failure-rate" ). then ( rateStr => {
const rate = parseFloat (rateStr ?? "0" );
if (rate > 0 && Math. random () < rate) {
throw new Error ( "synthetic_canary_failure" );
}
});
}
// 期待する動作:
// drill:failure-rate に "0.03" を入れると約3%のカナリア要求が失敗扱いになり
// 2ティック以内にウォッチドッグが発火する
2 つ目は「シャドーロールバック」演習です。挙動が十分理解されている既存エージェントで、5 分間カナリア状態にしたあと、Antigravity Agent コンソールから手動で forceFlag を stable に倒し、すべての層を追跡します — KV が更新されたか、エッジキャッシュが切り替わったか、観測ダッシュボードでバージョンフィールドが戻ったか、デプロイ状態機械がきちんと遷移ログを出しているか。四半期に 1 回これをやることで、私は過去 2 件の回帰を捕まえました(Vary ヘッダ変更によりスティッキネスが壊れていたケースと、CDN キャッシュ TTL の設定ミスで 6 時間前の CanaryConfig が配信されていたケース)。
最も重要な指標は「ロールバックレイテンシ」です。ウォッチドッグが判定した瞬間から、全エッジ POP が stable を返すまでの所要時間。私の運用では 90 秒を上限にしています。この数字を測れていないなら、ブラスト半径を「知っている」のではなく「知っているつもり」になっているだけです。
コストを第一級のロールバックシグナルとして扱う
レイテンシと成功率は分かりやすい SLI です。コスト/リクエストは静かな指標で、私が最も痛い目を見たのもこの指標を明示的に監視していなかったときでした。
2025 年初頭、壁紙アプリのエージェントに「自己訂正ループ」を入れたリリースがありました。最初に生成した画像が品質しきい値を下回ったとき、エージェントが自分でリファインを再リクエストする仕組みです。意図は素直で、テスト結果は良好、成功率もほぼ変わりません。カナリア中に見落としていたのは、想定 5% で発動するはずだった再リクエスト経路が、プロンプト解析のエッジケースで約 40% の要求に対して発動していたことです。コスト/リクエストはおよそ 3 倍に跳ね上がりました。気づくまでにカナリアは 10% で 6 時間動き続け、数千円相当の推論予算が静かに溶けていました。
それ以来、コストは 4 つの必須 Burn-Rate ストリームの 1 つになりました。閾値は意図的に失敗率より厳しめに設定しています。技術的には何も壊れていなくても、コスト劣化はビジネスを直接削るからです。
// src/lib/cost-burn-rate.ts
// コストを第一級SLOとして扱う
// 持続的なコスト劣化は失敗と同じ重さで扱う
export interface CostWindow {
totalRequests : number ;
totalCostUsd : number ;
windowSeconds : number ;
}
export function computeCostRatio (
canary : CostWindow ,
stable : CostWindow
) : number {
if (canary.totalRequests === 0 || stable.totalRequests === 0 ) return 1 ;
const canaryAvg = canary.totalCostUsd / canary.totalRequests;
const stableAvg = stable.totalCostUsd / stable.totalRequests;
if (stableAvg === 0 ) return Number.POSITIVE_INFINITY;
return canaryAvg / stableAvg;
}
// 期待する動作:
// 1.0 はコスト/リクエストが変化していない
// 2.5 はカナリア要求が stable の 2.5 倍のコストになっている
// 私の運用では 30 分窓で ratio > 1.5 が続いたらロールバック
設計上の重要なポイントは、過去の絶対値ではなく「同時刻の stable コホート」と比較することです。これによりプロバイダ側の料金改定や、ワークロード自体の自然な変化(両系統を等しく押し上げるもの)を自動補正できます。本当に検出したいのは「先週同曜日より高い」ではなく「いま隣で動いている stable より高い」ことです。
プロンプトバージョニングとの役割分担
エージェントのリリースの多くは「全体が変わった」ではなく、もっと小さな変更です。プロンプトをひと言締めた、ツールの説明文を書き直した、システムメッセージにガードレールを 1 行足した、といったレベル。これら全部を完全なカナリアプロセスにのせると、個人開発では維持できないオーバーヘッドになります。
私の運用では、プロンプトのみの変更は別のもっと軽いトラックで扱い、コードやモデルの変更だけがフルカナリアパイプラインに乗ります。境界はこう引いています。
プロンプト変更のみ : プロンプトバージョニング側で A/B テスト。Burn-Rate ウォッチドッグは観測するが、ブラスト半径が小さいぶん閾値は緩める。ロールバックは KV を 1 行戻すだけ
コードまたはモデル変更 : フルカナリアパイプライン。4 段階のトラフィック昇格、二重ウィンドウ Burn-Rate、自動ロールバック、状態機械による遷移強制をすべて適用
SKILL.md レベルでこの線をはっきり引いておくことで、「すべての変更を同じ重さで扱って運用注意力を浪費する」状態から抜け出せます。逆に注意したいのは、「プロンプト変更」を装っていても実質的にはコード変更と等価なケース(YAML に新しいツール参照を 1 行足すなど)です。差分が小さくても役割としてはコード変更側に分類するのが安全です。
個人開発でも回せる軽量版運用
ここまでの設計は中規模以上を想定した内容ですが、個人開発でも 80% は同じ仕組みを再利用できます。軽量化のポイントを 3 つに絞ります。
メトリクスは Cloudflare Analytics Engine 1 本でよい : Datadog や Honeycomb は強力ですが、月額数百ドル単位の固定費がかかります。Analytics Engine は 1,000 万書き込みまで無料相当で、Burn-Rate 計算には十分な精度が出ます
Burn-Rate ウォッチドッグは Cron Trigger 1 本で十分 : 専用の Durable Object を立てる必要はなく、5 分ごとに Cron で起動する Worker で運用できます。CPU 時間も 1 回数十ミリ秒で済みます
状態遷移の通知は Discord Webhook で十分 : Slack 有料プランを契約していなくても、Discord の Webhook はチームの可視性を担保するのに必要十分です。私は個人プロジェクトの通知は基本的に全部 Discord に流しています
エージェントの本番モニタリングと プロンプトのバージョニング・AB テスト と組み合わせると、個人開発でも「壊れたら気づいて、自動で戻る」最低限のセーフティネットが揃います。同じ仕組みは エージェントの評価フレームワーク とも連動させやすく、回帰検知の精度が高まります。
まずは今日できる小さな一歩から
明日からいきなり全 Agent をカナリアデプロイ化する必要はありません。私のおすすめは、まず「最もユーザー影響の大きい Agent 1 つ」を選び、その Agent 専用の Burn-Rate ダッシュボードを Cloudflare Analytics Engine で 1 枚作ることです。ダッシュボードがあるだけで、何かが壊れたときの初動が劇的に速くなります。そこから、カナリアトラフィック分割 → 自動ロールバック → 状態遷移管理の順に積み上げると、無理なく仕組みが育っていきます。
エージェントは生き物のように挙動が日々ズレていきます。完全に止められないなら、せめて「悪い方向にズレたときに、誰の手も借りずに巻き戻る」仕組みを置いておく — それが個人開発であっても本番品質を維持するための、私なりの結論です。読んでくださってありがとうございました。同じように個人開発で AI エージェントを本番運用されている方の参考になれば幸いです。