ANTIGRAVITY LABEN
記事一覧/Agents & Manager
Agents & Manager/2026-04-20上級

Antigravity × Google Workspace API で構築するビジネス自動化エージェント — Gmail・Drive・Sheets・Docs 完全連携実装ガイド

Antigravity と Google Workspace API(Gmail・Drive・Sheets・Docs)を組み合わせ、ビジネス文書処理・メール自動応答・データ集計を完全自動化するAIエージェントの設計から本番運用までを解説します。

antigravity435google-workspace2gmail-apigoogle-driveagents90automation51gemini15

業務の中で「また同じメールに同じような返信を書いている」「スプレッドシートへのデータ転記を毎週手動でやっている」と感じたことはないでしょうか。

私がアプリ開発の傍らで直面したのもまさにそれでした。問い合わせメールの対応、リリースノートの作成、アプリの収益データのシート整理——どれも重要な作業ですが、毎回手を動かすのは非効率です。これらを Antigravity と Google Workspace API、そして Gemini API を組み合わせて自動化したとき、週に数時間分の作業が完全になくなりました。

ここではその仕組みを完全に再現できるように、認証設定から本番運用まで順を追って解説します。単なるチュートリアルではなく、実際に私が運用しているシステムの設計思想と、途中でぶつかった落とし穴も含めてお伝えします。

全体アーキテクチャの設計思想

実装に入る前に、システム全体の構造を整理しておきます。

今回構築するのは、4つのGoogle Workspace APIをAgentsとして連携させる自律型パイプラインです。

  • Gmail エージェント: 受信メールを分類し、Gemini API で意図を解析。ルールに基づいて自動返信を生成・送信
  • Drive エージェント: 指定フォルダを監視し、新しいファイルをトリガーに処理を開始。テンプレートから文書を自動生成
  • Sheets エージェント: 外部APIやDBからデータを収集してスプレッドシートに書き込み、サマリーレポートを生成
  • Docs エージェント: Sheetsのデータを元にGoogleドキュメントを自動生成し、指定のユーザーと共有

Antigravity の agents.md でこれら4つのエージェントの役割を定義し、Gemini API がコンテンツ判断のコアを担います。

重要な設計判断として、すべての自動返信に「人間レビューフラグ」を実装しています。信頼スコアが一定以下のメールは自動送信せず、Draft として保存する設計にしました。これは誤送信のリスクをゼロにするための必須安全弁です。

Google Workspace API 認証の正しい設定

Workspace APIへの接続で最初に迷うのが認証方式の選択です。2種類あります。

サービスアカウント vs OAuth 2.0 の使い分け

サービスアカウント(推奨:サーバーサイド自動化)

ユーザーの操作なしにバックグラウンドで動作させるには、サービスアカウントが最適です。ただし、Gmailへのアクセスにはドメイン管理者によるドメイン全体の委任(Domain-Wide Delegation)が必要です。Google Workspace(旧G Suite)の有料プランを使っている場合はこちらを使いましょう。

OAuth 2.0(推奨:個人アカウントや小規模利用)

個人のGmailアカウントや、管理者権限のない環境では OAuth 2.0 を使います。初回のみブラウザ認証が必要ですが、リフレッシュトークンをファイルに保存することで以降は無人実行が可能です。

今回は OAuth 2.0 + リフレッシュトークン保存方式 で実装します。個人開発者に最も現実的な方法です。

Python クライアントの初期設定

# requirements.txt
google-auth==2.29.0
google-auth-oauthlib==1.2.0
google-auth-httplib2==0.2.0
google-api-python-client==2.125.0
google-generativeai==0.5.3
 
# setup.py: 認証モジュール
import os
import json
from pathlib import Path
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
 
# 必要なスコープを全て宣言する(後から追加するとトークン再認証が必要)
SCOPES = [
    "https://www.googleapis.com/auth/gmail.modify",
    "https://www.googleapis.com/auth/drive",
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/documents",
]
 
