Enterprise

Roles and features

Built-in and custom roles, feature grants, and priority-gated assignment.

Roles in Neuralis govern humans and agents with one vocabulary: the same grants that decide which tabs a user sees decide which tools, skills, and files an agent's model is even told about, and agent provisioning is gated exactly like inviting a colleague — nobody can mint an agent stronger than themselves.

Authorization has two orthogonal dimensions. Feature grants answer "what can this role do" — every route, tool, and contribution declares the feature it requires, checked server-side. Role priority answers a different question: "what strength of role may this caller hand out". Both are defined per project on the project's role definitions.

Built-in roles

Five built-in roles anchor the scale. Their priorities are immutable; their feature grants are project-editable like any role's.

RolePriorityAgent accessCan inviteCan manage rolesDefault grants
owner1all agentsyesyes* (wildcard)
admin2all agentsyesyesevery first-party non-platform.* feature, enumerated
manager10all agentsnonocurated feature set
member20own agentsnonocurated feature set
viewer30view onlynonocurated feature set

The gaps between 2, 10, 20 and 30 are deliberate — see role priority.

The agents column is part of the role definition: * grants access to every agent in the project, own restricts a user to agents they created or are assigned to, and view is read-only. See multi-tenancy for agent ownership.

Custom roles

Projects can define additional roles. A custom role is a normal role definition — its own feature grants, agent access, and an explicitly declared priority. Two rules keep custom roles safe:

  • the name is cosmetic, the priority is enforced — calling a role "Director" grants nothing by itself;
  • a role label with no declared priority resolves to the weakest possible strength, so an undeclared custom role can always be assigned but can never be an escalation.

Feature grants

A feature is a string key such as core.execute, drive.read, or platform.config. The semantics are uniform across the platform:

  • a role's grantedFeatures array lists what it holds; the '*' wildcard passes every check,
  • a surface that requires multiple features requires all of them,
  • a surface that declares no required features is ungated,
  • a feature id that no loaded package provides is never granted to any role except a wildcard holder — so requiring an unknown feature is wildcard-holder-only by construction, deny-by-default.

Running a shell needs two ids, not one

core.execute is the id for running an agent — the chat loop, conversations, the execute tool itself. Opening a shell additionally needs the entry feature of the plane it opens on:

PlaneEntry featureNotes
The app containerexec.containerGranted wherever core.execute is. A packaged skill's shell scripts therefore need BOTH — core.execute to run the tool, exec.container to open the shell.
The app container, unrestrictedexec.unconfinedNot an entry but a bypass: it skips the path policy and the OS sandbox inside the container, and opens the container plane by itself. Owner/admin by default.
The operator's host machineexec.hostAdmin tier and never a bypass; an operator must also have provisioned the host broker.
A virtual desktop (or any other exec-capable source)Whatever that source's connector DECLARESEach connector names its own entry id in its manifest; the desktop's is exec.machine.

An entry feature grants nothing by itself. After it, the source's own path policy decides whether that caller may start a command in that directory — the same exec bit the Files UI shows, per role, user and agent.

The two tiers

The prefix states where a decision ACTS, so a feature's blast radius is readable from its id:

  • project.* — the decision acts inside your current project. It never reads or writes another tenant.
  • platform.* — the decision crosses the project boundary: another tenant's data, a platform-global file, cross-user records, or the platform-wide credential scope.

No platform.* feature carries a default role grant. They are grantable — an owner can hand one to any role — but nothing seeds them.

The split is what makes the project tier safe to grant broadly. It replaced a single administration namespace in which one id simultaneously opened the Users tab, the cross-tenant project list, the platform audit log and the whole cross-scope guard. Those are four independent powers and are now four independently grantable ids.

What the seeded roles hold

The owner role holds the '*' wildcard.

The admin role holds the enumerated union of every first-party non-platform.* feature, derived from the package manifests rather than hand-listed. So an admin is the strongest role inside a project, and does not cross the tenant boundary by default: they cannot see a project they are not a member of, read the platform audit log, patch platform settings, or act on another member's scope.

Two consequences worth knowing:

  • Because the admin list is enumerated rather than a wildcard, an admin does not automatically inherit features contributed by a project-dropped package. Those features stay listed in the role editor and an owner can grant them by hand. This is deliberate — a dropped package must not be able to grant itself onto the strongest in-project role.
  • The wildcard itself is not restricted: a role that holds '*' can still grant '*' to another role. What changed is that the seeded admin role no longer ships with it.

Governance is a flag, not a feature

Editing a project's role map, members, limits or agent ownership is gated by the role's stored canManageRoles flag — not by a feature. Governance must not be self-grantable through a capability toggle. A role granted project.roles can READ the role map; rewriting it additionally requires the flag.

Checks happen server-side before data access; UI affordances (hidden tabs, filtered lists) mirror the same grants but are never the defense. Feature gating is also applied to what agents see: skills, instructions, and tools from a package the caller cannot use are absent from the model's context entirely (see the security model).

