An agent sub-identity moves through a small number of states. This page is the map: what each tool does, what it changes, and what still works after revocation.
Register and resume
Two tools can create the first agent, and they differ.
identity_register is explicit. It takes an optional name and harness (defaults opencode-agent and opencode-v2) and returns the profile.
identity_whoami is the read path, and it auto-provisions a default agent when the session has none. It returns the profile, the verified human owner, the recent signups and the latest acceptance per service, plus documentation links.
identity_register resumes in this order:
- The agent bound to this OAuth session, if one exists.
- Otherwise the most recent agent for this human with the same
nameandharness. - Otherwise a new agent with those values.
identity_whoami and the inbox tools resume differently:
- The agent bound to this OAuth session, if one exists.
- Otherwise the most recent agent for this human, whatever its name.
- Otherwise a new default agent.
The difference matters once a human has more than one agent. In a session that already resolved an agent, identity_register returns that same agent whatever name you pass; a distinct named identity needs a new OAuth session, where the name and harness match picks it.
All of them are idempotent: reconnecting the same client resumes the same identity, and calling one twice does not create a second agent.
A sub-identity contains:
| Field | Example | Notes |
|---|---|---|
id |
agt_01JAB2C3D4E5F6G7H8J9K0MN1P |
Stable. The sub claim and the provider's attribution key. |
name |
opencode-macbook |
Display name you chose, or the default. |
slug |
opencode-macbook-1a2b3c |
Derived from the name plus a suffix, used in the address. |
harness |
opencode-v2 |
The client label. Part of the identity_register resume match. |
email_address |
opencode-macbook-1a2b3c@agents.agentregistrar.dev |
The dedicated inbox. |
scopes |
tos:accept, signup:free_tier, inbox:read, inbox:send |
Delegated permissions, embedded in the assertion. |
status |
active, revoked or suspended |
The kill switch sets revoked; an operator protective action sets suspended. |
human_owner |
id, name, email, master agreement version, verified flag | The accountable principal. |
Prove and sign up
identity_signup is the default path. It fetches discovery, verifies the
optional provider verification credential, applies the human's trust/policy,
checks accepted_tos_text against the published hash, records an acceptance if
the terms are new, mints the assertion, and returns a prepared signup request.
There is no manual discovery, JWT decode, or attestation handling.
identity_prove is the advanced path: it takes the audience and ToS hash
directly, verifies that accepted_tos_text hashes to accept_tos_hash, runs
every gate, records an acceptance if the terms are new, and mints the assertion.
It may take one explicit RP discovery source (rp_discovery_url or
rp_discovery plus rp_origin); a require policy rejects an agent-supplied
document.
Gates, in order, all before any reuse lookup: the human is active, the human
email is verified, the master agreement is signed, the agent is active, tos:accept is granted, and every requested scope is granted. Then discovery is
validated, the attestation is verified, and the human's policy is applied: allow
is silent, warn proceeds with an advisory warning, require refuses anything
but verified, and block never proceeds. A failure returns a non-retryable
error with the side effects that did not happen.
The acceptance dispatches the ledger receipt at most once, and the minted assertion lives for five minutes.
Acceptance versus assertion. The acceptance is the durable legal event. The assertion is a short-lived proof that points at it. Re-proving is not re-accepting.
| Situation | reuse.reason |
New acceptance | New ledger mail | New JWT |
|---|---|---|---|---|
| First prove for this agent and terms | new_acceptance |
yes | yes | yes |
| Identical call, assertion still unexpired | existing_assertion |
no | no | no, the same bytes are returned |
| Identical call after the assertion expired | fresh_assertion_existing_acceptance |
no | no | yes, same acceptance and consent time |
| Different terms URL or hash | new_acceptance |
yes | yes | yes |
force_new: true |
forced_new_acceptance |
yes | yes | yes |
| Same terms, different requested scopes | fresh_assertion_existing_acceptance |
no | no | yes |
force_new exists for a genuine second consent event, for example a human who wants a new record after a dispute. Leave it unset for normal use.
Two rules that matter when you write code against the result:
- Never decode the JWT.
assertion.claimsis the exact signed payload; use it for inspection. Sendassertion.jwtverbatim. - Retry the same call, not a new one. A retry with identical inputs cannot create a second acceptance. Changing the inputs can.
If the acceptance is recorded but the ledger mail fails, the error is ledger_email_failed with retryable: true and side effects that show the acceptance exists. Retrying re-sends the mail and reuses the acceptance.
Record the signup
identity_record_signup takes either the provider's RP-signed v1.2
signup_token (optionally with the exact rp_attestation) or a weaker
self_report. For a token it verifies the historical attestation named by
service.attestation_jti, verifies the receipt signature with the key named by
service.key_id, links it to the acceptance, and stores one proof row. There is
no broker-key fallback and no substitution of a newer attestation.
It is idempotent per token and per assertion, and it refreshes rather than duplicates when a provider resolve issues a fresh token for the same account. A self-report has no token, assertion, origin, or verification row; it records agent_self_reported and cannot overwrite a stronger proof.
| Error | Meaning | What to do |
|---|---|---|
signup_token_invalid |
Signature, shape, historical window, or conflict failed | Get an RP-signed v1.2 receipt and the exact historical attestation; do not retry the same one |
signup_token_not_linked |
The receipt references an acceptance this broker has not recorded | Run identity_signup/identity_prove for that audience, then retry |
signup_token_ownership_mismatch |
The token belongs to another human owner | Stop; use the token issued for your account |
rp_attestation_invalid |
The exact attestation is invalid or unavailable | Submit the exact attestation, or omit it so the broker fetches discovery and requires that jti |
rp_broker_untrusted |
The attesting broker is outside the trust list | Ask the human owner to review the broker trust list |
rp_broker_unavailable |
Transient broker/JWKS outage | Retry; recording is idempotent |
The result always includes proof_source, deduplicated, rp_verification_id (nullable), context_url, context_sha256 and the handoff block. The broker never stores the credential and never fetches the guide.
Revoke, suspend and re-register
Revocation is per agent, from the portal kill switch. It sets the agent status to revoked with a reason and a timestamp. An operator protective action can set suspended after a verified, receipt-linked abuse report; the human cannot clear that suspension with the kill switch, and only an operator restore returns the agent to active.
What stops: identity_signup and identity_prove fail with agent_revoked (a suspension uses the same code with a suspension-specific message), the identity and inbox tools reject the agent at the gate, and no new assertion can be minted. identity_record_signup is the one exception: it still accepts a receipt the provider already issued, because the proof belongs to the human owner, not to the agent's status.
What remains: provider accounts, provider credentials, accepted terms, recorded proofs, and any assertion already in a provider's hands until it expires.
Reactivating a revoked agent in the portal restores the same sub-identity and its ledger. A suspension is operator-only to restore. Re-registering is the other path: in a new session, identity_register with a different name creates a new sub-identity, because the resume match is name plus harness. The revoked identity is never reused.
States
| State | How it is reached | What works | Next |
|---|---|---|---|
| No sub-identity | A session with no agent yet | The first call auto-provisions a default | identity_register to choose a name |
| Active, no acceptance | Registered | Register, whoami, inbox | identity_signup |
| Acceptance recorded, no signup | A successful mint | Re-running reuses the acceptance | Post the assertion to the provider |
| Signed up, recorded | Signup plus Contract F/identity_record_signup |
Whoami shows the proof; the credential is yours | Use the credential |
| Acceptance recorded, mail failed | A failed ledger dispatch | Re-running retries the mail only | Retry with the same inputs |
| Revoked | Human kill switch | No new assertions and no identity or inbox calls; identity_record_signup still records a receipt the provider already issued |
Reactivate, or register a new identity in a new session |
| Suspended | Operator protective action after a verified abuse report | Same tool blocks as revoked; the human cannot clear it | Operator review/restore |
Next
- Inbox and verification for the inbox tools.
- Credentials and recovery for what happens after a lost key.
- ToS and ledger for the acceptance record itself.