A model-string change can be easy. A tool-contract change is a production migration. Here is how to move safely when an agent client reads, edits, or dispatches work locally.
The dangerous kind of AI-agent upgrade is the one that looks boring in a release note.
A team sees a new preview identifier, changes one configuration value, and deploys. The agent still returns a polished final message. Everyone relaxes. Then the first local run either ignores a file edit, sends malformed arguments to an internal executor, or records a successful job with no usable artifact.
That is not a model-quality problem. It is a contract problem.
Google’s September Antigravity Agent preview update is a useful reminder. The current Gemini API notes describe a new agent identifier and changes to built-in local-tool shapes: file operations move toward explicit, PascalCase parameters and line-range replacement rather than a broad full-file write. The managed agent itself can reason, run code, manage files, browse, and keep context in a hosted sandbox. But any application that inspects tool steps, proxies them to a local environment, or has its own audit layer must treat the update as an interface migration, not a prompt tweak.
This guide is for the developer who owns that client. It shows how to find the risky boundary, build a compatibility adapter, write contract tests, and roll the change out without trusting a friendly completion message.
Traditional API migrations usually fail loudly. A missing field produces a 400 response. A renamed method will not compile. Agent runtimes add a deceptive third option: the agent may complete its reasoning loop while your integration mishandles the action it asked to take.
Suppose an old runtime emits a conceptual request like this:
{ "type": "write_file", "args": { "path": "src/auth.ts", "content": "...entire file..." }}
A newer runtime can instead express an edit as a line-scoped replacement with a richer argument set. If your dispatcher only accepts the old tool name or expects path and content, it has choices—and none should be silent:
The agent may believe it edited the file. Your observability dashboard may call the interaction complete. The repository will tell a different story.
“Completed” is an agent status. “The intended change exists, tests pass, and the audit record is complete” is a delivery status. Keep those states separate.
Do not start by rewriting code. Start by classifying your integration. The official Antigravity Agent guide shows both a hosted remote environment and customization points such as tools, local environments, and function handling. Those choices determine the blast radius.
If you submit work to a Google-hosted environment and consume only a final result such as output_text, your change may be modest: update the agent identifier, run representative tasks, and check the response and artifacts. You still need acceptance tests, but you are not responsible for translating built-in filesystem actions.
If you read function_call steps, replay built-in actions locally, enforce a custom allowlist, or store tool arguments for compliance, assume a breaking contract. Your code must recognize new operation names and argument casing. More importantly, it must understand the different safety meaning of line-range replacement.
Custom functions create a second boundary. Your own tool names may remain stable, while the envelope, ordering, or lifecycle around them changes. Test your custom-tool dispatcher independently from the built-in filesystem adapter. A green test for one does not prove the other.
Practical rule: inventory every place a tool step crosses a trust boundary: logging, policy checks, queues, local executors, audit storage, and UI rendering. A contract is more than the one switch statement that invokes a command.
Version checks scattered across a codebase age badly. They force product code to know the exact vocabulary of each provider preview. A narrow adapter gives you one place to map provider events into the stable actions your application understands.
Define an internal action vocabulary around intent, not provider spelling:
type FileAction = | { kind: "read"; path: string; startLine?: number; endLine?: number } | { kind: "replace"; path: string; startLine: number; endLine: number; expected: string; replacement: string } | { kind: "list"; path: string } | { kind: "search"; path: string; query: string; isRegex: boolean };
type NormalizedStep = | { kind: "file"; action: FileAction; raw: unknown } | { kind: "command"; command: string; raw: unknown } | { kind: "web"; queries: string[]; raw: unknown };
The adapter should perform four jobs:
That last point matters. Do not transform a line-range edit into a whole-file write just because it is convenient. Fetch the target, verify the expected text where available, apply the smallest replacement, then record the resulting file digest.
function normalizeToolCall(call: { name: string; args: unknown }): NormalizedStep { const a = call.args as Record<string, unknown>;
if (call.name === "replace_file_content") { requireString(a, "TargetFile"); requirePositiveInt(a, "StartLine"); requirePositiveInt(a, "EndLine"); requireString(a, "TargetContent"); requireString(a, "ReplacementContent");
return { kind: "file", raw: call, action: { kind: "replace", path: a.TargetFile as string, startLine: a.StartLine as number, endLine: a.EndLine as number, expected: a.TargetContent as string, replacement: a.ReplacementContent as string } }; }
throw new Error(`Unsupported agent tool: ${call.name}`);}
This code is deliberately strict. It does not pretend that a new shape is interchangeable with an old one. Keep legacy normalization in a separate branch or adapter module, then delete it after a measured retirement window.
Before touching production, create a small inventory of all behavior you depend on. The Gemini API release notes are the source of truth for preview changes, but they are not your test plan.
For each operation your client receives, write down:
For filesystem work, include common edge cases: missing files, mixed line endings, a stale line number, a target string that appears twice, Unicode text, generated files, and a change that crosses your allowed project root. For commands, include an empty command, a shell metacharacter, a working-directory escape, an unavailable binary, and a timed-out test.
This may sound painstaking. It is faster than debugging an agent that appears healthy while it repeatedly does no useful work.
End-to-end prompts are valuable, but they are too broad to locate a schema regression. Contract tests feed recorded tool events into the adapter and assert exactly what internal action results.
Keep sanitized fixtures from the old and new runtimes. Each fixture should be a real payload shape with secrets, customer text, and repository names removed. Test both success and rejection.
it("maps a line-scoped replacement without widening the write", () => { const step = normalizeToolCall({ name: "replace_file_content", args: { TargetFile: "src/auth.ts", StartLine: 12, EndLine: 15, TargetContent: "return legacyToken(user);", ReplacementContent: "return issueSession(user);" } });
expect(step).toMatchObject({ kind: "file", action: { kind: "replace", startLine: 12, endLine: 15 } });});
js
it("rejects an edit without expected text", () => { expect(() => normalizeToolCall({ name: "replace_file_content", args: { TargetFile: "src/auth.ts", StartLine: 12, EndLine: 15 } })).toThrow("ReplacementContent");});
Then test the executor. Given a normalized replacement, assert that it refuses to operate outside the workspace, confirms the expected target content, creates a diff, and returns a hash of the final file. The model does not need access to your test fixtures; the adapter does.
A preview migration should not begin with a full traffic switch. Run the new path beside the old one when possible.
For selected low-risk interactions, let the new adapter parse the returned steps without executing them. Compare its normalized actions against the old adapter or a human-reviewed expected result. Log unknown names, absent fields, range mismatches, and policy decisions. This tells you whether you understand the contract before the contract changes files.
Next, execute a limited task set in disposable repositories or sandbox workspaces. Use small tasks: update a fixture, add a unit test, rename one symbol, and run a known command. Require an artifact bundle containing the raw event, normalized action, file diff, command output, test result, and final status.
Only then put the new agent behind a small production cohort. Keep a runtime-level switch so a bad payload can route back to the previous version or a review queue without a redeploy.
A useful migration dashboard has boring metrics. That is a compliment.
Do not use only completion rate. An agent can complete a task after producing an invalid call, and a cautious integration can correctly fail a task to protect the system. The second behavior may look worse in a vanity chart and far better in a postmortem.
A provider event says what an agent requested. It does not prove that the request belongs in your workspace, that the caller is allowed to make it, or that the destination has not changed since the agent inspected it. Run policy after normalization, not only before the agent starts. For a file action, that means checking the canonical path, the repository boundary, the change size, the target content, and any required reviewer rule.
Line numbers are useful, but they are not an identity. An agent can read a file, another process can change it, and the agent can later request a replacement at the now-stale range. Treat the old text as a precondition. If the expected content is absent or appears in an ambiguous place, stop the action and ask the agent to re-read the file. A rejected stale edit is a successful safety control.
The final response tells a human what the agent thinks occurred. It does not explain why an executor allowed a write or why a test passed. Store a compact event ledger instead: interaction ID, raw tool event digest, normalized-action digest, policy decision, executor result, artifact location, test command, and final verification status. Keep sensitive raw content behind an appropriate retention and access policy. The point is not to preserve every token forever; it is to make a surprising outcome reconstructable.
These controls also improve ordinary engineering. Once the adapter produces a stable action model, a reviewer can see a meaningful diff, a security tool can examine a normalized command, and a future runtime can be added without teaching every downstream service a new dialect.
Preview runtimes are valuable precisely because they move. Your application does not need to move with every spelling change if provider-specific details stop at the boundary.
Make the provider adapter responsible for:
Make the product core responsible for:
This separation also makes multi-provider work easier. The same internal replace action can represent a tool call from Antigravity, a local coding assistant, or a custom workflow—while each adapter keeps its own quirks contained.
Antigravity Agent gives developers a powerful managed loop. The official guide makes the platform’s capabilities clear: code execution, files, web access, long-running context, and customization. Those capabilities are useful only if the boundary around them is understood.
So treat every agent runtime update as a delivery change. Name the inputs, validate the actions, keep raw evidence, prove the result, and roll it out in a small enough slice that you can learn safely. When the next preview arrives, the work should be an adapter update and a test run — not a hopeful configuration edit.
It is Google’s managed general-purpose agent on the Gemini API. It can reason, use tools, execute code, work with files, and browse within an environment you configure. Check the current official guide for supported capabilities and preview constraints.
It can be enough for a remote integration that only consumes a final result. It is not enough when your application parses tool calls, executes agent actions locally, or applies its own policy to built-in steps.
They can narrow the intended change and make review easier. They are not automatically safe: your executor should still check the path, confirm the expected source content, record the diff, and run the required verification.
No. Treat an unknown tool or argument shape as a compatibility failure. Preserve the raw event, report a clear error, and route the task to review or a supported runtime rather than guessing what the action means.
It is a focused test that passes a captured tool payload to your adapter and checks the normalized action or expected rejection. It catches field-name, type, and operation changes without depending on an unpredictable end-to-end prompt.
Monitor unknown tools, rejected calls, successful evidence bundles, verification outcomes, human corrections, and rollbacks. Completion rate alone cannot tell you whether the agent’s work was safe or useful.
Gemini Antigravity Agent Migration: Update Tool Calls Without Silent Breakage was originally published in Towards AI on Medium, where people are continuing the conversation by highlighting and responding to this story.