Files
byre/docs/antigravity-protocol.md
T

5.7 KiB

Antigravity CLI protocol

How internal/antigravity drives the Antigravity CLI, agy (brew install --cask antigravity-cli; agy --version prints the version, agy changelog the release notes, and agy --help the flags). The CLI is signed in once, interactively, by running agy with no arguments as the user byre runs as; agy models lists the models that login offers, and fails with "Please sign in" until then. The CLI updates itself.

Launch

agy --input-format stream-json --output-format stream-json \
  --dangerously-skip-permissions --add-dir <session dir> \
  [--model M] [--conversation ID] --print ""
  • --print "" puts the CLI in print mode; with --input-format stream-json it reads one message per line from stdin and runs a turn for each, in one conversation, until stdin closes. A bare -p takes the next flag as its prompt, so the empty prompt is passed explicitly.
  • --dangerously-skip-permissions sets the permission mode to always-proceed; without it the mode is request-review and tool calls wait for approvals print mode can't give.
  • --conversation ID resumes a conversation with its history; the model comes from --model, not from the conversation.
  • Commands the agent runs (run_command) inherit the CLI's environment.

The session directory

There is no flag for a system prompt or MCP servers, so byre hands them over as a plugin. The plugin directory is passed as --add-dir. The CLI treats it as a second workspace and loads the plugin in its .agents/plugins/byre/:

  • plugin.json: {"name": "byre"}.
  • rules/AGENTS.md: always-on guidance. rules/notes.md uses trigger: always_on frontmatter. The CLI cuts each rule file at 24,000 bytes, so longer guidance or notes must be refused before launch.
  • mcp_config.json: one entry per server, with a Streamable HTTP url or a stdio command and args. The CLI namespaces plugin servers as <plugin>_<server>, so byre's own server is byre_byre.
  • hooks.json: handlers keyed by hook event, each a command that reads the hook's JSON from stdin and prints its reply, with a timeout in seconds (default 30). Flat events (SessionStart, PreInvocation, PostInvocation, Stop) list handlers directly; tool events (PreToolUse, PostToolUse) wrap them in groups, [{"matcher": "*", "hooks": [...]}], where matcher is a regex on the tool name.

The session's listener

Each session serves byre's MCP tools (Streamable HTTP, no SSE stream) and hooks on a 127.0.0.1 port. Every path starts with a random token made per session, written only into the plugin's mcp_config.json and hooks.json, so a page in a browser or another local process that can reach the port can't call byre's tools without reading those files.

The CLI loads MCP tool schemas lazily. The model reads a tool's schema from ~/.gemini/antigravity-cli/mcp/<server>/<tool>.json with view_file, then calls it through the built-in call_mcp_tool tool with ServerName, ToolName and Arguments.

Messages

User turn, written to stdin: {"event":"user","message":{"role":"user","content":[{"type":"text","text":"..."}]}}. content may also be a plain string. Only text blocks are accepted: an image block fails the line with stream input content block type "image" is not supported, so byre saves each image to a file and adds a text block naming its path. A line without event or with event other than user fails or is ignored; control_request is "not supported yet", so there is no control channel.

Stdout, one JSON object per line, each {"event": E, E: {...}}:

  • init arrives at launch, before any input, with conversation_id, model, cwd, the built-in tools and permission_mode.
  • step_update: one step of the conversation, by step_index, with state (ACTIVE while it runs, then DONE) and step_type:
    • user_input: a user message, including one a hook injected.
    • agent_response: model output. text_delta streams its text across ACTIVE updates; the DONE update carries duration_seconds and usage (input_tokens, output_tokens, thinking_tokens, cache_read_tokens, total_tokens). A response that only calls tools has no text. Thinking is counted but never streamed.
    • tool: a tool call, tool_name and tool_info (name, parameters, and on DONE output). A shell command is run_command with CommandLine.
    • system_message: a system note, such as the one after a resume.
  • result ends a turn: status (SUCCESS or ERROR), response (the turn's last text), error, duration_seconds, num_turns and usage totals for the whole conversation, earlier processes included.

Interrupts and model switches

SIGINT ends the running turn: the CLI prints error: interrupted on stderr, sends a result with status ERROR and error interrupted, and exits. A tool step cut off this way gets no DONE update. Resume with --conversation after the old process exits to keep using the conversation.

There is no way to switch the model of a running process. Close the idle process's stdin, wait for it to exit, and restart with the new --model and the same --conversation.

Hooks

Hook input and output are camelCase JSON. byre uses PreInvocation, which runs before every model call with invocationNum (0 for a turn's first call) and initialNumSteps; an output of {"injectSteps":[{"userMessage":"..."}]} adds a user message the model reads on that call. byre also uses PreToolUse (input toolCall with name and args), whose output needs decision: allow, or deny with a reason the agent reads in place of the tool's result. The Stop hook gets no final message, so byre doesn't use it.

Forking

The CLI can't fork a conversation at a given point, so Antigravity sessions can't be forked.