Pre-release Harn is pre-1.0 — the language, standard library, and CLI may change between releases. See the release notes

A2A RFC: actor-chain extension for delegated authority

  • Upstream repo: a2aproject/A2A
  • Issue: A2A #2028 — actor-chain extension
  • Status: Open and by far the most active of our filings — 24 comments, no maintainer verdict. The thread converged on two properties that must not be conflated (well-formedness versus proven authority), then on scopes being derived from a validated credential rather than authorization-bearing. Two participants agree on an executable vector set. See Thread state for who is actually in the thread.
  • Last verified: 2026-08-22
  • Authors: Burin Labs
  • Reference impl: harn_vm::actor_chain::ActorChain (crates/harn-vm/src/actor_chain.rs), carried through the harn-serve A2A adapter (crates/harn-serve/src/adapters/a2a/schema.rs) under task and message metadata
  • Anchor threads: A2A #1937 (context-binding profile for delegated authority) and A2A #153 (confused deputy)

Written against A2A v1.0 conventions: a2a.proto is normative, operations are PascalCase, and enum values are SCREAMING_SNAKE_CASE ProtoJSON strings.

Problem statement#

A2A v1.0 carries no identity in the payload. Authentication is out-of-band: SecurityScheme declarations on the agent card plus transport-level mechanisms (HTTP auth, mTLS). The canonical data model has no field on Message, Task, or the agent card for the human (or upstream agent) a request is made on behalf of. The only identity-adjacent field is tenant, which is an opaque routing id, not a principal. The only escape hatch is the untyped metadata map.

That gap matters as soon as agents delegate to agents:

  • Attribution. When agent C receives work from agent B, which received it from agent A acting for user U, C sees only B's transport credentials. The full chain A→B→C-for-U exists nowhere on the wire.
  • Audit. Post-hoc "who caused this side effect" reconstruction has to join transport logs across every hop, each with its own identity representation.
  • Confused deputy (#153): a callee that can't see the originating principal can be tricked into using its own broader authority on behalf of a less-privileged caller.

The OAuth ecosystem already has the vocabulary for this: RFC 8693 token exchange expresses delegation as nested act (actor) claims, and the ID-JAG / WIMSE drafts under active development build agent-specific delegation on the same shape. A2A can interoperate with that work without inventing a token format — the missing piece is a standard, optional place in the A2A payload where a chain travels.

#1937 proposes the complementary half: binding an already-valid delegation to a task/session/target/scope. This proposal is about carrying the chain itself in a standard shape. The two compose: a context-binding profile needs a chain representation to bind.

Proposed shape#

An A2A extension (v1.0 extension mechanism: an AgentExtension entry in AgentCard.capabilities.extensions[], keyed by URI), rather than a core schema change. Peers that support it declare:

{
  "capabilities": {
    "extensions": [
      {
        "uri": "https://a2a-protocol.org/extensions/actor-chain/v1",
        "required": false
      }
    ]
  }
}

The extension defines one reserved metadata member, actorChain, valid on Message.metadata and echoed on Task.metadata:

{
  "actorChain": {
    "origin": { "sub": "user:alice@example.com" },
    "actors": [
      { "sub": "agent:coordinator", "scopes": ["repo:read", "ci:trigger"] },
      { "sub": "agent:rebase-worker", "scopes": ["repo:read"] }
    ],
    "mayAct": "agent:rebase-worker"
  }
}
  • origin — the outermost principal the work is ultimately for (typically a human).
  • actors — the delegation hops in order, mirroring RFC 8693 nested act claims flattened for readability; each hop optionally carries the authority scopes that hop held, so attenuation is visible.
  • mayAct — optional RFC 8693 may_act analogue.

Audit, not authz. The chain is attribution and audit history. It MUST NOT be used to grant access: authorization stays with the transport-level credentials and whatever token the callee validated. This mirrors the position the ID-JAG authors have taken and avoids the "self-asserted identity as capability" trap — a payload field anyone can write must never be a credential.

Compatibility and migration#

The extension is additive and optional. Peers that don't declare it ignore the metadata member (A2A metadata is already tolerate-by-default). Peers that do declare it SHOULD echo the chain (with their own hop appended) when they delegate onward.

The reference implementation ships this today under metadata.actor_chain / metadata.harn.actor_chain with the same entry shape (sub + scopes), validated on ingest and appended per hop. Migration to a ratified extension URI and member name is a rename.

Thread state#

The discussion has moved further than the original filing. Four things are settled enough in-thread to treat as the current shape:

  1. Two properties, never conflated. Well-formedness is checkable from the payload alone: each hop's scopes must be a subset of its predecessor's, and a violation is exactly the confused-deputy shape from #153. Authority is not, because actorChain is caller-supplied and a fabricated chain can narrow perfectly at every hop. Proving a grant needs a per-hop proof_ref that an outside verifier resolves without trusting the payload.
  2. Scopes are derived, not authoritative. Per-hop scopes render what a validated credential already granted; they never carry authority themselves. A malformed or missing chain therefore degrades attribution, never access. This came from a 2026-08-20 review mapping OAuth token exchange onto the shape, and it is the correction to fold into the extension text.
  3. Missing is not absent. No proof_ref means no reconciliation aid was supplied. It must not be read as evidence that no grant occurred. Present but unresolvable, and present but invalidly signed, are further distinct states.
  4. An originAnchor slot is reserved and inert. Named, unpopulated, and pinned by two negative cases: a populated anchor must verify identically to an unpopulated one, and an anchor can never substitute for scope a predecessor actually held.

Two participants have since checked each other against a shared executable vector set covering those cases, agreeing on the negatives and on one documented limit: a hop that rewrites its predecessor's scopes upward narrows perfectly in the forwarded chain, so that case is only catchable receiver-side.

Read that agreement carefully. No maintainer, member, owner, or collaborator of a2aproject has replied to this issue — all 24 comments carry author_association: NONE. The two parties who agree on the vector set wrote 15 of those 24 comments and have been in continuous conversation with each other since 2026-07, so their agreement is corroboration between two participants, not independent confirmation. The technical content above stands on its own reasoning; the thread's volume is not evidence of upstream demand. The per-participant detail is in the status ledger.

Open questions for upstream maintainers#

  1. Extension vs core. Is the v1.0 extension mechanism the intended incubation path for this, or is payload identity in scope for core once #1937 settles?
  2. Nested vs flattened. RFC 8693 nests act claims; the shape above flattens to an ordered array for readability. Should the wire shape stay byte-compatible with RFC 8693 claims instead?
  3. Relationship to signed evidence. A2A v1.0 already does JWS for agent-card signatures. Should hops be signable, or is that deliberately deferred to token-level mechanisms (ID-JAG / WIMSE)?
  4. Interaction with tenant. Should the extension state that tenant remains routing-only, to keep it from being repurposed as a principal?

References#