Skip to main content

Google ADK Integration

Google Agent Development Kit (ADK) is Google's framework for building multi-agent AI systems on top of Gemini models. Control Zero provides first-party Python integration for ADK, wrapping agents and tools with policy enforcement and audit logging.

This integration is Python-only. The Node.js and Go SDKs do not support ADK.

Installation

pip install 'controlzero[hosted]' google-adk

Quick Start: Governed Agent

The simplest way to add governance to an ADK agent is GovernedAgent with wrap_tools=True. This wraps every tool attached to the agent automatically.

from google.adk.agents import LlmAgent
from controlzero import Client
from controlzero.integrations.google_adk import GovernedAgent

cz = Client(api_key="cz_live_your_api_key_here")

agent = LlmAgent(
name="research_agent",
model="gemini-2.0-flash",
tools=[search_tool, summarize_tool],
instruction="You are a research assistant.",
)

governed = GovernedAgent(
agent=agent,
client=cz,
wrap_tools=True,
)

# Run the agent through the ADK runner as normal.
# All tool calls and agent invocations are now governed.

When wrap_tools=True (the default), every tool in agent.tools is replaced with a GovernedTool wrapper before the agent runs. No changes to your application logic are required.

GovernedAgent parameters

ParameterTypeDefaultDescription
agentBaseAgentrequiredThe ADK agent to wrap.
clientClientrequiredControlZero client.
agent_namestragent.nameName used for policy lookups and audit log entries. Defaults to the agent's own name.
wrap_toolsboolTrueAutomatically wrap all tools in agent.tools with governance.
allowed_toolslist[str]NoneOptional allowlist of tool names. Tools not in this list are removed from the agent before wrapping.
log_outputsboolFalseInclude a preview of the run output (up to 500 characters) in the audit log entry.

Running the governed agent

GovernedAgent exposes run_async, matching the ADK BaseAgent interface:

result = await governed.run_async(invocation_context=ctx)

If a policy denies execution, PolicyDeniedError is raised before the agent runs. All executions (allowed and denied) are recorded in the audit trail.

Manual Wrapping: governed_adk_tool Decorator

Use the governed_adk_tool decorator when you define tools as plain async functions. The decorated function must accept args and tool_context keyword arguments, matching the ADK run_async signature.

from controlzero import Client
from controlzero.integrations.google_adk import governed_adk_tool

cz = Client(api_key="cz_live_your_api_key_here")

@governed_adk_tool(cz, tool_name="web_search")
async def web_search(*, args, tool_context):
"""Search the web for information."""
query = args.get("query", "")
results = await do_search(query)
return {"results": results}

@governed_adk_tool(cz, tool_name="write_file")
async def write_file(*, args, tool_context):
"""Write content to a file."""
path = args.get("path")
content = args.get("content", "")
await save_file(path, content)
return {"status": "ok"}

The decorated function retains its original signature and docstring. Control Zero adds .name, .description, and .is_governed attributes to it so the ADK runner can introspect it normally.

The tool_name argument is optional. If omitted, the function name is used.

Bulk Wrapping: wrap_adk_tools

When you have a list of existing BaseTool instances, use wrap_adk_tools to wrap them all at once:

from google.adk.tools import BaseTool
from controlzero import Client
from controlzero.integrations.google_adk import wrap_adk_tools

cz = Client(api_key="cz_live_your_api_key_here")

# Wrap a list of existing ADK tool instances
governed_tools = wrap_adk_tools([search_tool, summarize_tool, write_tool], cz)

# Pass the governed tools to your agent
agent = LlmAgent(
name="ops_agent",
model="gemini-2.0-flash",
tools=governed_tools,
instruction="You are an operations assistant.",
)

wrap_adk_tools returns a list of GovernedTool instances in the same order as the input. Tools that are already GovernedTool instances are not double-wrapped.

Wrapping Individual Tools: GovernedTool

To wrap a single BaseTool instance directly:

from google.adk.tools import BaseTool
from controlzero import Client
from controlzero.integrations.google_adk import GovernedTool

cz = Client(api_key="cz_live_your_api_key_here")

class DatabaseQueryTool(BaseTool):
def __init__(self):
super().__init__(name="db_query", description="Run a read-only SQL query.")

async def run_async(self, *, args, tool_context):
sql = args.get("sql", "")
rows = await run_query(sql)
return {"rows": rows}

governed = GovernedTool(base_tool=DatabaseQueryTool(), client=cz)

GovernedTool delegates all attribute access to the underlying tool, so it behaves identically to the original from the ADK runner's perspective.

Handling Denied Actions

If a policy denies a tool call or agent run, PolicyDeniedError is raised:

from controlzero.errors import PolicyDeniedError

try:
result = await governed.run_async(invocation_context=ctx)
except PolicyDeniedError as e:
print(f"Blocked by policy: {e.decision.reason}")
# Handle gracefully: return fallback, notify user, etc.

Example Policy

{
"name": "adk-agent-policy",
"rules": [
{ "effect": "allow", "action": "tool:call", "resource": "tool/web_search" },
{ "effect": "allow", "action": "tool:call", "resource": "tool/summarize_tool" },
{ "effect": "deny", "action": "tool:call", "resource": "tool/write_file" },
{ "effect": "deny", "action": "tool:call", "resource": "tool/*" }
]
}

With this policy, web_search and summarize_tool are allowed. write_file and any other tool are denied.

Audit Logging

All governed tool calls and agent runs are written to the audit trail with the provider identified as google_adk. You can view them in the dashboard under Audit Log and filter by provider.

Each audit entry includes:

  • Tool or agent name
  • Policy decision (allow or deny) and reason
  • Execution latency
  • Run count (for agents, within the current session)
  • If log_outputs=True: a 500-character preview of the output

Python-Only

The ADK integration is available in the Python SDK only (controlzero[hosted] or controlzero[adk]). There is no equivalent integration in the Node.js or Go SDKs, as Google ADK does not have official bindings for those languages.