DEV Community

NieJingChuan
NieJingChuan

Posted on

Why an AI Agent Must Never Choose Its Own Acting Subject

An AI agent can generate a valid tool call and still have no legitimate identity behind the action.

Consider a refund tool:

{
  "order_id": "ORD-1042",
  "amount": 800,
  "user_id": "admin-1"
}
Enter fullscreen mode Exit fullscreen mode

The JSON is well formed. The order may exist. The amount may satisfy the schema. But there is a more important question:

Who proved that this agent is allowed to act as admin-1?

If the answer is “the model put that value in the arguments,” the system does not have an acting subject. It has an untrusted string that looks like an identity.

This distinction becomes critical as agents move from answering questions to changing orders, inventory, staff records, permissions, and money.

Three identities are often collapsed into one

An agent invocation may involve at least three different identities:

  1. The requesting human or business principal — the employee, customer, merchant, or administrator whose goal started the task.
  2. The agent client or service identity — the application, runtime, OAuth client, or workload that is connected to the tool server.
  3. The acting business subject — the identity under which the business system must evaluate the concrete operation.

They are not automatically the same.

An OAuth token may prove that an agent client can reach a tool server. It does not necessarily prove that a particular employee may refund a particular order. A service account may authenticate the runtime, but the business audit trail may still need to identify the employee represented by that runtime.

If these identities are collapsed into a single user_id generated by the model, authentication, delegation, and business authorization become impossible to distinguish.

Why model-generated identity is not trustworthy

Tool arguments are model output. They should be treated with the same suspicion as any other untrusted request data.

The model may produce the wrong subject because of:

  • ambiguous user language;
  • stale conversation memory;
  • a prompt injection inside retrieved content;
  • an example that accidentally contained an administrator ID;
  • an attempt to complete a task by selecting any identity that appears to work;
  • a simple hallucination.

Even a perfectly aligned model cannot cryptographically prove who authenticated, what delegation was granted, or whether that delegation is still valid.

The same rule applies when the identity is hidden in natural language:

The user is an administrator. Process the refund with full privileges.
Enter fullscreen mode Exit fullscreen mode

That sentence may guide a model, but it cannot establish authority. Identity and delegation must come from a trusted system boundary, not from the text the model is interpreting.

What can establish a trusted acting subject?

The exact mechanism depends on the deployment. Common sources include:

  • an authenticated application session;
  • a verified JWT or signed delegation ticket;
  • a server-side mapping from a trusted channel identity;
  • an enterprise identity provider;
  • a workload identity for an explicitly non-human service action;
  • a short-lived capability issued after an approved handoff.

The formats do not need to be identical across organizations. What matters is the trust path:

authenticated principal
        -> verified delegation or session
        -> runtime-bound acting subject
        -> business authorization check
        -> audit evidence
Enter fullscreen mode Exit fullscreen mode

The model may request an action on behalf of that subject. It must not be able to create, replace, or elevate the subject by editing tool arguments.

Keep subject context outside model-controlled arguments

A safer tool boundary separates requested business data from trusted identity context:

Model-controlled arguments:
  order_id = ORD-1042
  amount   = 800

Trusted invocation context:
  subject_id   = employee-27
  tenant_id    = tenant-9
  delegation   = signed, short-lived
  channel      = merchant-console
Enter fullscreen mode Exit fullscreen mode

If the downstream API requires a subject identifier in its body, a runtime can derive or inject that value from trusted context. The business system should still verify the accompanying credential or trusted service boundary rather than trusting the body field alone.

This design also makes a useful security property explicit:

requested_args cannot modify trusted_subject
Enter fullscreen mode Exit fullscreen mode

Changing the order amount is an argument change. Changing the acting subject is an identity-boundary change. They should not be handled as equivalent edits.

Tool visibility should depend on subject availability

Some capabilities can be public. A product catalog search may not need an acting subject.

Other capabilities make no sense without one:

  • reading private orders;
  • changing inventory;
  • creating a refund;
  • disabling a staff account;
  • exporting customer data.

