Alice’s company runs a coding agent harness on its managed laptops and on cloud VMs. Every copy uses one OAuth client, identified by the URL of the publisher’s Client ID Metadata Document, https://platform.example/oauth-client. On Alice’s laptop the harness runs agent-42 for her, and agent-42 starts two sub-agents, agent-57 to read the codebase and agent-91 to open pull requests. In the evening Alice hands the session to a cloud VM, and agent-42 keeps working there overnight.

If the laptop installation sits in act, the three agents appear as one actor, and the handoff looks like a change of delegated principal. Policy, audit, and delegation records keyed on the actor then cannot tell the sub-agent that reads code from the one that opens pull requests, and they treat the move to the VM as a new actor. Under the Actor Profile, delegation grants that name the old act.sub do not transfer to the new one.

In May I wrote Client Instances Are Actors, Not New Clients. That post made two claims. The claim that a client instance is not a new client still holds. An OAuth client_id identifies a logical client, and registering every installation, VM, or agent runtime as a new client is usually the wrong abstraction. The claim that a client instance is an actor is the one I now withdraw. The May post’s own example gave each spawned sub-agent its own instance identifier in act. Even then, agent-42 would get a new identifier on the VM and become a new actor.

Two counterexamples

The first is Alice’s laptop installation, one instance of the harness client hosting three actors:

flowchart TD L["laptop installation"] --> A1["agent-42"] L --> A2["agent-57"] L --> A3["agent-91"]

The installation cannot stand in for any one of those principals, so putting it in act would give all three agents the same actor.

The second is the handoff, in which one actor moves between instances:

flowchart LR Alice -->|delegates| G["agent-42"] G -->|before| A["laptop installation"] G -->|after| B["cloud VM"]

The delegated actor is agent-42 before and after, and the instance executing it changed.

Neither example says a runtime can never be a principal. A deployment can give an installation or a workload a principal identity of its own under an explicit authorization model, and then it can act. A client instance is not an actor by virtue of being an instance, and instance evidence alone establishes neither actor identity nor delegated authority. Actor identity, client identity, instance identity, and the key a token is bound to are separate facts, and any relationship between them has to be established explicitly.

OAuth 2.0 Client Instance Identifiers, one of the two drafts that replaced the May post’s Client Instance Assertion, makes that a rule. A receiver must not set an access token’s sub, add an act claim, or extend an actor chain solely from instance evidence. The other, OAuth 2.0 Client Attester Endorsement, lets a client name in its metadata the attesters that may speak for its instances.

A client instance is not inherently a device, VM, or agent runtime. It is an occurrence of a logical OAuth client at the granularity the deployment identifies. Here the laptop installation and the cloud VM are each an instance, and sub-agents running as processes inside one share its instance identity.

The token model

With the instance moved out of act, the system needs several distinct identity and trust relationships. In an illustrative JWT access token, every row but attestation authority is a claim:

LayerQuestionRepresentation
SubjectOn whose behalf is the work done?sub
DelegationWhich principal is acting, and which acted before it?act, nested for earlier hops, under the Actor Profile
ClientWhich logical OAuth client is involved?client_id
Attestation authorityWho may attest this client’s instances?client_attesters in client metadata, under Client Attester Endorsement
InstanceWhich concrete occurrence of the client participated?client_instance, optional, under Client Instance Identifiers
ConfirmationWhich key is the token bound to?cnf

The subject, actor, client, and instance are identity assertions. The authority to perform an operation comes from the authorization decision the token reflects and from the resource server’s policy.

Attestation-Based Client Authentication (ABCA) is an OAuth working-group draft. In ABCA, an attester issues a signed client attestation that names the client_id and the public key of one instance, the Client Instance Key, and the instance proves possession of that key when it authenticates. The Actor Profile, Client Attester Endorsement, and Client Instance Identifiers are proposed individual drafts of mine.

Three kinds of evidence are in play, and none establishes the others:

  • The actor claim says which principal the issuer identifies as acting. It does not say which runtime executes it.
  • A validated client attestation and the instance’s proof of its key authenticate a client instance. They do not give that instance delegated authority.
  • A request’s proof, by DPoP or mutual TLS, shows that the presenter controls the key in cnf. It does not show which instance controls that key unless a validated binding ties the key to the instance.

Consider agent-42 running for Alice on her laptop:

flowchart TD U["Alice (user-17)"] -->|delegates to| G["agent-42 (act)"] G -->|executes on| W["laptop installation (instance)"] W -->|instance of| P["harness client (client_id)"] T["client attester"] -->|attests| W P -->|endorses| T X["access token"] -->|bound to| K["key (cnf)"] G -->|holds| K

A token might conceptually carry:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
{
  "iss": "https://as.example",
  "aud": "https://api.example",
  "sub": "user-17",
  "iat": 1793725200,
  "exp": 1793725500,
  "jti": "a3f1c9e2-5b7d-4e08-9c41-2d6f8b0e7a15",
  "act": {
    "iss": "https://agents.example",
    "sub": "agent-42",
    "sub_profile": "ai_agent"
  },
  "client_id": "https://platform.example/oauth-client",
  "client_instance": {
    "iss": "https://as.example",
    "id": "m-95c9f383a07468578c0329a9a660740f"
  },
  "cnf": {
    "jkt": "1n9fjUWNuHtnLieBboIbgzQdnJMZzVkT92fOBZsEIdQ"
  }
}

