The Agent Platform API contract: what your agent must expose, and why

Copy as Markdown

Agent 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.

How a call reaches your agent through the Agent Platform API contract

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).

How api_mode determines request routing

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.

The AdkApp method surface

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.