· 10 min read · Nadeera Silva
Google ADK 2.14 Upgrade Guide: Breaking Changes and Fixes
Google ADK for Python 2.14 changes Enum tool arguments, Vertex sessions and skill scripts. Here is what breaks, how to fix it and how to upgrade safely.

Google ADK for Python 2.14.0 shipped to PyPI on 7 November 2026, and it is a bigger upgrade than the version number suggests. The release moves execution environments, A2A and most of the evaluation stack out of experimental. It adds multi-run evals and Microsoft Teams support. It also includes three breaking changes that can quietly change how your tools behave. This guide walks through what changed in google adk 2.14, which parts of an existing agent need edits, and the order I would do the upgrade in. There is a runnable test so you can see the Enum change for yourself.
I checked every claim here on 8 November 2026 against the v2.14.0 release notes on GitHub, the google-adk 2.14.0 page on PyPI and the installed package itself.
What is Google ADK, briefly
ADK (Agent Development Kit) is Google's open-source framework for building, evaluating and deploying AI agents. The ADK docs list Python, TypeScript, Go, Java and Kotlin versions, and you install the Python one with pip install google-adk. It works with Gemini and, through adapters, with other model providers. This post only covers the Python package. If you are still choosing a framework, my Microsoft Agent Framework guide covers a comparable stack from the other side.
What changed in Google ADK 2.14
Here is the release in one table. The left column is what the notes call the change, and the right column is what it means for code you already have.
| Area | What changed in 2.14.0 | Do you need to act? |
|---|---|---|
| Enum tool arguments | Tools now receive Enum members, not raw strings | Yes, if any tool takes an Enum |
| VertexAiSessionService | create_session only accepts display_name, ttl, expire_time and labels as extra kwargs | Yes, if you pass other keys |
| RunSkillScriptTool | No free-form command in environment mode | Yes, if prompts or evals send command |
| FirestoreSessionService | New storage format, no rollback to 2.13.0 | Back up first |
| DatabaseSessionService | New PostgreSQL columns are json, not jsonb | Only for outside queries on new tables |
| Environments and A2A | Now stable, plus adk generate_agent_card | No, but warnings go away |
| Evaluation | --num_runs, --parallelism, ToolArgsMatcher | Optional, but worth it |
| Integrations | TeamsRunner, OpenAIDecisionsClassifier, MongoDB vector memory | Optional |
| Performance | 30-node, 200-turn workflow run 23% faster, 9% lower peak memory | No |
The performance figure comes from the release notes, not from my own benchmark.
Breaking change 1: Enum tool arguments arrive as members
This is the change most likely to break something without an error. If a FunctionTool parameter is typed as Enum, Optional[Enum] or list[Enum], ADK now converts what the model sent into Enum members before calling your function. In 2.13 your function got the raw string.
The release notes list what stops working for a plain Enum: comparing with arg == "value", string-keyed dict lookups, match on string literals, json.dumps, and string methods. The nasty part is the first one. Comparing a member to a string just returns False, so your tool takes the wrong branch and the agent carries on.
Before and after, as a test you can run
I ran this file with pytest against both versions. On 2.13.0, triage_old routes "high" to the urgent queue. On 2.14.0 the same call falls through to the normal queue, and all three tests below pass.
# test_enum_tool.py
# pip install google-adk==2.14.0 pytest
# python -m pytest -q test_enum_tool.py
import asyncio
from enum import Enum, StrEnum
from unittest.mock import MagicMock
from google.adk.tools import FunctionTool
class Priority(Enum):
LOW = "low"
HIGH = "high"
# Before: written for ADK 2.13, where the tool got the raw string "high"
def triage_old(priority: Priority) -> dict:
if priority == "high":
return {"queue": "urgent"}
return {"queue": "normal"}
# After: compare against the member (or use priority.value)
def triage_new(priority: Priority) -> dict:
if priority is Priority.HIGH:
return {"queue": "urgent", "label": priority.value}
return {"queue": "normal", "label": priority.value}
class Channel(StrEnum):
EMAIL = "email"
SMS = "sms"
def notify(channel: Channel) -> dict:
# StrEnum members are real strings, so string code keeps working
return {"sent_via": f"{channel}", "is_email": channel == "email"}
def call(fn, args):
ctx = MagicMock()
ctx.tool_confirmation = None
ctx.function_call_id = None
return asyncio.run(FunctionTool(fn).run_async(args=args, tool_context=ctx))
def test_old_string_compare_breaks_on_2_14():
# The model sends "high", but the tool now receives Priority.HIGH
assert call(triage_old, {"priority": "high"}) == {"queue": "normal"}
def test_member_compare_works():
assert call(triage_new, {"priority": "high"}) == {"queue": "urgent", "label": "high"}
def test_strenum_keeps_string_behaviour():
assert call(notify, {"channel": "email"}) == {"sent_via": "email", "is_email": True}The MagicMock tool context is only there so the tool can run without a model or a session. In a real agent, ADK passes its own context.
How to fix your tools
You have three options, and the notes mention all of them:
- Compare against members:
priority is Priority.HIGH. - Use
.valuewherever you need the string, for example as a dict key or in JSON. - Switch the class to
StrEnum(Python 3.11+), whose members behave as strings.
Two details from the notes: a callable require_confirmation still receives the raw values unless FUNCTION_TOOL_ARG_VALIDATION is on, and Enum members your tool returns are still sent to the model as their values.
Breaking change 2: VertexAiSessionService.create_session kwargs
If you use Vertex AI sessions, create_session now only accepts display_name, ttl, expire_time and labels as extra keyword arguments (camelCase forms are accepted too). Keys like http_options, session_state, sessionId and wait_for_completion now raise ValueError. Through adk api_server the same mistake comes back as HTTP 400.
# Before (raises ValueError on 2.14.0)
session = await session_service.create_session(
app_name="support_app",
user_id="u-123",
sessionId="s-1",
session_state={"plan": "pro"},
)
# After
session = await session_service.create_session(
app_name="support_app",
user_id="u-123",
session_id="s-1",
state={"plan": "pro"},
display_name="Support chat",
)Breaking change 3: RunSkillScriptTool no longer runs a shell command
When a SkillToolset runs in environment mode, the model used to be able to pass a free-form command to RunSkillScriptTool. In 2.14 it has to pass args, short_options and positional_args. Scripts run from the skill's own directory, and only .py, .sh and .bash files are allowed. Anything else returns UNSUPPORTED_SCRIPT_TYPE, and a call that still sends command returns INVALID_ARGUMENTS.
The release notes say plainly why: the old behaviour "let a prompt-injected model run arbitrary shell commands." That alone is a reason to upgrade if your agents load skills. If you are new to skills, my Agent Skills and SKILL.md guide explains how they are loaded.
Session storage changes to plan for
Two storage changes are listed as behaviour changes, not breaking changes, but I would treat the first one as the most important item in the release.
FirestoreSessionService: back up before you upgrade
FirestoreSessionService now stores event payloads as JSON strings and wraps nested lists in app and user state. Old sessions still load and accept new events. The catch is in the notes: sessions that 2.14 writes to "cannot be read by 2.13.0 or earlier after a rollback."
The same release also fixes a Firestore bug where list_sessions(user_id="") listed sessions for every user, and it now rejects session and user IDs that would break the document path.
DatabaseSessionService on PostgreSQL
New tables now get json columns instead of jsonb. Existing jsonb tables are not migrated and keep working. This only matters if something outside ADK queries new tables with jsonb operators or relies on GIN indexes.
New in Google ADK evaluation: adk eval --num_runs
Agents give different answers on different runs, so one passing eval can be luck. adk eval now accepts --num_runs to run each case several times and --parallelism to cap how many cases run at once. I checked both flags in adk eval --help on 2.14.0. If neither is set, each case runs once, and parallelism defaults to 4.
You can also put this in the eval config. This file validates against the 2.14.0 EvalConfig model:
{
"criteria": {
"tool_trajectory_avg_score": 1.0,
"response_match_score": 0.6
},
"parallelism": 4,
"multi_run_config": {
"num_runs": 3,
"cross_run_scoring_by_metric": {
"response_match_score": "min_over_runs"
}
}
}pip install "google-adk[eval]==2.14.0"
adk eval my_agent my_agent/evals/support.evalset.json \
--config_file_path eval_config.json \
--num_runs 3 --parallelism 2 --print_detailed_resultsAccording to the eval config guide, the default averages every invocation across all runs, so a case can pass even if one run failed. min_over_runs makes the metric pass only if every run met the threshold. There is also a pass_rate rule. Each run starts from a fresh session, and every run is saved to the eval history.
Other eval additions: AgentEvaluator can take a BaseNode or App built at runtime, --eval_storage_uri stores eval files outside a read-only agents folder, and ToolArgsMatcher checks tool call arguments with exact, case-insensitive, numeric-tolerance or LLM-based semantic matching.
Other behaviour changes worth checking
/run_livenow closes the socket with code 1007 on a malformed frame and 1000 when a run ends. Open a new connection for each live run.- Token-threshold compaction now counts tool outputs, so it triggers earlier with the same
token_threshold. - The 16 features promoted to stable (including Bigtable, Computer Use, the Daytona and E2B environments, GCS, Pub/Sub and Spanner) can no longer be switched off with
ADK_DISABLE_<FEATURE>or--disable_features, and you get no warning. - OpenAPI tool query parameters now follow their declared
styleandexplode. - New
teamsandfsspecextras, and thee2bminimum is now 2.29.5.
Google ADK 2.14 upgrade checklist

