Add Cryptographic Identity to Your Google ADK Agents
Wire AIP into a Google ADK agent hierarchy: the coordinator and every sub-agent get distinct cryptographic identities, delegation depth is enforced at the token level, and tool calls carry verifiable auth headers.
Prerequisites: Python 3.10+, google-adk installed
The problem
Your ADK agents call tools anonymously. When the coordinator routes a task to a sub-agent, no identity verification happens. The tool server has no way to confirm which agent is making the request, let alone whether that agent was authorized to do so. If a tool server is compromised, any agent can impersonate any other. There is no audit trail of who authorized what.
AIP gives each agent a self-certifying Ed25519 keypair and an AIP identifier. Every tool call carries a signed token that records the issuing agent, the permitted scope, the budget ceiling, and the delegation depth. The tool server verifies the signature before serving the request. If the token is missing, expired, or out of scope, the request is rejected.
Install
Install the Google ADK adapter for AIP:
pip install aip-agents[adk]This pulls in agent-identity-protocol (core SDK) and the ADK integration layer. For other frameworks: aip-agents[crewai], aip-agents[langchain], or aip-agents[all].
Add AIP to your app
Create a plugin, build your agent tree as normal, wrap it in a Runner, then register. One call wires identity into the root agent and every sub-agent:
from google.adk import Agent, Runner
from aip_agents import AIPConfig
from aip_agents.adapters.adk import AIPAdkPlugin
# Create the plugin
plugin = AIPAdkPlugin(AIPConfig(app_name="my-adk-app"))
# Define the root agent
root_agent = Agent(
name="coordinator",
model="gemini-2.5-flash",
instruction="Route tasks to the appropriate sub-agent.",
description="Task coordinator",
tools=[],
)
# Register AIP -- this is the only line you add
runner = Runner(agent=root_agent)
plugin.register(runner)
# Get auth headers for outgoing tool calls
headers = plugin.get_tool_call_headers("specialist")
print(headers)
# {"X-AIP-Token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9..."}
What just happened
When you called plugin.register(runner), AIP walked the agent tree starting from the root and:
- Generated an Ed25519 keypair for each agent in the hierarchy
- Assigned each agent an AIP identifier (
aip:key:ed25519:z6Mkf5...) - Issued a signed compact token scoped to that agent's tools and position in the tree
- Stored the tokens so
get_tool_call_headers()can retrieve them on demand
The token flow looks like this:
Agent (coordinator)
--> AIPAdkPlugin.get_tool_call_headers("coordinator")
--> returns {"X-AIP-Token": "<signed-jwt>"}
--> outgoing request to MCP server
--> server verifies signature + scope
--> serves request or returns 401
You can inspect the delegation depth for any agent by name:
depth = plugin.get_chain_depth("coordinator")
print(f"coordinator depth: {depth}")
# coordinator depth: 0 (root has no parent)
Multi-agent delegation
ADK's sub_agents tree maps naturally onto AIP delegation chains. The coordinator gets depth 0; each sub-agent gets depth 1; their sub-agents get depth 2; and so on. Scope attenuates at each hop -- a sub-agent can only exercise a subset of the permissions the coordinator was granted:
from google.adk import Agent, Runner
from aip_agents import AIPConfig
from aip_agents.adapters.adk import AIPAdkPlugin
plugin = AIPAdkPlugin(AIPConfig(
app_name="my-adk-app",
log_tokens=True, # prints identity and delegation events
))
# Sub-agents
summarizer = Agent(
name="summarizer",
model="gemini-2.5-flash",
instruction="Summarize the research findings concisely.",
description="Summarization specialist",
tools=[],
)
fact_checker = Agent(
name="fact_checker",
model="gemini-2.5-flash",
instruction="Verify claims against sources.",
description="Fact verification agent",
tools=[],
)
# Root agent with sub-agent tree
coordinator = Agent(
name="coordinator",
model="gemini-2.5-flash",
instruction="Route tasks to the appropriate sub-agent.",
description="Task coordinator",
tools=[],
sub_agents=[summarizer, fact_checker],
)
runner = Runner(agent=coordinator)
plugin.register(runner)
# Inspect the delegation chain depths
for name in ["coordinator", "summarizer", "fact_checker"]:
depth = plugin.get_chain_depth(name)
print(f"{name}: depth {depth}")
# coordinator: depth 0
# summarizer: depth 1
# fact_checker: depth 1
Set max_depth in AIPConfig to enforce a ceiling on delegation depth. Any token with a depth exceeding the limit is rejected by the verifier, preventing runaway delegation chains.
Try to break it
Understanding what AIP rejects is as important as what it accepts.
Tamper with the token:
from aip_token.compact import CompactToken
token = plugin.get_tool_call_headers("summarizer")["X-AIP-Token"]
tampered = token[:-5] + "XXXXX"
# On the server side:
# CompactToken.verify(tampered, public_key_bytes) raises:
# SignatureError: signature verification failed
Use a token outside its scope:
# summarizer token is scoped to ["summarize", "fetch"]
# Trying to call a tool outside that scope:
#
# Server checks: "tool:email" not in token.scope
# Server returns: 401 Unauthorized -- scope mismatch
Exceed the delegation depth ceiling:
from aip_agents import AIPConfig
from aip_agents.adapters.adk import AIPAdkPlugin
plugin = AIPAdkPlugin(AIPConfig(
app_name="my-adk-app",
max_depth=1, # allow coordinator -> sub-agent, nothing deeper
))
# Any attempt to create a depth-2 delegation is rejected:
# DelegationError: max_depth exceeded (got 2, limit 1)
Next steps
- CrewAI integration guide -- role-based identity for CrewAI crews
- LangChain integration guide -- supervisor patterns with multi-agent auth
- MCP proxy guide -- enforce AIP at the transport layer
- Full specification -- protocol details for implementers
- Read the paper -- design rationale, experiments, adversarial evaluation