The Agent Platform API contract: what your agent must expose, and why
Copy as MarkdownAgent Runtime can host a container with arbitrary HTTP endpoints. To use it through the Agent Platform Python SDK or the Google Cloud console playground — or through Gemini Enterprise’s native ADK integration — the container must also implement the operations those integrations expect. These routes and request formats are the HTTP side of the Agent Platform runtime contract. Agent Platform builds the serving layer for object and source deployments; with a custom Dockerfile or prebuilt container, you serve it yourself. I covered those choices in 5 ways to deploy agents on Agent Platform. This post explains why the contract exists and what an ADK agent exposes through it.
Why the contract exists
With object and source deployments, Agent Platform builds the serving layer around your agent. A custom Dockerfile or container image, however, is opaque: Agent Runtime can start it, but it can’t infer which URL begins a turn, which operations stream, how to pass arguments, or how the response is framed. The contract supplies those answers.
This dispatch mechanism is also what lets the rest of the platform reach your
agent, but the transport alone isn’t enough. The Python SDK can expose any
declared operation, leaving your code to choose which one to call. Named
integrations use a more specific profile: the Google Cloud console playground
calls stream_query, while Gemini Enterprise’s native ADK integration calls
streaming_agent_run_with_events.
For standard query calls, the contract has two pieces. First, your container
implements one or both fixed POST routes: /api/reasoning_engine for unary
calls and /api/stream_reasoning_engine for streaming. Second, the deployed
reasoningEngine declares a classMethods list. Each entry names an operation
on your agent wrapper — create_session or async_stream_query, for
example — and says whether it is unary or streaming. The SDK uses that mode to
choose the public API; Agent Runtime maps the call to a container route and
sends the operation name and its arguments in the request body.
Figure: Agent Runtime translates the public API request into a method invocation inside your container.
Agent Runtime dispatches by classMethod because agents don’t all expose the
same interface. Your own application could declare my_custom_method and invoke
it explicitly. A standard integration wouldn’t know what my_custom_method
means, though; it calls the operation name and payload shape defined by its
integration profile. The fixed routes and request envelope are
framework-independent; the declared method surface isn’t. A LangGraph agent
might expose query, stream_query, and checkpoint operations, while AdkApp
supplies the conventional operations expected by ADK callers.
Note: Agent Runtime also supports Agent2Agent (A2A)
agents.
A2A is a separate agent-facing protocol built around an Agent Card and message
and task operations. This post covers the general Agent Runtime container
contract, then uses AdkApp as the detailed example. A2A support is currently
in Preview.
Tip: If you want to expose A2A directly rather than use the reasoningEngine
query interface, you can deploy an A2A agent from source on Cloud
Run. That path doesn’t
use classMethods or the two fixed Agent Runtime routes, although your source
still needs to provide the A2A server, Agent Card, and task storage. Cloud Run’s
A2A support is also in Preview.
How a call reaches your agent
Every call through the standard contract is a POST to one of two endpoints on
the reasoningEngine resource, with a small envelope naming the method and
carrying its arguments:
curl -s -X POST \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
"https://${REGION}-aiplatform.googleapis.com/v1/${RESOURCE_NAME}:query" \
-d '{"classMethod": "create_session", "input": {"user_id": "user1"}}'
Note: The public REST schema calls the field classMethod, while the container
contract calls it class_method. Because the public API uses ProtoJSON, it
accepts either spelling — which is why some Google examples use class_method
there too. I’ll use the canonical REST spelling, classMethod, for public calls
and class_method when testing the container directly.
The input dict becomes the method’s keyword arguments, and the result comes
back wrapped in output:
{"output": {"id": "5390331552844087296", "userId": "user1", "appName": "...", "events": []}}
Which endpoint a method lives on is decided by the api_mode you declare for
it: "" (sync) and "async" methods are served by :query, while "stream"
and "async_stream" methods are served by :streamQuery, which returns
newline-delimited JSON events as they’re produced (add ?alt=sse if you’d
rather have server-sent-events framing).
Figure: api_mode selects the public Agent Platform endpoint, the fixed route inside your container, and whether the response is unary or streaming.
Tip: You can discover any deployed agent’s surface by fetching the resource
itself — a GET on the reasoningEngine returns spec.classMethods, the same
list its deployer declared.
The standard AdkApp operation surface
Our Trading Agent uses the complete surface that AdkApp declares today. Think
of it as an ADK integration profile layered on the runtime contract: a set of
operation names and payload shapes that different callers recognize.
| Methods | api_mode |
Served by | Primary caller and purpose |
|---|---|---|---|
create_session, get_session, list_sessions, delete_session |
"" |
:query |
Older direct clients: conversation lifecycle |
async_create_session, async_get_session, async_list_sessions, async_delete_session |
"async" |
:query |
Direct clients: conversation lifecycle |
async_add_session_to_memory, async_search_memory |
"async" |
:query |
Direct clients: long-term memory |
stream_query |
"stream" |
:streamQuery |
Console playground and older direct clients |
async_stream_query |
"async_stream" |
:streamQuery |
Direct clients: the conversation itself |
streaming_agent_run_with_events |
"async_stream" |
:streamQuery |
Gemini Enterprise’s native ADK integration: conversation and context handoff |
No single caller uses all thirteen methods. Gemini Enterprise’s native ADK
integration calls streaming_agent_run_with_events; the console playground
calls stream_query; and direct application code chooses among the SDK-facing
operations. In that sense, this table is AdkApp’s interface contract, not the
minimum BYOC runtime contract or a checklist of methods Gemini Enterprise calls.
The four synchronous session methods and stream_query are deprecated for
direct application use in favor of their async counterparts, but they remain
in the registered surface for compatibility. Notice what’s missing from this
surface: there’s no plain query. A custom wrapper can declare a unary query,
but only a caller that explicitly chooses that operation will use it; Gemini
Enterprise’s native ADK integration won’t substitute it for its handoff
operation.
Figure: AdkApp exposes thirteen methods grouped into session lifecycle, conversation, long-term memory, and Gemini Enterprise’s native ADK integration.
Here’s what each group is for.
Streaming queries: the conversation itself
async_stream_query is the normal application-facing conversation method: pass
a user_id, an optional session_id, and a message, and it yields ADK events
through :streamQuery. stream_query provides the older synchronous-generator
version of the same flow.
Streaming is a natural fit for the Trading Agent because one turn can fetch
news, query the portfolio, and place several orders. The caller sees tool calls,
tool results, and model events as they happen instead of waiting for the whole
cycle to finish. It also gives you the exact arguments passed to
place_trade_order, which is pretty useful when the tool moves money.
Sessions: the conversation state
A session is one conversation: its event history plus working state. The create,
get, list, and delete methods provide the lifecycle you need for a chat UI, with
every operation scoped to a user_id. On Agent Runtime, AdkApp uses the
persistent Sessions service by default, so that state lives outside your
container and survives instance replacement.
The sync and async names perform the same jobs and all go through :query; the
api_mode tells the serving layer how to invoke them. New application code
should use the async versions.
Memory: what survives the conversation
Sessions preserve one conversation; Memory Bank extracts information that can
be recalled in later sessions. async_add_session_to_memory triggers memory
generation from a session’s events, while async_search_memory retrieves
relevant memories for a user.
That write step isn’t automatic — the application or agent decides when to add
a session or its events to memory. Our agent does it from an
after_agent_callback and also saves a structured record of each trade, so a
later cycle can recall why it bought or sold a stock.
streaming_agent_run_with_events: the native Gemini Enterprise ADK handoff
The awkward name makes more sense if you think of this method as a handoff
adapter, rather than another way to send a chat message. async_stream_query is
shaped for your own application code: it accepts a message, user id, and
optional session context. streaming_agent_run_with_events lets an outer
application hand over the additional context needed to run the next turn.
Inside its single request_json string, the envelope looks roughly like this:
{
"message": {"role": "user", "parts": [{"text": "Hello"}]},
"events": [],
"artifacts": [],
"authorizations": {},
"userId": "user1",
"sessionId": "session1"
}
AdkApp parses that handoff and resumes the supplied session. If it can’t find
the session, it creates one and seeds it with the supplied prior events and
artifacts. Forwarded OAuth tokens become temporary agent state without being
saved in the session. AdkApp then runs the agent asynchronously and streams
back envelopes containing the new ADK events, any changed artifacts, and the
session id. If the caller doesn’t supply a session id, it uses a temporary
in-memory session and deletes it after the turn.
That’s the useful distinction for the native ADK integration: Gemini Enterprise
can remain the outer chat application while handing an ADK agent the history,
files, credentials, and identity needed for one turn, then receive the updated
state back. Google’s
AdkApp
reference
recommends async_stream_query for normal application code;
streaming_agent_run_with_events exists for this richer native ADK integration
with Gemini Enterprise.
For this AdkApp deployment, the Google Cloud console playground is separate.
It uses the standard streaming contract: /api/stream_reasoning_engine and
stream_query.
streaming_agent_run_with_events isn’t restricted to Gemini Enterprise — you
can call it yourself if you declare it — but its richer envelope is designed
for that product handoff, not as the default query API for your own application.
Serving the contract
So that’s the surface. For object and source deployments, Agent Platform builds the serving layer for you. With a custom Dockerfile or prebuilt container, you implement the fixed routes you need, dispatch each declared class method, and return the unary or streaming response that Agent Runtime expects.
In Serving the Agent Platform API
contract, I’ll build that wrapper around
AdkApp, declare its classMethods, and call the deployed agent end to end.