Multi-Agent Systems Without Authority Amplification
Secure delegation in multi-agent systems with bounded mandates, subset checks, shared budgets, runtime binding and deterministic authorization failures.

Multi-agent systems turn delegation into a security boundary. A procurement agent receives authority to research three approved suppliers and spend up to EUR 10,000. It delegates catalog search to one subagent and negotiation to another. The negotiation agent then creates a purchasing worker. What can that third agent do?
If the system answers by copying roles, inheriting scopes or asking a model to summarize the parent's instructions, authority will eventually expand. A missing field becomes "unrestricted." A budget is duplicated rather than subdivided. An expiry is reset. A child gains a tool its parent could not use.
Safe delegation needs one property that can be tested without interpreting prose: every child mandate must be equal to or narrower than its parent.
Model delegation in multi-agent systems as a constraint set
A mandate is a short-lived record of delegated authority. For a useful subset proof, represent each security-relevant dimension explicitly:
type Mandate = {
id: string
subject: string
parentId?: string
actions: Set<string>
resources: Set<string>
counterparties: Set<string>
destinations: Set<string>
purposes: Set<string>
notBefore: Instant
expiresAt: Instant
maxBudget?: Decimal
maxUses?: number
maxDelegationDepth: number
proofKeyId: string
}
Real systems will have more dimensions: data classifications, regions, contract references, rate limits, tool parameters, required approvals and commercial terms. The rule stays the same. A child can remove options or tighten limits. It cannot add options or relax limits.
Formally, for set-valued constraints:
child.actions subset_of parent.actions
child.resources subset_of parent.resources
child.counterparties subset_of parent.counterparties
child.destinations subset_of parent.destinations
child.purposes subset_of parent.purposes
For ordered limits:
child.not_before >= parent.not_before
child.expires_at <= parent.expires_at
child.max_budget <= parent.remaining_budget
child.max_uses <= parent.remaining_uses
child.max_delegation_depth < parent.max_delegation_depth
The evaluator should return a structured reason for every failed comparison. "Unauthorized" is safe for an external response but too vague for an operator debugging a delegation chain.
Missing does not mean unlimited
Optional fields create the most common semantic trap. Suppose the parent contains destinations: ["supplier_api"] and the child omits destinations. Does omission inherit the parent constraint, deny delegation or mean any destination?
It must never mean any destination.
Choose one of two defensible semantics and apply it consistently:
- Materialize inherited constraints into the child before signing it.
- Treat omission as inherited during effective-authority calculation.
The first produces self-contained child documents and simpler offline verification. The second makes documents smaller but requires the verifier to resolve the complete parent chain. Either can work. Mixing the two produces gaps.
A secure evaluator can normalize first:
function effectiveChild(parent: Mandate, draft: Partial<Mandate>): Mandate {
return {
...draft,
actions: draft.actions ?? parent.actions,
resources: draft.resources ?? parent.resources,
counterparties: draft.counterparties ?? parent.counterparties,
destinations: draft.destinations ?? parent.destinations,
purposes: draft.purposes ?? parent.purposes,
notBefore: maxInstant(draft.notBefore ?? parent.notBefore, parent.notBefore),
expiresAt: minInstant(draft.expiresAt ?? parent.expiresAt, parent.expiresAt),
maxBudget: minDecimal(draft.maxBudget ?? parent.maxBudget, parent.maxBudget),
maxUses: minInt(draft.maxUses ?? parent.maxUses, parent.maxUses),
maxDelegationDepth: parent.maxDelegationDepth - 1
}
}
This sketch omits error handling. Production code must reject a parent with no remaining depth, unknown constraints, malformed decimals or an unresolvable ancestor. Silent defaults are the problem, not the solution.
Separate permission scope from consumable capacity
Set inclusion alone does not stop budget amplification. If a parent with EUR 10,000 creates two children, each capped at EUR 10,000, both children satisfy an individual numeric subset check. Together they can spend EUR 20,000.
Budgets, usage counts and quotas are consumable capacity. Delegation must reserve or subdivide them atomically.
The allocation ledger below is a runtime integration pattern. The current OATI evaluator accepts a supplied usage snapshot; it does not operate this database or coordinate concurrent child allocations.
async function allocateBudget(parentId: string, childId: string, amount: Decimal) {
return database.transaction(async tx => {
const parent = await tx.mandates.lockForUpdate(parentId)
const remaining = parent.maxBudget.minus(parent.committedBudget)
if (amount.gt(remaining)) throw new Error("budget_allocation_exceeds_remaining")
await tx.allocations.insert({ parentId, childId, amount })
await tx.mandates.update(parentId, {
committedBudget: parent.committedBudget.plus(amount)
})
})
}
Decide what happens when a child expires with unused capacity. You may release it to the parent, leave it unavailable for audit simplicity or require explicit revocation and release. Whatever the choice, it must be deterministic under retries and concurrent delegation.
The same rule applies to max_uses. A parent with ten remaining calls cannot create ten children with ten calls each. A distributed counter that converges later is too weak for a payment or irreversible operation. The allocation or consumption needs a strongly consistent boundary.
Bind delegation to the child runtime
A child mandate should name the exact child subject and proof key. Otherwise, any process that obtains the document may exercise it.
The child proves possession of its runtime key when submitting a transaction. A complete deployment should check that the proof key matches the mandate, each issuer was allowed to delegate and the chain terminates at an accepted organisational authority. The current reference evaluator checks one supplied parent relationship. Arbitrary-depth chain resolution remains a runtime responsibility.
Organisation principal
signs parent mandate for procurement agent
procurement agent signs child mandate for negotiation agent
negotiation agent signs child mandate for ordering agent
Each link needs its own validity period, signature and revocation state. The verifier must not accept a valid leaf if an ancestor has expired or been revoked. Cache the chain carefully. A fast local result based on stale authority can be worse than a visible timeout.
A procurement example
The root procurement mandate allows:
{
"actions": ["catalog.search", "offer.request", "order.create"],
"resources": ["category:industrial-sensors"],
"counterparties": ["supplier:A", "supplier:B", "supplier:C"],
"purposes": ["plant-7-maintenance"],
"constraints": {
"currency": "EUR",
"max_budget": "10000.00",
"max_unit_price": "850.00",
"destinations": ["plant:7"],
"max_delegation_depth": 2
}
}
The research child receives catalog.search, the same product category and three suppliers. It receives no purchasing action and no budget. The negotiation child receives offer.request for suppliers A and B, plus a requirement that offers expire within the parent's window. The ordering child receives order.create for supplier B, one selected product, EUR 4,200 allocated budget, one use and delivery only to plant 7.
The child did not receive all parent fields by convenience. Each task got the minimum authority it needed. When every execution path enforces the effective mandate and shared capacity store, the ordering child cannot search another category, negotiate with supplier C, redirect delivery or create a second order.
At transaction time, enforcement still evaluates the effective chain. A correctly issued mandate can become unusable after its parent is revoked, its allocated budget is consumed or the approved supplier leaves the registry.
Delegation is not prompt inheritance
Frameworks often pass instructions from a manager agent to a specialist agent. That is useful orchestration, but it is not a security boundary.
This prompt is not authority:
Buy replacement sensors from approved suppliers.
Do not spend more than EUR 10,000.
Ask before placing an expensive order.
It leaves basic terms undefined. Which supplier registry? What counts as expensive? Does the limit include tax and shipping? Can a child agent change the delivery address? How many orders are allowed? What happens after expiry?
Prompts can help an agent choose a plan. Signed mandates and deterministic policy constrain which plans may execute. Keep both. Do not mistake one for the other.
Resource hierarchies need explicit containment rules
Simple string equality is safe but often too restrictive. An infrastructure parent might delegate access to cluster:prod-eu/namespace:billing, then create a child limited to one deployment inside that namespace. A procurement parent might cover a product category while a child covers one SKU.
Do not infer hierarchy from arbitrary string prefixes. database:prod must not contain database:production-archive merely because one name starts with the other. Define typed resource relationships in a registry or use structured identifiers with a tested containment function.
function resourceContained(child: Resource, parent: Resource): boolean {
if (child.kind !== parent.kind) return false
if (child.tenant !== parent.tenant) return false
if (parent.id === child.id) return true
return resourceGraph.hasAncestor(child.id, parent.id)
}
Snapshot or digest the resource graph used for the decision. If an administrator moves a resource between groups while a mandate is active, the effective scope can change without changing the signed document. For high-risk operations, resolve current membership at execution time and record which graph version the evaluator used.
Parameter ranges need the same care. A child may narrow an allowed port range, geographic region or data classification, but only if both issuers and verifiers share exact subset semantics. Free-form conditions such as only safe databases cannot participate in a proof. Convert them into typed attributes or require a separate policy result.
Decide who may mint a root mandate
Non-amplification protects descendants only when the root is legitimate. Restrict root issuance to accountable organisational principals, use protected signing keys and separate issuance from approval for sensitive authority. A runtime agent that can create a new root mandate can bypass the entire delegation chain.
Record the sponsor or business function behind the root, even when the immediate issuer is an automated control-plane service. That gives operators and auditors a path back to the human or governed process that authorized the capacity in the first place.
Attack the chain, not only the leaf
Delegation testing needs adversarial chains.
Action expansion. Give the child one action absent from the parent. The evaluator must reject the whole child, not ignore the extra action.
Resource wildcard. Replace a specific resource with * or a broader path prefix. Define resource hierarchy semantics explicitly and prove containment.
Budget cloning. Create children concurrently whose combined allocation exceeds the parent's remaining budget. Only transactions with a successful atomic reservation may proceed.
Expiry reset. Issue a child that starts before or expires after its parent. Reject it even when its own signature and dates are valid.
Depth laundering. Create a new root-looking mandate from a child issuer. The trust chain must show who delegated to whom, and issuer policy must restrict which principals can create roots.
Unknown constraint removal. Send a child document through an older evaluator that does not understand a new safety field. For material actions, unknown mandatory constraints should cause failure rather than be dropped.
Ancestor revocation. Revoke the root while a leaf remains cached. Later leaf transactions must fail according to the revocation freshness policy.
Cross-audience reuse. Replay a child mandate at a service that was not an allowed destination. Verify audience on both the signed mandate and transaction proof.
Partial execution. A provider accepts an order but the local usage commit fails. Use idempotency keys, a durable transaction state machine and reconciliation. Retrying blindly may duplicate the business action.
Return explainable failures
An evaluator should produce machine-readable paths and stable reason codes:
{
"decision": "deny",
"reason": "authority_amplification",
"issues": [
{
"path": "constraints.destinations",
"code": "child_not_subset",
"parent": ["plant:7"],
"child": ["plant:9"]
}
],
"evaluated_chain": [
"mandate:procurement:root-18",
"mandate:negotiation:44",
"mandate:order:91"
]
}
Do not expose sensitive policy internals to an untrusted caller. Keep a detailed operator record and return a reduced external error where needed. Both should share the same decision ID so support teams can trace the event.
An implementation checklist
- Define every authority dimension as typed data.
- Publish exact semantics for omitted and unknown constraints.
- Normalize inherited values before subset evaluation.
- Compare sets, intervals, numeric limits and resource hierarchies deterministically.
- Treat budgets, quotas and usage as consumable capacity.
- Reserve delegated capacity atomically across concurrent children.
- Bind every mandate to a subject, proof key, issuer, audience and validity window.
- Limit delegation depth and verify the complete ancestor chain.
- Recheck revocation and status at transaction time.
- Reject children that contain constraints the evaluator cannot safely interpret.
- Bind approvals to the exact child mandate digest.
- Use provider idempotency and reconcile partial execution.
- Add negative conformance vectors for every amplification path.
Related engineering guides
- See how MCP authorization separates tool access from delegated authority.
- For incremental adoption, start with one-company transaction security for enterprise AI agents.
OATI's current boundary
OATI Mandates and the deterministic evaluator cover action, resource, counterparty, destination, purpose, one supplied parent and child subset proof, budgets, usage and one-time constraints. The public developer-preview conformance suite includes non-amplification cases and runs the same language-neutral vectors in TypeScript, Python and Go. Atomic allocation across several children, durable usage coordination and arbitrary-depth chain resolution belong to the integrating runtime.
The broader Intelliger product map adds managed templates, approvals, an authority graph and lifecycle operations. Those are target product boundaries, not a claim that every service is complete. The current policy compiler is still a scaffold, customer gateway enforcement is a reference path, and independent protocol review remains outstanding.
Multi-agent systems need delegation because one all-powerful agent is hard to operate and harder to trust. Each child should receive narrower scope, and shared capacity must be allocated rather than copied.