TOKEN_PATH = Path("token.json")
CREDENTIALS_PATH = Path("credentials.json")  # Google Cloud Consoleからダウンロード
 
def get_credentials() -> Credentials:
    """OAuth 2.0 認証情報を取得する。token.jsonが存在する場合は再利用する。"""
    creds = None
    
    if TOKEN_PATH.exists():
        creds = Credentials.from_authorized_user_file(str(TOKEN_PATH), SCOPES)
    
    # 期限切れの場合はリフレッシュトークンで更新
    if creds and creds.expired and creds.refresh_token:
        try:
            creds.refresh(Request())
        except Exception as e:
            print(f"トークン更新失敗: {e} — 再認証が必要です")
            creds = None
    
    # 有効な認証情報がない場合のみブラウザ認証フローを起動
    if not creds or not creds.valid:
        if not CREDENTIALS_PATH.exists():
            raise FileNotFoundError(
                "credentials.json が見つかりません。"
                "Google Cloud Console からOAuth 2.0クライアントIDをダウンロードしてください。"
            )
        flow = InstalledAppFlow.from_client_secrets_file(str(CREDENTIALS_PATH), SCOPES)
        creds = flow.run_local_server(port=0)
        
        # リフレッシュトークンをファイルに保存(次回以降は無人実行可能)
        TOKEN_PATH.write_text(creds.to_json())
        print(f"✅ 認証成功。{TOKEN_PATH} に保存しました。")
    
    return creds
 
def build_services():
    """全Workspace APIのサービスオブジェクトを一括で構築する。"""
    creds = get_credentials()
    return {
        "gmail": build("gmail", "v1", credentials=creds),
        "drive": build("drive", "v3", credentials=creds),
        "sheets": build("sheets", "v4", credentials=creds),
        "docs": build("docs", "v1", credentials=creds),
    }

重要なポイント: SCOPEは後から追加するとtoken.jsonを削除して再認証が必要になります。設計段階で必要なスコープを全て確定させてから実装を始めてください。私はこれを見落として2回再認証する羽目になりました。

Gmail エージェント — 受信メール自動処理の実装

メール取得フィルターの設計

import base64
from email.mime.text import MIMEText
from typing import Optional
import google.generativeai as genai
 
