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.
Scope
Scope in Onbe is not a list the agent asks for and receives. It is an intersection, and the result is never larger than the smallest of the three sets that go into it.
effective = user_scopes ∩ agent.allowed_scopes ∩ requested_scopesThe three sets
Section titled “The three sets”- What the user has. Carried in the subject token from your identity provider. This is the ceiling: an agent acting for someone can never do more than that person could do alone.
- What the agent may.
allowed_scopesin the agent’s registration. This is the operator’s say: even if a user could close projects, an agent registered forjira:readcannot. - What was asked for. The
scopeparameter on the exchange. This is the agent’s own say, and it is the one that should be smallest — an agent that asks only for what the job in front of it needs limits the damage of its own bugs.
Worked through: a user holds jira:read, jira:comment, jira:admin and confluence:read;
the agent is registered for jira:read, jira:comment and confluence:read; the request asks
for jira:read, jira:comment and jira:admin. The token carries jira:read jira:comment.
jira:admin is dropped because the agent may not use it; confluence:read because nobody asked
for it.
An empty intersection is an error
Section titled “An empty intersection is an error”If nothing survives, the answer is invalid_scope. It is never a token with an empty scope.
This is a deliberate choice and worth stating plainly. A scopeless token is a credential that appears to work — it authenticates, it has a subject, it passes a signature check — and fails somewhere further downstream, in a different service, at a different time, with an error nobody can trace back to this decision. Refusing at the point of decision is less convenient and much easier to debug.
It only ever narrows
Section titled “It only ever narrows”The intersection is not a one-off at issue. The same rule runs:
- At issue, as above.
- At refresh. The requested scope must be a subset of what the task was already granted, and
then the agent’s current registration is checked again. Narrowing an agent’s
allowed_scopestakes effect on the next refresh of every live task, because registrations are read from storage per request and never cached. - At every delegation hop, by construction. A sub-agent’s exchange takes the parent’s task token as its subject token, so the parent’s scope becomes the ceiling for the child and a chain can only lose authority as it gets longer. This is how the rule extends; the control plane issues depth 1 in v0.1, so there are no hops to narrow at yet.
There is no path through the server where a scope gets wider. Two conformance tests exist purely to hold that line: one that the intersection is honoured at exchange, and one that a scope the task was not granted is refused on refresh even when the user and the agent would both allow it.
Naming scopes
Section titled “Naming scopes”Onbe does not care about the shape of your scope strings; they are opaque to it and compared as whole tokens separated by spaces. What matters is that they mean the same thing in three places: in your identity provider’s user claims, in the agent registration, and in the tool server that enforces them.
A pair like jira:read and jira:comment works well because each names one capability against
one system. A scope like admin works badly, because nobody can say what it permits without
reading the tool server’s source.
© 2026 Onbe