# Agent Platform BYOC without the wrapper: new in ADK 2.2 In [The Agent Platform API contract](/agents/ap-api-contract/) I described the two routes a bring-your-own-container agent has to serve — `/api/reasoning_engine` and `/api/stream_reasoning_engine` — and the FastAPI wrapper I wrote to dispatch them to an `AdkApp`. It worked, but the wrapper contained 117 lines of code unrelated to the agent itself. Comment: I wrote this post after updating the site's agent examples to ADK 2.2. –William As of ADK 2.2.0, you can delete that wrapper. A new flag, `--gemini_enterprise_app_name`, lets `adk api_server` serve those routes directly. The name is a little misleading for our purposes — the flag exists so you can register a self-hosted ADK server as an agent in Gemini Enterprise — but Gemini Enterprise uses the Agent Engine wire protocol, which is also the protocol Agent Runtime uses to forward requests to a BYOC container. Set the flag, and the same container image described in [4 ways to deploy agents on Agent Platform](/agents/4-ways-to-deploy/) (option 4) serves the entire contract without custom request-handling code.
Before, you implement the Agent Runtime API contract in a custom wrapper and maintain the agent code. With ADK 2.2, ADK implements the contract and the dashed responsibility box shrinks to just your agent code.
Scroll horizontally to explore. Open full-size diagram
`adk deploy agent_engine` in ADK 2.x uses the same approach: it generates a Dockerfile running `adk api_server` with `--gemini_enterprise_app_name` set. Here we're configuring it in our own image. You'll need `google-adk>=2.2.0` in `requirements.txt` (the flag doesn't exist in 1.x or 2.0/2.1). ## The previous wrapper Here is the previous implementation. The container served a hand-written FastAPI app instead of `adk api_server`: ```dockerfile {hl_lines=[16]} FROM python:3.12-slim WORKDIR /app COPY trading_agent/requirements.txt . RUN pip install --no-cache-dir -r requirements.txt RUN mkdir -p trading_agent COPY trading_agent/agent.py trading_agent/ COPY main.py . # Agent Runtime injects $PORT. Serve the BYOC FastAPI wrapper (not adk api_server) # so the container implements the :query / :streamQuery contract. Sessions + Memory # come from SESSION_SERVICE_URI / MEMORY_SERVICE_URI in the deployment env. CMD ["sh", "-c", "uvicorn main:app --host 0.0.0.0 --port ${PORT:-8080}"] ``` *[Dockerfile](https://github.com/WilliamDenniss/agent-examples/blob/master/08_ContainerizedForAgentPlatform/Dockerfile)* That `main.py` is the wrapper. It wraps the agent in `AdkApp`, then dispatches the `class_method` that the platform forwards. The dispatch code looks like this: ```python adk_app = agent_engines.AdkApp(agent=root_agent) @app.post("/api/reasoning_engine") async def query(http_request: Request) -> responses.JSONResponse: request = await _parse_request(http_request) method = getattr(adk_app, request.class_method) output = await _invoke_callable_or_raise(method, request.input or {}) return responses.JSONResponse( content=encoders.jsonable_encoder({"output": output}) ) @app.post("/api/stream_reasoning_engine") async def stream_query(http_request: Request) -> responses.StreamingResponse: request = await _parse_request(http_request) method = getattr(adk_app, request.class_method) output = await _invoke_callable_or_raise(method, request.input or {}) return responses.StreamingResponse( content=json_generator(output), media_type="application/json", ) ``` The complete implementation also JSON-encodes each streamed chunk, handles both sync and async generators, and parses the request body defensively. The full 117 lines are in [main.py](https://github.com/WilliamDenniss/agent-examples/blob/b79d454c65d61a049aa57cd2b149de37dfc460be/08_ContainerizedForAgentPlatform/main.py). ## The updated Dockerfile The updated version uses the site's general-purpose ADK container, which already runs locally, on Cloud Run, and on GKE, with two additions to the `CMD`: ```dockerfile {hl_lines=[16]} FROM python:3.12-slim WORKDIR /app COPY trading_agent/requirements.txt . RUN pip install --no-cache-dir -r requirements.txt RUN mkdir -p trading_agent COPY trading_agent/agent.py trading_agent/ # Sessions + Memory precedence: use explicit SESSION_SERVICE_URI / MEMORY_SERVICE_URI # when set. Otherwise, when Agent Runtime injects GOOGLE_CLOUD_AGENT_ENGINE_ID (a # BYOC deploy, see ../09_DeployContainerToAgentPlatform), use the engine's own # resource for both services. With neither set, use local storage. CMD ["sh", "-c", ": ${SESSION_SERVICE_URI:=${GOOGLE_CLOUD_AGENT_ENGINE_ID:+agentengine://$GOOGLE_CLOUD_AGENT_ENGINE_ID}}; : ${MEMORY_SERVICE_URI:=${GOOGLE_CLOUD_AGENT_ENGINE_ID:+agentengine://$GOOGLE_CLOUD_AGENT_ENGINE_ID}}; adk api_server --host 0.0.0.0 --port ${PORT:-8080} ${SESSION_SERVICE_URI:+--session_service_uri $SESSION_SERVICE_URI} ${MEMORY_SERVICE_URI:+--memory_service_uri $MEMORY_SERVICE_URI} ${GEMINI_ENTERPRISE_APP_NAME:+--gemini_enterprise_app_name $GEMINI_ENTERPRISE_APP_NAME} --no-reload /app"] ``` *[Dockerfile](https://github.com/WilliamDenniss/agent-examples/blob/master/05_Containerized/Dockerfile)* The image no longer includes `main.py`. When `--gemini_enterprise_app_name` is set, `adk api_server` registers `POST /api/reasoning_engine` and `POST /api/stream_reasoning_engine` alongside its normal routes, dispatching to an `AdkApp` internally. It uses the same `class_method` dispatch as our wrapper, with the thirteen-method allowlist we declare as `classMethods` at deploy time. It also adds middleware that reads the platform's trace header, which the wrapper didn't handle. One rule to know: the flag's value must be the name of an agent folder in the image (`trading_agent` here). It isn't a display name. It identifies the agent folder that the server exposes as the engine, and the server exits during startup if no folder matches. ## The environment variables Here's the `CMD` again with line wrapping enabled: ``` CMD ["sh", "-c", ": ${SESSION_SERVICE_URI:=${GOOGLE_CLOUD_AGENT_ENGINE_ID:+agentengine://$GOOGLE_CLOUD_AGENT_ENGINE_ID}}; : ${MEMORY_SERVICE_URI:=${GOOGLE_CLOUD_AGENT_ENGINE_ID:+agentengine://$GOOGLE_CLOUD_AGENT_ENGINE_ID}}; adk api_server --host 0.0.0.0 --port ${PORT:-8080} ${SESSION_SERVICE_URI:+--session_service_uri $SESSION_SERVICE_URI} ${MEMORY_SERVICE_URI:+--memory_service_uri $MEMORY_SERVICE_URI} ${GEMINI_ENTERPRISE_APP_NAME:+--gemini_enterprise_app_name $GEMINI_ENTERPRISE_APP_NAME} --no-reload /app"] ``` There are two parts to the environment-variable handling: **Opt-in flags.** `${VAR:+--flag $VAR}` is shell parameter expansion that produces the flag only when the variable is set, and nothing at all otherwise. The contract routes are enabled only when the deployment provides `GEMINI_ENTERPRISE_APP_NAME=trading_agent` in its env. Run the same image locally with just an API key and it runs `adk api_server` without the additional flag, exactly as it did before this change. Hardcoding `--gemini_enterprise_app_name` would make the server require Google Cloud credentials at startup, and the local `docker run` workflow would fail. Making the flag conditional preserves the local workflow. **Session and memory defaults.** The first two statements — `: ${SESSION_SERVICE_URI:=...}` — use the shell's no-op command (`:`) to assign a default when the variable is unset. The default is also conditional: `agentengine://$GOOGLE_CLOUD_AGENT_ENGINE_ID`, only if that variable exists. Agent Runtime injects `GOOGLE_CLOUD_AGENT_ENGINE_ID` into every BYOC container, and it's the engine's *own* resource id. On the platform, with no explicit URIs configured, the container configures sessions and Memory Bank to use its own `reasoningEngine` resource, matching the old wrapper's `AdkApp.set_up()` behavior. ADK accepts the resource id without a full `projects/.../locations/...` path because it obtains the project and location from `GOOGLE_CLOUD_PROJECT` and `GOOGLE_CLOUD_LOCATION`, which the platform also injects. The precedence order is: 1. **Explicit `SESSION_SERVICE_URI` / `MEMORY_SERVICE_URI`** (from the deploy env, or your `docker-env` file) — used as-is. This is how you'd configure several agents to use one shared sessions backend. 2. **No URIs, but running on Agent Runtime** — automatically use the engine's own resource through the injected id. 3. **Neither** (a plain local `docker run`) — use non-persistent local storage, which is suitable for a short local test. The engine's id isn't available when you set `SESSION_SERVICE_URI` at deploy time: the `create` call that carries the env vars also creates the engine. The `CMD` can set the URI once Agent Runtime provides the id at startup. Setting the value to the literal string `agentengine://$GOOGLE_CLOUD_AGENT_ENGINE_ID` doesn't work, because the shell doesn't re-expand variables found inside another variable's value. ## Deploying it The deploy script requires two changes. Build and push the general-purpose ADK image described above instead of the wrapper image, then add one line to the deployment env in the create request: ```json "env": [ {"name": "GOOGLE_GENAI_USE_VERTEXAI", "value": "TRUE"}, {"name": "GEMINI_ENTERPRISE_APP_NAME", "value": "trading_agent"}, ... ] ``` Everything else — the service account, the Artifact Registry IAM, and notably the `classMethods` list — stays exactly as it was in [deploy_byoc.sh](https://github.com/WilliamDenniss/agent-examples/blob/master/09_DeployContainerToAgentPlatform/deploy_byoc.sh). You still declare `classMethods` even though ADK now handles the requests: the platform can't introspect a container, the SDK builds its client methods from that list, and it's the same thirteen entries `adk deploy agent_engine` declares for you with a managed deployment. The [contract post](/agents/ap-api-contract/) covers what each method is for. One issue I observed: calling the sync `stream_query` method logs a `RuntimeError: coroutine raised StopIteration` traceback at the end of every stream. It's a bug in ADK's sync-generator bridging (present through 2.4.0 at the time of writing). The error occurs *after* the last event has been delivered, so the stream itself is complete and correct, but the traceback still appears in the logs. Call `async_stream_query` instead to avoid this behavior. [trade.sh](https://github.com/WilliamDenniss/agent-examples/blob/master/09_DeployContainerToAgentPlatform/trade.sh) runs against the new deployment unchanged, sessions are stored in the engine's own resource, and the custom wrapper file is no longer required.