class GmailAgent:
    """Gmail の受信メールを AI で分類・処理するエージェント。"""
    
    # 自動処理しない信頼スコアの下限(これ以下はDraftに保存)
    AUTO_REPLY_THRESHOLD = 0.85
    
    def __init__(self, services: dict, gemini_api_key: str):
        self.gmail = services["gmail"]
        genai.configure(api_key=gemini_api_key)
        self.model = genai.GenerativeModel("gemini-2.0-flash")
        
    def fetch_unread_emails(self, query: str = "is:unread -from:me", max_results: int = 10) -> list[dict]:
        """未読メールを取得する。queryでフィルタリング可能。"""
        try:
            result = self.gmail.users().messages().list(
                userId="me",
                q=query,
                maxResults=max_results
            ).execute()
            
            messages = result.get("messages", [])
            if not messages:
                return []
            
            emails = []
            for msg in messages:
                detail = self.gmail.users().messages().get(
                    userId="me",
                    id=msg["id"],
                    format="full"
                ).execute()
                
                headers = {h["name"]: h["value"] for h in detail["payload"]["headers"]}
                body = self._extract_body(detail["payload"])
                
                emails.append({
                    "id": msg["id"],
                    "from": headers.get("From", ""),
                    "subject": headers.get("Subject", ""),
                    "body": body[:3000],  # 長すぎる場合は先頭3000文字
                    "thread_id": detail["threadId"],
                })
            
            return emails
            
        except Exception as e:
            print(f"❌ メール取得エラー: {e}")
            return []
    
    def _extract_body(self, payload: dict) -> str:
        """メールペイロードからテキスト本文を抽出する。"""
        body = ""
        if "parts" in payload:
            for part in payload["parts"]:
                if part["mimeType"] == "text/plain":
                    data = part["body"].get("data", "")
                    body = base64.urlsafe_b64decode(data).decode("utf-8", errors="ignore")
                    break
        elif "body" in payload:
            data = payload["body"].get("data", "")
            if data:
                body = base64.urlsafe_b64decode(data).decode("utf-8", errors="ignore")
        return body
    
    def analyze_and_reply(self, email: dict) -> dict:
        """
        Gemini API でメールの意図を分析し、返信文を生成する。
        戻り値: {"action": "send"|"draft"|"skip", "reply": str, "confidence": float, "category": str}
        """
        prompt = f"""
あなたはメール対応アシスタントです。以下のメールを分析してください。
 
送信者: {email['from']}
件名: {email['subject']}
本文:
{email['body']}
 
次のJSON形式で回答してください(コードブロックなし):
{{
  "category": "inquiry|support|spam|notification|other",
  "confidence": 0.0から1.0の数値,
  "should_reply": true or false,
  "reply_draft": "返信文(日本語)。should_replyがfalseなら空文字",
  "reason": "判断理由を1文で"
}}
"""
        try:
            response = self.model.generate_content(prompt)
            import json
            result = json.loads(response.text.strip())
            
            # 信頼スコアに基づいて自動送信するか Draft 保存するか決定
            if result["should_reply"] and result["confidence"] >= self.AUTO_REPLY_THRESHOLD:
                action = "send"
            elif result["should_reply"]:
                action = "draft"  # 人間のレビューが必要
            else:
                action = "skip"
            
            return {
                "action": action,
                "reply": result.get("reply_draft", ""),
                "confidence": result["confidence"],
                "category": result["category"],
                "reason": result.get("reason", ""),
            }
            
        except Exception as e:
            print(f"❌ Gemini API エラー: {e}")
            # エラー時は必ず draft に回す(自動送信しない)
            return {"action": "draft", "reply": "", "confidence": 0.0, "category": "unknown", "reason": str(e)}
    
    def send_or_save_draft(self, original_email: dict, analysis: dict) -> Optional[str]:
        """分析結果に基づいてメールを送信または Draft として保存する。"""
        if analysis["action"] == "skip" or not analysis["reply"]:
            print(f"  ↳ スキップ(カテゴリ: {analysis['category']})")
            return None
        
        # 返信メールを構築
        reply_text = analysis["reply"]
        message = MIMEText(reply_text, "plain", "utf-8")
        message["to"] = original_email["from"]
        message["subject"] = f"Re: {original_email['subject']}"
        message["In-Reply-To"] = original_email["id"]
        
        raw = base64.urlsafe_b64encode(message.as_bytes()).decode("utf-8")
        body = {
            "raw": raw,
            "threadId": original_email["thread_id"]
        }
        
        try:
            if analysis["action"] == "send":
                sent = self.gmail.users().messages().send(userId="me", body=body).execute()
                print(f"  ✅ 送信完了 (信頼スコア: {analysis['confidence']:.2f})")
                return sent["id"]
            else:  # draft
                draft = self.gmail.users().drafts().create(
                    userId="me", body={"message": body}
                ).execute()
                print(f"  📝 Draft保存 (信頼スコア: {analysis['confidence']:.2f} — 要レビュー)")
                return draft["id"]
        except Exception as e:
            print(f"  ❌ 送信エラー: {e}")
            return None

この設計で特に気を付けているのは「エラー時は必ずdraftに回す」という原則です。Gemini APIが失敗した場合に空のメールが自動送信されるのは最悪のシナリオです。エラーハンドリングを書くときは「失敗したらどうなるか」を常に最初に考えるようにしています。

Google Drive + Docs — 文書生成パイプラインの実装

テンプレート駆動の文書自動生成

from googleapiclient.errors import HttpError
import copy
 
