DocsGovernanceIdentity & Principals

Identity & Principals

A principal is anything that can act on a Core: a person, an AI agent, or a service account. All 3 are rows in rootcx_system.users and are governed by the same RBAC, the same enforcement, and the same audit trail. There is no second system for non-human actors.


The 3 kinds

The users.kind column distinguishes them:

kind Principal How it authenticates
human A person Password, OIDC SSO, or magic link
agent An AI agent Acts under delegation from a human (no direct login)
service A service account Client-credentials token (client_id + client_secret)

A principal's kind is not cosmetic. Delegation rules read it: a delegatee can never be a human, and an agent cannot initiate an act-as delegation.


Humans

Created by registration, OIDC SSO, or a magic link. A human row stores an email, an optional Argon2id password hash, and a display name.

The first registered user is promoted to the admin role, atomically: the promotion only succeeds if no admin exists yet. Every later user starts with no roles until an admin grants one (or, for OIDC, the provider's default_role, which is base by default).

See Authentication for registration, login, OIDC, and magic links.


AI agents

An agent is an app with a brain. On deploy, the Core gives it an identity automatically:

Property Value
UUID Deterministic UUIDv5 derived from the app id (stable across restarts and redeploys)
Email agent+{appId}@localhost
is_system true
kind agent
owner_of_record The user who deployed it

Because the UUID is deterministic, the audit trail can always answer "what has this agent done over time" by querying one stable id.

On first deploy the agent receives a least-privilege role, not admin. See AI Agent Governance for how its authority is bounded.


Service accounts

A non-human principal owned by a human, for scripts and machine-to-machine access. A service account is a users row with kind = 'service', a random UUID, the email sa+{slug}@localhost, and no password or OIDC identity.

A service account is born with zero permissions. An admin grants it a least-privilege role through the standard roles API. It authenticates with a client-credentials token.

See Service Accounts for the full lifecycle.


Owner of record

Agents and service accounts carry an owner_of_record: the human accountable for them. For an agent it is the deployer; for a service account it is the creator. Ownership can be transferred, but only to another human.

The owner matters at enforcement time. When an autonomous trigger fires, the Core checks that the owner still exists, is still enabled, and still holds the permission to invoke. An offboarded owner kills the automation.


Disabling a principal

Setting disabled_at on a principal cuts its access immediately. A disabled service account can no longer mint or use tokens, and any standing mandate that depends on a disabled owner stops firing. No token needs to expire first. The check runs live against the database on every request.


The system user

One internal user (00000000-0000-0000-0000-000000000001) exists for the Core's own bookkeeping. It is stored as a service principal but is excluded from the service-account API (never listed or managed there), and is not seeded with the admin role. Internal operations run on the Core's superuser connection (which bypasses RLS), so no system identity needs to pass permission checks. This also keeps the "first registered user becomes admin" rule intact.