← Writing

Add Cryptographic Identity to Your AI Agents in 5 Minutes

The problem

Your agents call tools over HTTP. Every request is anonymous. The tool server has no idea which agent is calling, who authorized the call, or what scope constraints should apply. If an agent delegates to another agent, that delegation leaves no trace. There is no audit trail. No scope attenuation. No way to enforce "this agent can search but not send email."

OAuth 2.1 solves the first hop. It authenticates a client to a server. But when an orchestrator delegates to a researcher that delegates to a specialist, OAuth only sees the last hop. The chain of authority that led there is invisible. For regulated environments (finance, healthcare, government), this is not an inconvenience. It is a compliance gap.

BEFORE & AFTER WITHOUT AIP Orchestrator ??? Researcher ??? Tool Server No identity No identity Who called me? WITH AIP Orchestrator scope: * Researcher scope: search Tool Server aip:key:z6Fk3... aip:key:z6Mk2... Verified chain Every agent has an identity. Every delegation narrows scope. Every tool call is traceable to the root authority.

What AIP adds

The Agent Identity Protocol gives each agent a cryptographic identity (Ed25519 keypair) and issues scoped capability tokens that flow across delegation hops. Each hop can only narrow scope, never widen it. The full chain is tamper-evident and verifiable by any party in the chain.

The aip-agents package wraps this into framework adapters for Google ADK, CrewAI, and LangChain. You keep writing normal framework code. The plugin handles identity creation, token issuance, and delegation chains automatically.

1 Install

pip install aip-agents[adk]       # Google ADK
pip install aip-agents[crewai]    # CrewAI
pip install aip-agents[langchain] # LangChain
pip install aip-agents[all]       # all frameworks

Each variant pulls in agent-identity-protocol (the core SDK) and the cryptographic dependencies (Ed25519 via cryptography, Biscuit tokens via biscuit-python).

Google ADK Agent hierarchies

The ADK adapter understands agent trees. When you register a runner, it recursively walks the agent hierarchy, assigns each agent a cryptographic identity, and creates delegation chains that match the parent-child structure.

from google.adk import Agent, Runner
from aip_agents import AIPConfig
from aip_agents.adapters.adk import AIPAdkPlugin

# Define your agent hierarchy normally
coordinator = Agent(
    name="coordinator",
    tools=[search_tool, email_tool, browse_tool],
    sub_agents=[researcher, writer],
)

runner = Runner(agent=coordinator)

# Add identity (three lines)
plugin = AIPAdkPlugin(AIPConfig(
    app_name="research-app",
    log_tokens=True,
))

plugin.register(runner)
[AIP] Identity created: coordinator -> aip:key:ed25519:z6Fk3...
[AIP] Token issued: coordinator scope=['search', 'email', 'browse'] mode=chained
[AIP] Identity created: researcher -> aip:key:ed25519:z6Mk2...
[AIP] Delegation: coordinator -> researcher [scope: search, browse] [chain depth: 2]
[AIP] Identity created: writer -> aip:key:ed25519:z6Jk7...
[AIP] Delegation: coordinator -> writer [scope: write] [chain depth: 2]

Every agent in the tree now has an Ed25519 keypair, a verifiable identity, and a scoped delegation token. The coordinator's token covers all tools. Each sub-agent's token is automatically narrowed to the tools it actually has. A 3-level tree (coordinator > specialist > worker) produces a 3-hop delegation chain.

# Get headers for authenticated tool calls
headers = plugin.get_tool_call_headers("researcher")
# {"X-AIP-Token": "eyJ0eXAiOiJhaXArand..."}

# Check delegation depth
depth = plugin.get_chain_depth("researcher")  # 2

CrewAI Crews and roles

The CrewAI adapter registers identity for each agent by role. Define your crew normally, then add the plugin.

from crewai import Agent, Crew, Task, Process
from aip_agents import AIPConfig
from aip_agents.adapters.crewai import AIPCrewPlugin

researcher = Agent(
    role="researcher",
    goal="Find accurate, relevant information",
    backstory="Expert research analyst",
    tools=[search_tool],
)

writer = Agent(
    role="writer",
    goal="Produce clear summaries",
    backstory="Technical writer who values precision",
    tools=[],
)

crew = Crew(
    agents=[researcher, writer],
    tasks=[research_task, write_task],
    process=Process.sequential,
)

# Add identity
plugin = AIPCrewPlugin(AIPConfig(
    app_name="research-crew",
    log_tokens=True,
))

plugin.register(crew)