class DocumentAgent:
    """Google Drive と Docs を使って文書を自動生成するエージェント。"""
    
    def __init__(self, services: dict, gemini_api_key: str):
        self.drive = services["drive"]
        self.docs = services["docs"]
        genai.configure(api_key=gemini_api_key)
        self.model = genai.GenerativeModel("gemini-2.0-flash")
    
    def create_from_template(
        self,
        template_id: str,
        replacements: dict[str, str],
        output_folder_id: str,
        title: str
    ) -> str:
        """
        Googleドキュメントのテンプレートをコピーして変数を置換し、文書を生成する。
        
        Args:
            template_id: テンプレートドキュメントのID
            replacements: {"{{変数名}}": "置換後の値"} の形式
            output_folder_id: 生成ファイルを保存するフォルダのID
            title: 生成するファイルのタイトル
        Returns:
            生成されたドキュメントのID
        """
        # テンプレートをコピー
        try:
            copied = self.drive.files().copy(
                fileId=template_id,
                body={
                    "name": title,
                    "parents": [output_folder_id]
                }
            ).execute()
            doc_id = copied["id"]
            print(f"  📄 テンプレートをコピーしました: {title}")
        except HttpError as e:
            raise RuntimeError(f"テンプレートのコピーに失敗しました: {e}")
        
        # 変数を一括置換するリクエストを構築
        requests = [
            {
                "replaceAllText": {
                    "containsText": {
                        "text": placeholder,
                        "matchCase": True
                    },
                    "replaceText": value
                }
            }
            for placeholder, value in replacements.items()
        ]
        
        if requests:
            try:
                self.docs.documents().batchUpdate(
                    documentId=doc_id,
                    body={"requests": requests}
                ).execute()
                print(f"  ✅ {len(requests)}個の変数を置換しました")
            except HttpError as e:
                # 置換失敗でもファイルは残す(部分的な成果は保持)
                print(f"  ⚠️ 変数置換中にエラー: {e}")
        
        return doc_id
    
    def generate_release_notes(self, app_name: str, version: str, changes: list[str]) -> str:
        """
        アプリのリリースノートを Gemini で自動生成してドキュメントにまとめる。
        戻り値: 生成した Google Doc の URL
        """
        # Gemini でリリースノートの文章を生成
        prompt = f"""
以下の変更点を元に、App Store / Google Play 向けのリリースノートを日本語で作成してください。
ユーザーフレンドリーな表現で、技術的な専門用語は避けてください。
200文字以内でまとめてください。
 
アプリ名: {app_name}
バージョン: {version}
変更内容:
{chr(10).join(f"- {c}" for c in changes)}
"""
        response = self.model.generate_content(prompt)
        release_notes = response.text.strip()
        
        # テンプレートの変数
        replacements = {
            "{{APP_NAME}}": app_name,
            "{{VERSION}}": version,
            "{{RELEASE_DATE}}": "2026-04-20",
            "{{RELEASE_NOTES}}": release_notes,
            "{{RAW_CHANGES}}": "\n".join(f"• {c}" for c in changes),
        }
        
        # ここではテンプレートIDと保存先フォルダIDを環境変数から取得する
        import os
        template_id = os.environ.get("RELEASE_NOTE_TEMPLATE_ID", "")
        folder_id = os.environ.get("RELEASE_NOTES_FOLDER_ID", "")
        
        if not template_id or not folder_id:
            # テンプレートなしの場合は新規ドキュメントを作成
            doc = self.docs.documents().create(
                body={"title": f"{app_name} v{version} リリースノート"}
            ).execute()
            doc_id = doc["documentId"]
            # 本文を挿入
            self.docs.documents().batchUpdate(
                documentId=doc_id,
                body={"requests": [{
                    "insertText": {
                        "location": {"index": 1},
                        "text": f"{app_name} v{version}\n\n{release_notes}\n\n変更詳細:\n{chr(10).join(f'• {c}' for c in changes)}"
                    }
                }]}
            ).execute()
        else:
            doc_id = self.create_from_template(
                template_id=template_id,
                replacements=replacements,
                output_folder_id=folder_id,
                title=f"{app_name} v{version} リリースノート"
            )
        
        url = f"https://docs.google.com/document/d/{doc_id}/edit"
        print(f"  ✅ リリースノート生成: {url}")
        return url

