Skip to content

Identities, orgs & membership

Three records sit at the base of every Chorus instance: identities (who acts), the org (the singleton tenant), and memberships (the join that says an identity belongs to the org and in what capacity).

An identity is any principal that can act — a person, an autonomous agent, or an internal process. The type field is a fixed enum (src/identity/types.ts:28):

type Meaning
human A person operating an account.
agent An autonomous agent (LLM worker, bot).
system An internal process / service identity.

Beyond type, an identity carries name, capabilities, tags, an is_admin flag, and optional Web3 fields (wallet_address, agent_8004_id, reputation) used by the SIWE / ERC-8004 auth paths (src/identity/types.ts:30-50). A status (active / idle / disconnected) is computed from last_seen at response time, not stored — handlers like whoami and list-identities derive it (:43-49).

The server infers the acting identity from your authenticated credentials. You don’t pass a created_by — see the core loop.

Chorus is single-tenant by design. The org is a singleton: exactly one can exist per instance. createOrg throws if one is already present (src/db/adapter.ts:774; in-memory adapter raises Org already exists). The record is minimal — just name and created_at (src/org/company.ts:18-22).

Terminal window
# GET /org/company → the one org, or 404 ORG_NOT_FOUND if none created yet

A membership is the join row between an identity and the org (src/org/company.ts:25-34). It carries two enums:

claw_type — the capacity the identity holds in the org:

claw_type Constraint
central Unique — at most one central claw per org.
employee Standard member. Unlimited.
service Service / machine member. Unlimited.

status — the membership lifecycle:

invited → active → suspended → revoked

status defaults to invited on create (src/org/validation.ts:68). The states are a fixed enum; the arrows above are the intended progression — the API lets an admin set status to any value via PATCH .../status, so transitions are not state-machine-guarded.

  • One membership per identity. Re-creating for an identity that already has one returns ORG_MEMBERSHIP_DUPLICATE (company-routes.ts:183-196).
  • One central claw per org. Creating or PATCH-ing a second central returns ORG_CENTRAL_CLAW_EXISTS. This is not a DB constraint — the schema only asserts the claw_type enum (011-org.surql:11). Uniqueness is enforced in the adapter layer, which SELECT-counts existing central memberships before insert and throws if any exist (src/db/memory-adapter.ts:2007-2015, src/db/surreal-adapter.ts:2548-2558).

All membership routes are mounted under /org/company/* (src/app.ts:288) and are admin-only — every handler runs requireAdminPolicy (policy admin:manage on org):

Method + path Op Notes
GET /org/company org.get_company The singleton org.
GET /org/company/memberships org.list_memberships Filter by claw_type, status.
POST /org/company/memberships org.create_membership Org must exist first.
PATCH .../memberships/:id/status org.update_membership_status Set lifecycle status.
PATCH .../memberships/:id/claw-type org.update_membership_claw_type Re-cap a member.

(Source: src/org/company-routes.ts:586-591. The same file also hosts peer-org admin CRUD, which belongs to federation, not org membership.)