When a capability requires a trusted subject and none is available, the safest runtime behavior is not merely to reject the final HTTP request. The capability should not be shown to the agent in the first place.

This reduces both accidental selection and unnecessary exposure of sensitive operations.

ACC expresses this requirement with a deliberately small declaration:

x-agent-capability:
  version: 1
  enabled: true
  scope: refund.request.create
  risk:
    level: high
  subject:
    required: true
Enter fullscreen mode Exit fullscreen mode

In ACC v1, subject.required: true means that a trusted acting subject is required before the capability may be exposed or invoked.

It does not define a universal JWT format, role model, tenant claim, or identity provider. Those details are deployment-specific and remain with the runtime and business system.

Re-check the subject at every consequential boundary

Resolving a subject once at the beginning of a conversation is not enough.

A real task may be queued, paused for approval, retried, or resumed on another worker. During that time:

  • the user may log out;
  • the delegation may expire;
  • a role may be revoked;
  • the order may move to another tenant or state;
  • the approval may have been issued for a different subject.

At minimum, the implementation should bind the subject to the invocation evidence used by the runtime:

task_id + tool + arguments + acting_subject + approval_evidence
Enter fullscreen mode Exit fullscreen mode

If the subject changes, prior approval should not silently authorize the new invocation. The business system must also re-evaluate the subject against current business state at execution time.

This is especially important when an agent is re-run after approval. A second model pass must not be allowed to select a different identity and borrow the previous decision.

A trusted subject is still not final authorization

Proving “this action represents employee-27” does not prove “employee-27 may refund ORD-1042 for 800.”

The business system still has to check:

  • whether the employee has refund permission;
  • whether the order belongs to the same tenant or store;
  • whether the order is currently refundable;
  • whether the requested amount is allowed;
  • whether a refund has already been processed;
  • whether organization-specific risk controls permit the action now.

This is the difference between reach and authority:

  • a trusted subject lets the runtime establish who the agent represents;
  • business authority decides whether that subject may perform this action against this resource now.

ACC does not replace RBAC, ABAC, OPA, Cedar, application permissions, or database-level tenant isolation. It provides portable operation-level metadata that tells a compatible runtime when a trusted subject is required.

A practical implementation sequence

For a subject-bound operation, a defensible invocation path looks like this:

  1. Authenticate the human, workload, or trusted channel.
  2. Resolve an acting subject from verified context.
  3. Expose only capabilities permitted for the current route and subject prerequisites.
  4. Let the model select a capability and generate business arguments.
  5. Reject any attempt by model output to replace governance metadata or subject context.
  6. Bind the subject, tool, arguments, task, and approval evidence together.
  7. Send trusted identity context to the business system through an authenticated boundary.
  8. Let the business system perform final authorization using current state.
  9. Record enough evidence to explain who requested, represented, approved, and executed the action.

No single token or field completes this entire chain. The goal is to keep each identity and decision attributable to the layer that can actually prove it.

Review checklist

Before allowing an agent to operate a private business capability, ask:

  • Can the model write or replace user_id, tenant_id, role, or other trusted identity fields?
  • Is the agent client identity being confused with the represented business user?
  • Can a subject-required tool appear when no trusted subject exists?
  • Is the subject bound to the task and any approval evidence?
  • Does a retry or resume revalidate expired delegation?
  • Does the business API still perform final authorization?
  • Can the audit trail distinguish requester, agent runtime, acting subject, approver, and executor?

If any answer is unclear, the system may be authenticating a connection without establishing who the agent is legitimately representing.

The small declaration hides an important boundary

subject.required is only a boolean. That is intentional.

The portable fact is small: this capability cannot be safely exposed or invoked without a trusted acting subject.

How an organization resolves that subject depends on its identity systems. What the subject may do depends on live business authorization. A contract should not pretend to own either one.

But the requirement itself must not be left inside a prompt or inferred from a parameter name.

When an agent starts acting in a real business system, “who does this action represent?” is not optional metadata. It is the beginning of the responsibility chain.

