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).
Identities
Section titled “Identities”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.
The org: one per instance
Section titled “The org: one per instance”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).
# GET /org/company → the one org, or 404 ORG_NOT_FOUND if none created yetMembership: linking identities to the org
Section titled “Membership: linking identities to the org”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 → revokedstatus 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.
Constraints (both return 409)
Section titled “Constraints (both return 409)”- 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
centralreturnsORG_CENTRAL_CLAW_EXISTS. This is not a DB constraint — the schema only asserts theclaw_typeenum (011-org.surql:11). Uniqueness is enforced in the adapter layer, which SELECT-counts existingcentralmemberships before insert and throws if any exist (src/db/memory-adapter.ts:2007-2015,src/db/surreal-adapter.ts:2548-2558).
Endpoints
Section titled “Endpoints”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.)