Repository navigation
Offer Google Antigravity as a Bot, and let a harness need one vendor's key - #808
Merged
Merged
Conversation
Every harness so far either takes whatever provider the deployment chose or brings its own address, so `credential` had three values and `any-provider` was the default answer. Antigravity is the first that is neither: its bundled harness requires a Gemini key wherever its base url points, so offering it as "any provider you already have" would install it against an OpenAI-only deployment and produce a Bot that starts, accepts work, and fails every call. That is the failure `webCreate` already exists to prevent on the web side, so this is the same decision made one level down: `google` is its own credential, the desktop picker says "Needs a Gemini key" on the row, and the test that every row's credential is one the type names keeps the two lists from drifting. Nothing changes for the rows that were already there.
AG-UI publishes an Antigravity integration, so this is the thirteenth harness and mostly follows the twelve before it: a small FastAPI service, the shared token gate, an open /health for Compose to poll, a row in the catalogue, and the vendored mark from the same MIT set as the rest. Two things are not like the others. The provider is PINNED. Every other Bot reads `BOT_PROVIDER`, because they all reach their model through LiteLLM or an equivalent and moving them is a setting. Antigravity's SDK drives a bundled Go harness that wants a Gemini key wherever its base url points, so a deployment that moved its other Bots to OpenAI must not drag this one along — `bot_settings(..., pinned_provider="google")` is what stops it. The model is still the deployment's to choose: `BOT_MODEL` names which Gemini, and the pin only holds the provider. The image is glibc, not musl, and the Dockerfile says why: the SDK ships a platform wheel bundling a Go `localharness` built against manylinux_2_17, and alpine cannot load it. Proved rather than assumed — the linux/amd64 image was built and run, /health answered, a POST without the server token was refused 401, and the same POST with it reached the AG-UI endpoint. `.github/published-images.json` gains the image, which is the file CI checks against the Dockerfiles in the tree and the release derives its build matrix from, so one entry wires both. Worth knowing for whoever reviews the security of this: that Go harness does real file and shell work. In this image it has no volumes and no host mount, so its reach is the container — but it sits outside agent-computer's barriers, authorisation and handoff entirely, which is where every other Bot's computer work goes.
CI gives each Python Bot an environment named after it — `.venv-adk`, `.venv-antigravity` — and `.gitignore` only knew the bare `.venv` and `venv` spellings. Running those same commands locally, which is the obvious way to check a Python Bot before pushing, left hundreds of megabytes of untracked platform binaries in the tree. Biome does not read `.gitignore`, so it needs telling separately: linting a freshly made environment reported fifteen errors inside urllib3 and failed `bun run lint` for reasons that had nothing to do with the change in hand.
The `google` row in `shared/model-providers.json` declares a `base_url_variable`, and this Bot ignored it: it read `GOOGLE_API_KEY` by hand and had no way to reach a Gemini-compatible endpoint that was not Google's. Both variable names now come from the row, so renaming either is one edit to the file every language reads. A base url is spelled as `endpoint=GeminiAPIEndpoint(...)`. `AntigravityAgent(base_url=...)` is a different and mutually exclusive path, meant for unauthenticated local servers, and it drops the key — so the test asserts the key came along rather than only grepping the source. That url is the API ROOT, with no version path. The harness appends `/v1beta/models/<model>:streamGenerateContent` itself, and a url carrying the version gets it twice and 404s, which the harness reports as the unplaceable `agent executor error: Error 0, Message: Not found`. Measured against a logging proxy, because that message names neither the url nor the status.
A Composio tool call read "Gmail fetch emails" with a muted "composio-gmail" beside it. The broker is how the credential is held, which is an administrator's fact on the Bot's access page, not something the person who asked about their email needs on screen — the same argument this file already makes for relabelling research calls. Two things kept it there. The prefix was never stripped, and the rule that drops a server the label already names skips the label's first word, so a verb cannot be mistaken for the server. Composio publishes `<APP>_<VERB>_<NOUN>`, so the app always leads and was always the word being skipped. The app is still shown when the action does not name it — `FETCH_EMAILS` keeps `gmail` as its detail. Only the broker is dropped, never what was acted upon.
guidovizoso
requested review from
MikeRyanDev,
davidmckayv,
mxmzb and
tylerslaton
as code owners
October 19, 2026 13:47
`gemini-2.5-flash` is retired: Google answers a request for it with 404 "no longer available to new users", so this Bot as merged would have failed every run on a fresh deployment. Measured against a real key, not read off a changelog — `gemini-3.6-flash` answers through the harness. The provider row's own `default_model` is stale in the same way, but it is not this Bot's: the pin means `bot_settings` reads the `bots` entry, never the provider fallback. Left alone here rather than widened into a change to every Bot a deployment might move to Google. The `bots` block is back to one object per line. Adding an entry had reformatted all thirteen, which made a one-line change read as fifty-six; biome accepts either, so the churn bought nothing. The override test now names a model the file does not, because an override asserting the row's own value passes whether or not the environment is read.
The managed Bot and any harness picked during setup refuse every call that does not carry the deployment's token, and nobody types that token anywhere: the run path attaches it by recognising the address. The connection test did not, so testing the Bot in the box answered "The agent refused this request. If it needs a key, add it as a header." — a refusal reported for a Bot that works, and advice that would have broken it. That is the worst case for a wrong verdict. The address was filled in by this deployment rather than typed by the person reading the result, so they cannot tell this from a real refusal. The comparison moves to `managed-endpoint.ts` and both paths call it. They had already drifted once in the other direction — matching a single endpoint left the picked harness registered, routed to, and answering 401 to everything — so the rule is written down once rather than twice. An address this deployment does not run is still knocked on bare. The token's whole value is that it is ours, and the tests pin that alongside the trailing-slash case and a deployment with no managed Bot at all.
With the token attached the test reached the agent, got its 200, and still failed: "The agent started answering and the connection broke." Nothing broke. The opening read stopped at an eight kilobyte cap or the end of the stream, and an agentic harness supplies neither — Antigravity opens with `RUN_STARTED`, ninety bytes, then goes away to call a model and run tools while holding the stream open. The read waited out the whole run and was cut off by the request timeout. It now stops at the first recognised AG-UI event, which is the entire question the test asks. Events rather than bytes, deliberately: a stream of SSE comments or heartbeats would satisfy a "something arrived" test while being no evidence of an agent at all. There is a case for that.
The tile reads `credential === "google"` to say "Needs a Gemini key", but the type it reads from still listed three variants, so the comparison could never be true and `tsc` refused it. The desktop app is outside the workspace and reads the catalogue over the Tauri boundary as JSON, so it cannot import the union from shared/harnesses.ts — it keeps its own copy, and adding a variant means adding it in both places. Said so above the type, since nothing else will. `cargo fmt` on the catalogue parse, which rustfmt wanted joined onto one line. Both were missed locally for the same reason: `bun run typecheck` covers the workspace, and desktop is not in it.
Every CrewAI run started failing with `APIConnectionError: cannot import name 'CacheableResult' from 'mcp.types'` — a model call reporting a missing import, which reads as a network fault and is not one. Nothing in this repository changed: `agent-crewai/requirements.txt` pinned nothing, and litellm 1.106.0 began importing a symbol that exists only in mcp 2.x while ag-ui-crewai 1.0.0 requires mcp<2. The two cannot both be satisfied. Held litellm rather than raising mcp, because mcp>=2 is reachable only by letting the resolver walk ag-ui-crewai back to 0.3.1 and crewai to 1.6.1 — a silent downgrade of the harness under test. 1.105.1 does not reference the symbol anywhere and leaves every other version where CI already had it: ag-ui-crewai 1.0.0, crewai 1.15.28, mcp 1.28.1. Resolved and imported locally to check. Only this harness is affected. The other five that name litellm resolve without mcp at all, so the import is never reached; ADK was checked directly because it runs next in the same job. Unrelated to the rest of this branch, and in it only because the job stops at the first harness that fails — which left the Antigravity step skipped rather than run.
The CrewAI pin fixed CrewAI and the job walked on to LlamaIndex, which failed the same way with a different face: `APIConnectionError: OpenAIException - No module named 'mcp'`. litellm 1.106.0 reaches for mcp during a model call and reports whatever it finds as an OpenAIException, so the error arrives as a connection fault rather than the missing import it is. Both ends of that break: an older mcp pinned by another dependency gives `cannot import name 'CacheableResult' from 'mcp.types'`, and no mcp at all gives `No module named 'mcp'`. I had checked ADK, seen mcp absent, and concluded it was safe — absent is the second failure, not the absence of one, so the remaining five are pinned together rather than one per CI round trip. Verified by resolving and running the suite that actually failed: agent-llamaindex on litellm 1.105.1, 10 passed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds Google Antigravity to the harness catalogue and ships it as a Bot, which turned out to need one
change to how a harness states what it wants and one to how a Bot reads the provider spec.
A harness can need one vendor's key
Every other row in the catalogue takes
any-provider: the Bot reaches its model through LiteLLM oran equivalent, so moving a deployment to Anthropic is a setting rather than a break. Antigravity is
not like that. Its SDK drives a bundled Go
localharnessthat wants a Gemini key wherever its baseurl points, so
credentialgained agooglevariant and the picker asks for the right key insteadof offering the row to a deployment holding only an OpenAI one.
shared/model-providers.jsongained the matchinggoogleprovider row, andpinned_providerinshared/model_providers.pyis what makesBOT_PROVIDER=openaiunable to drag this one Bot along —it would otherwise start, accept work, and fail every call.
The Bot
agent-antigravity/is a small AG-UI server like its ten Python siblings: the same token gate onevery path but
/health, the same venv-per-Bot step in CI, the same row in.github/published-images.json.Two things are not like the siblings, both measured rather than assumed:
python:3.12-slim, notalpine. The SDK wheel bundles a Go binary built againstmanylinux_2_17, and musl cannot load it.
/v1beta/models/<model>:streamGenerateContentitself. A url carrying the version gets it twice and404s, which the harness reports as the unplaceable
agent executor error: Error 0, Message: Not found— naming neither the url nor the status. Found with a logging proxy.That second one came out of a real gap: the Bot ignored the
base_url_variableits own provider rowdeclares, and read
GOOGLE_API_KEYby hand. Both names now come from the row, so a deployment canpoint this Bot at a gateway, a proxy holding the real key, or a mock.
Also here
app/src/lib/plugins/tool-name.ts— a Composio tool call read "Gmail fetch emails" with a mutedcomposio-gmailbeside it. The broker is how the credential is held, which belongs on the Bot'saccess page rather than in the transcript; this is the argument the file already makes for
relabelling research calls. Independent of the rest of this branch and reviewable on its own.
Checks
bun test7054 pass / 0 fail.agent-antigravitypytest 14 pass. Format, lint, typecheck and buildclean;
cargo test harness43 pass; the Dockerfile builds;published-images.jsonmatches the*/Dockerfileset exactly.actionlintis not installed on the machine this ran on, so the new CIstep was reviewed by hand against its ten siblings rather than linted.
Known gaps, not addressed here
agentInputFromsends name, title, role, visibility,endpoint and auth; there is no
harnessin the agents route, the profile store or the schema. ABot created by picking Google Antigravity is indistinguishable from one created by picking
LangGraph. Flagged on Name the frameworks in the New Bot wizard, ask visibility last, and share one harness catalogue #799 and still open; closing it needs a migration and deserves its own PR.
is not in the catalogue.