Skip to content

Roles and permissions

Who can do what in Axon: seven canonical roles, their permissions, scope (instance vs project) and how they're assigned. Plus the engineer/"architect" persona, which is not in RBAC. The source of truth is core/models/auth.py; if this manual disagrees with the code — the code wins.


1. What it is and why

Every write command (CommandEnvelope) in Axon is gated by four checks before any side effect (invariant 4 "security before execution"):

permission check  →  object-scope check  →  project-scope check  →  policy pre-check  (+ budget check)
  • permission — does the actor's role have the right to this action (the ROLE_PERMISSIONS table in core/models/auth.py);
  • object-scope — does the target object belong to a zone the actor may touch;
  • project-scope — for project roles: is the object's project_id in actor.project_scopes;
  • policy pre-check — the Policy Engine; for side effects also a budget check.

A role defines what you can do; scope — in which projects. Seven roles: owner, admin, manager, operator, reviewer, read_only, system.

engineer / "architect" (the programmer) is not an RBAC role: it's not in core/models/auth.py. It's a persona that works in code (modules/, git) and at deploy time. For Console/API actions (e.g. publish_definition, create_credential) it needs an ordinary manager+ role. Manuals describing a programmer's work have a dedicated section "what's done in code vs in Console" (example — Connectors-Credentials.md §2.1).


2. Roles and access

2.1 Roles and scope

Role Type Scope Who it is
owner instance-scoped all projects of the instance the instance owner; the only role with breakglass
admin instance-scoped all projects of the instance the instance admin; everything owner has, minus breakglass
manager project-scoped only projects in project_scopes sets up and runs a project: connectors, credentials, binding profiles, publishing workflows, budgets, approvals, cases
operator project-scoped only projects in project_scopes runs processes, manages tasks/conversations, uses credentials (but does not create them)
reviewer project-scoped only projects in project_scopes approvals (approve/reject); reads the rest
read_only project-scoped only projects in project_scopes read only
system service actor (actor_type=system) any project (is_system() → access granted) auto-refresh OAuth, health snapshots, retention sweepers, pre-dispatch hooks; not assigned to humans (invariant I-9)

