Files
byre/docs/antigravity-protocol.md
T
2026-10-20 23:03:08 -07:00

5.3 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. Only flat events (SessionStart, PreInvocation, PostInvocation, Stop) fit this shape.

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. 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.