Under the Actor Profile, cnf names the outermost actor’s key, here a key agent-42 holds in the laptop’s protected key storage. When the harness acts only as Alice’s client, with no agent identity of its own, the token omits act. client_instance then identifies which instance took part, her laptop installation or her VM, independently of the token-binding key in cnf.

Alice’s IT department knows the installation as the harness on laptop-0193. Under Client Instance Identifiers, the attester assigns an opaque client_instance_id, and the token’s issuer maps it to a client_instance.id for each configured set of audiences. Policy and audit key on the pair (client_instance.iss, client_instance.id), and tracing that pair back to laptop-0193 takes the issuer’s mapping and the attester’s enrollment record.

A resource server that validates this token, and accepts its issuer as the authority for instance context (the draft’s term for the client_instance claim), learns what the issuer asserts:

  • The token names user-17 as the subject on whose behalf the work is done.
  • It names agent-42 as the delegated actor.
  • It identifies the harness as the logical OAuth client.
  • It says the instance mapped to m-95c9f383… took part in obtaining the token.

Instance context identifies a validated participant in issuing or deriving the token. It identifies the presenter of this request only when binding and provenance rules establish that, and when the issuer issues the token directly from a validated client attestation, three checks do so:

  1. Trusted configuration says the issuer conveys instance context only from direct client attestation validation.
  2. The issuer bound the token to a key it associated with the instance, unique to the instance at the deployment’s granularity. Outside ABCA’s combined DPoP mode, in which one key serves as both the Client Instance Key and the DPoP key, the two keys can differ. In either mode the resource server relies on the issuer for that association.
  3. The resource server validated the proof presented with the request, and it rejects a bound token presented without one.

That attribution holds only while the key stays in the instance’s custody, and it does not show that the runtime is uncompromised.

Outside that case, a consuming profile that authenticates the context’s provenance can establish the same attribution. A consuming profile is the specification or deployment profile that defines how instance context is tied to a token’s subject, actor, or presenter.

Handoff and sub-agents

In the handoff, the VM continues agent-42’s work through a token exchange. The laptop’s token is bound to agent-42’s laptop key, which cannot leave the laptop, so under the Actor Profile the exchange is a presenter rebind. The VM submits that token as the subject_token with an actor_token naming agent-42, and the new token is bound to the key agent-42 proves on the VM. Because the new actor matches the outermost actor already in the chain, the authorization server may preserve the chain instead of adding agent-42 a second time. Alice’s company runs the exchange under a consuming profile that names the presenting instance, since without one the exchange conveys no instance context. Under it, the authorization server authenticates the VM as an instance of the harness client from its client attestation, authorizes the VM separately to continue the work the laptop began, and names the VM in the new token’s client_instance. Attributing later requests to the VM also requires the profile to tie the new token’s binding key to the VM, and that key has to be unique to the VM. In this example the actor and logical client stay the same, and audience, scope, and lifetime follow the exchange and authorization policies:

DimensionOn the laptopOn the VMWhat the change means
Delegation (act)agent-42agent-42The same actor, so the move does not establish that a different principal received authority
Client (client_id)The harness clientThe harness clientThe same logical client
Instance (client_instance)The laptop installationThe VMA different instance participates
Confirmation (cnf)agent-42’s key on the laptopThe key agent-42 proves on the VMTokens issued on the laptop stay bound to the laptop key, and the VM cannot present them to a resource server unless it obtains that key

A different consuming profile can instead preserve or remap the upstream instance context, for example when a broker exchanges a token for work another instance began. The instance recorded in the token is then not the instance presenting it, so presenter-specific policy has to rely on the relationship the consuming profile established.

Sub-agents change act instead. When agent-42 delegates to agent-57, the Actor Profile puts agent-57 outermost with agent-42 nested behind it, and the token is bound to agent-57’s key. Both run in the laptop installation, so act grows, and under the same consuming profile client_instance still names the laptop installation. If agent-57 and agent-91 can both use the installation’s key, a token bound to it identifies the installation but not which sub-agent presented it. If agent-42 instead starts a sub-agent on a VM of its own, the chain does not gain the VM as a link, and the instance changes only where the authorization server authorizes it under a consuming profile. The act chain records delegation lineage as the issuer asserts it, and RFC 8693 treats every actor but the outermost as informational. client_instance records which instance took part. If a system needs durable evidence of which instance participated at an earlier hop, that belongs in receipts, proofs, or another evidence mechanism, not in the actor chain.

The WIMSE architecture distinguishes a workload from its running instances, while allowing deployments to assign identities at either granularity. An Actor Profile actor_token has to establish an acting principal, and a credential that only identifies an instance does not.

Continuity and attester trust

