Agent-to-agent delegation
Why the upstream agent asked — a legitimate grant, exercised for an injected reason
every questionOn this page
Union is an escalation primitive, not a convenience
Consider two agents. A triage agent reads tickets and holds a grant on one read-only tool. A finance agent holds a grant on the refund tool. Somebody wires them together so triage can ask finance to handle a case. If the system evaluates the refund against the finance agent’s permissions because finance is the one making the call, then triage has just acquired the refund tool. Nothing was compromised; the composition did it.
The mechanism that fixes it is that the whole chain is the subject, and the effective permission set is the intersection across it. Token Observe evaluates each link independently against the requested action and returns on the first refusal, with the reason naming which link of how many denied it — so a refusal is diagnosable rather than a flat denial that sends an engineer looking in the wrong place.
Two properties of that design are worth stating because they get lost. First, intersection means adding a hop can only ever reduce what is possible, which makes composition safe by construction rather than by review. Second, the intersection is evaluated at the moment of the call against the current rows, not baked into a token at the start of the workflow, so revoking a grant halfway through a long-running chain takes effect on the next call rather than when something reconnects.
The corresponding cost is real and should be planned for: a chain is only as capable as its least privileged member, so a workflow designed by giving each agent exactly what it needs will find that composing them yields nothing at all until somebody widens a grant deliberately. That is the control working. The failure to accept is widening the least-privileged member to make the chain work, which quietly reintroduces the union.
chain: triage -> finance action: tool:payments/issue_refund
union semantics
triage : no grant
finance : granted
effective = union -> ALLOWED
// triage has acquired the refund tool by asking
intersection semantics
link 1/2 triage : no role grants call on tool:payments/issue_refund
-> refused; reason names delegation link 1/2
// adding a hop can only narrow, never widenThe mechanism, and the two ways an unresolvable hop is handled
In its simplest form the chain arrives as a header listing the upstream agent ids, origin first. Token Observe resolves each id to its roles, builds a role set per link, and evaluates the action against every set in order. The gateway validates the shape of each entry before anything else and caps the list at 32 entries, refusing a longer one rather than truncating it.
What happens to a hop that cannot be resolved is the interesting part, and the two surfaces answer it differently while both failing closed. On the model gateway, a chain entry naming an unknown agent, or one that is not currently active, refuses the whole request with a typed delegation error naming the offending id — because a chain is only as trustworthy as every hop in it, and a dormant or deleted upstream cannot lend authority it does not currently hold. On the tool path, an unresolvable hop instead contributes an empty role set, which denies through the ordinary intersection. The tool path also bounds the chain at eight hops by truncation rather than refusal, which is worth knowing precisely: beyond eight, later entries are dropped from the intersection rather than causing a refusal, so a chain that long is not intersected across every hop it claims.
Record the chain on the trace either way, in order, with each hop’s grants, because a delegation refusal that leaves no evidence is indistinguishable from a bug in the calling framework. It also matters for approvals, covered below.
One design note if you are building this rather than buying it. Resolve the chain’s roles at decision time from the same rows the registry holds rather than from anything cached in a session or a token, for the same reason a session should carry no permissions: a permission set with a lifetime nobody manages will outlive the grant it was derived from.
An unauthenticated claim may only narrow
A header naming the upstream agents is a string the caller chose. So is a header naming the human an agent is acting for. Neither carries a signature, and treating either as a source of authority converts a value the caller controls into a permission — which is the shape of most authorisation bugs, not a novel agent problem.
The safe semantics are one-directional. A delegation chain can only add more links to intersect, which can only reduce the result. A named human can only contribute the roles mapped from their directory groups as another set to intersect, which can only reduce the result. Token Observe implements the second exactly that way: the on-behalf-of mask is off by default, has a shadow stage that computes the intersection and records what it would have refused without acting on it, and when enforcing appends the human’s mapped roles to the delegation chain so it can only narrow. Failures to resolve the principal refuse rather than fall through — unknown principal, disabled principal, no claims, stale claims, unresolved — and a group capture older than a configured maximum age is refused rather than trusted, because those groups are what the identity provider asserted at that person’s last sign-in rather than a live directory read.
The corollary that people miss is that an agent can simply omit the header, which skips the mask entirely. That is why the registry carries a per-agent switch requiring a named human, to be set on the agents where an unattributed call is the anomaly rather than the norm — a copilot, a ticket triager, anything operating inside one person’s authority. Requiring the header estate-wide where the mask does not change any decision would be theatre.
The strongest form of this rule is what happens when the caller has authenticated with something better than a bearer token. Where a request arrives on a capability derived from a signed workload assertion, Token Observe refuses a caller-supplied delegation header outright and binds the session identifier and the named human from the capability instead. A verified claim and an asserted one must not be mixable, because mixing them means the weaker one decides.
The stronger form: assertion in, one-use capability out
Where the delegation claim needs to be worth more than a header, the shape that works is a token exchange. An identity provider you configured signs an assertion about a composite identity — the workload subject, the agent id, the human actor and the task — and the gateway checks the bindings it owns rather than trusting the assertion wholesale: the workload subject against the agent’s recertification-covered metadata, and the human against that user’s durable identity binding. Only then does it mint anything.
What it mints is deliberately unlike an access token. It is short-lived, bound to a specific audience, scoped to a single operation, and single-use, with the assertion itself capped at fifteen minutes and both assertion and capability identifiers consumed atomically in the primary store so replicas sharing that store share one replay domain. It is a token for that one deployment rather than a portable credential, which is the point: something that is only meaningful to one gateway is worth much less to whoever steals it.
Downstream hops then get a derived capability rather than a copy of the original, narrowed to the audience and scopes the next hop needs. That is what lets a multi-agent workflow carry a verified identity across hops without any hop holding a credential that would let it do more than its part.
There is a further property that only becomes obvious in an incident: because the capability carries the human actor and the task, every call in the workflow is attributable to the same person and the same unit of work without anyone having to correlate timestamps. The cost is that this requires an identity provider that can issue workload assertions and a place to record the bindings, which is real integration work rather than a header.
- What is checked
- The issuer’s signature, then the two bindings the gateway owns — workload subject against the agent record, human subject against the user’s durable binding. An assertion is not trusted for the identity it asserts about your own entities.
- What is minted
- A short-lived, audience-bound, single-use capability for this deployment. Not a portable access token, and not a vendor credential.
- What is refused
- A caller-supplied delegation chain on a capability-authenticated request. A verified claim and an asserted one must not be mixable.
- What is derived
- A narrower capability for the next hop, rather than a copy of the one that arrived. The chain carries identity without carrying spare authority.
Protocol ingress, and the parts worth refusing
There is now an interoperability protocol for agent-to-agent messaging, and the sensible posture towards it is the same as towards any other dialect: accept the subset you can govern completely, refuse the rest with a typed error, and put everything through the same decision path as the rest of the estate.
Token Observe accepts the synchronous text and structured-data subset and deliberately omits the parts it cannot inspect: no dereferencing of URLs or files, no streaming, no push callbacks, no background task store, and no silent downgrade to an older protocol revision. The inference itself is then sent through the ordinary governed route using a derived one-use capability, so authentication, permissions, policy, routing, budget, approvals, redaction, metering and trace closure remain one implementation rather than two that will disagree. Message content is bounded — a cap on content bytes, on the number of parts, and on the depth and node count of structured data — because everything crossing this boundary is untrusted in both directions.
The refusals matter as much as the acceptances. A protocol feature that lets a peer hand you a URL to fetch is a request to make an outbound call on someone else’s behalf, which is an egress control question rather than a messaging feature. A background task store is state you would be holding on behalf of a peer. A silent downgrade is an unbounded compatibility surface, and an unbounded compatibility surface is an unbounded attack surface.
Two smaller things belong here. Agent cards, where they are accepted, are input like anything else and need the same bounds on size, depth and node count. And a peer’s claim about its own identity is worth exactly what the transport authenticated, which is the same rule as the header above, applied to a protocol that makes the claim look more official.
Approvals across a chain, and what intersection still does not bound
When a delegated action needs a human, the binding has to cover the chain. Token Observe binds an approval to a hash of the canonical action plus its stable execution context, and that context includes the subject and team, the effective role grants, the ordered delegation identities and their grants, the named human, and the caller’s session and tags. Changing the chain therefore invalidates the approval, which is exactly right: a human who approved this action performed by this agent on behalf of this person through these hops did not approve the same action reached by a different route. Transport-level identifiers such as request, trace and session ids generated by the gateway are deliberately excluded, so a reconnect does not invalidate a reviewed action.
Now the limit, and it is the one to carry away. Intersection bounds what a chain can do. It says nothing about why the upstream agent asked. An agent with a legitimate grant on the refund tool, exercising that grant because a paragraph in a retrieved document told it to, is fully authorised at every hop and is still an incident. Delegation control is a containment mechanism, not an intent mechanism, and no permission model closes that gap.
What does narrow it is everything in the layers around delegation: keeping the grant set small so the intersection has less to allow; gating the irreversible actions on a human bound to the exact payload; scanning tool results for injected directives and weighting them higher than user text; and recording the refusals so a chain probing for authority it does not hold is visible afterwards.
The recording point deserves emphasis in a multi-agent estate specifically, because the failure is quiet. A delegated call refused at link three of four leaves no trace at all in most home-grown implementations — the framework swallows the error and tries something else. If the refusal is not recorded against a trace with the chain on it, the only evidence that an agent spent a week trying to reach a tool it was never granted is an absence.
How to put agent-to-agent delegation into practice
- 01
Make the chain the subject, not the last hop
Evaluate the requested action against every link’s roles independently and return on the first refusal, naming which link denied it. Union across a chain is escalation by construction. - 02
Fail closed on a hop you cannot resolve
Refuse the request, or contribute an empty role set so the intersection denies. Never skip a link because its agent record is missing or dormant. - 03
Bound the chain and refuse rather than truncate where you can
A cap that refuses is a bound; a cap that truncates drops links from the intersection. Know which one your enforcement point does, because they differ in whose favour they fail. - 04
Treat any unauthenticated claim as narrowing only
A delegation header and a named-human header are strings the caller chose. Let them add sets to intersect and never let them grant, and refuse a caller-supplied chain on a request that authenticated with a verified capability. - 05
Move to signed assertions where the claim has to be worth more
An issuer-signed composite identity, checked against the bindings you hold, exchanged for a short-lived, audience-bound, single-use capability, with a narrower derived capability for each downstream hop. - 06
Accept only the protocol subset you can inspect
No URL or file dereference, no background task store, no silent version downgrade, and bounds on content size, part count, depth and node count. Route the inference through the same governed path as everything else. - 07
Bind approvals to the chain and record every refusal
Include the ordered delegation identities and their grants in the approval binding, and open a trace before the delegation check so a refusal at link three is evidence rather than an absence.
Where this argument meets an implementation
Agent permissions
Deny by default, explicit deny wins, and delegation intersects — so an agent cannot borrow authority it was never granted.
MCP gateway
One endpoint in front of every upstream tool server, and the same evaluator deciding a tool call that decides a model call.
Human approvals
One human decision, bound to one exact payload, spendable once.
Audit chain
Every administrative act hash-chained; seal it under a key held off the box, and anchor it with a signature your auditor can check alone.
Those pages are one product's implementation of what this guide argues for; describe what your agents actually do and you will get a straight answer about whether you need any of it, including when the answer is no.
Talk it throughWhy can a delegation chain only narrow permissions?
Because the alternative hands every agent the authority of the most privileged agent it can talk to. If the effective permissions of a chain are its union, a low-privileged agent obtains a grant it was refused simply by routing the work through a higher-privileged one, with no attack and no compromise — the composition does it. Intersection makes adding a hop monotonically reducing, so composing agents is safe by construction rather than by review. The cost is that a chain is only as capable as its least privileged member, and the failure to accept is widening that member to make the workflow run.
What happens if an agent in the chain has been suspended?
On the model gateway the whole request is refused with a typed delegation error naming the offending agent id, because an agent that is not active cannot lend authority it does not currently hold, and skipping the link would silently convert a suspension into a hole. On the tool path the unresolvable hop contributes an empty role set instead, which denies through the ordinary intersection. Both fail closed by different routes, and both are worth testing rather than assuming, because a chain that silently drops a hop is the specific defect this rule exists to prevent.
Is the delegation header safe if anyone can set it?
It is safe precisely because it can only narrow. Adding an id to the chain adds another role set to intersect, which can only reduce what is allowed, so a caller cannot use it to gain anything. It is not, however, evidence of who really called: it is an assertion the caller made, recorded as such. Where the claim needs to carry weight, the shape is a signed workload assertion exchanged for a short-lived, audience-bound, single-use capability — and on a request that authenticated that way, Token Observe refuses a caller-supplied chain header entirely rather than letting a weaker claim sit alongside a verified one.
How does a human approval interact with a delegation chain?
The approval is bound to the chain. Token Observe hashes the canonical action together with its stable execution context — the subject and team, the effective role grants, the ordered delegation identities and their grants, the named human, and the caller’s session and tags — so a changed chain invalidates the approval and it cannot be spent on the same action reached by a different route. Identifiers the gateway generates for transport, such as request, trace and protocol session ids, are excluded, so a reconnect does not force a human to review the same action twice.
Does intersection stop a hijacked agent from doing damage through a chain?
It bounds the damage and does not prevent it, and that distinction is worth being explicit about. Intersection guarantees that a chain cannot exercise authority no member holds. It cannot tell that a member with a legitimate grant is exercising it because a paragraph in a retrieved document told it to — every hop is authorised, and the action still should not have happened. The layers that narrow that are the ones around delegation: a smaller grant set, a payload-bound human approval on irreversible actions, higher scoring for directives arriving in tool results, and a record of refusals so an agent probing for authority it does not hold is visible afterwards.
Prefer to ask a person? Write to us →
Bring us the question this guide did not answer.
Write to hello@tenhaw.com with what your agents do, which providers they call and what would have to be true for you to put something in front of them. James Rooney replies. You will get a straight answer about whether Token Observe fits, including when it does not.
no form · no qualification step · no sales desk · the other three ways in