Deploying an Agent With Vertex AI Reasoning Engine
10 min read · updated August 11, 2026
A Vertex AI endpoint hosts a model. This runtime hosts your agent — the Python object that decides which tools to call and in what order. That difference determines everything about how you package it and what can go wrong.
What the runtime hosts
When you deploy an agent here, Google takes a serialised Python object, installs the dependency list you supply, and runs it behind a managed HTTP surface with sessions, scaling and IAM attached. The model calls your agent makes still go to Vertex AI as ordinary API calls; the runtime is not a model server.
Which is why the failure modes are unlike an endpoint’s. An endpoint deployment fails on machine types, accelerators and container contracts. An agent deployment fails on pickling, on a dependency that resolved differently in the build than on your laptop, and on the deployed identity not having a permission that your own account had all along. Almost every problem here is a packaging or identity problem.
The trade against running the same loop on Cloud Run is real in both directions. You get managed sessions and no container to build; you give up control of the runtime image, the ability to run anything that is not Python, and the straightforward debuggability of a service you can run locally with docker run. If your agent is a plain request-response loop with no session state, Cloud Run is the simpler answer and the min-instances question becomes yours to make instead.
Reasoning Engine, Agent Engine, reasoningEngines
The product has been renamed and the resource has not. Google’s documentation now presents this as Agent Engine, part of the agent platform surface, while the REST resource is still reasoningEngines and a deployed agent’s name is still projects/PROJECT_NUMBER/locations/LOCATION/reasoningEngines/RESOURCE_ID.
The SDK moved too. Google documents that the agent_engines module in the Vertex AI SDK for Python was refactored to a client-based design in version 1.112.0. Code written before that uses a different entry point, so a snippet that does not match what you see below is probably not wrong so much as older. Pin the SDK version in your requirements and you will not be surprised by which shape you get.
The agent, locally
Write and test the agent as an ordinary object first. Everything that works locally may still fail to deploy, but nothing that fails locally will start working once deployed.
# agents/support_agent.py
from google.adk.agents import Agent
def lookup_order(order_id: str) -> dict:
"""Return the status of a customer order.
Args:
order_id: The order identifier, e.g. "A-10423".
"""
# A real implementation would call your order service. Note that
# this runs inside the managed runtime, so whatever it calls must
# be reachable from there and authorised as the deployed identity.
return {"order_id": order_id, "status": "shipped", "carrier": "DHL"}
root_agent = Agent(
name="support_agent",
model="gemini-2.5-flash",
instruction=(
"You answer customer questions about orders. "
"Use lookup_order for anything about a specific order. "
"If you do not have an order id, ask for one."
),
tools=[lookup_order],
)The docstring on lookup_order is not documentation, it is the tool description the model reads to decide when to call it. A vague docstring produces a tool that is called at the wrong times, and there is no error to tell you that is what happened — only an agent that behaves oddly. Type annotations matter for the same reason: they become the parameter schema.
Deploying it
- Create a staging bucket in the same region. The deploy uploads the pickled agent and any extra packages there:
gcloud storage buckets create gs://PROJECT_ID-agent-staging \ --location=us-central1
- Create a service account for the deployed agent and grant it what the agent needs — at minimum the ability to call models, plus whatever its tools reach. This is the identity the agent runs as, not yours:
gcloud iam service-accounts create svc-support-agent gcloud projects add-iam-policy-binding PROJECT_ID \ --member="serviceAccount:svc-support-agent@PROJECT_ID.iam.gserviceaccount.com" \ --role="roles/aiplatform.user"
- Deploy with the client-based SDK, pinning the requirements:
import vertexai from agents.support_agent import root_agent client = vertexai.Client(project="PROJECT_ID", location="us-central1") remote_agent = client.agent_engines.create( agent=root_agent, config=dict( display_name="support-agent", description="Answers order status questions", requirements=[ "google-cloud-aiplatform[agent_engines,adk]==1.112.0", ], extra_packages=["./agents"], service_account=( "svc-support-agent@PROJECT_ID.iam.gserviceaccount.com" ), env_vars={"ORDER_SERVICE_URL": "https://orders.internal"}, min_instances=0, max_instances=10, ), ) print(remote_agent.api_resource.name) # projects/123456789012/locations/us-central1/reasoningEngines/6543210987654321 - Record the resource name in whatever your deploy process reads. It is the only handle to the deployment and it is not derivable from the display name.
extra_packages is the argument that decides whether the deploy works. Anything your agent imports from your own codebase must be listed there — a single file or a directory — because the pickled object carries references, not source. The characteristic symptom of getting it wrong is a deploy that succeeds and a first query that fails with an import error, since the missing module is only touched at call time.
Pin the version in requirements, and pin the same version in your local environment. The agent is serialised locally and deserialised in the runtime; a version skew between the two is the other common cause of a deploy that builds and then cannot answer.
Calling the deployed agent
import vertexai
client = vertexai.Client(project="PROJECT_ID", location="us-central1")
agent = client.agent_engines.get(name=RESOURCE_NAME)
# what this deployment actually exposes — frameworks differ
for op in agent.operation_schemas():
print(op["name"], op.get("api_mode"))
for event in agent.stream_query(
user_id="customer-8891",
message="Where is order A-10423?",
):
print(event)operation_schemas() is worth calling once against any deployment you did not write. The available operations depend on the framework the agent was built with — ADK, LangChain, LangGraph and custom classes expose different method names and different modes — so this is how you find out what the deployed object can do rather than guessing from the framework’s documentation.
The user_id is what ties turns together into a session. Passing a constant, or a fresh value per request, are both ways to lose the conversational memory that is one of the main reasons to use this runtime rather than a plain service.
Operating it
Treat the deployed agent as a versioned artifact. Deploy a new one, test it against its resource name, switch traffic in your own application, then delete the old one — there is no built-in traffic split between two agent versions the way there is between Cloud Run revisions, so the cutover is yours to run.
# list what exists, because display names are not unique
for a in client.agent_engines.list():
print(a.api_resource.name, a.api_resource.display_name)
# delete, forcing removal of child sessions
client.agent_engines.delete(name=RESOURCE_NAME, force=True)Two operational notes. min_instances=0 means the first request after an idle period pays a cold start that includes the runtime restoring your agent, which is longer than a model call — the same trade as minimum instances and cold starts on Cloud Run, with the same answer that it depends on your traffic shape. And the tools your agent calls run as the deployed service account from inside Google’s network, so anything on a private network needs a reachable path and anything requiring a secret needs that grant on the agent’s identity rather than on yours.
Budget for two cost lines rather than one. The managed runtime bills for the instances it holds, on the min_instances and max_instances you set, and the model calls the agent makes bill separately as ordinary Vertex AI usage. For most agents the second dominates the first by a wide margin, and the reason is structural: an agent that calls a tool and then reasons about the result sends the entire conversation — system instruction, tool definitions, every previous turn and every tool response — on each pass. A three-tool-call answer is four model calls over a context that grows each time, so token spend scales roughly with the square of the turn count rather than linearly with it. That is the number to watch, and it is invisible in the runtime’s own metrics because it is billed against the model, not against the agent.