Running ADK Agents on Your Own PC — Practicing Custom Agents Without Google Cloud

In the exam guide for Google Cloud’s Professional Agentic Architect, §3 “Developing custom agents” accounts for about 33% of the exam. At its center is building custom agents with the Agent Development Kit (ADK), a Python library Google publishes under Apache 2.0. You can write and run ADK agents locally on your own PC without a Google Cloud account, and if you point the model at a local Ollama, you do not need an API key either.
This article contains affiliate links. As an Amazon Associate, we earn from qualifying purchases.
So on October 12, 2026 we installed ADK Python v2.11.0 into a Python 3.13 venv on Windows 11 and, using Ollama’s gemma4:12B through LiteLLM, ran three things: an agent with three tools (stock, printer status and reservation); a Workflow that runs LLM → Python function → LLM in order; and a Workflow that runs three LLMs in parallel and joins them with a JoinNode. The subject is a workshop’s printers and filament stock, with made-up values throughout.
The results first. Across two runs, tool calls followed the instructed order on all five questions. In the sequential Workflow, rule checks on material and quantity were handed to a Python function instead of the LLM; the function caught a request that broke the rules, and the final LLM explained why. The parallel Workflow also worked, but on a single local Ollama it barely saved time: 41.2 s and 36.9 s in parallel versus 43.1 s for the same three run one by one. The legacy SequentialAgent class also printed a deprecation warning in v2.11.0.
We have not taken this exam. The numbers here come from one or two runs per condition and mean no more than “this is what happened in this run”. Local practice is not a substitute for the exam’s hands-on labs either; the second half tabulates, item by item, what can be practiced locally and what needs Google Cloud.
- What §3, a third of the exam, asks for
- The local environment — ADK v2.11.0, LiteLLM and Ollama’s gemma4:12B
- Writing an ADK agent with three tools
- Sequential Workflow — hand rule checks to a Python function, not the LLM
- Parallel Workflow — run three viewpoints at once and join them with a JoinNode
- Legacy SequentialAgent and ParallelAgent are deprecated in v2.11.0
- Sessions and memory — what runs locally and what needs Google Cloud
- Splitting the §3 items into local, partial and Google Cloud
- What this run cannot tell us
- Summary — write ADK agents locally, and fill in the Google Cloud parts from the official docs
- Sources
What §3, a third of the exam, asks for
The exam guide is a four-page English PDF. §3 is headed “Section 3: Developing custom agents (~33% of the exam)” and has three subsections:
- 3.1 Designing and building agentic workflows in code: choosing models (LLM vs SLM, self-hosted vs SaaS, OSS vs proprietary, weighing cost, security and configuration), building custom agents with open-source libraries such as ADK, configuring sessions and memory (examples: Agent Platform Memory Bank and managed sessions), and configuring skills with Agents CLI (examples: plugins and agent vs. human mode)
- 3.2 Integrating enterprise domain knowledge: RAG and vector search (examples: Vector Search and Agent Retrieval), agent permissions (example: Agent Identity), and connecting capabilities with Google Cloud tools (examples: Agent Registry and Google Cloud MCP Servers)
- 3.3 Orchestrating and coordinating agentic workflows: connecting agents with MCP and Agent2Agent (A2A), and handoffs in parallel agents, sequential agents and graph workflows using Google Cloud tools such as Agent Identity, Agent Registry, Agent Runtime (formerly Agent Engine) and agent policies
Laid out like this, parts you can write in ADK code are mixed with parts that assume Google Cloud managed services. Building agents, assembling workflows and connecting MCP tools are library features you can practice locally. Memory Bank, Agent Identity, Agent Registry and Agent Runtime are Google Cloud services and cannot run locally. This article runs the former and checks in the ADK docs what the latter require.
Exam status and dates are as of October 2026: GA registration opens November 2 and delivery starts in mid-November. The exam guide PDF has no revision date and may change for GA. For the overall picture and plan, see What the Google Cloud Professional Agentic Architect Certifies. How well AI knows the in-scope products is checked in Agentic Architect Exam Scope: What AI Knows About 28 Tools; with so many renames and new products, cross-check AI explanations against official materials before memorizing them.
The local environment — ADK v2.11.0, LiteLLM and Ollama’s gemma4:12B
| Item | Value in this run |
|---|---|
| OS and GPU | Windows 11, RTX 5070 Ti (16,303 MiB VRAM) |
| Python | 3.13 in a venv (not the system Python) |
| ADK | google-adk 2.11.0 |
| LiteLLM | 1.105.0 (installed separately; ADK requires 1.84 or later) |
| Ollama | 0.40.2 (Windows) |
| Model | gemma4:12B (11.9B, Q4_K_M; ollama show lists tools and thinking, with thinking on by default) |
| Context length | 4096 (as shown by ollama ps in this run) |
| Model setting | LiteLlm(model=”ollama_chat/gemma4:12B”) |
| Run date | 2026-10-12 |
ADK version and requirements
ADK for Python is published on GitHub at google/adk-python under Apache 2.0, which we confirmed in the LICENSE itself. The PyPI author is Google LLC, and the repository is Google’s own, not a fork. We used v2.11.0, published to PyPI on October 2, 2026 (UTC); as of October 12 there was no newer release. v2.11.0 requires Python 3.10 or later, which covers the 3.13 we used, but the unreleased main branch raises the requirement to 3.11 or later, so recheck your Python version before installing the next release.
The README is titled “Agent Development Kit (ADK) 2.0”; Python v2.0.0 was published on May 19, 2026 (UTC). The README says apps are built from two classes, Agent and Workflow, and graph workflows (Workflow) are available in Python from v2.0.0. ADK also has Java, Kotlin, Go and TypeScript versions; this article covers Python only. The README also says “While optimized for Gemini, ADK is model-agnostic” — worth keeping in mind: many procedures assume Gemini, but local models work too.
Ollama models are called through LiteLLM
According to the ADK docs, ADK uses models served by Ollama through the LiteLLM library: you wrap the Ollama model in ADK’s LiteLlm class and pass it as the Agent’s model. That is not the only way to use non-Gemini models — Claude on Agent Platform can be given by model name, and models behind Apigee use ApigeeLlm. Mind the capitalization: LiteLLM is the library, LiteLlm is the ADK class.
LiteLLM is not installed by pip install google-adk; the ADK docs say to install it separately and require version 1.84 or later. The ADK docs also warn that unauthorized code was found in LiteLLM 1.82.7 and 1.82.8 on PyPI, another reason to respect the minimum version.
The model name takes the ollama_chat/ prefix. That is LiteLLM’s naming, not an ADK setting. The ADK docs ask for ollama_chat/ rather than ollama/, warning that plain ollama can cause infinite tool-call loops or ignore earlier context; the LiteLLM docs also recommend ollama_chat. The Ollama address is passed in the OLLAMA_API_BASE environment variable. The docs also describe calling Ollama’s OpenAI-compatible API with the openai prefix; we used only ollama_chat here. What the OpenAI-compatible API can do and how to let a local model call tools are covered in Wiring a Local LLM Into Real Work — OpenAI-Compatible APIs and MCP.
Choose a model that supports tool calling
Agents that use tools need a model that supports tool calling. The ADK docs say to use a model with tools support for reliability and show how to check: tools should appear under Capabilities in ollama show. On the Ollama side you can look for the tools label on a model page or filter ollama.com by tools. LiteLLM does fall back to having non-native models produce calls as JSON, so unsupported models are not useless; the docs’ wording is “choose a supported model for reliability”.
ADK’s official sample (hello_world_ollama) uses ollama_chat/mistral-small3.1; we did not use it and ran only gemma4:12B (written in lowercase, gemma4:12b, on Ollama’s model page). Gemma 4 has the tools label on Ollama, and its model card lists native function calling. It comes in five sizes — E2B, E4B, 12B, 26B A4B and 31B — under Apache 2.0, with open weights. The ADK Ollama and Gemma pages, however, show no example of Gemma 4 on Ollama (checked October 12, 2026), so what follows is our run, not a reproduction of an officially documented pairing. The Ollama page’s example uses gemma3, but the Gemma page says Gemma 3 lacks native function calling, so we do not use Gemma 3 as an example.
Measure VRAM separately from download size
Ollama’s tag list shows gemma4:12B at 7.7–8.0 GB to download — not VRAM use. In this run total GPU memory use went from 3,637 MiB before loading the model to 11,844 MiB after, an increase of 8,207 MiB. Those two values are from our notes; the raw outputs were not saved. Peaks sampled every second with nvidia-smi during the runs are in the result files: 13,440 MiB in the second run of the tool agent and 11,923 MiB in the first (we did not investigate the difference). Both fit in the 16,303 MiB GPU. Gemma 4 12B can handle up to 256K tokens of context, but this run used 4096, and the VRAM figures apply to that length. For estimating quantization and VRAM, see Estimating What Fits in Your VRAM — Quantization and Memory Math for Local LLMs.
Installing and running
Create a working venv and install ADK and LiteLLM into it, with LiteLLM at 1.84 or later as ADK requires.
pip install google-adk
pip install "litellm>=1.84"
The LiteLLM in this run was 1.105.0, and the results here are for that version; to reproduce, pin “litellm==1.105.0”. On our machine LiteLLM was later downgraded to 1.85.7 when we added ADK’s evaluation extras, so recheck the LiteLLM version after installing extras.
On Windows, LiteLLM can stop with a UnicodeDecodeError when it reads its cache file in a non-UTF-8 encoding. The ADK docs say to prevent this by setting the PYTHONUTF8 environment variable to 1. We did so and ran the script in bash syntax as below, where <venv> is the venv folder and the script is the file containing the Runner code below.
PYTHONUTF8=1 <venv>/Scripts/python.exe <experiment-script>.py
One more environment-specific problem appeared on our machine: on the first call, LiteLLM tried to download a tokenizer file (tiktoken’s cl100k_base) and failed certificate verification (CERTIFICATE_VERIFY_FAILED). Calling truststore.inject_into_ssl() from the truststore package at the top of the script fixed it. This was needed in our environment; it can happen behind corporate traffic-inspection setups and similar networks.
The code that runs the agent uses Runner and InMemorySessionService, as in the official sample hello_world_ollama’s main.py; logging and timing code is omitted.
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types
async def run_one(agent, app: str, prompt: str) -> list:
svc = InMemorySessionService()
runner = Runner(agent=agent, app_name=app, session_service=svc)
s = await svc.create_session(app_name=app, user_id="u1")
msg = types.Content(role="user", parts=[types.Part(text=prompt)])
events = []
async for ev in runner.run_async(user_id="u1", session_id=s.id, new_message=msg):
events.append(ev)
return events
To call it, pass an agent and a question and run it with asyncio. root_agent is the tool agent built in the next section. gemma4:12B sometimes returns a thinking part (parts where thought is True) alongside the answer, so strip those when printing. Our prompts were in Japanese; they are shown translated into English here and below.
import asyncio
def answer_text(events: list) -> str:
# Drop the thinking parts (where thought is True) and join only the answer text
texts = []
for ev in events:
parts = (ev.content.parts if ev.content else None) or []
texts += [p.text for p in parts if p.text and not p.thought]
return "\n".join(texts)
events = asyncio.run(run_one(root_agent, "workshop", "How many PETG spools are in stock?"))
print(answer_text(events))
adk run and adk web
ADK also has entry points that need no script. adk run chats in the terminal, and adk web opens a browser-based development UI; the docs say adk web is not for production deployment, and usage reporting is off by default. adk api_server exposes the agent as a REST API.
adk create asks you to pick a model interactively when generating a skeleton. In v2.11.0’s code, choosing a Gemini model asks for an API key or Google Cloud settings, while “Other models (fill later)” creates a skeleton with no authentication and a model name to fill in later. To use a local model, write LiteLlm(model=”ollama_chat/…”) into the skeleton’s model. The timings in this article come from the Runner code above; we did not time adk run or adk web.
If you want a solid grounding in how local and open models are run and called from code before wiring them into an agent framework, a hands-on book on large language models is a good companion.
Writing an ADK agent with three tools
The first agent manages a workshop’s stock and printers. We passed three functions as tools: one returns spool stock, one returns printer status and one reserves an idle printer. Stock is PLA 4, PETG 1, TPU 0 and ASA 2 spools; p1 is idle and p2 is printing. All values are made up for the experiment.
from google.adk import Agent
from google.adk.models.lite_llm import LiteLlm
MODEL = "ollama_chat/gemma4:12B"
# Made-up values for the experiment
STOCK = {"PLA": 4, "PETG": 1, "TPU": 0, "ASA": 2}
PRINTERS = {"p1": {"state": "idle"}, "p2": {"state": "printing", "progress": 0.42}}
RESERVATIONS: list[dict] = []
The three tool functions:
def get_spool_stock(material: str) -> dict:
"""Return how many unopened spools of the given filament material are in stock."""
m = material.strip().upper()
return {"material": m, "spools": STOCK.get(m, 0)}
def get_printer_status(printer_id: str) -> dict:
"""Return the current state of a printer ("idle" or "printing")."""
p = PRINTERS.get(printer_id.strip().lower())
if p is None:
return {"printer_id": printer_id, "error": "unknown printer"}
return {"printer_id": printer_id.strip().lower(), **p}
def reserve_printer(printer_id: str, material: str) -> dict:
"""Reserve an idle printer for a job with the given material."""
pid, m = printer_id.strip().lower(), material.strip().upper()
if PRINTERS.get(pid, {}).get("state") != "idle":
return {"reserved": False, "reason": "printer is not idle"}
if STOCK.get(m, 0) <= 0:
return {"reserved": False, "reason": "no spool in stock"}
RESERVATIONS.append({"printer_id": pid, "material": m})
return {"reserved": True, "printer_id": pid, "material": m}
List Python functions in tools and ADK wraps them as FunctionTool. According to the docs, ADK inspects the function name, docstring, parameters and type hints to build the description passed to the model, so the one-line docstring becomes the tool’s manual. That is also why the type hints should not be omitted.
The instruction was written in English and asked for answers in Japanese. It has four rules: call the stock function for stock questions and the status function for printer questions; before reserving, always check the printer’s status and then the material’s stock, and reserve only if the printer is idle with at least one spool; and answer greetings or unrelated questions without calling any tool.
INSTRUCTION = """You manage a small 3D-printing workshop. Answer in Japanese.
Rules:
- To answer a question about filament stock, call get_spool_stock.
- To answer a question about a printer, call get_printer_status.
- Before calling reserve_printer, always call get_printer_status for that printer first,
then get_spool_stock for that material. Call reserve_printer only if the printer is idle
and at least one spool is in stock. Otherwise explain why you did not reserve it.
- For greetings or questions unrelated to the workshop, answer without calling any tool."""
root_agent = Agent(
name="workshop_agent",
model=LiteLlm(model=MODEL),
instruction=INSTRUCTION,
tools=[get_spool_stock, get_printer_status, reserve_printer],
)
The reservation function itself also checks that the printer is idle and stock exists, and refuses otherwise — the same checks placed on the function side as a safeguard in case the model ignores the instruction.
Results — tool calls followed the instructions in both runs
We asked the same agent five questions, twice. The tool column is from the second run; the tools called and their order were the same in the first.
| Question (translated) | Tools called (in order) | Time (run 2) | Time (run 1) |
|---|---|---|---|
| How many PETG spools are in stock? | get_spool_stock(material=PETG) | 7.2 s | 7.6 s |
| Is p2 free right now? | get_printer_status(printer_id=p2) | 2.2 s | 2.9 s |
| I want to print with PLA on p1. Please reserve it if it is free. | get_printer_status(printer_id=p1) → get_spool_stock(material=PLA) → reserve_printer(material=PLA, printer_id=p1) | 5.1 s | 5.0 s |
| Please reserve p1 with TPU. | get_printer_status(printer_id=p1) → get_spool_stock(material=TPU) | 4.7 s | 5.0 s |
| Hello, thanks for your help today. | None | 1.7 s | 1.8 s |
In both runs, all five questions called tools in the instructed order. The PLA reservation went status → stock → reserve, exactly as the rules say. The TPU reservation stopped after checking status and stock and never called reserve — correct, since TPU stock is zero. The greeting called no tools. In the third question in both runs, and the fourth in run one only, status and stock were requested together in one response (status first); reserve was called only after both results came back.
Because the TPU question never called reserve, the checks on the function side had nothing to do in this run — the model followed the rules. But following them twice is no guarantee for the next run. For tools with operations that are hard to undo, such as reservations or orders, keep the checks on the function side too.
Per-question times were 1.7–7.2 s (run 2) and 1.8–7.6 s (run 1). The first question, with one tool call, took longer than the third, with three. The first question was the first one asked in both runs, and time also depends on answer length, so this record says nothing about the relation between number of tools and time.
Handling English reasoning text
In run one, four of the five final answers began with English reasoning text (the remaining one emitted English text while calling tools). But run one’s log joined the model’s thinking parts and answer parts without separating them, so we cannot tell whether that English text was thinking or part of the answer. In run two we logged them separately.
In run two, all five answers were Japanese only, and the English reasoning came back as separate thinking parts (thought: true) — six in total across the four non-greeting questions. Running the same agent in other sessions also returned thinking parts. gemma4:12B supports thinking and has it on by default according to ollama show. When you show answers on screen or in logs, write the code to strip thinking parts from the start.
Sequential Workflow — hand rule checks to a Python function, not the LLM
The second is a Workflow that accepts print requests. The first LLM (intake_agent) turns the request into JSON, a Python function (check_rules) checks material and quantity rules, and the last LLM (reply_agent) reports the result. Allowed materials are PLA, PETG, ASA and TPU, and quantity must be an integer from 1 to 20.
In a Workflow, listing nodes in order from START in edges makes them run in that order. Nodes can be LLM agents or plain Python functions. The official sample (workflows/sequence) does not set model on its Agents, so we passed LiteLlm to each Agent to run on the local model.
import json
import re
from google.adk import Agent, Workflow
from google.adk.models.lite_llm import LiteLlm
MODEL = "ollama_chat/gemma4:12B"
ALLOWED = {"PLA", "PETG", "ASA", "TPU"}
intake_agent = Agent(
name="intake_agent",
model=LiteLlm(model=MODEL),
instruction="""Read the user's print request and return only one JSON object:
{"part": <short English name>, "material": <PLA|PETG|ASA|TPU|OTHER>, "quantity": <integer>}.
No other text.""",
)
The rule-check function extracts JSON from the LLM output and checks material and quantity. If the JSON is broken and unreadable, it is treated as empty, so both material and quantity come back as problems.
def check_rules(node_input: str) -> str:
"""Deterministic checks the LLM is not trusted with."""
m = re.search(r"\{.*\}", str(node_input), re.S)
try:
spec = json.loads(m.group(0)) if m else {}
except Exception:
spec = {}
problems = []
if str(spec.get("material", "")).upper() not in ALLOWED:
problems.append("material is not one we stock")
q = spec.get("quantity")
if type(q) is not int or q < 1 or q > 20:
problems.append("quantity must be an integer from 1 to 20")
return json.dumps({"spec": spec, "problems": problems}, ensure_ascii=False)
Quantity is checked with type(q) is not int rather than isinstance(q, int) because in Python bool is a subclass of int, so with isinstance a JSON true would pass as a quantity of 1. The code we actually ran used isinstance(q, int), but the first LLM returned integers 3 and 50 for the two requests, so the result is the same either way. The last LLM and the Workflow that chains the three:
reply_agent = Agent(
name="reply_agent",
model=LiteLlm(model=MODEL),
instruction="""You receive a JSON object with "spec" and "problems".
If "problems" is empty, confirm the order in two Japanese sentences.
Otherwise, explain each problem in Japanese and ask the user to fix it.""",
)
root_agent = Workflow(
name="seq_workflow",
edges=[("START", intake_agent, check_rules, reply_agent)],
)
Two requests, one run each. The final output column is the last LLM’s reply with line breaks removed, translated from Japanese.
| Request (translated) | Time | Final output (translated) |
|---|---|---|
| Please print 3 phone stands in PETG. | 12.8 s | Order confirmed. We have accepted 3 phone stands in PETG. |
| 50 gears in nylon, please. | 8.0 s | Please fix the following problems: the material is not one we stock; the quantity must be an integer from 1 to 20. |
The first request met the rules, so the function returned no problems and the last LLM wrote a confirmation. In the second, nylon is not one of the four materials and 50 exceeds the limit of 20; the function returned both as problems and the last LLM explained them.
Why hand the checks to a function
Whether the material is one of four and the quantity is 1–20 are checks you can write as rules. An LLM might judge them correctly in this run, but there is no guarantee the next run will agree, and when it errs it is hard to trace where. A function returns the same answer for the same input every time.
So the LLM got only the parts that are hard to write as functions: turning a free-form request into fixed JSON, and turning the check results into text a person understands. This split is a concrete local exercise for the “sequential agents” and “graph workflow” in item 3.3 of the exam guide — deciding what to leave to the LLM and what to make deterministic is itself an agent design decision.
Parallel Workflow — run three viewpoints at once and join them with a JoinNode
The third Workflow looks at one request from three viewpoints at once: material choice, strength and print orientation, and print time and how to shorten it. Three LLMs run in parallel from START, a JoinNode waits for all three and a final Python function (merge) combines their answers.
According to the docs, writing START on several rows in a Workflow creates parallel branches, and a JoinNode is the merge point that waits for all of them. Like the official sample (workflows/fan_out_fan_in), we grouped the parallel nodes in a tuple.
from typing import Any
from google.adk import Agent, Event, Workflow
from google.adk.models.lite_llm import LiteLlm
from google.adk.workflow import JoinNode
MODEL = "ollama_chat/gemma4:12B"
def reviewer(name: str, focus: str) -> Agent:
return Agent(
name=name,
model=LiteLlm(model=MODEL),
instruction=f"""Review the user's 3D-print request only from the viewpoint of {focus}.
Answer in at most two Japanese sentences.""",
)
material_reviewer = reviewer("material_reviewer", "filament material choice")
strength_reviewer = reviewer("strength_reviewer", "mechanical strength and print orientation")
time_reviewer = reviewer("time_reviewer", "print time and how to shorten it")
join_node = JoinNode(name="join_reviews")
async def merge(node_input: dict[str, Any]):
yield Event(message="\n".join(f"[{k}] {v}" for k, v in node_input.items()))
root_agent = Workflow(
name="par_workflow",
edges=[("START", (material_reviewer, strength_reviewer, time_reviewer), join_node, merge)],
)
The request was one sentence — “I want to print an L-shaped shelf bracket in PLA and need it to hold about 5 kg” — run twice. For comparison we also timed the same three LLMs run one after another.
| How it ran | Time |
|---|---|
| Parallel Workflow (run 1) | 41.2 s |
| Parallel Workflow (run 2) | 36.9 s |
| Three in sequence (material 21.2 s + strength 10.3 s + time 11.6 s) | 43.1 s |
The three answers were joined by the JoinNode and labeled with the viewpoint name, such as [time_reviewer]. In run two the time viewpoint suggested shortening print time by raising layer height or print speed — model output, not advice we verified.
On one local Ollama, writing it in parallel barely shortened the time
Parallel runs took 41.2 s and 36.9 s, about the same as the 43.1 s for running them one by one; even the faster run saved only 6.2 s. Answer length varies each time, so the comparison is only indicative. We did not set Ollama’s OLLAMA_NUM_PARALLEL and did not compare other settings, so the cause is not isolated. What we can say is that writing a Workflow in parallel and finishing faster on a single local GPU are different things.
So do not judge parallel structure by speed alone. Writing three viewpoints as independent nodes and lining their answers up at a merge point for the next step are structural benefits regardless of time. Item 3.3 of the exam guide also lists parallel agents as something to choose (“Selecting”).
Once sequential and parallel workflows run locally, the next question is how to combine several agents in production and how to run them. A practical book on building multi-agent systems covers that next step.
Legacy SequentialAgent and ParallelAgent are deprecated in v2.11.0
ADK has long had three classes: SequentialAgent, ParallelAgent and LoopAgent. The docs call them template workflows that run child agents in a fixed order without relying on the model — SequentialAgent runs them in list order, ParallelAgent runs them at once and LoopAgent repeats them a set number of times or until a stop condition. In ParallelAgent, branches keep separate conversation histories, but v2.11.0’s code comments say session state is shared across all branches and the last write to a key wins, while the docs page says state is not shared automatically — the wording does not match.
The docs say that starting in ADK 2.0 for Python and Go, template workflows have been superseded by more flexible structures such as graph-based and dynamic workflows; this article covers only graph workflows. In v2.11.0’s code all three classes are marked deprecated and scheduled for removal in a future version. They still load in v2.11.0, and SequentialAgent can still be created — doing so printed this warning:
DeprecationWarning: SequentialAgent is deprecated in favor of Workflow and will be removed in a future version. Workflow cannot yet be used as an LlmAgent sub-agent.
The first half means SequentialAgent is deprecated in favor of Workflow and will be removed. The second half is a Workflow limitation: a Workflow cannot yet be a sub-agent of an LlmAgent. When moving from the legacy classes to Workflow, check whether you hit that limit. The docs pages still describe the legacy classes as normal features with a superseded note at the top; the code’s deprecation is the stronger statement, so this article follows the code.
Mapping the exam guide’s terms to ADK
Item 3.3 lists “parallel agents, sequential agents, and graph workflow”, so all three terms matter. How they map to v2.11.0:
| Exam guide term | How to write it in ADK v2.11.0 | Run in this article? |
|---|---|---|
| Sequential agents | List nodes in order in a Workflow’s edges; legacy SequentialAgent is deprecated | Yes, as a Workflow |
| Parallel agents | Branch from START to several nodes in a Workflow and join with a JoinNode; legacy ParallelAgent is deprecated | Yes, as a Workflow |
| Graph workflow | Workflow (Python v2.0.0 and later) | Used in both of the above |
The docs on template workflows help with what the legacy classes mean, but new code should use Workflow; we only confirmed the legacy warning. LoopAgent is also deprecated but was not run here.
Sessions and memory — what runs locally and what needs Google Cloud
Item 3.1 lists configuring sessions and memory, with Agent Platform Memory Bank and managed sessions as examples. ADK has local and Google Cloud implementations of both. Here is the split as we confirmed it in the official docs.
| Class or feature | Where it runs | What the docs say |
|---|---|---|
| InMemorySessionService | Local | Kept in local memory; conversation data is lost on restart. The Runner in this article uses it |
| DatabaseSessionService | Local | Stored as tables in SQLite, PostgreSQL, MySQL and others |
| adk web / adk run default | Local | Per-agent SQLite (.adk/session.db); saving locally is on by default |
| InMemoryMemoryService | Local | Keyword-match search; for prototyping and local development |
| SQLite memory | Local | Added in v2.11.0 (specified with sqlite://) |
| VertexAiSessionService | Google Cloud | Service name Agent Platform Sessions (which we read as the exam guide’s managed sessions). Needs a Google Cloud project, a Cloud Storage bucket and an Agent Runtime instance (no code deployment needed) |
| VertexAiMemoryBankService | Google Cloud | Memory Bank; needs an Agent Runtime instance on Agent Platform |
| VertexAiRagMemoryService | Google Cloud | RAG memory; needs Knowledge Engine |
The class names still say “VertexAi”, but the product is Agent Platform, and the exam guide writes “Agent Platform Memory Bank”. Do not confuse class names with product names. Locally you can practice the mechanics — whether sessions persist and how memory is searched. Per the docs, an agent on InMemorySessionService loses earlier conversations on restart, and switching to DatabaseSessionService or SQLite memory keeps them locally; we did not test persistence ourselves. How the managed Memory Bank remembers and retrieves can only be checked on Google Cloud.
Express mode does not count as “without Google Cloud”
The ADK docs also cover Agent Platform express mode, a free tier for trying Agent Platform sessions and Memory Bank without a full Google Cloud project, intended for prototyping and development. It is in preview and express-mode projects are valid for only 90 days; deploying to Agent Runtime requires paid access. Express mode is a Google Cloud service used with a Google account and API key, so it is outside this article’s “without Google Cloud” scope. Treat it as a separate, free way to try the cloud side, such as Memory Bank.
Splitting the §3 items into local, partial and Google Cloud
Based on these runs and the official docs, we sorted the §3 items into “local”, “partial” and “needs Google Cloud”. Local means it runs with Windows Python and Ollama alone, with no Google account or API key. Partial means the mechanics can be practiced locally but the Google Cloud products the guide names need the cloud.
| Exam guide item | What you can do locally | What needs Google Cloud | Split |
|---|---|---|---|
| 3.1 Choosing models | Use self-hosted open-weight models from ADK (gemma4:12B here) | Comparing with SaaS or proprietary models (such as Gemini) needs an API key. Gemini API keys can be created in Google AI Studio (keys are tied to a Google Cloud project; AI Studio creates a default project for new users) | Partial |
| 3.1 Building custom agents with ADK | Write and run Agents, function tools and Workflows (done here) | None | Local |
| 3.1 Sessions and memory | InMemory and SQLite sessions and memory | Memory Bank and managed sessions (both need an Agent Runtime instance) | Partial |
| 3.1 Configuring skills with Agents CLI | Use ADK’s own skills (experimental) and plugins in code | Agents CLI needs a Gemini API key or Google Cloud credentials | Partial |
| 3.2 RAG and vector search | We did not find an official fully local RAG procedure | Vector Search, Agent Retrieval, RAG memory (Knowledge Engine) | Needs Google Cloud |
| 3.2 Agent permissions | None | Agent Identity (the product is GA; ADK’s Auth Manager integration is marked preview in the docs) | Needs Google Cloud |
| 3.2 Connecting capabilities with Google Cloud tools | Connecting MCP servers with McpToolset | Agent Registry (enable its API in a project), Google Cloud MCP Servers | Partial |
| 3.3 Connecting with protocols | MCP (McpToolset); A2A (expose and call on localhost; experimental in Python) | None (production deployment aside) | Local |
| 3.3 Multi-agent handoffs and workflows | Sequential and parallel Workflows (done here); legacy classes deprecated | Agent Identity, Agent Registry, Agent Runtime, agent policies | Partial |
Of these, we actually ran only the local parts of “building custom agents with ADK” and “multi-agent handoffs and workflows”. For MCP, A2A, skills and plugins we only confirmed in the docs that local mechanisms exist.
Notes on reading the table
The exam guide does not define “SLM”, so we do not call gemma4:12B an SLM. Likewise for “OSS”: we describe Gemma 4 only as an open-weight model released under Apache 2.0, since we did not check whether training data or code are public. “Agent vs. human mode” in the Agents CLI row could not be pinned down in official materials, so we do not explain it here. Agents CLI runs on Python 3.11 or later, and we found no official procedure for using it with local models only.
For the RAG row, you can imitate the mechanics locally — a function tool that searches local documents gives you the search-then-answer flow — but that is our own exercise, not an official procedure. Vector Search and Agent Retrieval are Google Cloud products. Agent Runtime is a managed Google Cloud runtime and, per the docs, a paid service with costs beyond the free tier. Agent Identity went GA on April 22, 2026 and needs a Google Cloud project and authentication to use from ADK; ADK’s Agent Identity Auth Manager integration is marked preview in the ADK docs (the IAM release notes say auth manager went GA on August 22, 2026). Agent Registry is also used by enabling its API in a project. Do not let a local stand-in convince you that you have practiced these.
Local practice is not a substitute for the labs
The exam has a multiple-choice part and hands-on labs. According to the official FAQ, the labs run on Google Skills and check execution and coding ability in an active cloud environment. Being able to write ADK locally is only part of the preparation; the “needs Google Cloud” rows are what you practice in a Google Cloud environment.
What this run cannot tell us
- One or two runs per condition: the tool agent and parallel Workflow ran twice, the sequential Workflow once per request. Nothing generalizes beyond “this is what happened”
- Only gemma4:12B: not compared with the sample’s mistral-small3.1 or cloud models such as Gemini, and the ADK Ollama and Gemma pages show no gemma4-on-Ollama example
- Few questions, made-up data: five questions for the tool agent, two requests for the sequential Workflow and one for the parallel Workflow, with invented stock and printer values
- Times are indicative: answer length varies, Ollama settings were not changed for the parallel comparison, and adk run / adk web were not timed
- English reasoning text: run one’s log did not separate thinking from answers
- Parallel answers: the strength and time advice is model output we did not verify
- The Google Cloud side was not run: Memory Bank, Agent Identity, Agent Registry, Agent Runtime and the like were checked only in the docs
- Exam relevance: we have not taken the exam; what runs locally and the design judgment the exam tests are different, and local practice does not replace the labs
Still, the record shows that ADK v2.11.0 connects to a local model through LiteLLM, and that an agent with tools and both sequential and parallel Workflows ran in this environment.
Summary — write ADK agents locally, and fill in the Google Cloud parts from the official docs
- ADK Python v2.11.0 (Apache 2.0, Python 3.10+) runs locally with no Google Cloud account or API key when connected to Ollama models through LiteLLM. Model names take LiteLLM’s ollama_chat/ prefix
- The three-tool agent called tools in the instructed order on all five questions in both runs. Keep checks on the function side for operations that are hard to undo
- In the sequential Workflow, rule checks went to a Python function node, and the LLM handled turning text into JSON and explaining the result
- The parallel Workflow worked but barely saved time on one local Ollama (41.2 s and 36.9 s versus 43.1 s sequentially)
- Legacy classes such as SequentialAgent are deprecated in v2.11.0 and print a warning. Write new code with Workflow and know how it maps to the guide’s three terms
- Of §3, building agents, workflows, MCP and A2A can be practiced locally; Memory Bank, Agent Identity, Agent Registry, Agent Runtime and the like need Google Cloud
As a practice order, start with an ADK agent that has a single tool. Confirm tools support with ollama show, run it and check the call order in the log. Then build sequential and parallel Workflows and decide yourself which checks belong in function nodes. For the legacy classes, printing the warning and reading what it means is enough.
For items that need Google Cloud, learn from the official docs what they require, then practice in a cloud environment. The idea of dividing work between local and cloud by use case is covered in Deciding What Runs Where — A Reverse Guide to Local vs Cloud AI. Exam status and dates are as of October 2026, so recheck the official pages before registering.
Sources
- Professional Agentic Architect exam guide (PDF)
- Professional Agentic Architect certification page
- Professional Agentic Architect FAQ
- google/adk-python on GitHub
- ADK Python v2.11.0 release
- PyPI: google-adk 2.11.0 (JSON)
- ADK LICENSE (v2.11.0)
- ADK README (v2.11.0)
- sequential_agent.py (v2.11.0)
- Official sample hello_world_ollama
- Official sample workflows/fan_out_fan_in
- ADK docs: Ollama model host
- ADK docs: LiteLLM model connector
- ADK docs: Google Gemma models
- ADK docs: Workflows
- ADK docs: Template agent workflows
- ADK docs: Function tools
- ADK docs: Session
- ADK docs: Memory
- ADK docs: Agent Platform express mode
- ADK docs: Agent Identity Auth Manager
- ADK docs: Agent Registry
- ADK docs: Model Context Protocol tools
- ADK docs: Exposing a remote agent via A2A
- LiteLLM docs: Ollama
- Ollama library: gemma4
- Gemma 4 12B model card (Hugging Face)





