Skip to content
PricingBlog
✨ Markdown

Permissions & principals

Electric Agents servers authorize requests using a principal and permission grants. A principal identifies the caller; grants decide what that caller can do to entity types and entity instances.

Principals

Pass a principal key as the Electric-Principal header. The key shape is:

text
<kind>:<id>

Supported principal kinds are user, agent, service, and system. The server turns a key such as user:sam into the canonical principal URL /principal/user%3Asam.

From clients, use principalKey:

ts
import { createRuntimeServerClient } from "@electric-ax/agents-runtime"

const client = createRuntimeServerClient({
  baseUrl: "http://localhost:4437",
  principalKey: "user:sam",
})

The CLI can pass the same value through the environment:

sh
ELECTRIC_AGENTS_PRINCIPAL=user:sam electric agents ps

Servers may also accept additional auth headers through ELECTRIC_AGENTS_SERVER_HEADERS or serverHeaders, depending on the host.

Entity type permissions

Entity type grants control who can spawn or manage entities of a type. Type-level permissions are:

PermissionAllows
spawnSpawn entities of this type
manageManage the entity type and acts as the broader type-level permission

Declare initial type grants in an entity definition:

ts
registry.define("worker", {
  description: "Internal worker",
  permissionGrants: [
    {
      subject_kind: "principal_kind",
      subject_value: "user",
      permission: "spawn",
    },
  ],
  async handler(ctx) {
    // ...
  },
})

subject_kind can be principal for one principal URL/key or principal_kind for every principal of a kind.

Built-in Horton and Worker registrations grant all user principals both spawn and manage on those entity types. This makes local and hosted user principals able to create built-in sessions and see the type metadata needed by creation UIs. Custom entity types should choose their own permissionGrants.

Entity permissions

Entity grants control access to existing entities. Entity-level permissions are:

PermissionAllows
readRead entity metadata and streams
writeSend messages and write entity-owned resources
deleteDelete or kill the entity
signalSend lifecycle signals
forkFork from entity history
scheduleCreate, update, or delete schedules
spawnSpawn children from this entity
manageManage grants and acts as the broader entity-level permission

Server spawn routes can include initial entity grants:

ts
await fetch("http://localhost:4437/_electric/entities/assistant/support-ticket-42", {
  method: "PUT",
  headers: {
    "content-type": "application/json",
    "electric-principal": "user:sam",
  },
  body: JSON.stringify({
    grants: [
      {
        subject_kind: "principal",
        subject_value: "/principal/user%3Asam",
        permission: "read",
      },
    ],
  }),
})

When spawning from a parent, broad delegation requires manage on the parent. This applies to grants such as manage, principal-kind grants, descendant propagation, and copy_to_children.

Sharing roles

The server stores granular grants, but the UI presents common sharing roles:

RoleGrants
viewread, fork
chatread, write, signal, fork, schedule, spawn
managemanage, delete

These are presets for entity grants. They do not change the underlying permission model, and deployments can still grant individual permissions directly.

The server also exposes principal and effective-permission data through Electric shapes for sharing UIs. Point authorization still uses server-side permission checks; Electric visibility is derived from materialized effective permissions.

Grant propagation

Entity grants may include propagation options:

ts
{
  subject_kind: "principal",
  subject_value: "/principal/user%3Asam",
  permission: "read",
  propagation: "descendants",
  copy_to_children: true,
}
  • propagation: "self" applies to the entity itself.
  • propagation: "descendants" applies through descendant entities.
  • copy_to_children: true copies the grant when children are spawned.
  • expires_at can set a grant expiry timestamp.

Claim-scoped write tokens

Some low-level writes are protected by claim-scoped write tokens. Handler APIs such as ctx.setTag() and ctx.deleteTag() already have the active claim context. External clients should usually send messages instead of directly mutating entity-owned state.

If a host reserves the Authorization header for server auth, configure write token transport with writeTokenHeader or claimTokenHeader:

ts
const client = createRuntimeServerClient({
  baseUrl: "http://localhost:4437",
  headers: { authorization: `Bearer ${serverToken}` },
  writeTokenHeader: "electric-claim-token",
})

Development fallback

Local development servers can use a development principal fallback. Production deployments should authenticate requests and provide an explicit Electric-Principal header for every request.