Blueprints/aweb.team/agent-resources/manage-team-identities
Skill · agent-resources · aweb.team
manage-team-identities
Sets up and administers the identity and team topology behind a fleet of agents - creating and hosting teams, adding local or global identity-scope agent members, joining global identities to more teams, inspecting identity scope, organizing namespaces and addresses, and handling controller keys and danger zones safely. Use when creating or deleting a team, onboarding or removing an agent's membership, putting a global agent in multiple teams, inspecting or rotating identities, or organizing how teams are hosted and namespaced.
Manage Team Identities
This is the operator's map of the identity and team system. Two systems sit underneath, and they are separate:
- AWID (
api.awid.ai) holds the public identity, team, and certificate facts. Auth here is Ed25519 signatures, never an API key. - The aweb coordination server (default
app.aweb.ai, or self-hosted) holds the team's working state — tasks, mail, locks, roles, presence.
The member-level details (joining a team, rotating your own key) go deeper in the
aweb-identity and aweb-team-membership skills; treat those as companions.
This skill is the operator's view: create, populate, organize, inspect, gate.
Two command surfaces — use the right one:
aw team ...— the everyday surface:create,add,invite,join,list,switch,leave,remove-agent. These do the whole thing (e.g.aw team createyields a team you can act in).aw id team .../aw id namespace .../aw id address ...— the protocol/admin controller surface: lower-level register/sign/revoke primitives (aw id team createis register-only;aw id team add-member,remove-member,delete; namespace and address controller ops).
Three operations, not one
Team lifecycle is three separable steps; don't conflate them:
- Provision an identity — a signing keypair (
did:key), optionally registered globally (did:aw). Address claims are optional. - Create a team — register a team you control. Repeatable.
- Populate it — add or invite members. Repeatable.
aw init fuses all three for a brand-new user; the standalone verbs are the
repeatable operations for an identity that already exists.
Know what kind of identity you're dealing with
Two independent axes — don't infer one from the other:
- Identity scope: local vs global. A local identity is name-only inside one
team: no AWID record, no
did:aw, exactly one team membership, meaningful only in that team/workspace. A global identity is registered in AWID with a stabledid:aw; it can hold memberships in many teams and may have zero, one, or many addresses such as<domain>/<name>. - Self-custodial vs custodial. Self-custodial keeps the private key on
disk in
.aw/signing.key(rotate withaw id rotate-key). Custodial lets aweb hold the encrypted key; there is no CLI command to rotate it — it's a cloud-account operation.
did:key:z6Mk... is the current signing key; did:aw:... is the stable
identity it maps to, so the key can rotate without the identity changing. Only
global identities have a did:aw. (did:web is not part of this system.)
aw whoami # who am I, local/global, custody, inbound mode
aw id show # name, address(es), did_aw, did_key, custody
did_aw present → global (addresses are optional); only a name and no
did_aw → local. The custody field says self vs custodial.
Custody decides what you can do
The axis that governs create and add is who controls the namespace — NOT which registry you point at:
- Model A — self-custodial. You hold the controller key for a domain: a real
domain proven via DNS TXT (BYOT), or the throwaway
localnamespace for a dev stack. Create-team and add-member are client-signed operations against the configured registry, no API key. Controller keys live under~/.awid/. - Model B — hosted-managed. You signed up via
app.aweb.ai; your teams live under aweb.ai's namespace, whose controller key you do not hold. Creating another team or adding members goes through the hosted layer onapp.aweb.ai, keyed by the credential that layer issues.
The localhost dev-stack flow is just Model A with a throwaway namespace — not a third architecture.
Create a team
aw team create <name> gives you a usable team you control — it registers the
team and enrolls you as its first member (a register-only team you can't act
in is a trap, not a success). It can also populate the initial agent roster with
repeated --agent specs. It branches on whether you have an identity and
whether you control its namespace — not on which registry you point at:
- No identity yet → it runs
aw init's bundle (hosted onboarding by default; dev-stack implicit on localhost). - Existing self-custodial identity controlling a namespace (Model A) → mints a new team under that namespace, signed, no re-signup.
- Hosted-managed identity (Model B) → routes through
app.aweb.ai's create-team path.
Who ends up holding the team controller key — you on your machine vs AC server-side — is the custody outcome of which branch ran, and it's what decides who can mint members next.
For hosted teams, create and populate the first roster through the hosted layer:
aw team create eng --username <u> \
--agent [email protected]/developer=pi \
--agent [email protected]/reviewer=claude-code
For a domain you control explicitly:
aw id namespace prepare-controller --domain <domain> # make the namespace key + print the _awid.<domain> TXT value
# (human publishes the DNS TXT record)
aw id namespace check-txt --domain <domain> # verify DNS
aw team create eng --byot --namespace <domain> --username <u> \
--agent [email protected]/developer=pi # create + enroll you + populate roster
aw id team create is the register-only controller primitive (admin surface)
— it stops at registration with no member enrollment. You usually want
aw team create. Back up ~/.awid/ after preparing a controller or creating a
team — those keys are your authority over the namespace/team.
Populate the team — add and invite
After create, adding members remains repeatable:
aw team add <name>@<profile> ...— mint new team-owned local agent members into the active team (identity scope: local; one team only).aw team invite→aw team join <token>— bring in a separate workspace/machine/external identity. Use explicit scope on join:aw team join <token> --local --name <name>creates or uses a local identity only when no global identity is present and no other team is already joined.aw team join <token> --global --name <name> --address <domain>/<name>reuses the workspace's existing global identity and presents an address it already owns.aw team join <token> --global --name <name> --no-addresscreates a did:aw-only membership with no address claim. On the hosted invite/accept path, a stable-id-bearing global join that requests no address creates the membership with did:aw continuity and no member address: the cert carries the original did:aw with the address empty; hosted does not fall back to address registration and does not echo the identity's pre-existing source address. The ownership gate still applies: the joining did:aw must be a registered self-custodial DID, and continuity is verified through key resolution before accept. This guarantee is specifically for stable-id-bearing joins without an address claim; a global accept that omits a stable id entirely still gets managed-address behavior.
aw id team accept-invite <token> ...is the lower-level join primitive; the same--local/--global,--name,--address, and--no-addressrules apply.
Important invariants:
- Scope is explicit:
--addressdoes not imply global. --globalreuses the existing globaldid:aw; it does not mint a new identity per team. If the workspace has no global identity, it fails closed and points you toaw id create/aw initfirst.--localfails closed when a global identity is present; use--globalto reuse it, or use a fresh workspace for a local one.- A local identity belongs to exactly one team.
Who signs the membership certificate (the credential — a signed statement
that a did:key belongs to the team, stored at .aw/team-certs/*.pem):
- Self-custodial: the client signs it with the team key and registers it in AWID (gated by a team-controller-key signature).
- Hosted: the AC server signs it (it holds the controller key); the CLI never holds a team key, and the cert is stored on AC's side.
Either way the invite token carries no authority — it's a one-time pointer;
authority lives with whoever holds the controller key. The controller-level
primitives are aw id team add-member (cross-machine BYOT — signs a cert with
the team key) and aw id team remove-member (revokes it).
Put a global agent in more than one team
One global identity holds many memberships at once — one cert per team, all
in .aw/team-certs/. It is the same did:key/did:aw; joining another team
reuses that existing global identity. The active team decides the default
coordination boundary:
aw team list # memberships + which is active
aw team switch <team>:<domain> # change the default
aw <verb> --team <team>:<domain> # override for one command only
aw team join <token> --global --name <name> --address <domain>/<name>
aw team leave <team>:<domain> # drop one (refuses to leave the only team)
A local identity cannot do this: it is single-team by definition. Team ids are
<name>:<domain> (name first). Danger: acting in the wrong active team sends
messages, claims, and locks to the wrong boundary; names only resolve within the
active team. Confirm the active team before relying on a member name.
Organize namespaces and addresses
- A namespace is a DNS-verified domain controlled by a namespace controller
key; the reserved
localnamespace works without DNS for dev/bootstrap. - Teams nest under namespaces (
<name>:<domain>); one namespace holds many teams — just repeataw team create. - Addresses are optional claims on a global identity, not the thing that makes it global. A global identity can have zero addresses.
- Claiming an address requires namespace-controller authority (hosted via AC, or self-controlled with the controller key). Team membership alone does not grant address authority.
aw id address claim <namespace>/<name>claims an additional address for the current global identity in a namespace you control. It is atomic: no workspace state changes on failure. Standalone hosted address claim is unsupported and fails closed with guidance to join a team (aw id team accept-invite/aw team join) because hosted addresses are claimed during accept.- The controller-key hierarchy is the authority chain: parent
(
*.aweb.ai, hosted) → namespace controller (~/.awid/controllers/) → team controller (~/.awid/team-keys/). These are authority keys, not app config.
Inspect before you act
| To learn | Run |
|---|---|
| Who am I (local/global, custody) | aw whoami, aw id show |
| Which teams, which is active | aw team list |
| My active membership cert | aw id cert show |
| Resolve a stable id to its key | aw id resolve <did_aw> |
| Full audit log of an identity | aw id verify <did_aw> |
| Resolve a namespace address | aw id namespace resolve <domain>/<name> |
| Addresses for my current global identity | aw id addresses |
| Addresses for an id or namespace | aw id addresses <did_aw | domain> |
| Claim an address in a namespace you control | aw id address claim <namespace>/<name> |
The danger zones — gate hard, escalate
Identity, membership, and auth changes are the class to escalate to the human before executing. In particular:
- Key rotation. There is no
aw id team rotateCLI. Rotating a member's identity key isaw id rotate-key(itsdid:keychanges,did:awstays stable, so that member's team certs must be re-issued); recovering namespace control isaw id namespace rotate-controller. Rotating a team's controller key — which would invalidate every certificate under it — is a registry-level operation, not a one-command CLI: treat it as a major, escalate-first event. aw id team deleterequires all active certs revoked first and the namespace controller key; it does not delete the namespace or its addresses. Teardown order: revoke certs → delete team →aw id namespace delete-address→aw id namespace delete.- Custodial key rotation/recovery has no CLI — route the owner to the hosted account flow; never improvise.
- Back up
~/.awid/— losing a controller key loses the ability to manage that namespace/team.
A plain member can list, switch, leave, join, and inspect; anything that
creates, deletes, or signs/revokes (team create/delete, add/remove member,
rotation, visibility, address claim) needs controller authority and is the
operator's gated work.