Google Sheets エージェント — データ集計とレポート自動生成

アプリの収益データやユーザー数を毎週手動でスプレッドシートに貼り付けていた作業が、このエージェントで完全になくなりました。

from datetime import datetime, timezone, timedelta
from typing import Any
 
class SheetsAgent:
    """Google Sheets へのデータ書き込みとレポート生成を担うエージェント。"""
    
    JST = timezone(timedelta(hours=9))
    
    def __init__(self, services: dict, gemini_api_key: str):
        self.sheets = services["sheets"]
        genai.configure(api_key=gemini_api_key)
        self.model = genai.GenerativeModel("gemini-2.0-flash")
    
    def append_daily_metrics(
        self,
        spreadsheet_id: str,
        sheet_name: str,
        metrics: dict[str, Any]
    ) -> None:
        """
        日次メトリクスをスプレッドシートに追記する。
        metrics は {"ダウンロード数": 100, "収益": 5000, ...} の形式。
        """
        today = datetime.now(self.JST).strftime("%Y-%m-%d")
        row = [today] + list(metrics.values())
        
        try:
            self.sheets.spreadsheets().values().append(
                spreadsheetId=spreadsheet_id,
                range=f"{sheet_name}\!A:Z",
                valueInputOption="USER_ENTERED",
                insertDataOption="INSERT_ROWS",
                body={"values": [row]}
            ).execute()
            print(f"  ✅ {today} のデータを追記しました ({len(row)-1}項目)")
        except HttpError as e:
            print(f"  ❌ スプレッドシート書き込みエラー: {e}")
            raise
    
    def generate_weekly_summary(
        self,
        spreadsheet_id: str,
        data_sheet: str,
        summary_sheet: str
    ) -> str:
        """
        過去7日分のデータを読み込み、Gemini で週次サマリーを生成してシートに書き込む。
        戻り値: 生成されたインサイトテキスト
        """
        # 過去7行のデータを取得
        try:
            result = self.sheets.spreadsheets().values().get(
                spreadsheetId=spreadsheet_id,
                range=f"{data_sheet}\!A:Z"
            ).execute()
            all_rows = result.get("values", [])
        except HttpError as e:
            print(f"  ❌ データ読み込みエラー: {e}")
            return ""
        
        if len(all_rows) < 2:
            print("  ⚠️ データが不足しています(ヘッダー除いて最低1行必要)")
            return ""
        
        header = all_rows[0]
        recent_rows = all_rows[-7:]  # 最新7行
        
        # データをテキスト形式に変換してGeminiへ
        data_text = "\n".join(
            ", ".join(f"{h}: {v}" for h, v in zip(header, row))
            for row in recent_rows
        )
        
        prompt = f"""
以下は過去7日間のアプリパフォーマンスデータです。
このデータを分析して、以下の観点でインサイトを日本語で生成してください:
 
1. 今週のハイライト(最も良かった点)
2. 注意が必要なトレンド
3. 来週に試すべきアクション(具体的に1つ)
 
データ:
{data_text}
 
300文字以内で簡潔にまとめてください。
"""
        response = self.model.generate_content(prompt)
        insight = response.text.strip()
        
        # サマリーシートに書き込む
        today = datetime.now(self.JST).strftime("%Y-%m-%d")
        try:
            self.sheets.spreadsheets().values().append(
                spreadsheetId=spreadsheet_id,
                range=f"{summary_sheet}\!A:B",
                valueInputOption="USER_ENTERED",
                insertDataOption="INSERT_ROWS",
                body={"values": [[today, insight]]}
            ).execute()
            print(f"  ✅ 週次サマリーを {summary_sheet} に書き込みました")
        except HttpError as e:
            print(f"  ❌ サマリー書き込みエラー: {e}")
        
        return insight

Antigravity でマルチエージェントオーケストレーションを設定する

上記の3つのエージェントを Antigravity で連携させるための agents.md 設定を示します。

# Business Automation Agents
 
