diff --git a/md/SUMMARY.md b/md/SUMMARY.md index 0ed860b4..fdefaf79 100644 --- a/md/SUMMARY.md +++ b/md/SUMMARY.md @@ -96,6 +96,8 @@ - [Discovery & sync](./rfds/registry-centric-plugins/discovery-sync/README.md) - [User-managed plugins](./rfds/registry-centric-plugins/user-managed-plugins/README.md) - [Predicate caching](./rfds/predicate-caching/README.md) + - [Replace Gemini CLI with Antigravity CLI](./rfds/antigravity-cli/README.md) + - [Proposed agent details](./rfds/antigravity-cli/proposed-agent-details.md) - [Completed](./rfds/completed.md) - [Configuration parsing and normalization](./rfds/config-normalization/README.md) - [RFD Process](./rfds/rfd-process/README.md) diff --git a/md/rfds/antigravity-cli/README.md b/md/rfds/antigravity-cli/README.md new file mode 100644 index 00000000..aa7629b3 --- /dev/null +++ b/md/rfds/antigravity-cli/README.md @@ -0,0 +1,199 @@ + + +# Replace Gemini CLI with Antigravity CLI + +## TL;DR + +- Retire the `gemini` agent and add `antigravity` (the `agy` CLI) in its place. +- Skills reuse the vendor-neutral `.agents/skills/` path symposium already writes; global skills go to `~/.gemini/config/skills/`. +- MCP servers move to a dedicated `mcp_config.json`, keeping the familiar `mcpServers` shape. +- Hooks are a new wire format on a new file, and follow the configured `hook-scope` like any other agent. +- `SessionStart` is undocumented but real, and fires once per session. `PreInvocation` stands in for `user-prompt-submit`, gated on the first invocation of a turn. +- A `PreToolUse` hook that returns `{}` **denies the tool call**. Symposium must emit an explicit allow. + +## Motivation + +Gemini CLI can no longer sign in. Choosing "Sign in with Google" now fails with a server-side message from Google: + +> Failed to sign in. Message: This client is no longer supported for Gemini Code Assist for individuals. To continue using Gemini, please migrate to the Antigravity suite of products: https://antigravity.google + +The `google-gemini/gemini-cli` repository is not archived and carries no deprecation notice, and the API-key and Vertex AI paths may still authenticate. But the consumer sign-in that symposium's users rely on is closed, and Google's own message names Antigravity as the destination. Supporting an agent most users cannot log into is not worth a second hook wire format. + +Antigravity is also where the extensibility work is going: `agy plugin import gemini` migrates Gemini extensions, skills and settings, and its config lives under the same `~/.gemini/` root. + +Antigravity is not a rename. Every axis symposium depends on changed: hook file, hook shape, event names, payload field names, output contract, and MCP location. Keeping both agents would mean carrying a second hook wire format for a surface we do not expect users to stay on, so this replaces rather than adds. + +## Change in a nutshell + +Skills are the part that already works. Antigravity reads the same layout symposium writes today for Copilot, Codex, OpenCode and Goose: + +``` +.agents/skills//SKILL.md +``` + +Extra files in a skill directory are explicitly supported, so symposium's `.symposium` marker and wildcard `.gitignore` survive, and marker-based stale reaping keeps working unchanged. + +Hooks are the part that is genuinely new. Registrations are keyed by a **hook name** and live in their own file, not in `settings.json`: + +```json +{ + "symposium": { + "PreToolUse": [ + { + "matcher": "*", + "hooks": [{ "type": "command", "command": "cargo-agents hook antigravity pre-tool-use" }] + } + ], + "PreInvocation": [ + { "type": "command", "command": "cargo-agents hook antigravity pre-invocation" } + ] + } +} +``` + +Note the two shapes: `PreToolUse`/`PostToolUse` wrap handlers in a `matcher` group, while `PreInvocation`/`PostInvocation`/`Stop` are flat handler lists. + +## Detailed plans + +### Paths + +| Axis | Project | Global | +|---|---|---| +| Hooks | `.agents/hooks.json` | `~/.gemini/config/hooks.json` | +| Skills | `.agents/skills//SKILL.md` | `~/.gemini/config/skills//` | +| MCP | `.agents/mcp_config.json` | `~/.gemini/config/mcp_config.json` | + +`~/.gemini/config/` is the current global root. `~/.gemini/antigravity-cli/` holds runtime state and `settings.json`; earlier documentation points at it for plugins and skills, but a `.migrated` marker shows that move already happened. + +### Scope behaves normally + +Both scopes work. `agy` discovers `.agents/` by walking up from the working +directory, so project-scoped hooks and skills load without any special launch +flag, and symposium can honor the configured `hook-scope` as it does for every +other agent. + +One caveat is worth recording because it is easy to misdiagnose. In headless +print mode (`agy -p`), `agy` adopts no workspace at all unless it is given +`--add-dir `: it cannot read project files, and it loads no +project `.agents/` configuration. The interactive TUI adopts the working +directory on its own and loads project hooks on a second pass after adoption. +So a hook that appears not to fire under `agy -p` is not necessarily +misregistered — a relative `--add-dir .` does not work either, it must be +absolute. This affects automation and CI, not ordinary interactive use. + +### Events + +| Symposium event | Antigravity event | +|---|---| +| `pre-tool-use` | `PreToolUse` | +| `post-tool-use` | `PostToolUse` | +| `session-start` | `SessionStart` | +| `stop` | `Stop` | +| `user-prompt-submit` | `PreInvocation`, first invocation of a turn | + +Antigravity's documentation lists five events and omits `SessionStart`, but the binary's hook proto carries six, and a `SessionStart` key in `hooks.json` loads, fires **once per session** (verified across a `--continue` turn) and carries a populated `workspacePaths`. So session start maps natively and needs no derivation. + +`user-prompt-submit` is the one approximation. Antigravity has no prompt event, and `PreInvocation` fires before *every* model call — several times in a turn that uses tools — so dispatch runs the prompt event only when `invocationNum == 0`. That is stateless; no session tracking is involved. An unknown event key, incidentally, is accepted silently and never fires, so a typo there fails quietly. + +### The allow contract + +`PreToolUse` output decides the call, and exit codes are ignored entirely. Writing nothing allows. Writing `{}` **denies** — as do `{"decision": ""}` and any object carrying only other fields. Symposium's dispatcher returns `{}` when no plugin contributed, which is the common case, so the Antigravity output conversion must emit `{"decision": "allow"}` unless a plugin actually denied. + +`PreInvocation` carries context back the way `additionalContext` does for Claude: + +```json +{ "injectSteps": [{ "ephemeralMessage": "..." }] } +``` + +This is how the session-start discovery hint, consent hint and update nudge reach the model. + +### Tool names + +Antigravity tool names are the lowercased step type without its `CORTEX_STEP_TYPE_` prefix — `run_command`, `view_file`, `browser_*`. A plugin hook matcher written for Claude (`Bash`, `Edit`) will not match. This affects plugin authors, not symposium's own dispatch, and belongs in the hook reference. + +### Retiring Gemini + +Removal reuses the retired-agent machinery: a retired name in the user config is reported and skipped, a stale `cargo-agents hook gemini` registration exits cleanly, a plugin manifest declaring `format = "gemini"` still loads with that hook skipped, and a one-shot migration clears the leftovers under `~/.gemini/` that nothing would otherwise reap. + +See [proposed agent details](./proposed-agent-details.md) for the reference page this produces. + +## Frequently asked questions + +### Why not support both Gemini and Antigravity? + +Antigravity shares no hook wire format with Gemini — different file, shape, event names, payload fields and output contract. Supporting both means two full hook schemas for two surfaces with one user base. Google ships a migration path from one to the other, so users are not expected to hold both. + +### Why doesn't anything fire under `agy -p`? + +Headless print mode adopts no workspace unless given `--add-dir `. +Without it `agy` cannot read project files at all, so no project `.agents/` +configuration loads either. The interactive TUI adopts the working directory by +itself. Any automation driving `agy -p` against a project must pass the absolute +path; a relative `.` is ignored. + +### Should the headless behaviour be reported upstream? + +Worth reporting, but nothing here depends on it. Headless mode silently having +no workspace — rather than refusing, or defaulting to the working directory — is +surprising enough to be worth a bug, but symposium's registration is correct +either way and no part of this design works around it. + +### Does this affect the agent-plugin work? + +Antigravity's plugin manifest is `plugin.json` at a plugin root — the same filename as the Agent Plugins standard, with a different schema — so the two cannot share one compiled directory. `agy plugin import claude` exists and may cover the case without a dedicated emitter. That is left to the agent-plugin RFD; this one does not add a plugin emitter. + +## Implementation plan and status + +Two deviations from the plan above, recorded as implementation proceeded: + +- **Step 5 changed.** It proposed deriving a session start by tracking seen + `conversationId`s. Testing showed `SessionStart` exists natively and fires once + per session, so the derivation was dropped and the step became the + `user-prompt-submit` gate instead. The events section above reflects the native + event. +- **Order changed.** Antigravity is added first and Gemini retired last, so every + commit leaves users with a working agent. Step 1 therefore lands after steps 2 + to 6. + +One fix fell out of the work and is not listed as a step: `sync` registered hooks +and MCP by passing the workspace root to the *global* functions, which only +produces the right path for agents whose project and global locations share a +shape. Antigravity's do not, so project scope wrote to a file `agy` never reads. +The project-scoped functions existed but had no callers; `sync` now dispatches to +them, which also moves Copilot's project hooks to `.github/hooks/`. + +### Step 1: Retire the `gemini` agent + +Remove the agent, its hook schema and its MCP registration, keeping the retired-name shims and the one-shot cleanup migration. Verify with the existing init/sync suite plus tests that a stale `gemini` config entry is warned about rather than fatal, and that `cargo agents hook gemini ` exits zero. + +- [ ] not started + +### Step 2: Add the `antigravity` agent for skills and MCP + +Agent enum, skill paths, and `mcp_config.json` registration. No hooks yet, so the agent is skills-only at this point. Verify by syncing a workspace and asserting the skill directory and MCP entry land at the paths above; confirm against a real `agy` that the MCP server is listed. + +- [ ] not started + +### Step 3: Hook registration and unregistration + +Write and reap symposium's named entry in `hooks.json` at the configured scope. Unregistration keys on the hook name symposium owns, so unrelated entries in a shared file survive. Verify that registering, re-registering and removing the agent leaves other named hooks intact, and confirm against a real `agy` that the hook loads at both project and global scope. + +- [ ] not started + +### Step 4: Hook wire format + +Input and output conversion for the five events, including the explicit allow on `PreToolUse` and `injectSteps` for context. Verify with round-trip tests per event, a test asserting a no-contribution dispatch emits an allow rather than `{}`, and an end-to-end check that a tool call is not denied. + +- [ ] not started + +### Step 5: Gate `user-prompt-submit` + +`PreInvocation` fires before every model call, so dispatch must run the prompt event only on `invocationNum == 0` or plugin prompt hooks fire several times per turn. Verify that a turn making a tool call (two invocations) dispatches `user-prompt-submit` exactly once, and that `session-start` fires once per session across two turns. + +- [ ] not started + +### Step 6: Documentation + +Agent details page, supported-agents entry, and the `--add-dir` caveat where users will meet it. + +- [ ] not started diff --git a/md/rfds/antigravity-cli/proposed-agent-details.md b/md/rfds/antigravity-cli/proposed-agent-details.md new file mode 100644 index 00000000..0af897e5 --- /dev/null +++ b/md/rfds/antigravity-cli/proposed-agent-details.md @@ -0,0 +1,236 @@ +# Antigravity CLI Hooks Reference + + + +> **Disclaimer:** This document reflects our current understanding of Antigravity CLI's +> hook system. It is a working reference for symposium development, not a substitute for +> the official docs. Details may be outdated or incomplete — always consult the primary +> sources. +> +> **Primary sources:** +> [Hooks](https://antigravity.google/docs/hooks/) +> · [Plugins & Skills](https://antigravity.google/docs/cli/plugins/) +> · [GitHub repo](https://github.com/google-antigravity/antigravity-cli) +> · the bundled `agy-customizations` skill under +> `~/.gemini/antigravity-cli/builtin/skills/`, which is more precise than the website + +Google's Antigravity CLI (`agy`) exposes shell-command hooks through a dedicated +`hooks.json`. Antigravity ships three surfaces — the CLI, the IDE, and the web +app — which share the same configuration roots. + +## Configuration + +| File | Scope | +|---|---| +| `~/.gemini/config/hooks.json` | User-global | +| `/.agents/hooks.json` | Project-scoped | + +Both are additive; all matching hooks run, each with its working directory set to +the directory containing its own `hooks.json`. + +Project scope works in ordinary interactive use: `agy` walks up from the working +directory to find `.agents/`, and loads project hooks on a second pass once it has +adopted the workspace. + +**Headless print mode is the exception.** `agy -p` adopts no workspace unless +given `--add-dir ` — it cannot read project files and loads no +project `.agents/` configuration. A relative `--add-dir .` is ignored. Automation +driving `agy -p` against a project must pass an absolute path. + +### Configuration structure + +Each top-level key is a **hook name** mapping to its events. `PreToolUse` and +`PostToolUse` wrap handlers in a `matcher` group; `PreInvocation`, +`PostInvocation` and `Stop` take flat handler lists. + +```json +{ + "symposium": { + "PreToolUse": [ + { + "matcher": "*", + "hooks": [ + { "type": "command", "command": "cargo-agents hook antigravity pre-tool-use", "timeout": 30 } + ] + } + ], + "PreInvocation": [ + { "type": "command", "command": "cargo-agents hook antigravity pre-invocation" } + ] + } +} +``` + +`enabled: false` on a named hook disables all its handlers. `timeout` is in +**seconds** and defaults to 30. Only `type: "command"` is supported; commands run +via `sh -c` (`cmd /c` on Windows). + +## Events + +| Event | When it fires | Matcher | +|---|---|---| +| `PreToolUse` | before a tool step executes | tool name | +| `PostToolUse` | after a tool step completes | tool name | +| `PreInvocation` | before the model is called | n/a | +| `PostInvocation` | after tool calls finish | n/a | +| `Stop` | when the execution loop terminates | n/a | +| `SessionStart` | once per session | n/a | + +`SessionStart` is **absent from the official documentation** but present in the +binary's hook proto, and works: it loads from `hooks.json`, fires once per +session, and carries a populated `workspacePaths`. `PreInvocation` by contrast +fires before every model call, and its `invocationNum` restarts at 0 each turn, +so it marks the start of a turn rather than of a session. + +Unknown event keys are accepted silently and never fire, so a misspelled event +name fails without any error. + +Matchers are regexes over tool names, which are the lowercased step type without +its `CORTEX_STEP_TYPE_` prefix — `run_command`, `view_file`, `browser_.*`. A +matcher written for another agent's tool names (`Bash`, `Edit`) will not match. + +## Input Schema (stdin) + +All keys are camelCase (protojson). + +### Base fields (all events) + +```json +{ + "conversationId": "5e4f131c-…", + "workspacePaths": ["/path/to/workspace"], + "transcriptPath": "…/transcript_full.jsonl", + "artifactDirectoryPath": "…", + "modelName": "gemini-3.7-flash-high" +} +``` + +`workspacePaths` is populated once the workspace is adopted, and **empty** under +`agy -p` without `--add-dir`. A hook's working directory is always the directory +holding its own `hooks.json`, never the user's project. + +### PreToolUse / PostToolUse additions + +```json +{ + "toolCall": { "name": "run_command", "args": { "CommandLine": "npm test" } }, + "stepIdx": 2, + "error": "" +} +``` + +`error` is present on `PostToolUse` only. Both events carry `toolCall`. + +### PreInvocation / PostInvocation additions + +```json +{ "invocationNum": 0, "initialNumSteps": 1 } +``` + +### Stop additions + +```json +{ "executionNum": 0, "terminationReason": "NO_TOOL_CALL", "error": "", "fullyIdle": true } +``` + +## Output Schema (stdout) + +### PreToolUse + +```json +{ "decision": "allow", "reason": "optional", "permissionOverrides": [] } +``` + +`decision` is required: `allow`, `deny`, `ask`, `force_ask`, or +`deny_unless_prior_grant`. An `overwrite` object shallow-merges into the tool +call's arguments before it runs. + +**Writing `{}` denies the call.** So do `{"decision": ""}` and objects carrying +only other fields. Writing nothing at all allows. Any hook that does not intend to +block must emit an explicit `{"decision": "allow"}`. + +### PostToolUse + +Expects `{}`. + +### PreInvocation — inject context + +```json +{ "injectSteps": [{ "ephemeralMessage": "..." }] } +``` + +Each step accepts one of `toolCall`, `userMessage`, or `ephemeralMessage`. This is +the equivalent of Claude Code's `additionalContext`. + +### PostInvocation + +`injectSteps` as above, plus `terminationBehavior`: `force_continue`, `terminate`, +or omitted. + +### Stop + +```json +{ "decision": "continue", "reason": "required when continuing" } +``` + +Any value other than `continue` lets the agent stop. + +## Exit Codes + +**Ignored.** Exit 1 and exit 2 behave exactly as exit 0; only stdout decides the +outcome. This differs from every other agent symposium supports, where exit 2 +blocks. + +## Skills + +| Scope | Path | +|---|---| +| Project | `/.agents/skills//SKILL.md` | +| Global | `~/.gemini/config/skills//SKILL.md` | + +A skill is a directory containing `SKILL.md` with `name` and `description` +frontmatter. Additional files and subdirectories (`scripts/`, `examples/`, +`resources/`, `references/`) are supported, so symposium's `.symposium` marker and +`.gitignore` are preserved. The CLI also reads +`~/.gemini/antigravity-cli/skills/` and `~/.gemini/skills/`, but +`~/.gemini/config/skills/` is the location all three surfaces recognize. + +### Registering skills from another location + +`~/.gemini/config/skills.json` registers skill directories stored outside the +default locations, and is read on every run: + +```json +{ "entries": [{ "path": "/abs/path/to/repo/.agents/skills", "exclude": ["experimental-.*"] }] } +``` + +Paths must be **absolute**. The schema also documents workspace-relative paths, +but an entry of `.agents/skills` resolves to nothing in practice. + +The list is global with no notion of the active repository, so every indexed +directory loads in every session; `include_only` and `exclude` filter by skill +directory name, not by workspace. Symposium does not use this file — project +skills are found by ordinary discovery — but it is the mechanism for skills kept +outside the standard locations. + +Symlinks are followed, both a symlinked skill directory inside a skills folder +and a symlink of the folder itself. A separate report of symlinked skills being +ignored concerns the IDE and `~/.gemini/antigravity/skills/`. + +`plugins.json` follows the same schema for plugin directories. + +## MCP server configuration + +| Scope | Path | +|---|---| +| Project | `/.agents/mcp_config.json` | +| Global | `~/.gemini/config/mcp_config.json` | + +Standard `mcpServers` object; stdio entries use `command`/`args`/`env`, remote +entries use `serverUrl`. Unlike Gemini CLI, MCP configuration does **not** share a +file with hooks. `agy mcp add` has no scope flag and writes the global file. + +## Custom instructions + +`GEMINI.md` and `AGENTS.md` at the workspace root, plus `.agents/rules/*.md`, +loaded by walking up to the repository root.