夜のうちに走らせておいた作業がどうなったか、翌朝に外出先のブラウザから覗けるようになりました。長く走る作業を持っている人間にとって、これは素直に嬉しい変化です。
ただ、有効化のトグルに手を伸ばす前に一度止まりました。公式ドキュメントに、こう書かれていたからです。リモートから接続しても、ファイル・ワークスペース・ビルドツール・資格情報・環境変数へのアクセスは、ホスト機側にそのまま残ります。
つまり Remote Control で決めているのは「どこから操作するか」ではなく、「どのマシンを、ブラウザの向こう側から丸ごと使える状態にするか」です。手順そのものは4クリックで終わりますが、判断はそこにはありません。
有効化の手順より、ホスト機の選定が先に来ます
有効化自体は短い作業です。Cmd + ,(Windows と Linux は Ctrl + ,)または左サイドバー下部の Settings から設定を開き、Account セクションの Enable Remote Control をオンにします。あわせて workstation-primary のようなニックネームを付けておくと、インスタンス一覧でマシンを見分けられます。
ブラウザ側は、デスクトップと同じ Google アカウントでサインインし、インスタンス切替から対象のマシンを選ぶだけです。接続後は、進行中の会話の閲覧、新しいタスクの起動、実装プランのレビュー、アーティファクトの確認まで一通りできます。
なお本機能は現在も段階的に配信されており、Google AI Ultra プランが優先されると公式に案内されています。手元にトグルが出ていない場合は、まだ順番が来ていないだけの可能性があります。
手順が短い機能ほど、判断の重心は前工程に寄ります。私はこの機能について、入れ方ではなく「どのマシンに入れるか」だけを考えました。以下はその過程です。
ブラウザ側が担うのは操作面だけです
最初に境界をはっきりさせておきます。Remote Control で移動するのは操作面であって、実行環境ではありません。ビルドはホスト機で走り、ファイルはホスト機にあり、環境変数もホスト機のものが使われます。
この設計自体は好ましいものです。外出先の端末に鍵や環境変数を持ち出さずに済みますし、ネットワークが一時的に切れても、ホスト機がインターネットにつながっている限り、走っているバックグラウンドタスクやシェルコマンドは中断されません。ブラウザ側は自動で再接続を試みます。
一方で、同じ設計を裏返すとこうなります。ホスト機に置いてある資格情報は、ブラウザ越しの操作面から到達可能な範囲に入ります。移動していないからこそ、使える状態のまま残る、という言い方が正確です。
だからホスト機の選定は、性能や常時稼働のしやすさより先に、そのマシンが何を抱えているかで決まります。
daemon の生存条件は OS で三通りに分かれます
デスクトップアプリを開いていないマシンをホストにしたい場合は、headless daemon を入れます。Linux と macOS ではインストーラスクリプトを実行し、Windows は管理者権限のコマンドプロンプトから導入します。--name でインスタンス名を指定でき、status / restart / uninstall でサービスを管理します。
判断を実質的に決めたのは、この daemon がいつ生きているかという表でした。
挙動 Linux macOS Windows
起動タイミング ブート時(ログイン不要) ログイン時 ブート時(ログイン不要)
サインアウト後 走り続ける 停止し、次回ログインで復帰 走り続ける
クラッシュ後の自動復帰 する する しない
事前の予想と逆だったのはここです。私は「常時つながるなら性能の高いメイン機」と考えていましたが、表を見ると、常時稼働に向いているのは Linux と Windows で、macOS はログアウトのたびに落ちます。逆に言えば、macOS は「席を立って明示的にログアウトすれば遠隔操作面も閉じる」という性質を持っています。露出を絞りたいマシンには、この挙動はむしろ利点になります。
そして Windows は、ブート起動で粘り強いのに、クラッシュ後だけは自分では戻ってきません。次のブート、次の定期更新、あるいは手動の restart を待つことになります。常時接続を前提に置くマシンとしては、状態確認の手間がいちばん重くなる組み合わせです。
「どの OS が優れているか」ではなく、「そのマシンに担わせたい役割と、daemon の生き方が噛み合っているか」で選ぶ話だと理解しました。
サインアウトの経路が二つあります
もう一つ、ドキュメントを流し読みすると見落とす点があります。daemon のサインインは、Antigravity エディタのサインインとは別物です。セットアップ時にターミナルで一度サインインし、それ以降は再起動をまたいでサービスが自分でサインインを維持します。
これは運用上、次の意味になります。エディタ側で Enable Remote Control をオフにしても、daemon を入れてあるマシンでは daemon 側の経路が残ります。Hub の一覧に同じマシンが二行出るのは、この二つが別々の存在だからです。
止めたい場面は、たいてい落ち着いていません。だから手順を毎回考えないで済むよう、OS を見て並べ替えるだけの小さなスクリプトを置いています。
#!/usr/bin/env bash
# 「今すぐ遠隔操作面を閉じたい」ときの手順を、OS ごとに並べ替えて出す。
# 経路は二つある — デスクトップ側のトグルと、daemon 側のサインイン。
set -u
OS = "$( uname -s )"
echo "== 遠隔操作面の遮断手順( $OS )=="
echo
echo "[1] デスクトップ側"
echo " Settings > Account > Enable Remote Control を Off"
echo " → デスクトップのセッションだけが一覧から消えます"
echo
echo "[2] headless daemon 側(エディタとは別のサインインです)"
case " $OS " in
Darwin )
echo " agy-daemon uninstall"
echo " ※ macOS はログアウトでも daemon が止まります"
echo " ※ 再ログインで戻るため、席を離れるだけでは遮断になりません"
;;
Linux )
echo " agy-daemon uninstall"
echo " ※ Linux はブート時に起動し、サインアウト後も走り続けます"
echo " ※ ログアウトは遮断になりません。uninstall か agy のサインアウトが要ります"
;;
MINGW *| MSYS *| CYGWIN * )
echo " 管理者権限の cmd.exe で agy-daemon.cmd uninstall"
echo " ※ Windows もブート起動・サインアウト後も継続します"
echo " ※ クラッシュ時は自動復帰しないため、状態は status で毎回確かめます"
;;
*)
echo " agy-daemon uninstall"
;;
esac
echo
echo "[3] 確認"
echo " agy-daemon status"
echo " ブラウザ側のインスタンス一覧から当該マシンが消えたことを目視"
Linux 機で走らせると、こう出ます。
== 遠隔操作面の遮断手順(Linux)==
[1] デスクトップ側
Settings > Account > Enable Remote Control を Off
→ デスクトップのセッションだけが一覧から消えます
[2] headless daemon 側(エディタとは別のサインインです)
agy-daemon uninstall
※ Linux はブート時に起動し、サインアウト後も走り続けます
※ ログアウトは遮断になりません。uninstall か agy のサインアウトが要ります
[3] 確認
agy-daemon status
ブラウザ側のインスタンス一覧から当該マシンが消えたことを目視
大した処理はしていません。ただ、この十数行があるかないかで、「エディタを閉じたから大丈夫だと思っていた」という取り違えが起きなくなります。
ホスト機を選ぶ前に、読み取り面を数えます
CLI 1.1.14 で、ワークスペース外のパスは既定で読み取りのみ許可となり、書き込みには実行モードに応じた承認が必要になりました。守りが一段厚くなった変更で、これ自体は歓迎しています。ただし裏返すと、読み取りは既定で通ります。
資格情報の露出という観点では、危ないのは書き込み側ではなく読み取り側です。そこで、ホスト機の候補を決める前に、そのマシンで一度だけ棚卸しを走らせています。中身は一切開かず、種類と件数だけを数えるスクリプトです。
#!/usr/bin/env bash
# ホスト機の「読み取り面」棚卸し。
# Remote Control を有効にする前に、エージェントが読める位置にある
# 資格情報らしきファイルを種類別に数える。中身は一切表示しない。
set -u
ROOT = " ${1 :- $HOME } "
# 左が分類名、右が find の -name パターン。
PATTERNS = (
"AppStoreConnect API key:AuthKey_*.p8"
"Android signing key:*.jks"
"Android signing key:*.keystore"
"Service account JSON:*service-account*.json"
"Service account JSON:*serviceAccount*.json"
"dotenv:.env"
"dotenv:.env.*"
"npm credentials:.npmrc"
"netrc:.netrc"
"SSH private key:id_*"
"PKCS12 bundle:*.p12"
)
printf '%-28s %s\n' "分類" "件数"
printf '%-28s %s\n' "----------------------------" "----"
TOTAL = 0
declare -A COUNTS = ()
for entry in "${ PATTERNS [ @ ]}" ; do
label = "${ entry %%:* }"
glob = "${ entry #*: }"
n = $( find " $ROOT " -xdev -type f -name " $glob " \
-not -path '*/node_modules/*' \
-not -path '*/.git/*' \
-not -path '*/Library/Caches/*' \
2> /dev/null | wc -l )
COUNTS[ " $label " ] = $(( ${COUNTS[ " $label " ] :- 0} + n ))
done
for label in "${ ! COUNTS [ @ ]}" ; do
n = ${COUNTS[$label]}
[ " $n " -eq 0 ] && continue
printf '%-28s %4d\n' " $label " " $n "
TOTAL = $(( TOTAL + n ))
done
printf '%-28s %4d\n' "合計" " $TOTAL "
[ " $TOTAL " -gt 0 ] && exit 1 || exit 0
分類名を重複させてあるのは意図的です。*.jks と *.keystore は同じ「Android signing key」として合算され、.env と .env.* も一つの行にまとまります。連想配列で足し込んでいるため、パターンを増やしても出力の行数は増えません。
動作を確かめるため、鍵を模したファイルを八つ置いた検証用ディレクトリに対して実行しました。
分類 件数
---------------------------- ----
SSH private key 1
dotenv 2
Android signing key 1
Service account JSON 1
AppStoreConnect API key 1
npm credentials 1
合計 7
八つ置いて七件です。差の一つは node_modules の下に置いた .env で、除外パターンが効いています。依存ツリーの中に紛れ込んだサンプルの .env まで数えると、対処すべき件数が実態より膨らみます。ここを除外しておかないと、棚卸しの結果が意思決定の役に立ちません。
終了コードを件数に連動させてあるのも小さな工夫です。一件でも見つかれば非ゼロで終わるため、セットアップ手順の中に混ぜたときに「確認せずに素通りする」ことがなくなります。
個人開発で iOS と Android のアプリを並行して面倒を見ていると、署名鍵やストアの API キーは自然と一台に集まっていきます。集まっていること自体は効率的なのですが、そのマシンをホストにするかどうかは別の問いです。私はこの棚卸しを見てから、常時つなぐ役割を別のマシンへ寄せる方向で考えるようになりました。資格情報の扱いという点では、Managed Agents にクラウドで動いてもらうとき、資格情報をどう渡すか で整理した最小権限の考え方が、そのまま Remote Control のホスト選定にも当てはまります。
名前が二つ並ぶ理由と、直したのに戻る理由
daemon を入れると、設定ファイルに似た名前のキーが二つ並びます。
キー 指しているもの
cliRemoteControlHostnameheadless daemon(agy-daemon)側の表示名
remoteControlHostnameAntigravity 2.0 デスクトップ側の表示名
設定ファイルの場所は次のとおりです。
OS 設定ファイル
Linux / macOS ~/.gemini/config/config.json
Windows %USERPROFILE%\.gemini\config\config.json
厄介なのは、--name を付けてインストールした場合、サービスが再起動するたびにその名前がファイルの値を上書きする点です。ファイルを編集したのに毎回元に戻る、という現象はこれが原因です。ファイル側を正としたいなら、セットアップをやり直して名前の質問を空のまま通します。また、サービスが動いている間の編集は反映されないため、変更後は restart が必要です。
取り違えを避けるために、二つのキーを並べて表示するだけのスクリプトを使っています。
#!/usr/bin/env python3
"""~/.gemini/config/config.json の二つの似た名前を読み分ける。
cliRemoteControlHostname … headless daemon(agy-daemon)側の表示名
remoteControlHostname … Antigravity 2.0 デスクトップ側の表示名
Hub の一覧に二行出るのはこの二つが別物だからで、
どちらを直すのかを取り違えると「直したのに変わらない」が起きる。
"""
import json, os, sys
path = sys.argv[ 1 ] if len (sys.argv) > 1 else os.path.expanduser(
"~/.gemini/config/config.json"
)
if not os.path.exists(path):
print ( f "設定ファイルがありません: { path } " )
print ( "→ daemon 未導入か、まだ一度も起動していない状態です。" )
sys.exit( 2 )
with open (path, encoding = "utf-8" ) as f:
try :
cfg = json.load(f)
except json.JSONDecodeError as e:
# 壊れた設定を書き戻さないため、読めない時点で止める
print ( f "JSON として読めません( { e } )。手で直すまで触らないでください。" )
sys.exit( 3 )
rows = [
( "cliRemoteControlHostname" , "headless daemon (agy-daemon)" ),
( "remoteControlHostname" , "Antigravity 2.0 デスクトップ" ),
]
for key, owner in rows:
val = cfg.get(key)
shown = val if val else "(未設定 — 自動生成名が使われます)"
print ( f " { key :26 } { shown :38 } ← { owner } " )
if cfg.get( "cliRemoteControlHostname" ) == cfg.get( "remoteControlHostname" ) \
and cfg.get( "cliRemoteControlHostname" ):
print ( " \n ⚠️ 二つが同じ名前です。Hub の一覧で見分けがつかなくなります。" )
sys.exit( 1 )
両方が設定されている状態では、こう出ます。
cliRemoteControlHostname mini-server ← headless daemon (agy-daemon)
remoteControlHostname studio-desktop ← Antigravity 2.0 デスクトップ
二つに同じ名前を入れた状態で走らせると、終了コード 1 で警告が出ます。
cliRemoteControlHostname studio-desktop ← headless daemon (agy-daemon)
remoteControlHostname studio-desktop ← Antigravity 2.0 デスクトップ
⚠️ 二つが同じ名前です。Hub の一覧で見分けがつかなくなります。
JSONDecodeError を握りつぶさずに終了コード 3 で止めているのは、CLI 1.1.16 で入った「解析できない settings.json を既定値で上書きしてしまう問題」の修正と同じ発想です。読めない設定ファイルに対して自動で何かをすると、気付かないうちに設定が初期化されます。読めなかったら人間に返す、という一線をこちらの道具でも守っています。
同期フォルダの上で作業している場合の一手
作業ツリーをクラウド同期フォルダの下に置いている環境では、もう一段考える点があります。ワークスペース境界の内側にあるファイルでも、同期の実体は他のマシンにも届いているからです。
ホスト機を一台に絞ったつもりでも、同期先のマシンに同じ内容が並んでいるなら、「どのマシンをホストにするか」という判断の効き目はその分だけ薄まります。同期フォルダ特有の落とし穴についてはAntigravity がクラウド同期フォルダのファイルを空として読む問題—オンラインオンリー・プレースホルダーの落とし穴 でも触れましたが、Remote Control を前提にすると、同期範囲そのものを見直す動機になります。
私自身は、資格情報を同期フォルダの外へ出す作業を先に済ませました。順番としては、ホストを決めるより前です。
ホスト機を決めるときに立てた三つの問い
判断の骨格だけ残しておきます。順番にも意味がありますので、この並びのまま辿ることをおすすめします。
1. そのマシンは何を抱えているか
棚卸しスクリプトの出力がそのまま答えになります。件数がゼロなら次へ進めますし、署名鍵やストアの API キーが並ぶようなら、それらを別の場所へ移してから考え直すか、そもそもホスト候補から外します。ここを飛ばすと、残りの二つを丁寧に検討しても意味が薄くなります。
2. そのマシンにどう生きていてほしいか
常時つないでおきたいなら Linux か Windows、席を立ったら閉じたいなら macOS、という具合に daemon の挙動と役割を噛み合わせます。Windows を選ぶ場合は、クラッシュ後に自力では戻らない前提で agy-daemon status を確認する習慣まで含めて設計します。役割を先に決めてから OS を選ぶのではなく、手元にあるマシンの OS から役割を割り当てる方が、実際には無理がありません。
3. 止めたいときに何手で止まるか
経路が二つある以上、片方だけ覚えている状態は避けます。デスクトップ側のトグルと daemon 側のサインイン、その両方を紙かスクリプトに落としておきます。慌てている場面で思い出せない手順は、存在していないのと同じです。
この三つの順番が逆になると、「入れてから考える」ことになります。ワークスペースの外へ書いている箇所を数えたときと同じで、CLI 1.1.14 に上げる前に、ワークスペースの外へ書いている箇所を数える ときの感覚に近い作業でした。事前に数えておくと、あとで慌てずに済みます。
まとめ
今日できることを一つだけ挙げるなら、ホスト機の候補になっているマシンで、棚卸しスクリプトを一度走らせてみてください。件数を見てから有効化のトグルに触るかどうかを決める、という順番だけで、判断の質はだいぶ変わります。
遠隔から自分の環境を動かせる自由は、その環境が何を抱えているかを把握している人ほど安心して使えるものだと感じています。私もまだ運用を組み立てている途中ですので、噛み合わせの良い構成が見つかったら、また書き残していきます。
参考にした一次資料はGoogle Antigravity の Remote Control ドキュメント です。段階配信中の機能ですので、手順や挙動は更新される可能性があります。