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


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](https://docs.cloud.google.com/gemini-enterprise-agent-platform/scale/runtime/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](/agents/5-ways-to-deploy/). 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](agent-platform-api-contract.svg)

*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](https://docs.cloud.google.com/gemini-enterprise-agent-platform/build/runtime/create-an-a2a-agent).
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](https://docs.cloud.google.com/run/docs/deploy-a2a-agents). 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:

```shell
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`:

```json
{"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](api-mode-routing.svg)

*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](adk-method-surface.svg)

*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:

```json
{
  "message": {"role": "user", "parts": [{"text": "Hello"}]},
  "events": [],
  "artifacts": [],
  "authorizations": {},
  "userId": "user1",
  "sessionId": "session1"
}
```

![How Gemini Enterprise hands a turn to an ADK agent through Agent Runtime](gemini-enterprise-adk-handoff.svg)

*Figure: In the native ADK integration, Gemini Enterprise hands over the context for one turn and receives the updated events, artifacts, and session ID as a stream.*

`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](https://docs.cloud.google.com/python/docs/reference/agentplatform/latest/vertexai.agent_engines.AdkApp)
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](/agents/serving-the-ap-api-contract/), I'll build that wrapper around
`AdkApp`, declare its `classMethods`, and call the deployed agent end to end.

