Choosing a Remote Control Host Machine Comes Down to Daemon Lifetime and Revocation Paths
What to settle before you flip on Remote Control in Antigravity 2.0: how the headless daemon behaves per OS, why there are two separate sign-out paths, and how to inventory the read surface on a candidate host machine.
Being able to check on an overnight run from a browser while away from my desk is a change I welcome. Long-running work and a fixed desk have never been a comfortable pairing.
What made me pause before touching the toggle was a single line in the documentation. When you connect remotely, access to files, workspaces, build tools, credentials, and environment variables all stays on the host machine.
So the decision Remote Control asks you to make is not "where do I want to work from." It is "which of my machines am I willing to make fully drivable from the other side of a browser tab." Turning it on takes four clicks. The decision does not live there.
The setup is short, which pushes the weight onto host selection
Enabling it is quick. Open Settings with Cmd + , (Ctrl + , on Windows and Linux), or click Settings at the bottom of the left sidebar, go to the Account section, and toggle Enable Remote Control on. Setting a nickname such as workstation-primary makes the machine easy to pick out in the instance list.
On the browser side you sign in with the same Google Account, choose the machine from the instance switcher, and you have access to active conversations, new agent tasks, implementation plan reviews, and artifact inspection.
The feature is still rolling out, with Google AI Ultra plans getting priority access. If the toggle is not there yet, your turn may simply not have come.
When the setup is this short, everything that matters happens before it. I spent my time on one question only: which machine.
The browser only carries the control surface
Worth stating plainly: what moves to the browser is the control surface, not the execution environment. Builds run on the host. Files live on the host. Environment variables are the host's.
That design has real upside. You never carry keys or environment variables onto whatever device you happen to be holding, and if your local connectivity drops, background agent tasks and shell commands keep running on the host as long as the host stays online. The web interface reconnects on its own.
Turn that same sentence over, though, and it reads differently. Credentials sitting on the host are inside the reachable set of anything driving that control surface. Nothing moved, which is exactly why everything stays usable.
That is why host selection starts with what a machine is holding, not with how fast it is or how reliably it stays awake.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦You will be able to work backwards from per-OS daemon behavior to decide which of your machines should host Remote Control
✦You will know exactly which switch actually stops a remotely running agent, instead of guessing under pressure
✦You will be able to inventory, in one pass, which credentials on a host machine sit on an agent's read path
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
The daemon stays alive under three different rules
If you want a machine to be reachable without keeping the desktop app open, you install the headless daemon. Linux and macOS use an installer script; Windows needs an Administrator command prompt. You can pass --name to set the instance name, and manage the service with status, restart, and uninstall.
The table that actually decided things for me was the one describing when the daemon runs.
Behavior
Linux
macOS
Windows
Starts
At boot, no login needed
At login
At boot, no login needed
After you sign out
Keeps running
Stops, returns at next login
Keeps running
Recovers from a crash on its own
Yes
Yes
No
This ran against my assumption. I had expected to host on my most capable machine and leave it connected. The table says the machines suited to staying connected are Linux and Windows, while macOS drops the daemon every time you log out.
Read the other way, that macOS behavior is a feature. Logging out closes the remote surface as a side effect of something you already do when you leave. For a machine you want narrowly exposed, that is the property you want.
Windows is the awkward one. It survives sign-out and starts at boot, but it will not come back from a crash by itself — you wait for the next boot, the next scheduled update, or a manual restart. If you are treating a machine as always-reachable, that is the combination that costs the most attention.
None of this makes one OS better. It makes each OS suited to a different role, and the job is to match the role you want to the lifetime you get.
There are two sign-out paths, not one
Here is the detail that a quick read of the docs will skip past. The daemon's sign-in is separate from the Antigravity editor's. You sign in once in the terminal during setup, and from then on the service maintains its own session across reboots.
Operationally that means this: turning Enable Remote Control off in the editor does not touch the daemon. Two entries for the same machine in the Hub are not a bug — they are two distinct things, on purpose.
The moment you want to cut access is rarely a calm one, so I keep a small script that reorders the steps based on the OS rather than making me recall them.
#!/usr/bin/env bash# Print the shutdown steps for the remote control surface, ordered per OS.# There are two paths - the desktop toggle, and the daemon's own sign-in.set -uOS="$(uname -s)"echo "== Closing the remote control surface ($OS) =="echoecho "[1] Desktop side"echo " Settings > Account > Enable Remote Control -> Off"echo " -> only the desktop session leaves the instance list"echoecho "[2] Headless daemon (a separate sign-in from the editor)"case "$OS" in Darwin) echo " agy-daemon uninstall" echo " note: on macOS the daemon also stops when you log out" echo " note: it returns at next login, so leaving your desk is not revocation" ;; Linux) echo " agy-daemon uninstall" echo " note: on Linux it starts at boot and survives sign-out" echo " note: logging out revokes nothing; uninstall or sign agy out" ;; MINGW*|MSYS*|CYGWIN*) echo " agy-daemon.cmd uninstall from an Administrator cmd.exe" echo " note: Windows also starts at boot and survives sign-out" echo " note: it does not self-recover from crashes, so check status every time" ;; *) echo " agy-daemon uninstall" ;;esacechoecho "[3] Verify"echo " agy-daemon status"echo " confirm the machine is gone from the browser instance list"
Run on a Linux box it prints:
== Closing the remote control surface (Linux) ==
[1] Desktop side
Settings > Account > Enable Remote Control -> Off
-> only the desktop session leaves the instance list
[2] Headless daemon (a separate sign-in from the editor)
agy-daemon uninstall
note: on Linux it starts at boot and survives sign-out
note: logging out revokes nothing; uninstall or sign agy out
[3] Verify
agy-daemon status
confirm the machine is gone from the browser instance list
It does almost nothing. But a dozen lines like these are the difference between revoking access and believing you did because you closed the editor.
Count the read surface before you pick a host
CLI 1.1.14 made paths outside the workspace read-only by default, with writes requiring approval based on the active execution mode. That is a solid tightening, and I am glad it landed. Flip it over, though: reads still go through by default.
For credential exposure, the dangerous side is reads, not writes. So before committing to a host, I run a one-shot inventory on the candidate machine. It never opens a file — it counts categories.
#!/usr/bin/env bash# Inventory the read surface of a candidate host machine.# Counts credential-shaped files an agent could read, by category.# It never prints file contents.set -uROOT="${1:-$HOME}"# Left of the colon is the category label, right is the find -name pattern.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' "Category" "Count"printf '%-28s %s\n' "----------------------------" "-----"TOTAL=0declare -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 ))donefor label in "${!COUNTS[@]}"; do n=${COUNTS[$label]} [ "$n" -eq 0 ] && continue printf '%-28s %5d\n' "$label" "$n" TOTAL=$(( TOTAL + n ))doneprintf '%-28s %5d\n' "Total" "$TOTAL"[ "$TOTAL" -gt 0 ] && exit 1 || exit 0
Repeating category labels is deliberate. *.jks and *.keystore collapse into one "Android signing key" row, and .env and .env.* collapse into one "dotenv" row, because the associative array accumulates by label. You can keep adding patterns without the output growing.
I verified it against a fixture directory holding eight credential-shaped files:
Category Count
---------------------------- -----
SSH private key 1
dotenv 2
Android signing key 1
Service account JSON 1
AppStoreConnect API key 1
npm credentials 1
Total 7
Eight files in, seven counted. The missing one is a .env placed under node_modules, filtered by the exclusion. Counting sample dotenv files buried in a dependency tree inflates the number past anything actionable, and an inventory you cannot act on is not worth running.
Tying the exit code to the count is the other small piece. A non-zero exit on any hit means the check cannot be silently walked past when it sits inside a setup script.
As an indie developer maintaining iOS and Android apps side by side, signing keys and store API credentials naturally pile up on one machine. That concentration is efficient — but whether that machine should be the host is a separate question, and the inventory made the answer obvious to me. The least-privilege framing in When Managed Agents Run in the Cloud, How Do You Hand Them Credentials? transfers to host selection almost unchanged.
Why two similar names appear, and why your edit reverts
Once the daemon is installed, two similarly named keys sit in the same settings file.
Key
What it names
cliRemoteControlHostname
The headless daemon (agy-daemon)
remoteControlHostname
The Antigravity 2.0 desktop app
The file lives here:
OS
Settings file
Linux / macOS
~/.gemini/config/config.json
Windows
%USERPROFILE%\.gemini\config\config.json
The trap is that if you installed with --name, that name wins over the file every time the service restarts. An edit that keeps reverting is this, not a bug. To put the file back in charge, re-run setup and leave the name prompt blank. Edits also do nothing while the service is running, so restart afterwards.
To stop myself from fixing the wrong one, I print both side by side.
#!/usr/bin/env python3"""Disambiguate the two similar names in ~/.gemini/config/config.json.cliRemoteControlHostname ... the headless daemon (agy-daemon)remoteControlHostname ... the Antigravity 2.0 desktop appTwo rows in the Hub means two distinct things. Editing the wrong oneproduces the classic "I fixed it and nothing changed"."""import json, os, syspath = sys.argv[1] if len(sys.argv) > 1 else os.path.expanduser( "~/.gemini/config/config.json")if not os.path.exists(path): print(f"No settings file at: {path}") print("-> the daemon is not installed, or has never started.") sys.exit(2)with open(path, encoding="utf-8") as f: try: cfg = json.load(f) except json.JSONDecodeError as e: # Refuse to touch a settings file we cannot parse. print(f"Not parseable as JSON ({e}). Leave it alone until a human fixes it.") sys.exit(3)rows = [ ("cliRemoteControlHostname", "headless daemon (agy-daemon)"), ("remoteControlHostname", "Antigravity 2.0 desktop"),]for key, owner in rows: val = cfg.get(key) shown = val if val else "(unset - a generated name is used)" print(f"{key:26} {shown:38} <- {owner}")if cfg.get("cliRemoteControlHostname") == cfg.get("remoteControlHostname") \ and cfg.get("cliRemoteControlHostname"): print("\nWarning: both names are identical. The Hub list becomes ambiguous.") sys.exit(1)
Give both the same value and it exits 1 with a warning:
cliRemoteControlHostname studio-desktop <- headless daemon (agy-daemon)
remoteControlHostname studio-desktop <- Antigravity 2.0 desktop
Warning: both names are identical. The Hub list becomes ambiguous.
Exiting 3 on JSONDecodeError rather than swallowing it follows the same reasoning as the CLI 1.1.16 fix for settings files being overwritten with defaults when they could not be parsed. Automating anything against an unreadable config is how settings get silently reset. If we cannot read it, we hand it back to a person.
One extra move if you work inside a synced folder
If your working tree sits under a cloud-synced folder, there is another layer. Files inside the workspace boundary still have their real copies replicated to other machines.
You can narrow the host down to one machine and still have identical content sitting on every sync target, which dilutes the decision by exactly that much. I covered the sync-specific failure mode separately in When Antigravity Reads Cloud-Synced Files as Empty: The Online-Only Placeholder Trap, but with Remote Control in the picture, the sync scope itself becomes worth revisiting.
I moved credentials out of the synced folder first — before choosing a host, not after.
The three questions I answered before choosing
The shape of the decision, if nothing else survives from this article. The order matters, so walk them in sequence.
1. What is this machine holding?
The inventory output answers this directly. A zero count means you can move on. A list of signing keys and store API credentials means you either relocate them first or drop this machine from consideration. Skip this question and the other two lose most of their weight.
2. How do I want this machine to behave?
Match the role to the daemon lifetime — Linux or Windows for always-reachable, macOS when you want the surface to close as you leave. Choosing Windows means designing in the habit of checking agy-daemon status, since it will not return from a crash on its own. In practice it works better to assign roles based on the machines you already have than to pick a role and then go shopping for an OS.
3. How many moves does it take to cut access?
With two paths, remembering only one is the failure mode. Write both down — the desktop toggle and the daemon sign-in — on paper or in a script. A procedure you cannot recall under pressure may as well not exist.
If you do one thing today, run the inventory script on whichever machine you were about to nominate as host. Deciding whether to touch the toggle after seeing the count, rather than before, changes the quality of the decision more than anything else here.
The freedom to drive your own environment from anywhere is most comfortable for people who know what that environment is holding. I am still assembling my own setup, and I will write up whatever combination ends up fitting.
Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.