Each agent gets an identity keyed by its role. Create explicit delegation chains when agents hand off work:

delegation_token = plugin.create_delegation(
    parent_role="researcher",
    child_role="writer",
    task_description="Write summary based on research findings",
    scope=["write", "summarize"],
)
[AIP] Delegation: researcher -> writer [scope: write,summarize] [chain depth: 2]

LangChain Executors and supervisor patterns

The LangChain adapter works with AgentExecutors and the supervisor multi-agent pattern. Register agents by name.

from aip_agents import AIPConfig
from aip_agents.adapters.langchain import AIPLangChainPlugin

plugin = AIPLangChainPlugin(AIPConfig(
    app_name="my-app",
    log_tokens=True,
))

# Single agent
plugin.register(researcher_executor, name="researcher")

# Or multiple agents (supervisor pattern)
plugin.register_agents({
    "researcher": researcher_executor,
    "writer": writer_executor,
})

# Delegate from supervisor to specialist
plugin.create_delegation(
    parent_name="researcher",
    child_name="writer",
    task_description="Summarize findings",
    scope=["write"],
)

What delegation actually enforces

Regardless of framework, delegation tokens enforce the same constraints:

The delegation token is a chained Biscuit. It contains the full authority chain: who started it, who delegated to whom, what scope was allowed at each hop. Scope narrows at every hop. It can never widen.

DELEGATION CHAIN WITH SCOPE ATTENUATION Root Authority (Human/System) scope: search, browse, write, email depth 0 creates authority token Orchestrator scope: search, browse, write   $5.00 depth 1 email removed budget set delegates (narrows scope) Researcher scope: search, browse   $2.00 depth 2 Writer scope: write   $1.00 search removed tool:search ✓ tool:email ✗

Verifying tokens on the server side

If you are building the tool server that receives these tokens, verification is straightforward:

from aip_token.compact import CompactToken
from aip_token.error import TokenError

def verify_request(request, root_public_key):
    token_str = request.headers.get("X-AIP-Token")
    if not token_str:
        return 401, "Missing identity token"

    try:
        token = CompactToken.verify(token_str, root_public_key)
    except TokenError as e:
        return 403, f"Token rejected: {e.code}"

    if not token.has_scope("tool:search"):
        return 403, "Insufficient scope"

    # Token is valid. Agent identity is verified.
    # token.claims.iss tells you exactly who is calling.
    return 200, handle_request(request)

For chained tokens (multi-hop delegation), use ChainedToken.from_base64() followed by .authorize() which validates the entire delegation chain against the root public key.

Going further

Budget enforcement. Set budget_cents=500 in the authority token ($5.00). Each delegation can narrow the budget. Budgets are integer cents (Biscuit has no float support). The token encodes the budget constraint; your tool server enforces it at the point of action.

Delegation depth. Set max_depth=3 to cap how many hops a delegation chain can grow. An agent at depth 3 cannot delegate further, even if it tries. This prevents runaway agent spawning.

Key persistence. By default, keys are ephemeral (in-memory). Set persist_keys=True in AIPConfig to save them to ~/.aip/keys/. Persistent keys let agents maintain stable identities across restarts.

What is happening under the hood

AIP uses two token formats, selected automatically based on use case.

Compact tokens are standard JWTs with {"alg": "EdDSA", "typ": "aip+jwt"} headers, signed with Ed25519. Any JWT library that supports EdDSA can verify them. Use these for single-hop authentication (agent to tool server).

Chained tokens use the Biscuit format. Each delegation appends a new block signed by the delegating agent. Blocks are append-only; you cannot remove or modify earlier blocks. Scope attenuation is enforced by Datalog policy evaluation at each hop. Use these for multi-hop delegation chains.

The distinction matters for interoperability. If your tool server already handles JWTs, compact mode drops in with zero changes to your auth stack. If you need delegation chains, chained mode gives you cryptographic guarantees that no middleware or proxy can provide.

Token verification takes 0.049ms in Rust and 0.189ms in Python. In a real LLM agent chain, AIP adds 0.086% overhead to end-to-end latency. Identity is not the bottleneck.

Try it

pip install aip-agents[all]

Source: github.com/sunilp/aip

Paper: arXiv:2603.24775

The protocol is designed to solve a specific gap: agent systems have no identity layer. OAuth covers one hop. AIP covers the chain. If you are building multi-agent systems on ADK, CrewAI, LangChain, or any framework that delegates work across agents, this is the missing piece.

If you run into issues or have use cases that the current API does not cover, open an issue on the repo. The protocol is evolving based on what production deployments actually need.

Related