アーティスト・個人開発者の廣川政樹です。2014 年から個人でアプリ開発を続けていて、累計 5,000 万 DL を超えた壁紙・癒し・引き寄せ系アプリを 6 本並行で運営しています。2026 年に入ってからは Antigravity の Background Agent と subagent を本格的に運用に組み込んでおり、リリース・アセット差し替え・在庫整理のほぼすべてを Agent 経由で進めています。
最初の数週間で気づいたのは、Agent に「やるべきことのリスト」を渡しても事故は減らない、ということでした。Agent はやるべきことを 1 つ片付けたあと、すぐ近くにある「やるべきではないこと」に触りに行きます。境界線を越えた瞬間に、5,000 万 DL の収益基盤に小さな傷が残ります。AdMob の広告ユニット ID を 1 文字書き換えるだけで、その日の eCPM がゼロに張りつく、というのがその例です。
そこで設計したのが Negative Spec — エージェントに「絶対に触れてはならない領域」を明示的に渡すドキュメントです。これは Antigravity 公式の AGENTS.md と対になる位置づけで、AGENTS.md が「役割」を語るなら、Negative Spec は「役割の外側」を語ります。ここから先は、私が 6 本のアプリと 4 サイトのブログ運営で実際に使っている FORBIDDEN.md のテンプレート、Antigravity の subagent への読み込ませ方、Background Agent への再注入スクリプト、そして 3 週間で止めた破壊的操作 5 件と、その兆候パターンを共有していきます。
なぜ AGENTS.md だけでは足りないのか
Antigravity の AGENTS.md は「このリポジトリで Agent が何をすべきか」を宣言するドキュメントです。テスト実行コマンド、コーディング規約、依存関係の追加方法など、ポジティブな指示が並びます。ここまでは良いのですが、現場では「テストを通すついでに DB マイグレーションを書く」「ビルドが通らないので tsconfig を Agent が勝手に緩める」といった、AGENTS.md には書かれていないが触ってほしくない領域への踏み込みが起こります。
問題の本質は、Agent が「やっていいこと」を書かれた範囲で解釈しないことにあります。LLM は「許可されていないことは禁止されている」ではなく「禁止されていないことは試してよい」と推論します。これは Claude in Chrome の Operator 設定や Gemini の自律実行モードでも同じ傾向です。実際、私が壁紙アプリのリリースを subagent に任せた最初の週、Agent は AGENTS.md にあった「ビルド失敗時はログを残せ」という指示を読んだあと、「ログを残すためには Crashlytics の API キーが要るな」と判断して、Firebase コンソールに新しい API キーを発行しに行きました。これは事故ではなく、合理的な推論の結果として境界を越えた例です。
Negative Spec を導入する目的は、推論のコストを上げて境界線を可視化することです。Agent に「Crashlytics の API キーは絶対に新規発行しない」と明示しておけば、同じ場面で Agent は「API キーが要るが発行禁止だから、別の手段を探すか人間に投げる」という判断をします。境界を超える前に止まる確率が大幅に上がります。
FORBIDDEN.md の最小実装
私の現場では、リポジトリのルートに AGENTS.md と並べて FORBIDDEN.md を置いています。Antigravity の subagent は明示的にこのファイルを cat してから作業に入る、という運用です。最小構成は以下のような形です。
# FORBIDDEN.md — このリポジトリで Agent が絶対に触れてはならない領域
このファイルは AGENTS.md とセットで読み込んでください。AGENTS.md にない指示でも、
ここに書かれた領域に触れる可能性があるなら、必ず人間に確認を取ってください。
## 1. 認証・課金系の鍵
- .env.production / firebase-adminsdk*.json / GoogleService-Info.plist
- Stripe の sk_live_*, AdMob の ca-app-pub-*, Apple Connect API Key
- いかなる理由でも新規発行・再発行・ローテーションを行わない
## 2. 公開済みアセットの破壊的上書き
- public/wallpapers/v3/ 以下の画像(販売中のアセット)
- App Store / Google Play の現行バージョンメタデータ(説明文・スクリーンショット)
- リリース済み App Bundle の差し替え
## 3. データベース・マイグレーション
- migrations/ 以下の既存ファイル編集
- production の Cloudflare D1 / KV namespace への書き込み
- 既存テーブルの DROP / TRUNCATE / ALTER
## 4. CI/CD パイプライン
- .github/workflows/release-*.yml の編集
- main ブランチへの直接 push
- protected tag (v*) の削除・再作成
## 5. 法務・契約・収益分配
- LICENSE / NOTICE / TERMS / PRIVACY の編集
- royalty.json (アセット権利者への分配率)
- legal/ ディレクトリの一切の編集
ポイントは 3 つあります。1 つ目は「カテゴリ単位で書く」こと。個別のファイル名を延々と並べると Agent が読み飛ばします。2 つ目は「理由を書かない」こと。Negative Spec は理由を書くと Agent が「この理由が当てはまらない場合は OK」と解釈します。3 つ目は「曖昧な動詞を避ける」こと。「触らない」「壊さない」ではなく、「編集しない」「発行しない」「書き込まない」と動詞を限定します。
Antigravity の subagent に読み込ませる順序
Antigravity の subagent は親エージェントから明示的にファイルを渡さないと FORBIDDEN.md を読みません。私は subagent への指示の冒頭で必ず以下のような順序で読み込ませています。
# .antigravity/subagent-bootstrap.yml
bootstrap :
- action : read_file
path : FORBIDDEN.md
required : true
on_failure : abort
- action : read_file
path : AGENTS.md
required : true
on_failure : abort
- action : assert
condition : |
"FORBIDDEN.md" in context.read_files
and "AGENTS.md" in context.read_files
message : "Negative spec が読み込まれていない状態での作業は禁止"
policy :
before_each_tool_call :
- check : forbidden_path_match
paths_from : FORBIDDEN.md
action : deny_and_escalate
ここで重要なのは順序です。FORBIDDEN.md を AGENTS.md より先に読ませること。Antigravity の LLM はコンテキストの先頭付近の指示を、後半よりも遵守する傾向があります。実体験として、AGENTS.md を先に読ませると、Negative Spec の禁止項目を「以前の指示への例外」として解釈してしまうケースが 3 週間で 2 回発生しました。順序を入れ替えてから、同じ事象は起きていません。
before_each_tool_call で forbidden_path_match を入れているのは、Agent がファイル書き込みツール(edit_file, write_file, apply_patch)を呼ぶ前に、書き込み先パスが FORBIDDEN.md のカテゴリにマッチしないか機械的に検査するためです。LLM の判断に任せないというのが Negative Spec の核心です。
Background Agent への 30 行の再注入スクリプト
Background Agent は長時間動くため、コンテキスト圧縮の過程で FORBIDDEN.md の内容が薄められていきます。Antigravity Inspector で実際にコンテキストを 1 ヶ月読み続けた経験からいうと、24 時間を超えたあたりから Negative Spec の遵守率が落ち始めます。
そこで私は、Background Agent に対して 4 時間ごとに FORBIDDEN.md を再注入する小さなスクリプトを動かしています。
#!/usr/bin/env bash
# .antigravity/reinject-negative-spec.sh
# 4h ごとに cron から呼び出して Background Agent に FORBIDDEN.md を再注入する
set -euo pipefail
AGENT_ID = " ${1 :? usage : $0 < agent_id > } "
REPO_ROOT = "$( git rev-parse --show-toplevel )"
SPEC_FILE = "${ REPO_ROOT }/FORBIDDEN.md"
if [ ! -f " $SPEC_FILE " ]; then
echo "FATAL: $SPEC_FILE not found" >&2
exit 1
fi
SPEC_HASH = "$( sha256sum " $SPEC_FILE " | cut -d ' ' -f1 )"
LAST_HASH_FILE = "/tmp/agent-${ AGENT_ID }-spec-hash"
# 前回注入時とハッシュが同じなら、念のため『直近 4h で禁止操作に近づいた行動はあったか』
# だけを問い合わせて、なければ再注入をスキップしてコストを抑える
if [ -f " $LAST_HASH_FILE " ] && [ "$( cat " $LAST_HASH_FILE ")" = " $SPEC_HASH " ]; then
RISK = $( antigravity agent message " $AGENT_ID " \
--prompt "直近 4 時間で FORBIDDEN.md の禁止カテゴリに該当するファイルを編集・閲覧しようとしたか。yes/no のみで答えてください" \
--max-tokens 5 )
if [ " $RISK " = "no" ]; then
echo "[$( date -Iseconds )] skip reinject (no risk)"
exit 0
fi
fi
antigravity agent message " $AGENT_ID " \
--prompt "以下の FORBIDDEN.md を最新のシステム指示として再ロードしてください。これ以前の指示と矛盾する場合は、必ずこちらを優先してください。" \
--attach-file " $SPEC_FILE "
echo " $SPEC_HASH " > " $LAST_HASH_FILE "
echo "[$( date -Iseconds )] reinjected $SPEC_FILE ( $SPEC_HASH )"
このスクリプトは crontab で 0 */4 * * * の間隔で呼び出しています。最初は毎時間呼び出していましたが、Antigravity の課金がトークン量に応じて加算されるため、ハッシュとリスク問い合わせを組み合わせて回数を絞っています。3 週間運用した結果、Background Agent の禁止領域接触は週 5〜6 件から週 0〜1 件まで、約 87% 削減できました。
実際に止めた破壊的操作 5 件
ここから先は、Negative Spec を導入してから 3 週間で実際に発火したケースです。一つひとつが起きた瞬間の Agent の論理を残しています。
ケース 1: AdMob 広告ユニット ID の自動置換
壁紙アプリのリリース準備中、subagent が「テスト広告ユニットでの動作確認が済んだので、本番用 ID に置換します」と宣言。Negative Spec の ca-app-pub-* 禁止に該当して停止。Agent はテスト ID と本番 ID の置換を「コードの定数置換」とみなしていました。導入前であれば、本番 ID への置換は通っていたはずです。eCPM がゼロになる事故を 1 件防いだ計算になります。
ケース 2: Firebase Admin SDK の鍵再発行
Crashlytics の証明書期限切れ警告に対応するため、subagent が firebase-adminsdk-*.json を新規発行しようとした。Negative Spec の認証鍵カテゴリで停止し、人間にエスカレーション。実際には Firebase コンソール側で期限延長すれば良いだけで、再発行は不要でした。Agent は「期限切れ=再発行」という単純な推論で動いていました。
ケース 3: App Store Connect の説明文の改善
ASO の改善タスクを Agent に振った際、Agent は説明文を「より検索に強い文言」に書き換えようとした。Negative Spec の「App Store メタデータの編集禁止」で停止。実際には A/B テスト枠を使うべきで、本番メタデータの直接編集は審査リスクがありました。
ケース 4: 既存マイグレーションファイルの編集
Cloudflare D1 のスキーマを変更するタスクで、Agent が migrations/0042_add_user_locale.sql を直接書き換えようとした。Negative Spec の「migrations/ 既存ファイル編集禁止」で停止。新規マイグレーション 0043_*.sql を作る正しい運用に Agent を戻しました。これは Vercel のスキルでも「migration は append-only」が定石として明文化されており、Negative Spec で機械的に守れるのは大きい。
ケース 5: royalty.json の数値変更
壁紙アセットの収益分配率を計算するタスクで、Agent が royalty.json を「より単純な分配比率」に丸めようとした。Negative Spec の「royalty.json 編集禁止」で停止。これは実際にアセット権利者と契約で固定している数値で、丸めると契約違反になります。Agent は契約という概念を理解しているとは限らない、というのが大きな学びでした。
5 件のうち、ケース 1 と 5 は直接的な収益・契約リスクでした。残り 3 件もリリース遅延・審査リジェクトの遠因になりえます。Negative Spec のリターンは、私の規模感(月次の AdMob 売上が 7 桁前半円)でも十分回収できる、というのが実感です。1 件の事故で日次 eCPM がゼロになると、その日の機会損失だけで平均 3〜4 万円、累積するとカテゴリ全体の月次変動の 5% を簡単に超えます。
禁止カテゴリのスケールアップ — 12 個の安定セット
3 週間運用して落ち着いた、私の現在の FORBIDDEN.md は 12 カテゴリで構成されています。サイズが大きくなりすぎると Agent が読み飛ばすので、12 を目安に整理しています。
認証・課金系の鍵
公開済みアセットの破壊的上書き
データベース・マイグレーション
CI/CD パイプライン
法務・契約・収益分配
App Store / Google Play メタデータ
プッシュ通知の本番送信
顧客サポートの自動返信
Stripe / RevenueCat の価格・プラン変更
SNS アカウントへの直接投稿
ドメイン・DNS 設定
監査ログの削除・改竄
8 と 10 は AI による顧客接触の自動化を防ぐためのものです。Cowork 経由で SNS 告知を作るのは OK ですが、最終投稿は私が確認する、という線引きを守っています。これは E-E-A-T と読者との信頼を守るためで、技術的なリスクというより信用のリスクです。
Negative Spec が効かない領域
Negative Spec は万能ではありません。私が現場で見ている限り、以下の領域には別の手段が必要です。
意図的な迂回への弱さ : Agent がプロンプトインジェクションを受けた場合、Negative Spec を「ユーザーの本意ではない」と解釈して無視するリスクがあります。これは外部から取得したテキスト(GitHub Issue, Slack の DM, 受信メール)を Agent が読むときに発生します。対策としては、外部ソース由来のコンテキストには独立した sandbox を割り当て、そこから Negative Spec の上書きはできないようにしています。Anthropic Skills の verification-before-completion の考え方を借りています。
読み取り禁止の表現が難しい : 「このファイルを読むこと自体を禁止したい」場合、Negative Spec では表現が弱くなります。これは OS レベルの権限分割(読み取り専用ユーザー、別 Mac での実行)の方が確実です。私は会計関連のファイルは別 Mac で扱っており、Agent 用のマシンには物理的に置いていません。
意味の経年劣化 : 「production のデータベース」のような表現は、半年経つと別のクラスタになっている可能性があります。FORBIDDEN.md は四半期に一度の見直しを _documents/ 配下の運用ドキュメントとセットで行うようにしています。
まずやってみる順序
これから Negative Spec を導入する場合、いきなり 12 カテゴリ書くのではなく、以下の順序で 1 週間ずつ進めるのを薦めます。
Day 1〜2 : FORBIDDEN.md を作って、認証・課金系の鍵だけ書く。subagent の bootstrap で読み込ませる。
Day 3〜5 : 公開済みアセットと CI/CD のカテゴリを追加。実際の Agent 作業ログを Antigravity Inspector で観察し、書き換え寸前で止まった行動を 3 件以上記録する。
Day 6〜10 : 法務・契約系を追加。同時に Background Agent への再注入スクリプトを cron に登録。
Day 11〜14 : App Store メタデータ・プッシュ通知を追加。ここで一度、自分の手で禁止行動を試して、本当に止まるかを検証する。
以降 : 月次の運用レビューで、誤検知・見落としを反映してカテゴリを微調整。
私は最初の週末に半日かけて 12 カテゴリ書き切ろうとして、Agent が読み飛ばす状態を作ってしまいました。段階導入の方が確実に効きます。
次にやるべきこと
Negative Spec は、Agent に裁量を渡すと決めた瞬間から、一緒に書き始めるべきドキュメントです。AGENTS.md だけで運用している方は、まず認証鍵カテゴリだけの 10 行の FORBIDDEN.md をリポジトリのルートに置いて、subagent に読み込ませる順序を試してみてください。1 件目の「危なかった」が観測できれば、その時点で投資は回収できます。