Skip to main content

Roles and Permissions

AgentCore uses a hybrid RBAC model. The legacy Role enum stays for compatibility, while SystemRole holds editable permission sets and department scope. User-level overrides handle exceptions — no full permission join table yet.

System Roles

The four built-in roles are seeded as SystemRole rows with isSystem: true. They can be assigned to users and updated through migrations or seed data, but the product UI should not let tenants delete them.

RoleDepartment ScopeMain Permissions
adminall departmentsall known permissions
dept_headown and assigned departmentsdepartment users, namespaces, persona, approvals, knowledge, templates, employee profiles
approverown and assigned departmentsapprovals, escalation, all approvals view, knowledge read, document generation
employeeown and assigned departmentspersona read, knowledge read, document generation, plugin read, self profile

Legacy enum values are still on User.role. When roleId is set, User.systemRole is the primary source of permissions. Otherwise the backend falls back to the legacy enum mapping.

Custom Roles

Custom roles are rows in system_roles with isSystem: false.

Fields:

FieldPurpose
nameDisplay name.
slugStable lowercase key. Generated from name when omitted.
isSystemProtects built-ins from user deletion.
allDepartmentsGrants cross-department visibility when true.
departmentIdsExplicit department scope when allDepartments is false.
permissionsArray of permission keys, or * for all permissions.

Custom role routes:

GET /api/v1/permissions
GET /api/v1/roles
GET /api/v1/roles/:id
POST /api/v1/roles
PATCH /api/v1/roles/:id
DELETE /api/v1/roles/:id

Viewing roles requires canViewRoles. Creating, updating, and deleting roles requires canManageRoles. Deleting system roles is rejected.

Permission Catalog

Permissions are string keys grouped for UI display:

GroupKeys
UserscanViewAllUsers, canCreateUsers, canManageUsers, canManageDepartmentUsers
RolescanViewRoles, canManageRoles
DepartmentscanManageDepartments, canArchiveDepartments
Namespaces & PersonascanManageNamespaces, canEditPersona, canViewPersona
ApprovalscanApprove, canEscalate, canViewAllApprovals
KnowledgecanViewKnowledge, canUploadDocuments, canDeleteDocuments
Document GeneratorcanGenerateDocuments, canManageTemplates
PluginscanViewPlugins, canManagePlugins
AdministrationcanViewAudit, canManageEmployeeProfiles, canEditSettings, canEditSelfProfile
System*

The API validates every custom role and override against this catalog.

Permission Matrix

The seeded built-in matrix is:

PermissionAdminDept HeadApproverEmployee
canCreateUsersyesnonono
canManageUsersyesnonono
canManageDepartmentUsersyesyesnono
canViewAllUsersyesyesyesno
canViewRolesyesnonono
canManageRolesyesnonono
canManageDepartmentsyesnonono
canArchiveDepartmentsyesnonono
canManageNamespacesyesyesnono
canEditPersonayesyesnono
canViewPersonayesyesyesyes
canApproveyesyesyesno
canEscalateyesyesyesno
canViewAllApprovalsyesnoyesno
canUploadDocumentsyesyesnono
canDeleteDocumentsyesyesnono
canViewKnowledgeyesyesyesyes
canManageTemplatesyesyesnono
canGenerateDocumentsyesyesyesyes
canManagePluginsyesnonono
canViewPluginsyesyesyesyes
canViewAudityesnonono
canManageEmployeeProfilesyesyesnono
canEditSelfProfileyesyesyesyes
canEditSettingsyesnonono

Custom roles can grant any subset of these permissions.

User Overrides

Users carry four override arrays:

FieldEffect
extraPermissionsAdds permission keys after the role is evaluated.
revokedPermissionsRemoves permission keys after extras are applied.
extraDepartmentIdsAdds visible departments after role scope is evaluated.
revokedDepartmentIdsRemoves departments after extras are applied.

Override routes:

POST /api/v1/users/:id/permissions/grant
POST /api/v1/users/:id/permissions/revoke
DELETE /api/v1/users/:id/permissions/grant/:permission
DELETE /api/v1/users/:id/permissions/revoke/:permission
GET /api/v1/users/:id/effective-access

Grant and revoke requests can include permission, departmentId, or both. Each mutation writes an audit entry.

Effective Access Formula

Permissions:

rolePermissions = user.systemRole?.permissions ?? legacyPermissions(user.role)
effectivePermissions = (rolePermissions + user.extraPermissions) - user.revokedPermissions

If extraPermissions includes *, it expands to every known permission key. Revokes are applied last, so a revoked permission wins over the base role and over extras.

Departments:

if role.allDepartments or legacy role is admin:
effectiveDepartments = all
else:
effectiveDepartments =
role.departmentIds
+ user.primaryDepartmentId
+ user.departmentId
+ user.extraDepartmentIds
- user.revokedDepartmentIds

The forUser() helper wraps this computation for route code. It exposes:

  • directWhere() for models with a direct departmentId;
  • nestedWhere('namespace') for models scoped through a relation;
  • canSeeDept(id) for resource checks;
  • hasPermission(key) for RBAC checks.

Development Rules

  • Use requirePermission() on privileged routes.
  • Use forUser(request.user) in new code — don't check role === 'admin' directly.
  • Prefer custom roles for durable tenant policy, overrides for individual exceptions.
  • Keep revokedPermissions small. If many users need a permission removed, make a different custom role.
  • Hit GET /users/:id/effective-access when debugging access tickets.