Role priority

Priority is one ordinal number per role, lower = stronger. The built-in roles anchor the scale — owner 1, admin 2, manager 10, member 20, viewer 30 — and a role you define declares its own number anywhere in 1..99. The gaps are deliberate: a custom role slots between two built-ins without renumbering anything. The assignment rule:

A caller may assign a role only if the target role's priority is greater than or equal to the caller's own.

So a member (priority 20) can hand out member or viewer, but cannot invite an admin or mint an admin-strength agent; an admin (priority 2) cannot promote anyone to owner. The rule is enforced server-side at every assignment surface — project invites, role changes and agent provisioning — by one shared predicate; the admin UI additionally clamps its pickers to assignable roles, but the server decision is authoritative. Disallowed assignments return 403.

Member strength is always derived from the member's role; there is no per-member priority override. Removing a member is governed by the same rule: a caller cannot remove someone currently stronger than themselves.

Editing a role definition

Changing what a role is — its grants, its flags, its own priority — is a stricter act than handing that role to someone, and it follows two rules of its own:

Strength. You may edit only roles strictly weaker than your own priority. A role at or above your strength cannot be modified and cannot be deleted — including your own role.

Conservation. You may grant only, and revoke only, features you hold yourself.

The strictness on your own role is deliberate: it makes locking yourself out of your own project structurally impossible rather than merely unlikely. The practical consequence is that governance flows strictly downward — a role is tuned by something stronger than itself, and the strongest role in a project is fixed once the project is created. Defining a new role is the one place equality is allowed, so a second owner-strength role remains possible.

Conservation is symmetric on purpose. Being unable to hand out authority you lack is the obvious half; being unable to destroy authority you lack is the half that stops a project administrator from quietly stripping capabilities from a role above them. Deleting a role runs the same check over everything that role held, so deletion is not a way around it.

Both role and membership maps are submitted whole, so leaving an entry out is a deletion and is authorized as one.

The 1..99 range is a hard floor, not a convention: both role write paths — the project PATCH and the admin Config → Roles editor — reject anything outside it through one shared body, including 0, 100, a fraction, and a numeric string. Built-in priorities cannot be changed at all. That shared body is also where the two editing rules above live, so the two surfaces cannot answer differently for the same request. Existing projects created before the scale moved are migrated on first read; a custom role keeps its strength relative to the built-ins around it, and where the old number is ambiguous the migration always resolves it toward the weaker end.

Who created a project is not who governs it

The project record keeps a creator field for provenance and display, and it grants nothing. Everything that used to key on it now keys on strength or on a feature: archiving, restoring and permanently deleting a project — and changing its name or description, which every member's agents read — require an owner-strength role (priority 1 or stronger) in that project, while acting on a platform user's account — disabling, re-enabling, resetting the password, renaming or deleting it — requires governing that user in every project they belong to (a member there with the invite flag and a role at least as strong as theirs), and deleting additionally needs the platform.users feature. Because no role is seeded with platform.users, its holders out of the box are the wildcard (owner-strength) roles, until an owner grants it deliberately. So transferring governance is just a role change, and a creator who was later demoted keeps no residual powers — only the creator record itself cannot be deleted while it is some project's creator.

A custom role you define at priority 1 governs like the built-in owner across the layers that decide capability: feature grants, priority thresholds and route guards all read strength and grants, never the name. One boundary is worth knowing before you rely on it — per-source URI policies may carry per-role path overrides that are keyed on the role name. A custom role matches none of the built-in keys there, so it falls back to the source's default permissions instead of inheriting an owner-keyed override. That resolves fail-closed — less path access than owner, never more — but it does mean a custom owner-strength role needs its path overrides granted explicitly on the sources that define them.

Governance of the project record itself — members, roles, agent ownership and limits — is a separate per-role flag (canManageRoles) rather than a feature, so it can never be self-granted through a capability toggle. The built-in owner and admin ship with it; any role you define can be given it, and a role you configure without it does not get it back by being named something familiar.

How packages contribute features

Packages own their feature vocabulary — nothing is hardcoded in the host or the admin UI:

  • A package's manifest declares the features it provides, either as bare ids or as { id, title, description } objects that drive the labels and tooltips in the roles editor.
  • A package may declare default role grants — features that should land on specific roles when the package is installed. They merge into the project's roles once per package version, so an admin's later manual revoke is not silently re-applied on the next restart.
  • Uninstalling a project package revokes the features it granted (except any feature another loaded package still provides).
  • The roles editor consumes a runtime feature catalog built from the packages installed in that project, including which tools, skills, commands, and widgets each feature gates. Roles are project-level, so the catalog is too: another project's packages, the feature titles and descriptions they declare, and the contribution names they list as consumers are all absent — a package appears only once it is installed here.

The package-side contract — declaring providesFeatures, requiring features on routes and tools, and gating contributions — is documented in features and access. The admin screens for editing roles are covered under the admin package.

On this page