RBAC — Access Control for Agent Fleets
Powerloom uses an Active Directory-style access model to govern who can do what across your agent fleet. Organizational units, security groups, role bindings, and deny-override — the same patterns IT admins use for users, applied to agents and their tools.
The Model
Four concepts. Every access decision in your organization resolves to these.
Organizational Units
OUs are the hierarchy. They mirror your org chart — by team, environment, or tenant.
acme/
├── engineering/
│ ├── platform/ ← 2 agents, 2 MCP servers
│ └── support/
└── accounting/
Every resource — agents, MCP servers, skills, credentials — lives inside an OU. Policies at a parent OU flow down to children. A binding at acme/ applies to everything underneath it.
One root OU per organization. Unlimited nesting below that.
Groups
Groups collect principals. A principal is anything that can hold permissions — a user, another group, or an OU.
Groups support nesting. Add a group to a group, and the members of the inner group inherit the outer group's bindings transitively. Powerloom maintains a closure table to resolve transitive membership in constant time, so nested groups don't slow down permission checks regardless of depth.
Cycle detection is enforced at the database level. You can't create circular group memberships.
Roles
A role is a named set of permissions. Powerloom ships five built-in roles:
| Role | Purpose |
|---|---|
| OrgAdmin | Full access to everything in the organization |
| OUAdmin | Full access within a specific OU subtree |
| AgentBuilder | Create and configure agents and skills. Read-only on org structure. |
| AgentOperator | Invoke agents. No creation or modification rights. |
| AgentViewer | Read-only across agents, skills, and MCP servers |
Permissions use a resource:action format: agent:create, skill:read, mcp:register, binding:delete. Roles bundle these into meaningful job functions.
Role Bindings
A binding connects a principal to a role at a specific OU scope, with a decision type of allow or deny.
- principal: group:eng-leads
role: OUAdmin
scope: /acme/engineering
effect: allow
- principal: group:contractors
role: AgentBuilder
scope: /acme
effect: deny
The first binding grants eng-leads full admin rights on everything under /acme/engineering and its descendants. The second denies contractors the ability to build agents anywhere in the organization.
How Permissions Are Evaluated
When a user takes an action — creating an agent, invoking a session, attaching a skill — Powerloom evaluates whether they're allowed. The algorithm:
1. Collect the user's effective principals.
This includes:
- The user's own identity
- Every OU from the user's home OU up to the root (OU principals)
- Every group the user belongs to, including transitively nested groups
2. Find all matching bindings.
A binding matches if:
- Its principal is one of the user's effective principals
- Its role includes the required permission
- Its scope OU is an ancestor of (or equal to) the resource's OU
The OU closure table makes this a single indexed query — no tree traversal at request time.
3. Apply deny-override.
- If any matching binding is
deny→ access denied - Else if any matching binding is
allow→ access granted - Else → access denied (implicit default deny)
One deny anywhere in the matching set blocks the permission. This lets an admin revoke access with a single binding without touching existing allow bindings.
4. Cache the result.
Permission checks are memoized per request. The same user hitting the same permission on the same OU within one request resolves once.
Deny-Override in Practice
Deny always wins. This is the same semantics as Active Directory's deny precedence.
Scenario: Bob has two bindings on /acme:
- Allow:
AgentOperator(grantsagent:invoke) - Deny:
AgentOperator(deniesagent:invoke)
Result: denied. The deny binding overrides the allow, regardless of which was created first.
This is useful for broad-allow-with-exceptions patterns:
Allow: eng-leads → OUAdmin on /acme/engineering (broad access)
Deny: contractors → AgentBuilder on /acme (carved-out exception)
Every engineer in eng-leads gets full OU admin rights. But any engineer who's also in the contractors group is denied agent-building rights, even though their eng-leads membership would otherwise allow it.
Inheritance
Permissions flow in two directions:
Down the OU tree. A binding at /acme applies to /acme/engineering, /acme/engineering/platform, and every other descendant. You don't need to repeat bindings at every level.
Through group nesting. If sales-team contains managers, and managers contains Alice, then a binding on sales-team applies to Alice. The closure table resolves this transitively — it doesn't matter how many layers of nesting exist.
These combine naturally. A binding granting OUAdmin to group eng-leads at scope /acme/engineering gives every member of eng-leads — including transitive members from nested groups — full admin rights on every resource in every descendant OU of /acme/engineering.
Skill Access Grants
Skills have an additional access mechanism beyond standard RBAC. A skill can be granted to specific principals directly, independent of OU-scoped role bindings.
This covers the case where a skill lives in one OU but needs to be accessible by users or agents in another. A direct grant bypasses the OU scope check — the principal can access the skill regardless of where it sits in the hierarchy.
Grants are additive. Standard RBAC still applies in parallel. If a user has skill:read on the skill's OU via a role binding, the grant is redundant.
Safety Guarantees
Last-admin protection. You can't delete the sole OrgAdmin binding at the organization root. Powerloom prevents you from locking yourself out.
Implicit default deny. If no binding matches, the answer is no. Permissions are opt-in, never opt-out.
Closure table integrity. OU and group hierarchies are maintained by database triggers. Application code can't corrupt the transitive relationships — they're recomputed automatically on every structural change.
No cross-org leakage. Every binding includes an organization_id. Bindings, roles, and permissions are scoped to a single organization. There is no mechanism for one organization's RBAC to affect another.
API Reference
Organizational Units
| Method | Endpoint | Description |
|---|---|---|
| GET | /ous | List all OUs |
| GET | /ous/tree | Full hierarchy as a nested tree |
| GET | /ous/{ou_id} | Get a single OU |
| POST | /ous | Create an OU |
| PATCH | /ous/{ou_id} | Update an OU (rename, reparent) |
| DELETE | /ous/{ou_id} | Delete an OU (must have no children or groups) |
Groups
| Method | Endpoint | Description |
|---|---|---|
| GET | /groups | List groups (filter by ou_id) |
| GET | /groups/{group_id} | Get group with direct members |
| POST | /groups | Create a group |
| PATCH | /groups/{group_id} | Update a group |
| DELETE | /groups/{group_id} | Delete a group (cascades memberships) |
| POST | /groups/{group_id}/users | Add a user to the group |
| DELETE | /groups/{group_id}/users/{user_id} | Remove a user |
| POST | /groups/{group_id}/groups | Nest a group inside this group |
| DELETE | /groups/{group_id}/groups/{member_group_id} | Remove a nested group |
Role Bindings
| Method | Endpoint | Description |
|---|---|---|
| GET | /role-bindings | List all bindings in the organization |
| POST | /role-bindings | Create a binding (allow or deny) |
| DELETE | /role-bindings/{binding_id} | Delete a binding |
| GET | /roles | List all available roles |