## Gmail Agent
Role: メール受信監視と自動返信
Triggers:
  - 新着未読メール(15分間隔でポーリング)
  - subject_keywords: ["お問い合わせ", "サポート", "inquiry"]
Actions:
  - fetch_unread_emails(query="is:unread is:inbox")
  - analyze_and_reply(email)
  - send_or_save_draft(email, analysis)
Error handling:
  - API error → retry 3 times with 30s backoff
  - Gemini timeout → save as draft, log error
 
## Document Agent
Role: リリースノートとレポートの自動生成
Triggers:
  - Git tag push (via webhook)
  - Drive 指定フォルダへのファイル追加
Actions:
  - generate_release_notes(app_name, version, changes)
Dependencies: Gmail Agent(完成通知メールを送信)
 
## Sheets Agent
Role: 日次/週次データ収集とレポート生成
Triggers:
  - 毎日 09:00 JST(日次メトリクス追記)
  - 毎週月曜 08:00 JST(週次サマリー生成)
Actions:
  - append_daily_metrics(spreadsheet_id, "Daily", metrics)
  - generate_weekly_summary(spreadsheet_id, "Daily", "Weekly Summary")

Antigravity の Agent パネルで agents.md を認識させると、各エージェントのトリガーと依存関係が視覚的に確認できます。エージェントファーストのワークフロー設計を参考にしながら、このシステムを構築しました。

よくある落とし穴と回避策

実際に運用する中で遭遇した問題を3つ挙げます。

1. Gmail API のレート制限(1日250クォータユニット)

Gmail API には無料枠でも1日250クォータユニットという制限があります。メール1件の取得が5ユニット、送信が100ユニットを消費します。

15分ごとに未読メールをチェックする場合、1日96回のポーリングで480ユニット必要。これは制限を超えます。

解決策: fetch_unread_emails のポーリング間隔を30分に伸ばし、q="is:unread is:inbox newer_than:1d" で当日のメールのみ取得するように絞り込みました。また、Gmail の Push Notifications(Pub/Sub連携)を使えばポーリング自体が不要になります。

# Pub/Sub を使ったプッシュ通知設定(ポーリングより推奨)
def setup_push_notification(self, pubsub_topic: str) -> None:
    """Gmail の新着メールを Pub/Sub 経由でプッシュ受信する設定。"""
    try:
        self.gmail.users().watch(
            userId="me",
            body={
                "topicName": pubsub_topic,
                "labelIds": ["INBOX"],
                "labelFilterAction": "include"
            }
        ).execute()
        print(f"✅ Pub/Sub 通知を設定しました: {pubsub_topic}")
        print("⚠️ この設定は7日後に失効します。定期的に更新してください。")
    except HttpError as e:
        print(f"❌ Push 通知設定エラー: {e}")

2. OAuth トークンの有効期限(1時間)

access_tokenは1時間で失効します。長時間バッチ処理をしていると途中で認証エラーが発生します。

解決策: get_credentials() 関数でリフレッシュ処理を実装していますが、長時間処理では明示的に再認証するようにしました。

def refresh_if_needed(creds: Credentials) -> Credentials:
    """必要に応じてトークンをリフレッシュする。バッチ処理の各ループで呼ぶ。"""
    if creds.expired and creds.refresh_token:
        creds.refresh(Request())
        TOKEN_PATH.write_text(creds.to_json())
    return creds

3. スコープ設定のミスによる 403 エラー

https://www.googleapis.com/auth/gmail.modify ではなく gmail.readonly を設定して返信送信が 403 になったことがあります。drive スコープも drive.readonly にすると書き込みができません。

一度認証したトークンにスコープを追加するには token.json を削除して再認証が必要です。開発初期に正しいスコープを確定させる点が肝心です。

本番運用とスケジューリング

GitHub Actions を使ってサーバーなしで定期実行できます。

# .github/workflows/workspace-automation.yml
name: Google Workspace Automation
 
on:
  schedule:
    - cron: "0 0 * * *"   # 毎日 09:00 JST(UTC 00:00)
    - cron: "0 23 * * 0"  # 毎週月曜 08:00 JST(UTC 日曜 23:00)
  workflow_dispatch:  # 手動実行も可能
 