INSTANCE_SCOPED_ROLES = {owner, admin}. PROJECT_SCOPED_ROLES = {manager, operator, reviewer, read_only}. For instance-scoped roles project_scopes equals "all" (in ActorContext that's project_scopes=None); has_project_access(project_id) is always True for them. For project-scoped roles it's True only if project_id ∈ project_scopes.

actor_type ∈ {user, system, service}: user — a human; system — internal platform processes; service — external integrations (if used). is_system() grants cross-project access (but it's still gated by the system role's permissions + a handler-level allowlist (command_type, actor_id, source) for sensitive commands).

2.2 The "permission × role" matrix

From core/models/auth.py. owner = all "human" permissions (HUMAN_PERMISSIONS); admin = the same minus breakglass. — yes, — no, — not applicable.

Projects

Action Permission owner admin manager operator reviewer read_only system
Create a project create_project
Edit a project edit_project
Archive a project archive_project
Suspend / resume a project suspend_project / resume_project

edit_project is also the permission for the internal update_connector_health command (dispatched only by an internal/system actor).

Workflows

Action Permission owner admin manager operator reviewer read_only system
Create / edit a workflow create_workflow / edit_workflow
Start a workflow start_workflow
Pause / resume / cancel a workflow pause_workflow / resume_workflow / cancel_workflow
Retry / replan a workflow retry_workflow / replan_workflow
Publish a definition publish_definition
Configure a binding profile workflow:configure_bindings
Classification (collections/labels — UI navigation) manage_workflow_classification
Pin a run's binding snapshot workflow_run:pin_bindings ✅*
Invalidate bindings workflow_binding:invalidate ✅*

* system-only (in SYSTEM_ONLY_PERMISSIONS). pin_workflow_run_bindings runs as a sub-step of start_workflow (actor workflow_start_dispatcher). workflow_binding:invalidate is reserved (no command yet).

Tasks / conversations

Action Permission owner admin manager operator reviewer read_only system
Create / edit a task create_task / edit_task
Cancel a task cancel_task
Create a conversation create_conversation
Send a message send_message

Approvals

Action Permission owner admin manager operator reviewer read_only system
Approve / reject approve / reject_approval

Cases (case registry)

Action Permission owner admin manager operator reviewer read_only system
Create / edit / archive a case create_case / edit_case / archive_case
Attach an external ref attach_case_external_ref
Delete an external ref delete_case_external_ref

A manager can create/edit/archive cases and attach external refs, but not delete them — that's admin/owner.

Definitions, agents, prompts, templates, catalog

Action Permission owner admin manager operator reviewer read_only system
Manage agent definitions agent_definition:manage ✅†
Manage prompts prompt:manage ✅†
Manage templates manage_templates
Catalog: publish/archive catalog:manage
Catalog: install into a project catalog:install

system has agent_definition:manage and prompt:manage for publishing built-in definitions/prompts (origin='system'/'code'); handler-level scope checks still gate the actual action.

Read, admin, replay

Action Permission owner admin manager operator reviewer read_only system
Read (dashboards, statuses, audit) read
Manage users manage_users
Breakglass breakglass
Replay replay

breakglassowner only (admin = owner − breakglass). It's an emergency override; every use is written to audit.audit_log.

Credentials and connectors (details — Connectors-Credentials.md §2)

Action Permission owner admin manager operator reviewer read_only system
View credentials (redacted) credential:read
Create / update / revoke a credential credential:create / credential:update / credential:revoke
Test connection credential:test
Use in a run credential:use
Rotate / OAuth refresh-reauthorize credential:rotate
Maintain (auto-healing) credential:maintain ✅*
Purge (hard GDPR erasure) credential:purge ❌‡
Allowlist toggle project_connector:manage
Connector instance CRUD connector:manage
Webhook ingress verifier upsert/disable ingress_verifier:manage ✅*

* in SYSTEM_ONLY_PERMISSIONS — not granted to humans (I-9). ‡ credential:purge is reserved: it's in no role in ROLE_PERMISSIONS (including system) — it's a contract surface for Stage 3 retention sweepers, which get the right via a special grant. The delete_credential/purge_credential commands don't exist yet (see Connectors-Credentials.md §6.4).

2.3 System-only permissions (invariant I-9)

SYSTEM_ONLY_PERMISSIONS = {credential:maintain, credential:purge, ingress_verifier:manage, workflow_binding:invalidate, workflow_run:pin_bindings}. HUMAN_PERMISSIONS = all Permission − SYSTEM_ONLY_PERMISSIONS. Invariant: HUMAN ∩ SYSTEM_ONLY = ∅ (there's a blocking pytest). So owner/admin, even having "everything human", do not get these — auto-healing and hard-purge are by design executed only by system actors.


3. Where it is in Console

The Users sidebar section (under the Administration group; instance-level + per-project view). Available to owner/admin (manage_users).

Element What it does
User list actor_id, name/email, role, project_scopes (or "all projects" for owner/admin), status (active/deactivated)
Invite / New user create a user, assign a role and (for project-scoped roles) project scopes
Edit role change a user's role
Edit project scopes for manager/operator/reviewer/read_only — the list of projects they can access
Deactivate disable a user (new commands/approvals from them are no longer accepted)
Role indicator in the header the current user sees their role (e.g. "Administrator owner")

The operational side (how exactly to create/assign, the breakglass flow) — Users-And-Access-Management.md.


4. Concepts (mental model)

ActorContext  (propagated through all layers)
  ├─ actor_id        — stable actor id
  ├─ actor_type      — user | system | service
  ├─ actor_role      — owner | admin | manager | operator | reviewer | read_only | system
  └─ project_scopes  — list[str]  OR  None (= all projects, instance-scoped)

  has_project_access(project_id):
      instance-scoped role  → True
      actor_type == system  → True
      project_scopes is None → False         (a project-scoped actor with no explicit scopes — no access)
      else                  → project_id in project_scopes

Permission  — StrEnum (~45 values), grouped by domain (see §2.2)
ROLE_PERMISSIONS: dict[Role, frozenset[Permission]]
  owner   = HUMAN_PERMISSIONS                              (including breakglass)
  admin   = HUMAN_PERMISSIONS − {breakglass}
  manager = <explicit set> | _CREDENTIAL_MANAGER_PERMISSIONS
  operator/reviewer/read_only/system = <explicit narrow sets>

SYSTEM_ONLY_PERMISSIONS ⊂ Permission   — never for humans (I-9)
HUMAN_PERMISSIONS = Permission − SYSTEM_ONLY_PERMISSIONS

Key ideas: - instance-scoped vs project-scoped — owner/admin see/touch all projects; the rest are limited to project_scopes. This is orthogonal to role permissions: even if a role has edit_workflow, a project-scoped actor can only edit in their projects. - system is separated from humans — a system actor has a narrow set of rights (rotate/maintain/ingress/agent/prompt + pin/invalidate) and is additionally gated by a handler-level allowlist (command_type, actor_id, source) for sensitive commands. Humans don't get these system rights at all. - credential:use ≠ the right to decrypt — it's a "project membership capability"; secret plaintext is decrypted only in the worker. - breakglass ≠ bypassing the audit — it's an emergency owner-only override that is itself written to the audit (invariant 9: transparency).


5. Flows: step-by-step scenarios

Flow 1 — Provision a manager for a project (owner/admin)

  1. Console → Users → Invite / New user.
  2. Enter actor_id/email, name.
  3. Role = manager.
  4. Project scopes = [<project_id>] (one or several projects).
  5. Save → the user can configure those projects (connectors, credentials, binding profiles, publish workflows, budgets, approvals, cases). They have no access to manage_users, create_project, templates, agent/prompt definitions, classification.

Flow 2 — Give an operator another project (owner/admin)

  1. Console → Users → pick the user (operator) → Edit project scopes.
  2. Add <new_project_id> to the list.
  3. Save → the operator can run workflows / manage tasks / use credentials in the new project (but not create credentials/instances and not touch the allowlist — that's manager+).

Flow 3 — Emergency breakglass (owner only)

  1. Used when the normal path is blocked (an incident). Available only to owner (breakglass).
  2. Every use is automatically recorded in audit.audit_log — it's not a "silent root" but an audited override (invariant 9). More — Security-And-Audit.md.

Flow 4 — Deactivate a user (owner/admin)

  1. Console → Users → pick the user → Deactivate.
  2. Effect: new commands/approvals from them are not accepted. Already-performed actions stay in the audit. If they had pending approvals — reassign them to another approver with access.

6. Options reference

6.1 Roles (the Role enum)

Role Scope Base permission set
owner instance HUMAN_PERMISSIONS (everything human, including breakglass)
admin instance HUMAN_PERMISSIONS − {breakglass}
manager project an explicit set (workflow CRUD/lifecycle except retry/replan, publish_definition, tasks create/edit, approve/reject, conversations, read, cases create/edit/archive/attach, project_connector:manage, connector:manage, workflow:configure_bindings, catalog:install) + _CREDENTIAL_MANAGER_PERMISSIONS (credential read/create/update/rotate/revoke/test/use)
operator project create_task, edit_task, send_message, start_workflow, read, credential:read, credential:test, credential:use
reviewer project approve, reject_approval, read, credential:read
read_only project read, credential:read
system actor_type=system rotate_credential, maintain_credential, ingress_verifier:manage, workflow_binding:invalidate, workflow_run:pin_bindings, agent_definition:manage, prompt:manage

6.2 ActorContext (fields)

Field Type What it does
actor_id str a stable actor identifier (lands in the audit)
actor_type user / system / service the type; system grants cross-project access
actor_role Role determines the permission set
project_scopes list[str] | None the projects accessible; None = all (instance-scoped)

6.3 The full Permission list

See §2.2 (grouped by domain) and core/models/auth.py. Every write command maps to a single Permission (_COMMAND_PERMISSION_MAP in core/api/commands/execution_paths.py).


7. Lifecycle and maintenance

  • Changing a roleEdit role in the Console (via CommandEnvelope, written to the audit). The user's pending actions stay as they are; new ones are gated by the new role.
  • Deactivation — the user can no longer run commands; data/audit are preserved. Pending approvals on them — reassign.
  • Rotating admins / the owner — an operational procedure; the owner is the only one with breakglass, so changing the owner needs special care (usually: a new owner is assigned by the acting owner).
  • Audit of access changes — all role/scope/deactivation changes are in audit.audit_log.
  • credential:purge (reserved) — will be held by the retention-sweeper actor in beyond-Stage-3 (GDPR hard-purge); no role has it now.

8. Troubleshooting

Symptom Cause What to do
403 / "permission denied" on a command the actor's role doesn't have the corresponding Permission Check §2.2; if needed — elevate the role (owner/admin) or grant the right via the role that has it. Don't "invent" new permissions.
"No access to the project" / object not visible a project-scoped role, and project_id is not in project_scopes Edit project scopes (owner/admin) — add the project.
The owner can't find breakglass on an admin by design: admin = owner − breakglass breakglass is owner-only.
A system actor "can't" run a command despite the system role the command isn't in its permission set, or it didn't pass the handler-level allowlist (command_type, actor_id, source) Check that this action is even done by a system actor (not all are); for sensitive commands — the right actor_id/source is needed.
A manager can't delete an external ref / publish a prompt / create a project manager doesn't have those rights (see §2.2) Those actions are admin/owner (and prompt:manage / agent_definition:manage — owner/admin/system).

9. Constraints and invariants

  • HUMAN ∩ SYSTEM_ONLY = ∅ (I-9). credential:maintain, credential:purge, ingress_verifier:manage, workflow_binding:invalidate, workflow_run:pin_bindings — never for humans; there's a blocking test.
  • admin = owner − breakglass. Admin never gets breakglass, under any condition.
  • Security before execution (invariant 4): permission + object-scope + project-scope + policy pre-check (+ budget) — before the side effect, not after.
  • All mutations go via CommandEnvelope. RBAC is checked server-side, not in the UI. The UI merely hides unavailable actions — it is not a security boundary.
  • Optimistic concurrency. Where there's a version — mutating commands require expected_version.
  • Transparency (invariant 9): any access change and any use of breakglass is in audit.audit_log.
  • engineer/"architect" is not an RBAC role. For Console/API actions a real manager+ role is required.