Organizations & Teams
Multi-developer collaboration without sacrificing per-agent ownership. Bring your team in, scope agents under a shared umbrella, audit deployments at the org level — without losing the agent-centric identity model that makes The Protocol sovereign.
Why It Matters
A real-world AI team is rarely one developer. You ship a fleet of agents, you have a QA group, you have a customer-success engineer who needs to watch agent health but should never push a new version. The Protocol's identity primitive is the agent — sovereign, holds its own keys, carries its own reputation — but agents have owners, and owners have collaborators.
Organizations are the unit of collaboration. They give you:
- Multi-developer ownership — an agent belongs to an org, not just to the person who minted its bootstrap token
- Scoped permissions — a four-tier role hierarchy decides who can ship, audit, or just observe
- Shared CI/CD visibility — every pipeline + deployment + active version under your org rolls up into one summary endpoint
- Org-scoped bundle sharing — a bundle published with
visibility='org'reaches every member without going through the public marketplace - Lifecycle audit — every member invitation, join, and removal is an EventStore event
Click any diagram to enlarge.
The 4-Role Hierarchy
Every org member carries one of four roles. Roles are cumulative — a higher role does everything a lower role does, plus more.
The numeric weights matter because the permission check is "is your role ≥ required role?" rather than "is your role exactly X?" — admin can do everything member can do, and owner always passes any check.
The check itself lives in OrganizationService.check_member_permission(db, org_id, dev_id, required_role). The owner shortcut is unconditional: the org's owner_id field always wins, regardless of the membership row's role.
TIP
Pick "member" as your default invite role. It lets the invitee author pipelines and ship versions on agents the org owns, but blocks them from administrative mutations like inviting more people or deleting teams. Most contributors should stay at member until you have a specific reason to elevate them.
The Five Tables
Five tables shape the entire system. Two of them are wires onto the existing agents table.
Two soft-vs-hard delete quirks worth knowing:
- Org members are hard-deleted on removal — the row vanishes. This keeps the
UNIQUE(org_id, developer_id)constraint clean for re-invitations (you can be invited back to an org without conflicting with a tombstoned row). - Team members are soft-deleted (
is_active=false) on removal — the row stays. Re-add reactivates it. Different choice, different reason: team membership history is more useful for audits than org membership history.
Default org limits (settable per-org via the limits JSON column): 100 agents, 50 members, 10 teams. The limit check fires at create time on the relevant endpoint (e.g., max_teams is checked in POST /organizations/{org_id}/teams).
Inviting Members
The invitation flow is token-based and decoupled from the email layer — you create the invitation, the system returns an invitation_token, and your platform decides how to deliver it (email, in-app notification, copy-paste in Slack).
Properties:
- Token expiry is 7 days from creation (
expires_atcolumn onorganization_invitations). - Tokens are unique (
UNIQUEconstraint) —secrets.token_urlsafe(32)so collision is statistical zero. - The invitee must already have a developer account on the registry they're being invited to (the accept endpoint requires their dev JWT). If they don't, signup → login → accept.
- Email delivery is decoupled — if the registry's email layer isn't configured, the admin still gets the token in the response and can deliver it however they prefer.
- Status enum:
pending→accepted(success),cancelled(admin revoked),expired(7-day timeout, swept by token-expiry path).
INFO
Three lifecycle events go to the EventStore (audit-only, skip_in_projection=true so they don't burden the supply audit): OrganizationMemberInvited · OrganizationMemberJoined · OrganizationMemberRemoved.
Plus on org create/delete: OrganizationCreated · OrganizationDeleted. Member role changes are intentionally not emitted — the natural emit site is nuanced; deferred until a real need arises.
The Joined and Invited events also fire WebSocket broadcasts via three sub-reactors (OrganizationMemberInvitedReactor, OrganizationMemberJoinedReactor, OrganizationMemberRemovedReactor in background_tasks/reactor_org_lifecycle_notification.py). Connected admins see "Member joined" toasts in real time; the invitee gets "You've been invited."
What Becomes Org-Scoped
Three subsystems gain org-aware behavior once you're an org member.
Each of these wires is small, surgical, and ships independently — they're not bundled into a monolithic "Org mode" flag. The org system is opt-in and additive: a developer with zero orgs sees no behavioral change from the agent + CI/CD + bundle layers.
::: warn Agent assignment is creator + admin (AND, not OR). Both POST /organizations/{org_id}/agents/{agent_did}/assign and /unassign require the caller to be both the agent's creator AND an admin of the org. This is different from the CI/CD permission check, which is creator OR org member. The rationale: only the creator can decide which org an agent belongs to; the admin must accept the assignment. It's deliberate — the asymmetry prevents an org admin from absorbing somebody else's agent without consent, and prevents a non-admin from dropping an agent into an org they don't run. :::
Org-Scoped CI/CD Rollup
The single most valuable cross-feature wire is GET /api/v1/organizations/{org_id}/cicd-summary. Aggregates every agent under the org into one rollup.
The endpoint is empty-state friendly — an org with no agents still returns a clean shape with zero counts. The aggregation runs SQL-side (no per-agent N+1) so a 100-agent org responds in tens of milliseconds.
This is the data behind the "Org Dashboard" view (views/OrganizationDashboard.vue) and the surface most useful to an org admin reviewing how their team is shipping.
Multi-Org Developer Resolution
What happens when you belong to two or three orgs? The system is honest about ambiguity — it never picks for you when intent isn't clear.
The endpoint that resolves the ambiguity (POST /organizations/{org_id}/agents/{agent_did}/assign) requires the AND check from earlier — you must be both the agent's creator AND an admin of the target org. The reverse is POST /organizations/{org_id}/agents/{agent_did}/unassign and follows the same rule.
API Surface — 22 Endpoints
The full router is mounted at /api/v1/organizations. Grouped by intent:
Org lifecycle
| Method | Path | Min role | Notes |
|---|---|---|---|
POST | /organizations | dev JWT | Create org + auto-create owner row + "Default Team" |
GET | /organizations | dev JWT | List orgs where caller is owner or active member |
GET | /organizations/{id} | member | Org detail with member_count + team_count |
GET | /organizations/slug/{slug} | member | Lookup by slug |
PUT | /organizations/{id} | admin | Update name/description/website/logo_url/settings/limits |
DELETE | /organizations/{id} | owner | Soft-deactivate (is_active=false) |
Teams
| Method | Path | Min role | Notes |
|---|---|---|---|
POST | /organizations/{id}/teams | admin | Enforces limits.max_teams |
GET | /organizations/{id}/teams | member | List active teams + member counts |
GET | /organizations/{id}/teams/{tid} | member | Team detail |
PUT | /organizations/{id}/teams/{tid} | admin | Update |
DELETE | /organizations/{id}/teams/{tid} | admin | Refuses for "Default Team" |
POST | /organizations/{id}/teams/{tid}/members | admin | Add member; reactivates if previously soft-deleted |
DELETE | /organizations/{id}/teams/{tid}/members/{did} | admin | Soft-delete team membership |
Members + invitations
| Method | Path | Min role | Notes |
|---|---|---|---|
GET | /organizations/{id}/members | member | Active org members with developer info eager-loaded |
POST | /organizations/{id}/invite | admin | Create invitation (returns token) |
POST | /organizations/invitations/{token}/accept | dev JWT (invitee) | Accept; inserts org_members row |
DELETE | /organizations/{id}/members/{did} | admin | HARD-delete the membership row |
Org-scoped agents + CI/CD
| Method | Path | Min role | Notes |
|---|---|---|---|
GET | /organizations/{id}/agents | member | List agents WHERE organization_id=id |
POST | /organizations/{id}/agents/{did}/assign | admin AND agent's creator | Assign an existing agent to this org |
POST | /organizations/{id}/agents/{did}/unassign | admin AND agent's creator | Remove the org assignment |
GET | /organizations/{id}/cicd-summary | member | Org-wide CI/CD rollup (see diagram above) |
TIP
min role reads as a floor, not an exact match. "admin" in the table means "admin OR owner". "member" means "any active member, role doesn't matter for read paths". Owner always passes.
Cross-Frame Behavior
Orgs are per-frame — there is no cross-frame organization federation today. An org created on Frame A is invisible to Frame B; a developer with org membership on both frames is two separate organization_members rows in two separate registries.
This is deliberate (not a missing feature):
- Frames are sovereign (chapter 18). Each frame governs its own developers, agents, and now orgs.
- Cross-frame agent membership would require WS3-grade bilateral schema agreement — that's a roadmap item, not a near-term ship.
- Cloud-op fleets inherit their parent frame's org system. A developer on a Frame A operator joins orgs on Frame A; on a Frame B operator, orgs on Frame B.
The practical implication: if you operate teams on both frames (rare), you maintain parallel orgs and invitations on each. Cross-frame agent assignment cannot reference a foreign org by id.
Common Patterns
A — One-org-per-team (default)
The simplest model. Your team creates one org, everyone is a member (default role), the org owner is whoever set it up. Agents auto-link on creation. CI/CD summary shows everyone's pipelines in one view.
B — Multi-team within one org
A growing org needs sub-grouping. Create teams under the org (engineering, qa, ops). Use teams to scope agents you want sub-grouped by setting agent.team_id. Org-level visibility is unchanged; team membership adds a filtering dimension for fleet views.
C — Federated team (multiple orgs)
A consultancy that works with two clients keeps two orgs (one per client). A developer who works on both has two memberships and resolves agent ownership at create time by picking an org from available_orgs in the bootstrap response.
D — Solo developer
Don't create an org. The system works perfectly without one — agents have organization_id=NULL, CI/CD pipelines still work via the creator-path permission check, bundles default to visibility='private'. Orgs are opt-in collaboration sugar.
INFO
The Default Team. When you create an org, the system auto-creates one team named "Default Team" with permissions=['read_agents', 'create_agents'] and the org's owner as its lead. You can't delete the Default Team — the API refuses on DELETE /organizations/{org_id}/teams/{team_id} — because some org-scoped flows assume at least one team exists. Rename it (e.g., to "Engineering") via PUT; don't fight the default.
What's Next
- 🔗 Chapter 21 — Webhooks & Integrations — HMAC-signed event subscriptions for org-scoped CI/CD pipelines, bundle restore, and the full live event catalogue
- 🔗 Chapter 01 — Agents & Identity — the agent-centric identity model orgs collaborate around
- 🔗 Chapter 07 — Event Store & Reactors — the audit ledger that records every org lifecycle event
- 🔗 Chapter 09 — API Flows — the underlying HTTP surface, including the Idempotency-Key header that org-scoped bundle restore relies on
- 🔗 Chapter 19 — Compliance — multi-developer accountability + audit posture