Archived 4 completed OpenSpec changes (2026-04-02): - phase-3-enterprise (100/100 tasks) — 6 Phase 3 capabilities synced - devops-documentation (48/48 tasks) — 3 new + 1 merged capability - bedroom-developer-docs (33/33 tasks) — 4 new capabilities synced - engineering-docs (superseded by 2026-03-29 archive) — no tasks Main spec library grows from 21 → 35 capabilities (+14 new): federation, multi-tenancy, oidc, soc2, w3c-dids, webhooks, database, operations, system-overview, api-reference, core-concepts, developer-guides, quick-start + deployment (merged additive requirements) Active changes: 0 — project board is clear for Phase 4 planning. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
11 KiB
W3C Decentralized Identifiers (DIDs) — Specification
Workstream: 2 of 6 Phase: 3 — Enterprise Author: Virtual Architect Date: 2026-03-29
Overview
Issue a W3C did:web identifier for every registered agent and serve DID Documents over HTTPS. The AgentIdP instance itself has a root DID Document at /.well-known/did.json. Each agent has an individual DID Document at /agents/:id/did. A DID resolution endpoint wraps the standard resolution workflow. Agent cards in AGNTCY format are derivable from DID Documents.
The did:web method resolves to https://<host>/.well-known/did.json (instance) and https://<host>/agents/<agentId>/did (per-agent). All DID Documents are W3C DID Core 1.0 compliant.
API Endpoints
GET /.well-known/did.json
Root DID Document for the AgentIdP instance. No authentication required — this is a public discovery endpoint.
GET /.well-known/did.json
No authentication required
Responses:
200 OK:
Content-Type: application/json
schema:
type: object
description: W3C DID Core 1.0 compliant DID Document
required: [id, "@context", verificationMethod, authentication]
properties:
"@context":
type: array
items:
type: string
example:
- "https://www.w3.org/ns/did/v1"
- "https://w3id.org/security/suites/jws-2020/v1"
id:
type: string
description: DID for this AgentIdP instance
example: "did:web:idp.sentryagent.ai"
controller:
type: string
example: "did:web:idp.sentryagent.ai"
verificationMethod:
type: array
items:
$ref: '#/components/schemas/VerificationMethod'
authentication:
type: array
items:
type: string
description: References to verification methods for authentication
assertionMethod:
type: array
items:
type: string
service:
type: array
items:
$ref: '#/components/schemas/DIDService'
example:
"@context":
- "https://www.w3.org/ns/did/v1"
id: "did:web:idp.sentryagent.ai"
controller: "did:web:idp.sentryagent.ai"
verificationMethod:
- id: "did:web:idp.sentryagent.ai#key-1"
type: "JsonWebKey2020"
controller: "did:web:idp.sentryagent.ai"
publicKeyJwk:
kty: "EC"
crv: "P-256"
x: "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU"
y: "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"
authentication:
- "did:web:idp.sentryagent.ai#key-1"
service:
- id: "did:web:idp.sentryagent.ai#agent-registry"
type: "AgentIdentityProvider"
serviceEndpoint: "https://idp.sentryagent.ai"
500 Internal Server Error:
schema:
$ref: '#/components/schemas/ErrorResponse'
GET /agents/:id/did
Per-agent DID Document. No authentication required — DID Documents are public.
GET /agents/{agentId}/did
No authentication required
Path Parameters:
agentId:
type: string
description: Agent ID
Responses:
200 OK:
Content-Type: application/json
schema:
type: object
description: W3C DID Core 1.0 compliant per-agent DID Document
example:
"@context":
- "https://www.w3.org/ns/did/v1"
- "https://w3id.org/agntcy/v1"
id: "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890"
controller: "did:web:idp.sentryagent.ai"
verificationMethod:
- id: "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890#key-1"
type: "JsonWebKey2020"
controller: "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890"
publicKeyJwk:
kty: "EC"
crv: "P-256"
x: "abc123"
y: "def456"
authentication:
- "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890#key-1"
service:
- id: "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890#agent-card"
type: "AgentCard"
serviceEndpoint: "https://idp.sentryagent.ai/agents/agt_01HXK7Z9P3FKWABCDEF67890/did/card"
agntcy:
agentId: "agt_01HXK7Z9P3FKWABCDEF67890"
agentType: "orchestrator"
capabilities:
- "task-planning"
- "tool-use"
deploymentEnv: "production"
owner: "acme-ai"
version: "1.2.0"
404 Not Found:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
code: "AGENT_NOT_FOUND"
message: "Agent not found"
410 Gone:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
code: "AGENT_DECOMMISSIONED"
message: "Agent has been decommissioned — DID Document is no longer active"
GET /agents/:id/did/resolve
DID resolution endpoint: resolves any did:web DID and returns the DID resolution result in W3C DID Resolution format. This enables external systems to use AgentIdP as a resolver for agent DIDs. Authentication required (agents:read scope).
GET /agents/{agentId}/did/resolve
Authorization: Bearer <token with agents:read scope>
Path Parameters:
agentId:
type: string
Responses:
200 OK:
Content-Type: application/ld+json;profile="https://w3id.org/did-resolution"
schema:
type: object
required: [didDocument, didDocumentMetadata, didResolutionMetadata]
properties:
didDocument:
type: object
description: The resolved DID Document
didDocumentMetadata:
type: object
properties:
created:
type: string
format: date-time
updated:
type: string
format: date-time
deactivated:
type: boolean
didResolutionMetadata:
type: object
properties:
contentType:
type: string
example: "application/did+ld+json"
retrieved:
type: string
format: date-time
example:
didDocument:
"@context": ["https://www.w3.org/ns/did/v1"]
id: "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890"
didDocumentMetadata:
created: "2026-03-29T12:00:00Z"
updated: "2026-03-29T12:00:00Z"
deactivated: false
didResolutionMetadata:
contentType: "application/did+ld+json"
retrieved: "2026-03-29T14:00:00Z"
401 Unauthorized:
schema:
$ref: '#/components/schemas/ErrorResponse'
404 Not Found:
schema:
$ref: '#/components/schemas/ErrorResponse'
GET /agents/:id/did/card
AGNTCY-format agent card derived from DID Document. Returns a JSON object representing the agent's identity and capabilities in the AGNTCY agent card format. No authentication required.
GET /agents/{agentId}/did/card
No authentication required
Responses:
200 OK:
Content-Type: application/json
schema:
type: object
description: AGNTCY-format agent card
properties:
did:
type: string
name:
type: string
agentType:
type: string
capabilities:
type: array
items:
type: string
owner:
type: string
version:
type: string
deploymentEnv:
type: string
identityProvider:
type: string
description: DID of the issuing AgentIdP instance
issuedAt:
type: string
format: date-time
example:
did: "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890"
name: "acme-orchestrator"
agentType: "orchestrator"
capabilities: ["task-planning", "tool-use"]
owner: "acme-ai"
version: "1.2.0"
deploymentEnv: "production"
identityProvider: "did:web:idp.sentryagent.ai"
issuedAt: "2026-03-29T12:00:00Z"
404 Not Found:
schema:
$ref: '#/components/schemas/ErrorResponse'
Database Schema Changes
New Table: agent_did_keys
Stores the public/private key pair used to sign each agent's DID Document. The private key is stored in Vault; only the public key JWK is stored in PostgreSQL.
CREATE TABLE agent_did_keys (
key_id VARCHAR(40) PRIMARY KEY,
agent_id VARCHAR(40) NOT NULL UNIQUE REFERENCES agents(agent_id),
organization_id VARCHAR(40) NOT NULL REFERENCES organizations(organization_id),
public_key_jwk JSONB NOT NULL,
vault_key_path VARCHAR(255) NOT NULL, -- Vault path where private key is stored
key_type VARCHAR(20) NOT NULL DEFAULT 'EC',
curve VARCHAR(10) NOT NULL DEFAULT 'P-256',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
rotated_at TIMESTAMPTZ,
CONSTRAINT agent_did_keys_key_type_check CHECK (key_type IN ('EC', 'RSA'))
);
CREATE INDEX idx_agent_did_keys_agent_id ON agent_did_keys(agent_id);
CREATE INDEX idx_agent_did_keys_org_id ON agent_did_keys(organization_id);
New Column: agents.did
ALTER TABLE agents
ADD COLUMN did VARCHAR(255),
ADD COLUMN did_created_at TIMESTAMPTZ;
-- Populated automatically on agent creation
-- Example value: "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890"
Configuration
| Environment Variable | Description | Default |
|---|---|---|
DID_WEB_DOMAIN |
Domain name for did:web construction |
Required — derived from HOST if not set |
DID_KEY_TYPE |
Cryptographic key type for DID keys | EC |
DID_KEY_CURVE |
Elliptic curve for EC keys | P-256 |
DID_DOCUMENT_CACHE_TTL_SECONDS |
How long to cache DID Documents in Redis | 300 |
Dependencies
| Package | Version | Purpose |
|---|---|---|
did-resolver |
^4.1.0 |
W3C DID resolution interface |
web-did-resolver |
^2.0.27 |
DID:WEB method resolver |
Security Considerations
- DID Documents are public endpoints — no authentication, no rate-limit-sensitive data exposed
- Private keys for DID signing are stored in Vault; never written to PostgreSQL
- DID Document cache in Redis has a TTL — stale documents are evicted automatically
- Decommissioned agents return HTTP 410 Gone with
deactivated: truein DID Document metadata - DID rotation: when a credential is rotated, the DID Document key can optionally be rotated; the old key is retained in history
GET /agents/:id/did/cardexposes only data already present in the agent registration — no new sensitive fields
Acceptance Criteria
- Every new agent registration automatically generates a
did:webDID and key pair - Root DID Document at
/.well-known/did.jsonis W3C DID Core 1.0 compliant (validated bydid-resolver) - Per-agent DID Document returns correct
did:webidentifier and public key JWK - DID resolution endpoint returns W3C DID Resolution format
- Decommissioned agent DID Document returns 410 Gone with
deactivated: true - Agent card at
/agents/:id/did/cardmatches AGNTCY agent card format - Private keys never appear in any API response or log
- TypeScript strict, zero
any, >80% test coverage on DIDService