DocsOU model & RBAC
§ reference · operators

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:

RolePurpose
OrgAdminFull access to everything in the organization
OUAdminFull access within a specific OU subtree
AgentBuilderCreate and configure agents and skills. Read-only on org structure.
AgentOperatorInvoke agents. No creation or modification rights.
AgentViewerRead-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 (grants agent:invoke)
  • Deny: AgentOperator (denies agent: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

MethodEndpointDescription
GET/ousList all OUs
GET/ous/treeFull hierarchy as a nested tree
GET/ous/{ou_id}Get a single OU
POST/ousCreate 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

MethodEndpointDescription
GET/groupsList groups (filter by ou_id)
GET/groups/{group_id}Get group with direct members
POST/groupsCreate a group
PATCH/groups/{group_id}Update a group
DELETE/groups/{group_id}Delete a group (cascades memberships)
POST/groups/{group_id}/usersAdd a user to the group
DELETE/groups/{group_id}/users/{user_id}Remove a user
POST/groups/{group_id}/groupsNest a group inside this group
DELETE/groups/{group_id}/groups/{member_group_id}Remove a nested group

Role Bindings

MethodEndpointDescription
GET/role-bindingsList all bindings in the organization
POST/role-bindingsCreate a binding (allow or deny)
DELETE/role-bindings/{binding_id}Delete a binding
GET/rolesList all available roles