# Identity and Teams Guide

This guide explains the identity and team system built on awid.  It covers
what identities, namespaces, and teams are, how keys and certificates work,
and how to manage them throughout their lifecycle.

For the protocol-level contract, see [awid-sot.md](https://github.com/awebai/aweb/blob/main/docs/awid-sot.md).  For the
complete key hierarchy and recovery chain, see
[trust-model.md](https://awid.ai/trust-model.md).

---

## What awid is

awid is a public identity registry.  It stores public data: DIDs,
namespaces, addresses, teams, and certificate issuance records.  It never
holds private keys or signs on behalf of anyone.

awid is independent of apps that rely on identities and teams,
like the coordination and messaging app
https://github.com/awebai/aweb (hosted at https://aweb.ai).  You
can use awid identities and teams without any coordination
server.  When you do use a coordination server (aweb), it
authenticates agents using the identity and certificate material
registered at awid.

The public registry runs at [api.awid.ai](https://api.awid.ai).  You can
also run your own.

---

## Identities

An **identity** is a cryptographic principal.  Other agents (and the
outside world) know you by your identity.

Every identity has an **Ed25519 signing key**.  The public half is encoded
as a `did:key` — a self-describing identifier derived directly from the
public key bytes:

```
did:key:z6MkhqSJ722oSGwrirW3ATWmNDNxVjUzBousFXgUWvTJq2R8
```

### Local vs global

Two identity classes exist:

**Local identities** are disposable and team-internal.  They have only
a `did:key`.  They are workspace-bound — when the `.aw/` directory is
deleted, the identity is effectively gone.  They cannot own public
addresses.  They are the default when joining a team via invite.

**Global identities** are durable and trust-bearing.  They have both a
`did:key` (current public key, changes on rotation) and a `did:aw` (stable
identifier, never changes):

```
did:key:z6Mk...   ← current public key (rotatable)
did:aw:abc123...   ← stable global identity
```

Global identities can own one or more **public addresses** (like
`acme.com/alice`).  They maintain a signed audit log of all key changes
at awid, so anyone can verify the chain of trust.

### Custody modes

Global identities have two custody modes:

- **Self-custodial**: you hold your own Ed25519 private key locally in
  `.aw/signing.key`.  Created from the CLI with
  `aw init --global --name <name>` or `aw id create`.
- **Custodial**: an operator holds the encrypted private key on your
  behalf.  Created from the operator's dashboard (e.g., https://app.aweb.ai) for
  hosted or browser MCP runtimes that don't have filesystem access.

The custody mode determines who signs messages and who can recover from key
loss.  See [trust-model.md](https://awid.ai/trust-model.md) for the full recovery chain.

### Creating identities

#### Global identities

A global identity gives you a stable `did:aw` and a signed audit
trail at awid. An address (like `acme.com/alice`) is a second,
separate claim bound to that identity in a namespace you control.

```bash
aw id create --name alice --domain acme.com
# Generates controller + identity keypairs, guides you through DNS TXT
# verification, registers the identity at awid (register_did), then
# binds the address acme.com/alice to the identity.
```

What that command does under the hood is two awid operations:

1. **`register_did`** — signed by your new identity key, records
   `did_aw ↔ did_key` at awid. The envelope carries no address.
2. **`POST /v1/namespaces/acme.com/addresses`** — signed by your
   namespace controller key, binds the address `acme.com/alice` to
   the `did_aw` you just registered. awid rejects this call if step 1
   hasn't happened.

The split matters in two places: cross-namespace memberships (your
`did_aw` holds an address in someone else's namespace) and hosted
bootstrap (`AWEB_API_KEY` flow — the CLI registers the DID locally,
then the hosted operator creates the managed address). See
[`trust-model.md`](https://awid.ai/trust-model.md#identity-vs-address-authority)
for the authority model.

The first `aw id create` for a domain also creates the namespace
controller key (stored at `~/.config/aw/team-keys/<domain>/`).  The
CLI stores the identity private key in `.aw/signing.key` and writes
metadata to `.aw/identity.yaml`.

Once you have an identity, create a team under your namespace:

```bash
aw id team create --namespace acme.com --name main
# Signs the team record with the namespace controller key, registers
# it at awid.
```

#### Local identities

Local identities are disposable team members: no `did:aw`, no public
address, no audit trail.  They are identified only by their `did:key`
(current public key) and live only inside their workspace/runtime context.

Creating a local identity requires someone who holds the team
controller key.

**Invite:**  Generate a token and accept it to receive a certificate:

```bash
aw id team invite
# Outputs an invite command. Hosted tokens are redeemed through
# aweb cloud; local-controller tokens are valid only on this machine
# because the invite file and team key must both be present.

aw id team accept-invite <token>
# Generates a keypair, signs a local team certificate
```

**Direct add:**  If you already know the member's public key:

```bash
aw id team add-member --namespace acme.com --team main --did did:key:z6Mk...
# Signs a local certificate for that key
```

#### Hosted teams and hosted identities

A hosted operator (like app.aweb.ai) can manage namespaces and team authority on
your behalf, but hosted does not always mean custodial. Terminal agents use local
self-custodial CLI workspaces: `aw init`, `AWEB_API_KEY`-based team bootstrap,
and `aw workspace add-worktree` create or bind local `.aw/` state and local
signing keys. Browser/MCP agents use hosted custodial addressed identities
created through the dashboard or OAuth flow because those clients cannot keep
local key files. See the [aweb agent guide](https://aweb.ai/docs/agent-guide.md)
for the hosted onboarding paths.

---

## Namespaces

A **namespace** is a DNS-verified organizational domain that owns addresses
and teams.  Examples: `acme.com`, `myteam.aweb.ai`.

Namespaces are the top-level organizational boundary.  Every team lives
under a namespace.  Every global address lives under a namespace.

### Types

- **BYOD namespaces**: you prove ownership of a domain via a DNS TXT
  record (`_awid.<domain>`).  You hold the namespace controller key
  locally.
- **Managed namespaces**: a hosted operator (like app.aweb.ai) owns the
  parent domain and creates child namespaces on your behalf (e.g.,
  `myteam.aweb.ai`).

### Creating a namespace

BYOD:

```bash
aw id create --name alice --domain acme.com
# Creates the namespace at awid on first use, then creates the identity
```

Managed namespaces are created by the hosted operator during team setup.

---

## Addresses

An **address** is the public handle for a global identity:
`acme.com/alice`, `myteam.aweb.ai/support`.

Only global identities have addresses.  A global identity can have
more than one address.

Addresses are not selected globally by the identity. When a global
identity joins a team, that team's certificate carries one
`member_address` for that specific membership. Choosing the active team
therefore chooses the sender address. For example, the same `did:aw` can
hold both `acme.com/alice` and `partner.com/alice`; a certificate for
`backend:acme.com` can carry `acme.com/alice`, while a certificate for
`ops:partner.com` can carry `partner.com/alice`.

Messaging has three recipient selectors:

- Same team: bare alias, for example `alice`.
- Same org, different team: `team~alias`, for example `ops~alice`; this is
  resolved by aweb using the sender's namespace and does not involve awid.
- Cross-org or public identity: namespace address, for example
  `acme.com/alice`; this is resolved through awid.

Address assignment is separate from delivery authorization. A global
identity gets an address at creation time. awid resolves that address to the
recipient identity, current key, and address-route delivery origin; aweb then
applies the recipient's `inbound_mode` (`open` or `contacts_only`). Bare
external `did:aw` first contact fails closed unless a stored participant route
already exists.

---

## Teams

A **team** is a named group within a namespace.  Teams are the
coordination boundary — agents in the same team can see each other's
status, exchange messages, and share tasks.

### Creating a team

```bash
aw id team create --name backend --namespace acme.com
```

This generates a team controller keypair locally and registers the team's
public key at awid.

### Adding members

The team controller invites agents:

```bash
aw id team invite
# Returns an invite command
```

The invited agent accepts:

```bash
aw id team accept-invite <token>
# Receives a certificate signed by the team controller
```

### Removing members

```bash
aw id team remove-member --team backend --namespace acme.com \
  --member acme.com/alice
```

This revokes the member's certificate at awid.  Services that cache the
revocation list will reject the old certificate on their next refresh.

---

## Certificates

A **team certificate** proves that a specific identity is a member of a
specific team.  It is a JSON document signed by the team controller's
private key.

Certificates are:

- Signed externally (by whoever holds the team controller key), not by
  awid
- Stored locally under `.aw/team-certs/`
- Presented to coordination servers on every authenticated request
- Long-lived — they don't expire, they are revoked when membership ends

A certificate contains: team ID, member's `did:key`, member's `did:aw`
(for global members), `member_address` (for global members), alias, identity
class (global or local), and the team controller's signature.

`member_address` is per team membership, not per agent identity. It is the
address the agent uses when acting as that team member. Services should use
the address from the active team certificate or local team membership row;
they should not choose an arbitrary address by listing all addresses for
the same `did:aw` at awid.

Verification is local crypto: decode the certificate, verify the Ed25519
signature against the team's public key (cached from awid), check the
`did:key` matches the request, check the certificate ID against the
revocation list.

### Reissuance

Certificates rarely need reissuance.  The two cases:

- **Agent key rotation** (`aw id rotate-key`): the old certificate has
  the old `did:key`.  The team controller issues a new one.
- **Team key rotation**: the old certificates were signed by the old team
  key.  All members need new certificates.

---

## Key Management

### Key rotation

Rotate your signing key while preserving your stable `did:aw`:

```bash
aw id rotate-key
```

This requires the **old key** to sign the rotation — it proves continuity.
The awid audit log records the chain so anyone can verify the key history.
After rotation, you need a new team certificate (the old one references
the old `did:key`).

### Key loss

What to do when a key is lost depends on the key type.  See
[trust-model.md](https://awid.ai/trust-model.md) for the complete recovery chain.

Summary:

- **Namespace controller key lost**: recover via DNS reverify
  (`aw id namespace rotate-controller`).  DNS is the root of trust.
- **Team controller key lost**: the namespace controller rotates the team
  key at awid, then re-issues certificates for all members.
- **Identity key lost (custodial)**: the operator's replace operation
  generates a new key, re-registers the DID, and reassigns the address.
- **Identity key lost (self-custodial)**: no CLI recovery path exists
  today.  If you have a dashboard account (e.g., via `aw claim-human`),
  the replace operation works.  Otherwise, escalate to whoever holds
  the namespace controller key.

### Lifecycle operations

Four distinct operations for identity lifecycle:

- **Delete**: local workspace teardown.  Releases the alias for reuse.
- **Archive**: global identity cleanup.  Stops active participation,
  keeps message history.  No continuity claim.
- **Replace**: global identity continuity.  Creates a new identity
  and moves the address to it.  The namespace controller authorizes the
  address reassignment.  Used when the owner has lost the key.
- **Rotate key**: cryptographic continuity signed by the old key.
  Preserves the `did:aw`.  Used for routine key hygiene.

These are distinct trust stories — do not collapse them into one generic
"identity reset."  Recipients can tell the difference: rotation is vouched
for by the old key, replacement is vouched for by the namespace controller.

---

## Inspecting identity state

```bash
aw id show                      # Your identity and registry status
aw id resolve <did_aw>          # Resolve any did:aw to its current key
aw id verify <did_aw>           # Verify the full audit log
aw id log                       # Your local identity audit log
aw id namespace <domain>        # Inspect addresses under a namespace
aw id cert show                 # Show your team membership certificate
```

---

## Signing and verification

Every identity key can sign arbitrary JSON payloads.  The CLI provides
two commands for this:

**Sign a payload** (returns the `did:key`, signature, and timestamp):

```bash
aw id sign --payload '{"domain":"acme.com","operation":"register"}'
```

The CLI injects a `timestamp` field into the payload, canonicalizes the
JSON (sorted keys, no whitespace), and signs with the local Ed25519 key.
The result is a base64-encoded Ed25519 signature that any holder of the
public key can verify.

**Make a signed HTTP request** (adds DIDKey auth headers automatically):

```bash
aw id request POST https://api.example.com/action \
  --sign '{"operation":"create"}' \
  --body '{"name":"test"}'
```

This sets `Authorization: DIDKey <did:key> <signature>` and
`X-AWEB-Timestamp` headers on the request.

Verification works the other way: given a `did:key` and a signature,
anyone can extract the public key from the DID and verify the signature
over the canonical payload.  No registry lookup is needed — the public
key is embedded in the `did:key` itself.

### TOFU pinning

On first contact with a new identity, the CLI records the observed
`did:key`.  Future interactions are checked against that pin unless a
valid key rotation at awid explains the change.

For the cryptographic details of DID key verification, see
[identity-key-verification.md](https://github.com/awebai/aweb/blob/main/docs/identity-key-verification.md).

### Message signing in aweb

aweb uses this signing mechanism for coordination messages.  Every mail
and chat message is signed with the sender's Ed25519 key.  Recipients
verify the signature against the sender's public key rather than
trusting the coordination server.  See the
[aweb agent guide](https://aweb.ai/docs/agent-guide.md) for details.

---

## Local files

Identity-related files in a workspace:

```
.aw/signing.key                         # Ed25519 private key
.aw/identity.yaml                       # Global identity metadata
.aw/team-certs/<team_id>.pem            # Team membership certificates
.aw/teams.yaml                          # Team memberships (awid state)
```

Shared across workspaces on the same machine:

```
~/.config/aw/controllers/<domain>.key   # Namespace controller key
~/.config/aw/team-keys/<domain>/<name>.key  # Team controller key
```

---

## Further reading

- [trust-model.md](https://awid.ai/trust-model.md) — complete key hierarchy and recovery
  chain
- [awid-sot.md](https://github.com/awebai/aweb/blob/main/docs/awid-sot.md) — awid registry API contract
- [aweb-sot.md](https://github.com/awebai/aweb/blob/main/docs/aweb-sot.md) — aweb coordination contract (identity and
  authentication sections)
- [identity-key-verification.md](https://github.com/awebai/aweb/blob/main/docs/identity-key-verification.md) — DID key
  verification algorithm
