Skip to content

Pre-release. v0.1 is not out yet, so there is nothing to install and no public source to clone — the quickstart builds from a checkout.

Delegation, not impersonation

There are two ways to let software act for a person, and only one of them leaves you able to answer questions afterwards.

The agent gets a token that says it is the person. sub is the user’s identifier; nothing in the token mentions the agent at all.

It works. Every downstream system accepts it, because it looks exactly like the user logged in. And that is the problem: every downstream system records it as the user logging in. Six weeks later, when you are working out why a project was closed, the log says a person did it, and that person says they did not, and both are true.

The agent gets a token that says it is acting for the person. sub is still the user, so authorisation decisions downstream are about the user’s authority, where they belong. But the token also carries act:

{
"sub": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"act": {
"sub": "agent:jira-triage",
"depth": 1
}
}

This is RFC 8693’s actor claim, and it is the whole difference. The action is attributable to both: the human whose authority it drew on, and the software that took it.

  • A tool server can tell the difference. A token with no act claim was presented by a human directly. A token with one was presented by an agent. Routes that only ever make sense when a person is at the keyboard can refuse the second kind, and the SDK makes that one option — requireActor — rather than a thing you have to remember to check.
  • The audit answers the question people actually ask. Not “what did this service account do”, which nobody cares about, but “what did anything do on behalf of this person”, which is the first question in every incident review.
  • Authority cannot exceed the person. The token is derived from the user’s own token, so it cannot carry scopes the user does not have. An impersonation token minted by a privileged service has no such ceiling.

sub is always the human. The agent appears in act, never in sub.

It is the first of the five invariants, and the first conformance test: a subject token that names an agent rather than a person is invalid_grant.

There is no configuration flag that turns this off, because the moment there is one, somebody turns it on to get past an integration that was easier to fool than to fix.

The actor claim nests, per RFC 8693 §4.1, so an agent calling another agent is expressible without the human ever leaving sub:

{
"act": {
"sub": "agent:db-reader",
"depth": 2,
"act": { "sub": "agent:jira-triage", "depth": 1 }
}
}

sub still names the same human. It always does, however many hops there are — which is the point of delegation depth being something a tool server can put a limit on.

In v0.1 the control plane issues depth 1 only: it exchanges a user’s token, not a task token. The specification says so in as many words — sub-agent delegation is marked not in v0.1 — while keeping the claim shape, because a chain can reach a tool server from anywhere and both server SDKs enforce their own limit on one.

The discovery document advertises exactly one client authentication method, private_key_jwt: the agent signs a short-lived assertion with a key the control plane never holds. mTLS is not in v0.1 and is not advertised, so nothing reading discovery can pick a method that does not work.

What matters more is what is missing from that list: there is no client_secret_post, and there will not be one. A shared secret that authenticates an agent is a secret that leaks into a log, an environment dump or a config repository, and then anyone holding it is that agent.

OnbePre-release. v0.1 is not out yet.

© 2026 Onbe