- Export Firestore session data, and note any outside jsonb queries on PostgreSQL.
- Pin
google-adk==2.14.0on a branch and confirm you are on Python 3.11 or newer. - Search for Enum-typed tool parameters and fix string comparisons, dict keys and f-strings.
- Search for
create_session(calls on Vertex AI sessions and move state and IDs tostate=andsession_id=. - Remove
commandfrom skill prompts and eval cases. - Run your eval sets with
--num_runs 3before and after the upgrade and compare. - Reconnect per run on
/run_live, and review compaction thresholds.
I use the same habit with every agent framework release now: read the breaking section first, then write one small test per change before touching production. It is the same approach I took for the Pydantic AI 2.66 security upgrade.
Frequently asked questions
What is Google ADK?
Google ADK (Agent Development Kit) is Google's open-source framework for building, evaluating and deploying AI agents. It is available for Python, TypeScript, Go, Java and Kotlin, and the Python package installs with pip install google-adk.
What is the latest version of Google ADK for Python?
As of 8 November 2026 the latest release is google-adk 2.14.0, published to PyPI and GitHub on 7 November 2026. There is no 2.14.x patch yet, and the package requires Python 3.11 or newer.
What are the breaking changes in Google ADK 2.14?
There are three. Enum-typed tool parameters now receive Enum members instead of strings, VertexAiSessionService.create_session only accepts display_name, ttl, expire_time and labels as extra keyword arguments, and RunSkillScriptTool no longer accepts a free-form shell command in environment mode.
Can I roll back from ADK 2.14 to 2.13?
Be careful if you use FirestoreSessionService. The release notes say sessions written by 2.14 cannot be read by 2.13.0 or earlier, so export your session data before upgrading.
How do I run an ADK eval case more than once?
Use adk eval with the --num_runs option, or set multi_run_config.num_runs in your eval config file. The --parallelism option limits how many cases run at the same time and defaults to 4.
Why does my ADK tool now compare an Enum to a string and get False?
Since 2.14.0, ADK converts the model's string into an Enum member before calling your tool, so a check like priority == "high" is False for a plain Enum. Compare against the member, use .value, or change the class to StrEnum.
Sources
- google/adk-python release v2.14.0 (GitHub (Google), 2026-11-07)
- google-adk 2.14.0 (PyPI, 2026-11-07)
- Agent Development Kit (ADK) documentation (Google)
- ADK eval config guide (GitHub (Google))
- Commit ff61524: RunSkillScriptTool validated paths and arguments (GitHub (Google))
- Commit 0894c03: Firestore event payload serialization (GitHub (Google))
- Commit 82fe029: Enum tool arguments as members (GitHub (Google))
Keep reading
- Microsoft Agent Framework Guide: Agents, Harness, WorkflowsA practical guide to Microsoft Agent Framework: Agents vs Harness vs Workflows, Python and .NET get-started, and the new 4 Nov builder skills.
- Pydantic AI 2.66 Security Upgrade: Patch 10 AdvisoriesPydantic AI 2.66.0 patches 10 advisories (2 High). Upgrade paths for 2.x, 1.x and harness, plus rotate guidance.
- Agent Skills Guide: Write a SKILL.md That Works EverywhereWhat agent skills are, how to write a SKILL.md that loads in Claude Code, Copilot, Cursor and Codex, and how to install shared skills safely.
Comments
Share a question or note about this article.
Loading comments…