The lockfile chose the model
Google's Agent Development Kit gives any agent that names no model a default, and the default lives in the library. On its maintained 1.x line that default is still gemini-2.5-flash, the model the project's own commit history dates for earliest shutdown on 16 October. Pinning is what keeps you there.
The argumentWhen a framework supplies an agent's default model, the lockfile becomes the model selection, and semantic versioning has no correct number for retiring it, because changing the default breaks behaviour and keeping it breaks on a date.
Here is a small Python object that does real work in production somewhere: an LlmAgent with a name, an instruction and a set of tools, and no model argument. It runs. Since January, Google's Agent Development Kit has allowed exactly this. An agent with no model inherits one from its parent agent, and if nothing up the tree names one, it falls back to a class attribute called DEFAULT_MODEL. The commit that introduced the behaviour set that attribute to gemini-2.5-flash and added set_default_model() so the fallback could be overridden globally.
On 18 May the project changed the attribute to gemini-3-flash-preview. The commit message gives the reason plainly: the bump was made "ahead of the 2026-10-16 earliest-shutdown date for gemini-2.5-flash," pointing at Google's model deprecations page. That date is tomorrow. On 11 June the default moved again, to gemini-3.5-flash, because, as that commit puts it, "gemini-3-flash-preview never reached GA."
Now look at the other branch. ADK still ships a 1.x line. Its most recent release, 1.39.1, went out on 27 August carrying a handful of fixes explicitly ported "to v1": OAuth nonce forwarding, URL validation, container isolation defaults. In that release, DEFAULT_MODEL is gemini-2.5-flash. The docstring says so. The adk create scaffolding in the same release offers gemini-2.5-flash as option one. The maintainers back-ported security fixes to the line in late August and did not back-port the model.
I do not think that was an oversight, and that is the interesting part. I think there was no correct release to put it in.
A version number for a moving target
The thesis is narrow. When a framework supplies the model an agent uses by default, the model choice moves out of application code and into the dependency manifest, and semantic versioning, the discipline that manifest runs on, cannot describe the change that a model retirement forces. Changing the default alters what every unconfigured agent says, which is a behaviour change in anyone's reading of semver. Keeping the default means the same code, at the same pinned version, stops working on a date set by a different organisation. Both are breaking. The version number has a slot for one kind of break, the kind the maintainer causes. It has none for the kind the world causes to a constant.
Look at how the record files the two moves. The first change, to the preview model, appears in the 2.2.0 changelog under "Code Refactoring": default model to gemini-3-flash-preview. A refactoring, by definition, is a change that preserves behaviour. This one changed which model answers. The second change, to gemini-3.5-flash, was committed as a chore and does not appear in the changelog at all; the only mention of gemini-3.5-flash in the 2.3.0 notes is a sample agent being updated. Neither move went into 2.0.0, the major release cut on 19 May, the day after the first commit. Both landed in minor versions. I am not accusing the maintainers of carelessness. The tooling that writes their changelog sorts commits by type, and the types on offer are feature, fix, refactor, chore, docs. "The model your users run" is not a type.
The discipline that holds you in place
Every mature engineering organisation pins dependencies. The lockfile exists so that the build you shipped in August is the build you run in October, and so that changes arrive through a reviewed pull request rather than through a pip install on a fresh container. It is one of the few practices nobody argues about.
ADK's default inverts what pinning is for. A team that pinned google-adk==1.39.1 has frozen the name gemini-2.5-flash into its agent's behaviour. What it has not frozen is the thing behind the name, which belongs to a provider with its own lifecycle and its own calendar. The pin was meant to hold behaviour still. Here it holds a pointer still while the provider moves what it points at. The lockfile that was supposed to protect the agent is the reason the agent is still aimed at a model past its earliest shutdown date. The team that did nothing careful at all, that floated on the latest 2.x, has been on a GA model since June.
Then follow the remedy. The fix for the stranded team is to upgrade, which in practice means a dependency bump: a bot opens a pull request changing one line in a requirements file, the tests pass, and someone approves it. That approval is now a model migration. The reviewer of a version bump is deciding which model handles the agent's traffic, and nothing in the diff they are looking at says so. Upgrade from 2.1 to 2.2 in June and the reviewer approved a move to a preview model. Upgrade from 2.2 to 2.3 and they approved a second model change, one the changelog never mentions.
It is worth being precise about what the framework does and does not tell you at run time. LlmResponse carries a model_version field filled from the provider's response, so the record of what actually answered exists after the fact if you keep it. What does not exist is any signal at the moment of choice. The fallback path in _resolve_default_model resolves the class attribute and returns; it logs nothing. The same file raises a DeprecationWarning when someone uses the global_instruction field. Using a deprecated parameter is announced. Being routed to a model whose shutdown the project itself recorded is silent.
The case for the default
The opposing view deserves its strongest form, because it is mostly right. Defaults are how frameworks make the first ten minutes work. Nobody building a serious agent should rely on the fallback; ADK's own adk create writes the model explicitly into the generated agent.py, and the documentation for the field tells you how to override it. Inheritance from a parent agent is genuinely useful: set the model once on the root of a multi-agent tree and the children follow, which is less configuration to drift. And the 2.x maintainers did exactly what you would want. They moved the default months before the shutdown, they named the date in the commit, and when the preview model did not go GA they moved again. If you are on the current line, the framework has been quietly keeping you on a living model. That is a service.
I agree with almost all of it. Where I part company is the word "serious." The default exists precisely for code that was not written seriously, the prototype that worked and was deployed, the sub-agent added in a hurry that inherits from a root someone else configured, the YAML agent config where model is optional. That is a large share of the agents that exist, and it is exactly the share least likely to have an owner reading model deprecation pages. The default was designed for people who do not choose. A retirement is the one event that requires someone to choose. The mechanism routes the decision to the population least equipped to make it, and routes it through a dependency bump whose reviewer cannot see it.
And the 1.x line shows that even a diligent maintainer cannot solve this inside the version number. Back-port the default to 1.39.2 and you have shipped a model change in a patch release to the users who chose the old line precisely to avoid changes. Leave it and the line breaks on a date. A maintenance branch is a promise of stability, and the promise was made about code. It cannot be kept about a model the branch does not control.
What the manifest now carries
The broader lesson reaches beyond one framework. A model identifier hard-coded in a library is a runtime dependency with an expiry date, and our dependency tooling has no field for expiry. Lockfiles record versions. Vulnerability scanners match versions against advisories. Neither can express "this exact version is fine as code and will fail on 16 October as a service." The provider publishes the date on its own page, the framework records it in a commit message, and the application that will actually break holds neither.
For a working architect the Monday consequence is small and concrete. Treat the model as configuration that every agent must state, not a property it can inherit from the library. ADK makes that enforceable: a test can walk the agent tree and assert that every canonical_model resolves to something on an approved list, and a startup hook can call set_default_model with a value your team owns, so the fallback is at least a decision someone in your organisation made. Keep the model_version the provider returns. And when a bot opens a pull request bumping an agent framework, read that framework's default-model history before approving, because the diff will not show it to you.
There is an old argument that defaults are the most powerful design decisions in any system, because most people never change them. What ADK's record adds is a corollary about time. A default chosen in January was a reasonable decision about a model that existed in January. Pinned, it becomes a decision nobody in the building remembers making, about a model that, from tomorrow, the provider's own schedule no longer promises to keep.
This piece argues from one project's public record, read through its repository; Google's deprecation page itself could not be opened from here, so the shutdown date is the one ADK's maintainers wrote into their commit, and is quoted as theirs.
What this is argued from
Reporting and primary material the piece rests on, dated at the time of writing. The interpretation is mine; the facts belong to these.
- "refactor(agents): default model to gemini-3-flash-preview" (commit ad8b6c7)
- "feat: make LlmAgent.model optional with a default fallback" (commit b287215)
- "chore: update default model to gemini-3.5-flash" (commit 4810ddc)
- llm_agent.py at v1.39.1
- cli_create.py at v1.39.1
- CHANGELOG.md (through v2.12.0)
- llm_agent.py on main
Editorials on this site are written to be argued with. If you think the reading is wrong, it probably is in some particular way, and that is the useful part.
Other editorials
Selected is not filtered
A one-line fix in Feast 0.67.0 restores the point-in-time filter for the Polars backend. What it exposes is where that guarantee had been living — no…
7 min readOnly the tokens are recorded
On 6 October a general-purpose judgement endpoint appeared in OpenAI's official clients: ask a named question about a piece of text, get back a typed…
8 min read