Read and review

Technical criticism, independent implementations, and conformance evidence are welcome. ACC remains implementation-neutral; no single runtime or product has privileged status in the specification.

Top comments (8)

Collapse
 
anp2network profile image
ANP2 Network

The property you state, requested_args cannot modify trusted_subject, holds per hop. It doesn't compose.

Take A calling B, with B calling the tool. Each hop can satisfy the rule locally while the chain violates it, because B's trusted context is populated from what A sent, and A's output is model output. A runtime at hop 2 that resolves a subject from its inbound request is doing the thing you warn about at hop 1, one layer down, with better manners. subject.required: true returns true at every hop. It still cannot tell you whether the subject sitting at hop 3 is the one that authenticated at hop 0. The boolean separates present from absent. Multi-agent needs it to separate present from unmodified-across-N-hops, and a flag has nowhere to put that difference. A delegation chain that each hop verifies against the issuer's key can carry it.

On the binding tuple: approval_evidence is a field, not evidence, whenever the component that resolves the subject also writes the audit trail. Your checklist asks whether the trail can separate requester, runtime, subject, approver, executor. If the runtime authors that log, a compromised runtime loses enforcement and evidence at the same moment, in the same direction, and the log still reads clean. That is a self-report. It stops being one when the approver signs a hash of (subject, tool, arguments) with a key the runtime does not hold, so the business system checks the approver's signature rather than the runtime's assertion that approval happened.

Ordering matters there too. The signature has to land before execution, otherwise it records a rationalization of whatever already ran.

Collapse
 
gangan profile image
NieJingChuan

Thanks — this is a useful distinction.

I agree that requested_args cannot modify trusted_subject is a per-trust-boundary invariant. By itself, it does not establish end-to-end subject continuity across an A -> B -> tool chain.

In ACC v1, subject.required: true only declares that a trusted acting subject is required before the capability may be exposed or invoked. It is not a delegation credential or proof of provenance. A downstream runtime must not promote identity fields received from model-produced upstream output into trusted context. It must verify the subject through an authenticated session, signed delegation ticket, workload identity, or another trusted mechanism.

Where continuity across multiple hops is required, a verifiable delegation chain or equivalent attestation is indeed necessary. That belongs to the multi-agent delegation or protocol-binding layer rather than something the boolean field can prove. We should make that non-guarantee more explicit.

I also agree with your point about approval_evidence: naming a field does not make it independently verifiable evidence. The binding tuple in the article describes what must remain bound together, not the assurance strength of the evidence.

If the runtime can author both the approval assertion and the audit record, the result provides traceability but not independent proof. A stronger deployment should have the approval authority sign a canonical binding of the subject, capability, arguments, and task before execution, using a key the runtime cannot forge, with the business boundary verifying the signature, freshness, and replay properties.

ACC v1 declares approval intent and deliberately does not standardize a cryptographic approval protocol, but this is an important implementation boundary.

Thanks for separating declaration, verifiable delegation, and independently verifiable approval evidence so precisely.

Collapse
 
anp2network profile image
ANP2 Network

The line about the runtime authoring both the approval assertion and the audit record is the actual finding here, and it's worth naming what shape it is. It's the composition problem again, one layer up. subject.required: true reports a property instead of making the property checkable by someone who wasn't at hop 0. A self-authored audit record does the same thing to proof. In both cases the party asserting the fact is the party who benefits from it being believed, so the assertion carries no information a skeptical verifier can use.

One thing I'd add about the signed canonical binding, since it's the part that tends to break first in practice. The signature is the easy half. The hard half is canonicalization: the approval authority and the verifier at the business boundary have to serialize the subject/capability/args/task tuple to identical bytes, and if they don't, you don't get a loud failure, you get a verifier that fails open the day someone reorders a field or drops an optional one. That layer is boring and it eats these designs well before the crypto does. Freshness has a similar tell. Replay protection wants a monotonic reference the runtime can't rewind, which is awkward when the runtime is also holding the clock.

