Governed Google ADK in 3 minutes
Google’s Agent Development Kit is the cleanest path to a production agent on Gemini. LlmAgent, tools as plain Python callables, runners that handle the eval/deploy lifecycle, and a Plugin system for cross-cutting concerns. Three deployment targets out of the box (Agent Runtime, Cloud Run, GKE Autopilot) via the Agents CLI.
What it doesn’t ship: per-user identity attribution on tool calls, cross-tenant audit, output redaction, or pluggable policy. Google’s Agent Gateway + Model Armor exists for the managed Agent Runtime target, but it enforces Google policies, not yours.
This post is the 3-minute path to closing that gap with Agentic Control Plane on any ADK deployment — your tools, your code, no framework rewrite.
The pattern
Tool-layer governance via the @governed decorator. Each tool function is wrapped so every call runs preToolUse → handler → postToolOutput against ACP. Identity is bound once per request via set_context. ADK’s tool schema introspection follows __wrapped__ (preserved by functools.wraps), so the decorator is invisible to ADK’s signature builder.
LLM calls go direct to Gemini (or Vertex) with your own key. Governance is at the tool layer, not the LLM layer. If you also want LLM-layer governance — for cost attribution, rate limits, or model-call audit — pair this with LiteLLM pointing at the ACP proxy, but the tool-layer pattern is the production default.
Three minutes from blank slate
1. Install
pip install acp-governance google-adk
2. Wrap your tools
from acp_governance import configure, governed, set_context
from google.adk.agents import Agent
from google.adk.runners import InMemoryRunner
from google.genai import types
import asyncio, os
configure(base_url="https://api.agenticcontrolplane.com")
@governed("lookup_record")
def lookup_record(id: str) -> dict:
"""Look up a record by ID."""
return db.records.find_one({"id": id})
async def main():
set_context(
user_token=os.environ["ACP_USER_TOKEN"],
agent_name="my-adk-agent",
agent_tier="background",
)
agent = Agent(
name="my_adk_agent",
model="gemini-flash-latest",
instruction="You are an ACP-governed agent. Use the tools available.",
tools=[lookup_record],
)
runner = InMemoryRunner(agent=agent, app_name="acp-demo")
await runner.session_service.create_session(
app_name="acp-demo", user_id="user-1", session_id="session-1"
)
msg = types.Content(role="user", parts=[types.Part(text="Look up record id=abc-123.")])
async for event in runner.run_async(
user_id="user-1", session_id="session-1", new_message=msg
):
if event.is_final_response():
print(event.content.parts[0].text)
return
asyncio.run(main())
3. Run it
export ACP_USER_TOKEN=gsk_...
export GOOGLE_API_KEY=... # from aistudio.google.com/apikey
python my_agent.py
Open cloud.agenticcontrolplane.com/activity. One row appears for lookup_record — actor, tool, decision, session, latency, input/output preview with redactions applied if your policy says so. That’s it. Three minutes, two lines of integration code, full audit.
What @governed does
Every call through the wrapped function:
- POSTs to ACP’s
/govern/tool-usewith the tool name, input arguments, and the user token bound byset_context. - If ACP denies, returns the string
"tool_error: <reason>"— Gemini sees this as a tool result and adapts its next turn. - If ACP allows, runs your function.
- POSTs the output to
/govern/tool-outputfor audit logging and PII / secret scanning. - If ACP redacts, replaces the output with the redacted version (configurable per-handler).
- If ACP blocks the output post-hoc (a leaked secret pattern, for example), returns
"tool_error: <reason>".
The function signature, type hints, and docstring are preserved via functools.wraps. ADK’s inspect.signature walks through __wrapped__ to build the tool schema correctly — your tool’s schema is what you wrote, not what the decorator produced.
What set_context does
set_context binds the end-user identity to the current execution context. Every governed call inside the same context reads from it automatically — no threading the user token through every tool function.
set_context(
user_token=verified_jwt, # the verified end-user identity
agent_name="my-adk-agent", # how this agent appears in the dashboard
agent_tier="background", # interactive / subagent / background / api
session_id=request_id, # optional, auto-generated if omitted
)
agent_tier is the lever for tier-aware policy: a destructive verb that’s allowed in interactive (a human is at the keyboard) gets denied in background (autonomous, no human anywhere). Match the tier to actual deployment reality, not what’s permissive.
The user_token is the authenticated end user — a verified JWT from your IdP, a Firebase ID token, or a gsk_ workspace key for testing. ACP validates the signature on every governed call and uses the verified subject for audit attribution and policy evaluation.
Adding more tools
Each tool gets its own @governed decorator and gets passed to Agent(tools=[...]):
@governed("send_email")
def send_email(to: str, subject: str, body: str) -> dict:
"""Send an email."""
return mailer.send(to=to, subject=subject, body=body)
@governed("query_db")
def query_db(sql: str) -> list[dict]:
"""Run a SQL query."""
return db.execute(sql)
agent = Agent(
...,
tools=[lookup_record, send_email, query_db],
)
Add or remove tools by editing the tools list. Governance is automatic for every @governed-wrapped function in scope.
Why decorator and not a Plugin?
ADK has a Plugin class registered on the Runner that applies cross-cutting concerns across all agents and tools — and it’s a clean API for governance. ACP will eventually ship an ACPGovernancePlugin(BasePlugin) for ADK-specific deployments. Today, the decorator pattern is preferred because:
- It’s framework-agnostic. The same
@governeddecorator works in CrewAI, LangGraph, Pydantic AI, AutoGen, plain Python — anywhere a Python function is the unit of tool dispatch. If you have multiple frameworks in your stack, you don’t relearn governance per framework. - It’s invisible at the tool definition site. ADK’s signature inspection works through it transparently.
- It composes with anything. You can stack
@governedwith@function_tool, with@toolfrom LangChain, with whatever wrapper your framework expects.
The forthcoming Plugin will be the right pick for ADK-only deployments where you want one-line setup at the runner level. Until then, decorate.
Where the audit lands
Every governed call lands at cloud.agenticcontrolplane.com/activity with:
- Actor — the verified user from
user_token - Tool name — the string passed to
@governed("...") - Decision — allow / deny / redact / block / fail-open + reason
- Session — groups all calls from one
set_contextscope - Findings — PII or secret patterns detected in input or output
- Tier — the
agent_tieryou bound
ADK tool calls sit alongside Claude Code, Cursor, Codex CLI, CrewAI, LangGraph, and any other framework using ACP — one audit log across every agent surface in your stack. Your CISO asks “what did our AI do for user X this week?” and the answer is a single dashboard query, not a six-system join.
ADK-specific notes
Vertex AI vs direct Gemini. The starter uses direct Gemini (GOOGLE_GENAI_USE_VERTEXAI=False) with an API key from aistudio.google.com/apikey. For Vertex, set GOOGLE_GENAI_USE_VERTEXAI=True, GOOGLE_CLOUD_PROJECT=..., and use Application Default Credentials. The governance code is identical — only the LLM auth changes.
Single-shot via InMemoryRunner. ADK requires an explicit Runner + SessionService + event iteration even for one-shot calls. Heavier than CrewAI’s crew.kickoff() or LangGraph’s agent.invoke(), but the pattern is canonical. InMemoryRunner is fine for local dev; production deploys to Agent Runtime, Cloud Run, or GKE Autopilot via the Agents CLI.
Agent Gateway interaction. If you deploy to Google’s managed Agent Runtime, Google’s Agent Gateway + Model Armor enforces Google-defined policies before your code runs. ACP’s @governed decorator runs in your code, after Agent Gateway. The two layers are complementary — Agent Gateway for Google’s prompt-injection / data-leakage protections, ACP for your customer-pluggable policy, identity attribution, and audit.
Where this fits
The starter at acp-governance-sdks/examples/starters/google-adk is the runnable reference. Clone, swap the placeholder tool, ship.
Google Agents CLI integration guide → · Reference architecture → · Three-minute integrations index → · Get started →