---
title: "Client Instances Aren't Actors Either"
date: "2026-11-06T09:00:00-08:00"
lastmod: "2026-11-06T09:00:00-08:00"
description: "One installation of an agent harness can host an agent and its sub-agents, and an agent can move from a laptop to a cloud VM, so a client instance is not an actor by virtue of being an instance. Proposed OAuth drafts treat the instance as its own identity dimension beside the actor chain, authenticated by the authorization server through an attester that the client endorses and the server trusts, with a persistent identifier that supports targeted policy without granting authority."
summary: "In May I argued that client instances are actors, not new clients. An instance is still not a new client, but it is not an actor by virtue of being an instance. One installation of an agent harness can host an agent and its sub-agents, and the same agent can move from a laptop to a cloud VM, so instance evidence alone establishes neither actor identity nor delegated authority. The instance belongs beside the actor chain. The client endorses which attesters may speak for its instances, and the authorization server authenticates an instance by validating an attestation from one of them that it trusts and the instance’s proof of the attested key. Client Instance Identifiers gives the instance continuity within a lifecycle the deployment defines. That identity lets a company isolate one installation without blocking the client for everyone, though identifying an instance revokes nothing by itself."
slug: "client-instances-arent-actors-either"
tags:
  - "OAuth"
  - "Standards"
  - "Delegation"
  - "Client Instance"
  - "CIMD"
  - "Workload Identity"
  - "Agentic Identity"
  - "Internet-Draft"
---


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://datatracker.ietf.org/doc/draft-ietf-oauth-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](https://datatracker.ietf.org/doc/draft-mcguinness-oauth-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](/notes/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:

```mermaid
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:

```mermaid
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](https://datatracker.ietf.org/doc/draft-mcguinness-oauth-client-instance-id/), 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](https://datatracker.ietf.org/doc/draft-mcguinness-oauth-client-attesters/), 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:

| Layer | Question | Representation |
| --- | --- | --- |
| Subject | On whose behalf is the work done? | `sub` |
| Delegation | Which principal is acting, and which acted before it? | `act`, nested for earlier hops, under the Actor Profile |
| Client | Which logical OAuth client is involved? | `client_id` |
| Attestation authority | Who may attest this client's instances? | `client_attesters` in client metadata, under Client Attester Endorsement |
| Instance | Which concrete occurrence of the client participated? | `client_instance`, optional, under Client Instance Identifiers |
| Confirmation | Which 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](https://datatracker.ietf.org/doc/draft-ietf-oauth-attestation-based-client-auth/)) 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:

```mermaid
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:

```json
{
  "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:

| Dimension | On the laptop | On the VM | What the change means |
| --- | --- | --- | --- |
| Delegation (`act`) | `agent-42` | `agent-42` | The same actor, so the move does not establish that a different principal received authority |
| Client (`client_id`) | The harness client | The harness client | The same logical client |
| Instance (`client_instance`) | The laptop installation | The VM | A different instance participates |
| Confirmation (`cnf`) | `agent-42`'s key on the laptop | The key `agent-42` proves on the VM | Tokens 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](https://www.rfc-editor.org/rfc/rfc8693#section-4.1) 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](https://datatracker.ietf.org/doc/draft-ietf-wimse-arch/) 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:

```mermaid
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:

| Event | What happens to instance identity | What it means for policy |
| --- | --- | --- |
| A key rotates with verified continuity | The identifier is retained | Policy 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 replaced | New enrollment and identifier, and a detected fork retires the shared identifiers unless evidence shows which copy continues | Decisions about the old instance do not transfer, and a detected copy does not inherit the original's standing |
| An instance is suspended | The attester stops issuing attestations for that enrollment, and an authorization server that knows of the suspension rejects it | The 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.**