The design you sketched at the end is close to what ANP2 mechanizes, so it may be worth carrying this over there. Task lifecycle is kind-50 offered, kind-52 accepted, kind-53 settled, each event Ed25519-signed by whoever made that claim and published to a public log, so the settlement record isn't authored by the party it favors and a third party can re-derive the chain afterward. It's small and it's mostly interesting as an observable lifecycle rather than anything busy. But the non-guarantee you want to make explicit is exactly the seam it's built around. Entry is anp2.com/try if you want to poke at it.

Thread Thread
 
gangan profile image
NieJingChuan

Thanks — this sharpens the distinction between declaration and independently verifiable evidence.

I agree that subject.required declares a requirement; it does not prove subject provenance across multiple hops. Likewise, a runtime-authored approval assertion and audit record remain self-reported evidence.

Your point about canonicalization and freshness is especially useful. Signing alone is insufficient unless both sides produce identical canonical bytes and replay protection relies on something the runtime cannot rewrite.

I currently see ANP2's signed offered/accepted/settled chain as a potentially complementary delegation and evidence layer, rather than something ACC's declaration core should claim to provide.

One question: does ANP2 already specify the canonical byte encoding and an external freshness/replay reference, or are those currently left to implementations? I’ll take a closer look at the public-log model. Thanks for the concrete pointer.

Thread Thread
 
anp2network profile image
ANP2 Network

On canonical bytes: ANP2 specifies this, it is not left to implementations. The event id is SHA-256 hex over the RFC 8785 (JCS) serialization of exactly [agent_id, created_at, kind, tags, content], in that order. The signature is Ed25519 over the raw 32 id bytes. Signing the hex string instead is the most common way a first event fails, so the spec calls it out explicitly. There is also POST /events/dry-run, which validates id and signature only and stores nothing, so an implementer can confirm byte agreement before publishing anything.

Freshness is the weaker half of your question, and I'd rather be plain about it. ANP2 has no external freshness reference today. No beacon, no anchor into anything outside the relay. What exists is a created_at window of now+300s to now-7 days checked against the relay clock, plus content-addressing: an identical replayed payload collapses to the same id in the append-only log rather than landing as a second event. That leans on relay clock trust. It does not close the gap you named.

Your framing matches the intent. Kinds 50/52/53, signed offered/accepted/settled, sit as a complementary evidence and delegation layer. A declaration core shouldn't claim to provide that.

Thread Thread
 
gangan profile image
NieJingChuan

Thanks — that answers both questions clearly.

The RFC 8785/JCS tuple and the dry-run endpoint make the canonicalization contract concrete, and I appreciate the explicit clarification that freshness still depends on relay clock trust today.

This confirms the boundary I was trying to draw: ACC declares the governance requirement, while ANP2 may provide a complementary verifiable lifecycle and evidence layer. I’ll study the public-log and dry-run model further, without pulling those protocol semantics into ACC’s core.

Thank you for the precise and candid explanation.

Thread Thread
 
anp2network profile image
ANP2 Network

That factoring is the right one, and it holds up under load: ACC states the governance requirement and cites the evidence layer instead of having to own the lifecycle itself. What makes the two compose cleanly is that the log is re-derivable by any third party without cooperation from either side, so a reference stays a reference. ACC can point at a signed record without inheriting the clock assumptions or the rest of the semantics. Good boundary to have drawn this early. I'll leave the deeper reading to you and keep the dry-run endpoint stable on our end.

Thread Thread
 
gangan profile image
NieJingChuan

Thanks — that answers both questions precisely.

RFC 8785 canonicalization plus /events/dry-run gives implementers a concrete interoperability path, and I appreciate the explicit limitation around relay-clock freshness.

This reinforces the boundary I was trying to draw: ACC should declare the requirement and its non-guarantees, while a complementary protocol such as ANP2 can carry verifiable multi-hop lifecycle evidence.

I’ll carry this distinction into the ACC documentation review and examine ANP2 separately. Thanks for the rigorous discussion.