I had two tracks in flight the week I learned that Conductor had stopped being a Gemini CLI extension and become a plugin. One line, agy plugins install, apparently does it. My hands still hesitated. If a "work in progress" marker in plan.md shifted, or a spec.md quietly changed, I would lose the history of every decision I had made so far.
Here is the short version: there is almost nothing to migrate. Conductor's output lives in your repository, in the conductor/ directory, as plain Markdown. It does not live inside the extension. Google's developer blog post (July 16, 2026) says as much: your spec.md and plan.md "aren't going anywhere." What changes is the thing that reads and writes them.
I still wanted to compare the state before and after, once. The point was less the migration itself than being able to say "nothing broke" and mean it.
What changes and what doesn't
This is limited to what I could confirm from the official blog post and the Conductor repository README.
| Item | Stays the same | Changes |
|---|---|---|
| Where artifacts live | product.md, tech-stack.md, workflow.md, tracks.md under conductor/ | Nothing |
| Track layout | spec.md, plan.md, metadata.json under conductor/tracks/<id>/ | Nothing |
| How you start it | The old slash commands still work, according to the announcement | Natural conversation now works too |
| How you install it | — | agy plugins install for Antigravity; a marketplace registration for Claude Code |
| Where the plugin itself lives | — | ~/.gemini/config/plugins/ for a global install, .agents/plugins/ for one project |
The plugin and its artifacts live in different places, and that matters. Uninstalling the plugin leaves conductor/ in place, and installing it has no reason to rewrite conductor/. But "has no reason to" is a statement about design, not something I had seen on my own machine. So I built the check first.
A script that compares before and after
It does three things. It records which core files exist, a short hash of each track's three files, and a count of the checkboxes in plan.md ([ ] pending, [~] in progress, [x] done) into a JSON file. Later it takes the same measurement again and compares.
#!/usr/bin/env python3
"""conductor/ の状態を JSON に残し、プラグイン導入の前後で差分を取る。"""
import hashlib
import json
import re
import sys
from pathlib import Path
CORE = ["product.md", "product-guidelines.md", "tech-stack.md", "workflow.md", "tracks.md"]
BOX = re.compile(r"^\s*[-*]\s*\[( |x|~)\]", re.M) # [ ] 未着手 / [~] 作業中 / [x] 完了
def digest(p: Path) -> str:
return hashlib.sha256(p.read_bytes()).hexdigest()[:12]
def snapshot(root: Path) -> dict:
base = root / "conductor"
out = {"missing_core": [c for c in CORE if not (base / c).exists()], "tracks": {}}
for tdir in sorted((base / "tracks").glob("*/")) if (base / "tracks").exists() else []:
entry = {"files": {}, "boxes": {" ": 0, "~": 0, "x": 0}}
for name in ("spec.md", "plan.md", "metadata.json"):
f = tdir / name
entry["files"][name] = digest(f) if f.exists() else None
plan = tdir / "plan.md"
if plan.exists():
for m in BOX.findall(plan.read_text(encoding="utf-8")):
entry["boxes"][m] += 1
out["tracks"][tdir.name] = entry
listed = (base / "tracks.md").read_text(encoding="utf-8") if (base / "tracks.md").exists() else ""
out["unlisted_tracks"] = [t for t in out["tracks"] if t not in listed]
return out
def diff(a: dict, b: dict) -> list:
msgs = []
for t in sorted(set(a["tracks"]) | set(b["tracks"])):
x, y = a["tracks"].get(t), b["tracks"].get(t)
if x is None or y is None:
msgs.append(f"{t}: {'追加' if x is None else '消失'}")
elif x != y:
changed = [n for n in x["files"] if x["files"][n] != y["files"][n]]
msgs.append(f"{t}: 変更 {changed}")
return msgs
if __name__ == "__main__":
mode, root = sys.argv[1], Path(sys.argv[2])
if mode == "save":
snap = snapshot(root)
Path(sys.argv[3]).write_text(json.dumps(snap, ensure_ascii=False, indent=1))
print(f"tracks={len(snap['tracks'])} missing_core={snap['missing_core']} unlisted={snap['unlisted_tracks']}")
elif mode == "compare":
before = json.loads(Path(sys.argv[3]).read_text())
msgs = diff(before, snapshot(root))
print("\n".join(msgs) if msgs else "差分なし")
sys.exit(1 if msgs else 0)I cut the hash to 12 characters so I can compare it by eye. It isn't doing security work, so that length is enough.
To test it, I built a small dummy conductor/ with two tracks (dark-mode and login-fix) and ran save and compare:
python3 conductor_snapshot.py save . before.json
# tracks=2 missing_core=[] unlisted=[]
python3 conductor_snapshot.py compare . before.json
# no differences (prints 差分なし)
# after hand-editing an in-progress item in plan.md to done
python3 conductor_snapshot.py compare . before.json
# dark-mode_20261020: 変更 ['plan.md'] (exit code 1)The non-zero exit code means you can drop it into a runbook or a shell script. It also means you will see differences whenever you are normally working through a plan, so compare only two points: right before the install and right after it. The output strings are in Japanese; change them if you like, since only the exit code matters to a script.
The routine I follow
- Finish the current track to a natural stopping point and get
git statusclean. If an edited plan.md is still uncommitted, you won't be able to tell afterward whether you or the plugin caused a difference. - Run
python3 conductor_snapshot.py save . before.json, and keep before.json outside the repository (your home directory works). Inside the repo, it becomes a difference itself. - Run
agy plugins install https://github.com/gemini-cli-extensions/conductor. - Run
compareagain. You want "no differences" and a cleangit status, both. - If something differs, read
git diff conductor/. If it's an unintended rewrite, stop there and restore withgit checkout -- conductor/.
If step 4 is clean, the main work of the migration is over. What remains is on the usage side: whether you keep using the old commands or switch to talking to the agent.
Pitfalls and a judgment call
Some tracks may be missing from the index. unlisted_tracks catches directories under conductor/tracks/ whose names never appear in tracks.md. Hand-made or copied tracks show up there. It has nothing to do with installing the plugin, so I look at it once while saving, separately from the before-and-after comparison.
Decide whether to keep the Gemini CLI extension. If both routes write to the same conductor/, both can update the checkboxes in plan.md. I let the two coexist for a while, ran one track to completion on the new side, and only then removed the old one. I can't claim that is the right order. The one line I hold firmly is this: never advance the same track from two tools at once.
Token use goes up, and the official README says so. It notes that the spec-driven approach reads a lot of project context and can raise consumption on larger projects. Now that you can drive it by conversation, it is easy to call it casually and let usage creep up.
What to do next
Moving to the plugin turned out not to be a job of moving files. It was a job of confirming the files hadn't moved. Save right before, compare right after. The two runs together take under a minute.
I'd start by picking one in-progress track and running save on it. For related setup work, I wrote up how to carry MCP server settings around in Adding MCP Servers Without Opening the Config File: The agy mcp Subcommand, and how to check that the CLI actually answers before an unattended run in Ask Antigravity CLI Once Whether It Actually Answers, Right Before a Scheduled Run. As an indie developer working on my own apps, I keep this kind of before-and-after check close to hand.