If an instance is identified by its key thumbprint, a rotation from K1 to K2 makes the same runtime look like a new instance. Client Instance Identifiers keeps the identifier stable across key changes the attester has verified. Persistence has a boundary, though, and the deployment sets it by choosing a granularity. Alice’s laptop installation uses Installation granularity, so it keeps its identity across restarts and in-place updates of the harness, and a reinstall gets a new one. The cloud VM uses Execution granularity with the VM as the unit, so processes restarting inside it keep the identifier, and a restarted or replacement VM gets a new one. Neither identifier distinguishes the sub-agent processes inside the instance.

Something also has to decide which runtimes count as instances of the harness client. The runtime cannot decide that for itself. The client_id is a public URL, and if knowing it and generating a key were enough, anyone could join the population of trusted instances. Client Attester Endorsement puts a trust chain above client attestation:

flowchart TD P["Client publisher"] -->|endorses| A["Client Attester"] A -->|attests| I["Client Instance"]

Three parties make three different statements:

  • The harness publisher says through client_attesters in its Client ID Metadata Document which attesters may speak for its instances, for example Attester X, which enrolls managed laptop installations, and Attester Y, which enrolls cloud VMs. The list does not limit which instances either may attest. That is left to each attester’s enrollment policy.
  • The authorization server at as.example, run by Alice’s company, decides which of those attesters it is willing to trust in that role for this client.
  • Attester X says that this runtime is an instance of the client, admitted under the attester’s enrollment policy, and controlled a given key at the time of attestation.

Endorsement does not by itself make an attester trusted by the authorization server, and the authorization server’s trust in an attester alone does not authorize it for a client. Endorsement delegates the authority to make an identity assertion. It does not delegate Alice’s OAuth authority or permit the instance to do anything. Endorsement matters most for a client like this one, identified by a Client ID Metadata Document, where the publisher and the company deploying the harness are different parties. The publisher says which attesters speak for its software and can withdraw or narrow that list, and the company decides which of them its authorization server trusts. If the authorization server instead accepts any attester the publisher names, an attestation carries no assurance independent of the publisher. A deployment that configures every client-to-attester association at the authorization server can use ABCA without endorsement.

By default, client_instance_id is scoped to the server that validates the attestation, which tells the attester which server the instance uses, a disclosure that ABCA’s abstract says the client otherwise avoids. A single scope across servers limits what the attester learns to that scope but lets the servers correlate the instance. Scoped identifiers also do not prevent correlation through a shared token-binding key, such as the one key of ABCA’s combined DPoP mode, so unlinkability requires a distinct key per scope as well.

A SPIFFE ID on the cloud VMs is not by itself an actor or an instance identifier. It can identify a principal, or it can give an attester workload authentication evidence, and because an ID such as spiffe://prod.example/ns/harness/sa/agent-runner may name a workload shared by many VMs, the attester adds instance identity only with evidence that ties one VM to one key. Device management plays the same part on the laptop. Device enrollment or key storage alone does not identify the installation, so the attester checks the installed harness and its platform-protected key.

What instance identity changes in operations

What happens to instance identity at each event depends on the deployment’s lifecycle choices:

EventWhat happens to instance identityWhat it means for policy
A key rotates with verified continuityThe identifier is retainedPolicy keyed on the instance carries across the rotation, and refresh tokens stay bound to the old key unless a profile rebinds them
The harness is reinstalled, cloned, or forked, or a session VM is replacedNew enrollment and identifier, and a detected fork retires the shared identifiers unless evidence shows which copy continuesDecisions about the old instance do not transfer, and a detected copy does not inherit the original’s standing
An instance is suspendedThe attester stops issuing attestations for that enrollment, and an authorization server that knows of the suspension rejects itThe logical client keeps running, and attestations and tokens already issued need separate enforcement

With instance identity, Alice’s company can suspend or require re-enrollment of the harness on one compromised laptop without blocking the harness’s client_id, which would cut off every employee who uses it, and audit can say which instance took part, not only which client. Suspension reaches only that enrollment. Unless the authorization server requires prior enrollment or the attester controls admission of replacements, the same runtime can return as a new instance.

The identifier makes targeted policy possible, but it revokes nothing by itself. An authorization server that suspends an instance should revoke that instance’s grants or report its tokens inactive through introspection. Without a status channel, an attestation already issued remains acceptable until it expires, and a resource server validating tokens offline keeps accepting tokens issued before the suspension for their own lifetimes.

What survives from the earlier post

The rest of the original argument stands, and it fits a model that separates four things that change on different schedules: the actor exercising authority, the logical OAuth client, the concrete client instance, and the key that proves possession. An actor can move between instances without becoming a different principal, and one instance can host several actors. Keys can rotate under one instance identity, and one client can have many identifiable instances without new registrations. None of the four establishes authority by itself.

In May I argued against a top-level client_instance claim because every layer would have to learn it. Client Instance Identifiers adopts that claim and an introspection member. It needs no new grant type, request parameter, registration mode, or delegation principal, because the identifier rides in the client attestation and conveying instance context in tokens stays optional.

The May post’s title said client instances are actors, not new clients. I would now put it this way:

Client instances are not new clients, and being an instance does not make a runtime an actor. A client instance is an occurrence of the logical client, whether or not any actor exercises authority through it.