Use Case 025: Agent Identity Registration & Lifecycle
Overview
| Property |
Value |
| Use Case ID |
UC-025 |
| Use Case Name |
Agent Identity Registration & Lifecycle |
| Module |
Agent Identity — Identity Backend |
| Priority |
High |
| Status |
✅ Implemented |
| Version |
1.0 |
| Last Updated |
June 15, 2026 |
Implementation status (agent-identity release, June 2026). Shipping on development. The tenant-owned agent registry (AgentIdentity), scope ceiling, allowed task-types, per-turn budget, public-key registry, and disable/revoke lifecycle exist end-to-end (Web /agents, API /api/agents) with integration tests. Canonical code names differ from parts of this original spec — the real types are AgentIdentity, RegisterAgentCommand / UpdateAgentCommand, and IAgentRepository; scope and task-type lists persist as space-delimited strings.
Description
This use case describes how an owner/admin registers, configures, and manages the lifecycle of a non-human agent identity (an AI agent or automated process) in Application Manager. Each agent is a tenant-owned, first-class identity with a required human owner for accountability, a link to the registered application(s) that bound its scope ceiling, a set of allowed task types, governance limits, and registered public keys used for keyless private_key_jwt authentication. Agents minted from this registry receive scoped, ephemeral credentials (UC-026) and may delegate on behalf of subjects (UC-027). The registry is the upper bound for everything an agent can ever do: no credential, token, or delegation may exceed the agent's scope ceiling or governance limits.
This is part of the agent-identity release. The SDK master design is riptide-sdk/docs/plans/AGENT-IDENTITY-ARCHITECTURE.md; the Application Manager-side plan is docs/internal/agent-identity-plan.md (Section 1, plus the identity items in §9 Reuse and §10 Resolved decisions). The AgentIdentity lifecycle shape (ExpiresAt/RevokedAt/RevokedReason/IsActiveAt(moment)) and the AgentPublicKey substrate (ECDSA P-256, kid rotation, validity windows, revocation) are lifted from the existing SyntheticPrincipal and AttestationPublicKey patterns — reused, not rebuilt.
Actors
| Actor |
Description |
Role |
| Owner / Admin |
Human user who registers and owns the agent; holds agents.write capability |
Primary |
| AI Agent |
The non-human identity being registered; later authenticates with its private key |
Supporting |
| Application Manager Web |
Bootstrap 5 + Tabler MVC admin UI (AgentsController) |
Supporting |
| Application Manager API |
X-Api-Key-authenticated REST API (AgentsController) |
Supporting |
| AuditLog |
Append-only audit trail recording every lifecycle event |
Supporting |
| Cost Governance subsystem |
Validates budget headroom at registration (UC-032) |
Supporting |
| SDK AgentIdentity component |
Riptide.Platform.AgentIdentity — consumes the registry |
External |
Preconditions
- Application Manager is running and reachable on the Web (11401) and API (11402) ports.
- The acting owner/admin is authenticated and holds the
agents.read and agents.write capabilities.
- The owning tenant exists and is
Active or Trial.
- At least one registered application exists to bind the agent's scope ceiling.
- A Top-tier budget cap exists for the tenant with viable headroom (UC-032); agent creation is fail-closed without it.
- The owner has (or will register) at least one ECDSA P-256 public key for the agent.
Postconditions
Success Postconditions
- An
AgentIdentity row is persisted in IdentityDbContext with status Active, a required OwnerUserId, bound application link(s), scope ceiling, AllowedTaskTypes + DefaultTaskType, PerTurnBudget, governance limits, and audit fields.
- One or more
AgentPublicKey rows are persisted with kid, validity window, and Active state.
- The agent appears in the registry list and is resolvable by
GetAgentById.
- An audit entry recording the actor chain is written to
AuditLog.
Failure Postconditions
- No
AgentIdentity row is created when validation fails (missing owner, no app link, no budget headroom, requested ceiling outside the bound application's grantable scopes).
- A descriptive error (RFC 7807 Problem Details on the API; validation summary in the Web UI) is returned.
- No partial state: public keys are not orphaned if the agent insert is rolled back.
Primary Flow Sequence
sequenceDiagram
participant Admin as Owner/Admin
participant Web as AM Web (AgentsController)
participant Med as MediatR
participant Gov as Cost Governance
participant Repo as IAgentRepository
participant DB as IdentityDbContext
participant Audit as AuditLog
Admin->>Web: Submit "Register Agent" form
Web->>Med: Send RegisterAgentCommand
Med->>Med: FluentValidation (owner, app link, ceiling ⊆ app scopes)
Med->>Gov: Check viable budget headroom (UC-032)
Gov-->>Med: Headroom OK
Med->>Repo: Add AgentIdentity (status=Active)
Repo->>DB: INSERT AgentIdentity + AgentPublicKey
DB-->>Repo: Saved
Med->>Audit: Write actor-chain entry (agent.registered)
Med-->>Web: AgentDto (Id, status, ceiling)
Web-->>Admin: Redirect to agent detail page (success toast)
UC-025a: Register Agent
Triggers
- Owner/admin navigates to
/agents/create in the Web UI, or
- An SDK consumer calls
POST /api/agents.
Basic Flow
- Owner/admin opens Agents > Register Agent.
- System displays the registration form: name, owner (defaults to the acting admin), one or more bound applications, scope ceiling builder, task-type selection,
PerTurnBudget, optional TemplateId, governance limits (max credential TTL, allowed MCP servers, reservation rate-cap override, reservation count-cap override), optional ExpiresAt, and a public-key upload field.
- Owner/admin sets the scope ceiling — the two-axis set (coarse
{app}:{action} capabilities + fine-grained mcp:{server}:{tool} tool scopes) that bounds every credential the agent can ever mint. The ceiling must be a subset of the scopes grantable by the bound application(s).
- Owner/admin sets
AllowedTaskTypes and DefaultTaskType. Two provisioning UX paths produce this (purpose-picker or direct-model-picker); both set AllowedTaskTypes/DefaultTaskType (detail in UC-029).
- Owner/admin pastes/uploads at least one ECDSA P-256 public key; the system assigns a
kid and validity window.
- System validates: owner present, at least one app link, ceiling ⊆ app grantable scopes, task types valid, governance limits within tenant policy, public key well-formed.
- System checks budget headroom via the cost-governance subsystem (UC-032). Registration is blocked with a clear error if no viable budget headroom exists.
- System persists the
AgentIdentity (status Active) and AgentPublicKey row(s) in one transaction.
- System writes an
AuditLog entry (agent.registered) with the actor chain.
- System redirects to the agent detail page with a success message.
Alternative Flows
- A1: Missing owner — Validation rejects; required
OwnerUserId enforced for accountability.
- A2: No application link — Validation rejects; ceiling cannot be bound without a registered application.
- A3: Ceiling exceeds app scopes — Validation rejects requested scope ceiling that is not a subset of the bound application's grantable scopes.
- A4: No budget headroom — Cost governance blocks creation; admin is directed to set/raise a cap (UC-032).
- A5: Malformed public key — Key rejected; agent not created (or created without key if API allows deferred key registration, depending on policy).
- A6: Duplicate name within tenant — Validation rejects to keep the registry unambiguous.
UC-025b: View / List Agents
Triggers
- Owner/admin navigates to
/agents, or calls GET /api/agents.
- Owner/admin opens an agent detail page, or calls
GET /api/agents/{id}.
Basic Flow
- Owner/admin clicks Agents in the navigation bar.
- System runs
GetAgents scoped to the caller's tenant.
- System renders a Tabler table: name, owner, bound application(s), status badge (
Active/Disabled/Revoked/Expired), DefaultTaskType, active credential count, ExpiresAt, and an action dropdown.
- Owner/admin selects an agent to view detail via
GetAgentById.
- The detail page shows: identity card (name, owner, tenant, status, lifecycle dates), scope ceiling,
AllowedTaskTypes/DefaultTaskType, governance limits, registered public keys (with kid, validity window, state), and minted credential summary.
Alternative Flows
- A1: Agent not found —
GetAgentById returns not found; API returns 404 Problem Details, Web shows an error.
- A2: Cross-tenant access — A caller from another tenant cannot see the agent; result is filtered as not found.
- A3: Status auto-derivation — An agent past its
ExpiresAt displays as Expired via IsActiveAt(now) even if not yet swept.
UC-025c: Update Scope Ceiling & Allowed Task-Types / Deployments
Triggers
- Owner/admin edits an agent and submits scope-ceiling or task-type changes.
Basic Flow
- Owner/admin opens Agents > > Edit.
- Owner/admin modifies the scope ceiling and/or
AllowedTaskTypes/DefaultTaskType and/or allowed deployment set.
- For scope-ceiling changes, system sends
UpdateAgentScopesCommand; for deployment/task-type changes, UpdateAllowedDeploymentsCommand.
- System validates the new ceiling ⊆ bound application grantable scopes and that
DefaultTaskType ∈ AllowedTaskTypes.
- System persists the change and writes an
AuditLog entry recording before/after values.
- Already-issued credentials are unaffected at issue time but constrained by the new ceiling at introspection/refresh (the ceiling is the upper bound; tightening it narrows future mints — see UC-026).
Alternative Flows
- A1: New ceiling exceeds app scopes — Validation rejects; no change persisted.
- A2:
DefaultTaskType not in AllowedTaskTypes — Validation rejects.
- A3: Removing a task type still referenced by active plans — Change allowed; active plans complete against prior config, new mints use the new set.
UC-025d: Disable Agent
Triggers
- Owner/admin clicks Disable on the list or detail page, or calls a disable action.
Basic Flow
- Owner/admin confirms the disable action.
- System sends
DisableAgentCommand, setting status Disabled.
- New credential minting and token issuance are refused while disabled; existing credentials remain valid until expiry unless separately revoked.
- System writes an
AuditLog entry.
Alternative Flows
- A1: Already disabled — Operation is idempotent; no error.
- A2: Re-enable — A disabled agent may be re-enabled (status back to
Active) provided budget headroom still exists.
UC-025e: Revoke Agent
Triggers
- Owner/admin clicks Revoke on the list or detail page.
Basic Flow
- Owner/admin confirms revocation (irreversible) and optionally provides a
RevokedReason.
- System sends
RevokeAgentCommand, setting RevokedAt = now, RevokedReason, and status Revoked.
- System cascades: all active
AgentCredentials for the agent are invalidated and active delegation grants revoked (UC-026, UC-027).
IsActiveAt(moment) returns false for any moment ≥ RevokedAt; introspection on any minted credential returns inactive.
- System writes an
AuditLog entry with the RevokedReason.
Alternative Flows
- A1: Already revoked — Idempotent; no error.
- A2: Revoke during in-flight call — Outstanding credentials fail at the next introspection; mid-stream calls hard-stop per governance.
UC-025f: Per-Agent Audit View
Triggers
- Owner/admin opens Agents > > Audit, or calls
GET /api/agents/{id}/audit.
Basic Flow
- Owner/admin opens the per-agent audit view.
- System runs
GetAgentAudit filtering AuditLog to the agent.
- System renders a chronological list of lifecycle and credential events: registration, scope changes, deployment-set changes, credential mints/revocations, delegation grants/exchanges/revocations, disable/revoke — each with the full actor chain (agent ⇽ on-behalf-of subject where applicable) and before/after values for config changes.
Alternative Flows
- A1: No events — Empty state shown.
- A2: Filter by event type / date range — Query supports narrowing the result set.
API Endpoints
| Method |
Path |
Auth |
Purpose |
| POST |
/api/agents |
X-Api-Key + agents.write |
Register a new agent identity |
| GET |
/api/agents |
X-Api-Key + agents.read |
List agents for the caller's tenant |
| GET |
/api/agents/{id} |
X-Api-Key + agents.read |
Get a single agent by id |
| PUT |
/api/agents/{id}/scopes |
X-Api-Key + agents.write |
Update scope ceiling (UpdateAgentScopes) |
| PUT |
/api/agents/{id}/deployments |
X-Api-Key + agents.write |
Update allowed task-types / deployments |
| POST |
/api/agents/{id}/keys |
X-Api-Key + agents.write |
Register / rotate an AgentPublicKey |
| DELETE |
/api/agents/{id} |
X-Api-Key + agents.disable |
Disable or revoke the agent |
| GET |
/api/agents/{id}/audit |
X-Api-Key + agents.read |
Per-agent audit trail |
Representative Example — POST /api/agents
Request:
{
"name": "invoice-triage-agent",
"ownerUserId": "0190f3a1-2b6c-7c44-9b1e-1f2a3b4c5d6e",
"applicationIds": ["0190e2b0-1a2b-7c33-8a0d-9e8f7a6b5c4d"],
"scopeCeiling": {
"capabilities": ["billing:read", "billing:export"],
"toolScopes": ["mcp:invoices:list", "mcp:invoices:fetch"]
},
"allowedTaskTypes": ["analysis.financial", "summarization.document"],
"defaultTaskType": "analysis.financial",
"perTurnBudget": { "amount": 0.50, "currency": "USD" },
"templateId": "finance.triage",
"governance": {
"maxCredentialTtlSeconds": 900,
"allowedMcpServers": ["invoices"],
"reservationRateCapPerMin": 60,
"reservationCountCap": 100
},
"expiresAt": "2026-12-31T23:59:59Z",
"publicKeys": [
{
"kid": "key-2026-06",
"alg": "ES256",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...\n-----END PUBLIC KEY-----",
"notBefore": "2026-06-09T00:00:00Z",
"notAfter": "2027-06-09T00:00:00Z"
}
]
}
Response (201 Created):
{
"id": "0190f4c2-3d7e-7e55-ac2f-2a3b4c5d6e7f",
"name": "invoice-triage-agent",
"tenantId": "0190d1a0-0b1c-7a22-7f0c-8d7e6f5a4b3c",
"ownerUserId": "0190f3a1-2b6c-7c44-9b1e-1f2a3b4c5d6e",
"status": "Active",
"scopeCeiling": {
"capabilities": ["billing:read", "billing:export"],
"toolScopes": ["mcp:invoices:list", "mcp:invoices:fetch"]
},
"allowedTaskTypes": ["analysis.financial", "summarization.document"],
"defaultTaskType": "analysis.financial",
"publicKeys": [
{ "kid": "key-2026-06", "state": "Active", "notAfter": "2027-06-09T00:00:00Z" }
],
"createdAt": "2026-06-09T14:21:07Z",
"expiresAt": "2026-12-31T23:59:59Z"
}
Business Rules
| Rule ID |
Description |
| BR-1 |
Every agent must have a required OwnerUserId (human accountability); registration fails without it. |
| BR-2 |
An agent is tenant-owned; it is visible and manageable only within its owning tenant. |
| BR-3 |
The scope ceiling must be a subset of the scopes grantable by the bound application(s). |
| BR-4 |
The scope ceiling is the upper bound for every credential, token, and delegation the agent can ever produce. |
| BR-5 |
Agent creation is fail-closed: it is blocked unless viable budget headroom exists (UC-032). |
| BR-6 |
DefaultTaskType must be a member of AllowedTaskTypes. |
| BR-7 |
No agent secret is ever stored; authentication relies solely on registered AgentPublicKeys (private keys stay with the agent). |
| BR-8 |
Public keys support kid rotation, validity windows, and revocation; an expired or revoked key cannot authenticate. |
| BR-9 |
Revocation is irreversible and cascades to all active credentials and delegation grants. |
| BR-10 |
Governance limits (max credential TTL, allowed MCP servers, reservation rate/count caps) are enforced at credential issue time (UC-026). |
| BR-11 |
IsActiveAt(moment) returns false when the moment is past ExpiresAt, at/after RevokedAt, or while Disabled. |
| BR-12 |
Every lifecycle event is recorded in AuditLog with the full actor chain and before/after values for config changes. |
Data Requirements
AgentIdentity
| Field |
Type |
Constraints |
| Id |
Guid (UUIDv7) |
Primary key, application-generated |
| TenantId |
Guid |
Required, FK to Tenant |
| OwnerUserId |
Guid |
Required, FK to ApplicationUser (accountability) |
| Name |
string |
Max 256, required, unique within tenant |
| Status |
AgentStatus |
Active / Disabled / Revoked / Expired |
| ScopeCeilingJson |
string (JSON) |
Two-axis ceiling: capabilities + tool scopes; required |
| AllowedTaskTypesJson |
string (JSON) |
Set of task-type ids; required (replaces AllowedProviderModels) |
| DefaultTaskType |
string |
Max 128, required, must be in AllowedTaskTypes |
| PerTurnBudget |
decimal + currency |
Default per-turn budget for reactive-agent implicit plans |
| TemplateId |
string? |
Max 128, optional, for reporting aggregation |
| MaxCredentialTtlSeconds |
int |
Governance ceiling on credential TTL |
| AllowedMcpServersJson |
string (JSON) |
Allowed MCP servers for tool scopes |
| ReservationRateCapPerMin |
int? |
Override of default rate cap (default 60/min) |
| ReservationCountCap |
int? |
Override of default count cap (default 100) |
| CreatedAt |
DateTimeOffset |
Set on registration |
| CreatedBy |
string |
Acting principal |
| ExpiresAt |
DateTimeOffset? |
Null = no scheduled expiry |
| RevokedAt |
DateTimeOffset? |
Set on revoke |
| RevokedReason |
string? |
Max 1000, set on revoke |
AgentPublicKey
| Field |
Type |
Constraints |
| Id |
Guid (UUIDv7) |
Primary key |
| AgentId |
Guid |
Required, FK to AgentIdentity |
| Kid |
string |
Max 128, required, unique per agent (key id) |
| Algorithm |
string |
Fixed ES256 (ECDSA P-256) |
| PublicKeyPem |
string |
Max 4000, required (no private key ever stored) |
| State |
KeyState |
Active / Revoked |
| NotBefore |
DateTimeOffset |
Validity window start |
| NotAfter |
DateTimeOffset |
Validity window end |
| CreatedAt |
DateTimeOffset |
Set on registration |
| RevokedAt |
DateTimeOffset? |
Set on key revocation |
Security Considerations
- Authentication: API access uses the
X-Api-Key header; the Web UI uses cookie auth. Agents themselves never get a secret here — only their public keys are registered for later private_key_jwt authentication (UC-026).
- Authorization / capabilities:
agents.read, agents.write, and agents.disable capabilities gate the registry operations; admins compose these into roles via the existing Role/Capability/RoleCapabilityMapping model (capability-based RBAC, no prescribed roles).
- Data protection: Public keys are non-secret but integrity-protected; no private key material is ever accepted or stored. The scope ceiling and governance limits are enforced server-side, never trusted from the client at mint time.
- Audit: Every register/update/disable/revoke/key-rotation event writes an
AuditLog row with the actor chain and before/after values; heartbeats and other high-noise events are excluded by design.
Testing Scenarios
| Test ID |
Scenario |
Expected Result |
| T-1 |
Register an agent with valid owner, app link, ceiling ⊆ app scopes, budget headroom |
201 Created; AgentIdentity persisted Active; audit entry written |
| T-2 |
Register without OwnerUserId |
Rejected; required-owner validation error (BR-1) |
| T-3 |
Register without any application link |
Rejected; cannot bind ceiling (BR-3, A2) |
| T-4 |
Register with scope ceiling exceeding bound app's grantable scopes |
Rejected; ceiling-subset violation (BR-3) |
| T-5 |
Register when no viable budget headroom exists |
Blocked; fail-closed error directing to UC-032 (BR-5) |
| T-6 |
Register with DefaultTaskType not in AllowedTaskTypes |
Rejected (BR-6) |
| T-7 |
Register with malformed/non-ES256 public key |
Key rejected; no agent created (A5) |
| T-8 |
Register duplicate name within the same tenant |
Rejected (A6) |
| T-9 |
List agents as owner/admin of the tenant |
Returns only that tenant's agents |
| T-10 |
Get an agent belonging to another tenant |
404 / filtered as not found (BR-2, A2) |
| T-11 |
Get an agent past ExpiresAt |
Status displays Expired; IsActiveAt(now) false (BR-11, A3) |
| T-12 |
Update scope ceiling to a valid narrower set |
Persisted; before/after audit recorded |
| T-13 |
Update scope ceiling exceeding app scopes |
Rejected (BR-3, UC-025c A1) |
| T-14 |
Update allowed task-types/deployments validly |
Persisted; future mints use new set (UC-025c) |
| T-15 |
Disable an active agent |
Status Disabled; new mints refused; existing creds valid (UC-025d) |
| T-16 |
Disable an already-disabled agent |
Idempotent; no error (UC-025d A1) |
| T-17 |
Re-enable a disabled agent with headroom |
Status back to Active (UC-025d A2) |
| T-18 |
Revoke an agent with a reason |
RevokedAt/RevokedReason set; status Revoked; creds + grants cascaded (UC-025e) |
| T-19 |
Revoke an already-revoked agent |
Idempotent; no error (UC-025e A1) |
| T-20 |
Introspect a credential after the agent is revoked |
Returns inactive (BR-9, UC-026) |
| T-21 |
Register a new public key with rotated kid |
Key persisted Active; prior key still valid within its window (BR-8) |
| T-22 |
Authenticate with a public key past NotAfter |
Rejected at token issue time (BR-8, UC-026) |
| T-23 |
Caller lacking agents.write attempts registration |
403; no agent created (capability enforcement) |
| T-24 |
Caller lacking agents.read lists agents |
403 (capability enforcement) |
| T-25 |
View per-agent audit after several lifecycle events |
All events listed chronologically with actor chain (UC-025f) |
| T-26 |
Concurrent registration of two agents sharing one public key PEM |
Both stored; key uniqueness is per-agent kid, not global PEM (BR-8) |
| T-27 |
Tighten ceiling, then attempt to mint a credential exceeding the new ceiling |
Mint rejected (BR-4, UC-026) |
- UC-026: Agent Credential Issuance — mints ephemeral scoped credentials bounded by this agent's ceiling and governance limits.
- UC-027: On-Behalf-Of Delegation — delegation grants are bounded by the agent's own scope ceiling.
- UC-029: Agent Provisioning UX — the purpose-picker and direct-model-picker paths that set
AllowedTaskTypes/DefaultTaskType.
- UC-032: Budget Cap Lattice — supplies the budget-headroom check that gates agent creation.
- UC-033: Agent Plan Budget Lifecycle — consumes
PerTurnBudget and the per-agent overlay.
- UC-006: Role-Based Access Control — capability composition for
agents.*.
- UC-010: Activity Logging & Audit Trail — the underlying
AuditLog surface.
- UC-016: Tenant Provisioning Management — tenant ownership and Top-cap defaults.
Revision History
| Version |
Date |
Author |
Changes |
| 1.0 |
June 9, 2026 |
Platform Architecture Team |
Initial draft |