jobs:
  daily-metrics:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Python セットアップ
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
          cache: "pip"
      
      - name: 依存関係インストール
        run: pip install -r requirements.txt
      
      - name: OAuth トークンを Secrets から復元
        env:
          GOOGLE_TOKEN_JSON: ${{ secrets.GOOGLE_TOKEN_JSON }}
        run: echo "$GOOGLE_TOKEN_JSON" > token.json
      
      - name: 日次メトリクス収集
        env:
          GOOGLE_CREDENTIALS_JSON: ${{ secrets.GOOGLE_CREDENTIALS_JSON }}
          GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
          SPREADSHEET_ID: ${{ secrets.SPREADSHEET_ID }}
        run: |
          echo "$GOOGLE_CREDENTIALS_JSON" > credentials.json
          python scripts/daily_metrics.py
      
      - name: 更新したトークンを Secrets に書き戻す
        # token.json が更新された場合(リフレッシュが発生した場合)に対応
        env:
          GH_TOKEN: ${{ secrets.GH_TOKEN }}
          REPO: ${{ github.repository }}
        run: |
          NEW_TOKEN=$(cat token.json)
          gh secret set GOOGLE_TOKEN_JSON --body "$NEW_TOKEN" --repo "$REPO"

重要: token.json はシークレットに保存してください。GitHubリポジトリにコミットしてはいけません(.gitignore に追加必須)。また、GitHub Actions でリフレッシュが発生したとき、更新後のトークンをシークレットに書き戻す処理も忘れずに実装してください。

バックグラウンドエージェントの実践活用と組み合わせると、Antigravity上でのデバッグと GitHub Actions での本番運用を効率的に切り替えられます。

実際の運用結果とこれからの展望

このシステムを3週間運用した結果、問い合わせメール対応に使っていた週2〜3時間が0になりました。返信の自動送信率は全メールの約60%(confidenceが0.85以上)で、残り40%はDraftとして保存されて私がレビューする形になっています。

特に効果的だったのは SheetsエージェントによるAdMob収益の自動記録です。Firebase コンソールからの手動コピー&ペースト作業が完全になくなり、毎週月曜に週次インサイトが自動生成されるようになりました。

次のステップとして考えているのは、MCPサーバーの構築を使って、このシステムをAntigravityの内部ツールとして直接呼び出せるようにすることです。エージェントの実行状況をIDE上からリアルタイムで確認できるようになれば、さらに使いやすくなるはずです。

今日できる最初の一歩として、まず get_credentials() を実行してOAuth認証を完了させることをおすすめします。認証さえ通れば、あとは各エージェントを順番に試していくだけです。

シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

Agents & Manager2026-07-01
対話では動いたのに夜間だけ黙る——Antigravity のエージェント定義を対話と無人で同じ挙動にする
デスクトップの Antigravity では正しく動くのに、スケジュールした CLI 実行だと黙って何もしないエージェント。承認・文脈・秘密情報・実行環境の4点で経路差を吸収し、1つの定義を対話でも無人でも同じ挙動にする設計を、動くコードとプリフライト検査つきでまとめます。
Agents & Manager2026-06-28
処理させる記事の本文が、そのままエージェントへの指示になっていた話
Antigravity CLI で無人のコンテンツ整形パイプラインを回していると、処理対象ファイルの本文に紛れた指示文がエージェントの動作を乗っ取ります。指示チャネルとデータチャネルを構造的に分離し、出力スコープの受け入れゲートで逸脱を弾く設計をまとめました。
Agents & Manager2026-06-25
大型更新で無人実行が一晩で止まる前に — バージョンを段階導入するカナリアゲートの設計
大型更新の直後に無人実行が半分しか通らなくなった経験から、動作中の構成を凍結し、隔離プロファイルのカナリア検証で合否を判定してから本採用する段階導入ゲートの設計を、bashとPythonの実装とともにまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →