An AI agent rarely acts alone anymore. One agent researches, another writes, another calls tools, and a fourth may approve or retry the work. That chain feels powerful until one weak link borrows permissions it should never have had.
The next failure mode for production AI apps is not only “the model hallucinated.” It is “nobody can explain why this downstream agent was allowed to send that request.”
If you are building AI workflows for customers, internal teams, support operations, sales ops, analytics, or developer automation, you need more than a prompt that says “be careful.” You need an authorization ledger that records who delegated what, to which agent, for which purpose, under which limits, and whether every tool call stayed inside that chain of trust.
The goal is simple: every agent action should carry a verifiable permission story.
The problem: agent chains blur responsibility
A single-agent workflow is already hard to secure. A multi-agent workflow is harder because intent gets fragmented.
Imagine this workflow:
- A user asks an assistant to prepare renewal notes.
- The planner agent creates subtasks.
- A research agent pulls CRM and support data.
- A writing agent drafts the message.
- A send agent pushes it to email or Slack.
Each step may look harmless in isolation. The planner never sends an email. The research agent never writes to production. The writing agent only creates text. The send agent only executes a final action.
But the chain can still fail.
A prompt-injected support ticket may convince the research agent to include hidden instructions. The writing agent may transform those instructions into a confident recommendation. The send agent may inherit the original user's broad OAuth token and deliver the message to the wrong external recipient.
The issue is not only authorization at the final API call. The issue is authorization across the chain.
A safe system should answer these questions before every risky action:
- Who requested this work?
- Which agent delegated this step?
- What scopes were delegated?
- What constraints apply?
- Was the task purpose preserved?
- Did any agent try to expand its authority?
- Is the tool call allowed for this tenant, user, workflow, and risk tier?
- Can we replay the decision later?
If the answer lives only in logs, prompts, or vibes, the system is fragile.
What is an A2A authorization ledger?
An A2A authorization ledger is an append-only record of delegation and authorization decisions across agent-to-agent workflows.
It does not replace OAuth, API keys, IAM, row-level security, or your existing policy engine. It connects them.
Think of it as the permission receipt layer for multi-agent execution.
A good ledger records:
- the original human or system principal
- each agent in the chain
- the task purpose
- delegated scopes
- constraints such as time, cost, count, tenant, recipient, and data class
- policy decisions
- tool calls
- approvals
- denials
- expiry
- evidence links
The ledger should be easy to query. When something goes wrong, you should not need to reconstruct a timeline from chat transcripts.
The architecture in one flow
Here is a simple model:
User request
-> creates root delegation
-> planner receives scoped delegation
-> planner creates child delegation for research
-> research calls read tools through policy sidecar
-> writer receives only allowed context and draft scope
-> send agent requests write action
-> policy sidecar checks full delegation chain
-> approval gate may pause risky action
-> tool executes or is denied
-> ledger stores the decision and evidence
The key design choice is this: agents can request actions, but a policy layer decides whether those actions fit the delegation chain.
The core data model
Start with four objects.
1. Principal
A principal is the identity that originally authorized the work.
{
"principal_id": "user_123",
"principal_type": "human_user",
"tenant_id": "tenant_acme",
"auth_source": "oauth",
"session_id": "sess_789"
}
This must map to your real auth system. Do not invent a fake principal just because the request came through an agent.
2. Delegation
A delegation says one actor gave another actor limited authority for a specific purpose.
{
"delegation_id": "del_01JABC",
"parent_delegation_id": null,
"issuer": "user_123",
"subject": "planner_agent:v2",
"tenant_id": "tenant_acme",
"purpose": "prepare_renewal_summary",
"scopes": ["crm:read", "tickets:read", "draft:create"],
"constraints": {
"max_tool_calls": 30,
"max_cost_usd": 1.50,
"allowed_data_classes": ["customer_profile", "support_ticket_summary"],
"denied_data_classes": ["payment_card", "secret", "private_note"],
"expires_at": "2026-08-29T12:30:00Z"
},
"created_at": "2026-08-29T12:00:00Z"
}
Child delegations must be narrower than parent delegations. If the planner has crm:read, it can delegate crm:read:account_summary, but it should not delegate crm:write.
3. Authorization decision
Every tool call should produce a decision.
{
"decision_id": "authz_01JXYZ",
"run_id": "run_456",
"delegation_id": "del_01JCHILD",
"agent_id": "research_agent:v4",
"tool": "crm.get_account",
"action": "crm:read:account_summary",
"resource": "account_456",
"decision": "allow",
"policy_version": "agent-authz-2026-08-29.3",
"reason_codes": ["scope_match", "tenant_match", "budget_available"],
"created_at": "2026-08-29T12:04:11Z"
}
Reason codes matter. They make debugging faster and help you find policy gaps.
4. Tool receipt
The tool receipt records the actual execution outcome.
{
"tool_receipt_id": "tool_01J999",
"decision_id": "authz_01JXYZ",
"tool": "crm.get_account",
"status": "success",
"input_hash": "sha256:...",
"output_hash": "sha256:...",
"cost_units": 1,
"latency_ms": 240,
"redactions_applied": ["email", "phone"],
"created_at": "2026-08-29T12:04:12Z"
}
Policy checks that catch real failures
A useful authorization ledger needs policy rules. These are the checks I would implement first.
Scope narrowing
Every child delegation must be equal to or narrower than its parent.
Bad:
Parent: tickets:read
Child: tickets:read, tickets:write, email:send
Good:
Parent: tickets:read
Child: tickets:read:summary
This prevents a planner agent from accidentally creating a more powerful worker agent.
Purpose binding
The action must fit the original purpose.
If the purpose is prepare_renewal_summary, the system may allow CRM reads and draft creation. It should not allow discount:apply or user:delete just because the same user has those permissions elsewhere.
Tenant match
Every delegation and tool call must include a tenant. The tenant must match the principal and the resource.
Data-class limits
Classify tool outputs before they enter context. A research agent may be allowed to read ticket summaries but not payment fields, secrets, raw credentials, private admin notes, or health data.
Budget limits
Authorization is not only about security. It is also about cost.
Track:
- maximum model calls
- maximum tool calls
- maximum external API calls
- maximum dollars or credits
- maximum retries
- maximum wall-clock time
When the budget is gone, the agent should ask for help, downgrade, or stop.
Write-action approvals
Some actions should pause even when technically allowed.
Examples:
- sending external messages
- issuing credits
- deleting records
- changing permissions
- exporting data
- running code against production
A ledger makes approvals cleaner because the reviewer sees the full chain, not just a button that says “approve.”
A minimal TypeScript policy check
Here is a simplified policy function. Real systems should use a policy engine, but the shape matters more than the tool.
type Delegation = {
delegationId: string;
parentDelegationId?: string;
tenantId: string;
subject: string;
purpose: string;
scopes: string[];
constraints: {
expiresAt: string;
maxToolCalls?: number;
allowedDataClasses?: string[];
deniedDataClasses?: string[];
};
};
type ToolRequest = {
tenantId: string;
agentId: string;
action: string;
resourceTenantId: string;
dataClasses: string[];
purpose: string;
};
function isScopeAllowed(scopes: string[], action: string) {
return scopes.some(scope =>
action === scope || action.startsWith(scope + ":")
);
}
function authorizeToolCall(
delegation: Delegation,
request: ToolRequest,
now = new Date()
) {
const reasons: string[] = [];
if (new Date(delegation.constraints.expiresAt) < now) {
return { decision: "deny", reasons: ["delegation_expired"] };
}
if (delegation.tenantId !== request.tenantId || request.tenantId !== request.resourceTenantId) {
return { decision: "deny", reasons: ["tenant_mismatch"] };
}
if (delegation.subject !== request.agentId) {
return { decision: "deny", reasons: ["wrong_agent_subject"] };
}
if (!isScopeAllowed(delegation.scopes, request.action)) {
return { decision: "deny", reasons: ["scope_missing"] };
}
if (delegation.purpose !== request.purpose) {
return { decision: "deny", reasons: ["purpose_mismatch"] };
}
const denied = delegation.constraints.deniedDataClasses ?? [];
if (request.dataClasses.some(dataClass => denied.includes(dataClass))) {
return { decision: "deny", reasons: ["denied_data_class"] };
}
reasons.push("scope_match", "tenant_match", "purpose_match");
return { decision: "allow", reasons };
}
Where OPA or a sidecar fits
Open Policy Agent-style sidecars are attractive because they separate policy decisions from application code. Instead of sprinkling if agent can do X checks across every tool handler, each tool call asks a local or nearby policy service:
Can agent research_agent:v4 perform crm:read:account_summary
on account_456 for tenant_acme under delegation del_01JCHILD?
The sidecar can load policy bundles, check the delegation chain, evaluate budgets, and return a signed decision. The app then stores that decision in the ledger.
Start with a policy module inside your backend. Move to a sidecar when policy reuse becomes painful across tools and services.
How to prevent permission drift between agents
Permission drift happens when each agent adds a tiny assumption. Use these guardrails:
Make delegation explicit
Never pass raw user tokens from agent to agent. Pass delegation IDs and short-lived tool tokens minted from those delegations.
Narrow on every hop
Each child delegation should remove power, not add it.
Bind context to permissions
If an agent receives customer context, record which delegation allowed it. If that context is reused later, the next agent should inherit the same restrictions.
Block privilege laundering
An agent should not be able to turn read access into write access by summarizing hidden instructions into a new task.
Treat “agent said user wanted it” as untrusted
The ledger should trust signed delegations and policy decisions, not model-generated claims about user intent.
What to log without creating a privacy problem
A ledger can become a liability if it stores too much. Keep it useful but lean.
Log these by default:
- IDs for tenant, user, agent, run, delegation, decision, and tool receipt
- scopes and constraints
- policy version
- allow or deny
- reason codes
- data classifications
- hashes of inputs and outputs
- cost and latency
- approval references
Avoid storing these by default:
- raw prompts with secrets
- raw tool outputs
- full customer records
- OAuth tokens
- private user messages not needed for audit
- embeddings that cannot be deleted or scoped safely
For sensitive workflows, store detailed traces in encrypted, short-retention storage and link them from the ledger.
Common implementation mistakes
Mistake 1: using prompts as policy
A system prompt can instruct the agent. It cannot enforce authorization. If the tool accepts the call, the prompt is not a boundary.
Mistake 2: only checking the final tool call
By the time the final send or write happens, the harmful instruction may have passed through three agents. Check every hop.
Mistake 3: treating all agents as the same identity
assistant is not a useful identity. Use stable agent IDs with versions, roles, owners, and allowed tools.
Mistake 4: skipping deny logs
Deny logs are gold. They show attempted drift, missing scopes, bad tool design, and confusing UX.
Mistake 5: making approvals context-free
A human approval screen should show the delegation chain, risk reasons, resource, recipient, diff, and rollback plan. Otherwise reviewers rubber-stamp blind actions.
A practical rollout plan
You do not need to build the whole system in one sprint.
Phase 1: ledger-only visibility
Add delegation IDs, run IDs, agent IDs, tool receipt IDs, and decision logs. Even if policy is basic, visibility will show where risk lives.
Phase 2: hard-deny obvious violations
Deny expired delegations, tenant mismatches, missing scopes, wrong agent subjects, and denied data classes.
Phase 3: add budgets and approvals
Track tool counts, model spend, retries, and write-action approvals. This catches cost and trust failures before they become normal.
Phase 4: chain-aware policy
Evaluate the full parent-child delegation chain. Ensure every child is narrower than its parent and every action fits the original purpose.
Phase 5: customer-facing receipts
For high-trust workflows, expose safe receipts to admins: who authorized the work, what category of data was accessed, what action happened, and when.
Builder checklist
Before your next multi-agent workflow goes live, check this list:
- [ ] Every run has a root delegation.
- [ ] Every agent has a stable versioned identity.
- [ ] Every child delegation is narrower than its parent.
- [ ] Every tool call asks the policy layer before execution.
- [ ] Every decision stores reason codes.
- [ ] Tenant and resource scope are checked on every call.
- [ ] Sensitive data classes are denied or redacted.
- [ ] Budgets apply to model calls, tools, retries, and time.
- [ ] Risky writes require approval.
- [ ] The approval screen shows the full chain.
- [ ] Deny logs are reviewed.
- [ ] Receipts can be replayed during incidents.
Final thought
Multi-agent workflows make AI products feel more capable, but they also make responsibility harder to see. The answer is not to avoid agents. The answer is to make permission flow visible, narrow, and enforceable.
A2A authorization is not just an identity problem. It is a product trust problem.
If an agent chain can touch customer data, send messages, update records, run code, or spend money, every action should carry proof that it was allowed. Build the ledger before your users ask for the explanation.
FAQ
What is A2A authorization?
A2A authorization means checking what one AI agent is allowed to delegate or request from another AI agent. It is especially important when agents call tools, access customer data, or perform write actions across a workflow chain.
How is an A2A authorization ledger different from normal audit logs?
Normal audit logs often record what happened after the fact. An A2A authorization ledger records the permission chain behind the action: who delegated authority, which agent received it, what constraints applied, which policy allowed or denied it, and what tool receipt was produced.
Do small AI products need this much structure?
Not all at once. But even small products benefit from delegation IDs, scoped tool calls, tenant checks, and decision logs. Start with a simple ledger table and add chain-aware policy as workflows become riskier.
Should agents ever receive raw user OAuth tokens?
Usually no. A safer pattern is to keep OAuth tokens in your backend or secret broker, then mint short-lived, scoped tool permissions based on a delegation. The agent receives the ability to request work, not the raw credential.
Can prompts enforce A2A permissions?
Prompts can guide behavior, but they cannot enforce permissions. Authorization must happen outside the model in a tool gateway, API layer, sidecar, or policy service that can deny execution.
What should trigger human approval?
Trigger approval for external messages, destructive writes, permission changes, exports, high-cost runs, production code execution, billing changes, and any action that crosses a trust boundary. The approval view should include the delegation chain and risk reasons.
How does this help with prompt injection?
Prompt injection often works by tricking an agent into changing goals or using tools incorrectly. A ledger and policy layer reduce the damage by checking purpose, scope, tenant, data class, and approval requirements before tools execute.
Top comments (1)
Mistake 2 landed for me from an angle I wasn't expecting. The gate sitting in front of my tool calls started denying reads as well as writes.
A denied write is survivable. The agent sees the deny, replans, goes another way. A denied read took away the thing it needed to work out what state it was even in — I couldn't confirm whether an earlier action had gone through, because the channel I'd check it with was the one being blocked. Ended up verifying through an entirely different tool.
Probably worth separating in the policy design. Read denials and write denials have very different blast radii, even when the scope check that produced them is identical.