Skip to content

Add cross-site agent-to-agent caller context primitive #76

@lezama

Description

@lezama

Problem

The Auth subsystem that landed in #73 covers single-host agent execution well: WP_Agent_Token distinguishes multiple bearer tokens (and via client_id, multiple chat-bridge logins) for the same agent, and WP_Agent_Capability_Ceiling enforces that an agent never exceeds its owner's capabilities.

What's not yet in the substrate: a primitive for tracking cross-site agent-to-agent invocation chains. When agent A on host X invokes agent B on host Y over an authenticated request, host Y currently has no documented way to recover "this is an A2A call", "the calling agent is X", "this is depth N in a chain rooted at request R".

The reference shape is shipping in Extra-Chill/data-machine at inc/Core/Auth/CallerContext.php (referenced from inc/Abilities/PermissionHelper.php). It is populated by AgentAuthMiddleware from a small set of HTTP headers (X-*-Caller-* and X-*-Chain-*) after bearer-token resolution.

This pattern is universal across multi-agent hosts. Any substrate consumer that supports remote agent invocation will eventually reinvent it. Better to settle the contract in the substrate so adopters can implement against one shape.

Proposed shape

Generic value object plus a token authenticator hook:

final class WP_Agent_Caller_Context {
    public function __construct(
        public readonly string $caller_agent_id,        // '' = top-of-chain
        public readonly int    $caller_user_id,         // 0  = no user-on-caller-host
        public readonly string $caller_host,            // 'self' | absolute URL of remote host
        public readonly int    $chain_depth,            // 0 = top-of-chain
        public readonly string $chain_root_request_id,  // stable identifier for the originating request
        public readonly array  $metadata = array(),     // host-owned extension payload
    ) {}

    public function is_cross_site(): bool {
        return 'self' !== $this->caller_host && '' !== $this->caller_host;
    }
}

Resolution would happen alongside token resolution in the existing WP_Agent_Token_Authenticator flow:

  1. Token authenticator resolves bearer token → WP_Agent_Token.
  2. Authenticator parses caller-chain headers → WP_Agent_Caller_Context.
  3. Both attach to the request execution principal.

Contract concerns to nail

  • Header naming: X-Agents-Api-Caller-Agent, X-Agents-Api-Caller-User, X-Agents-Api-Caller-Host, X-Agents-Api-Chain-Depth, X-Agents-Api-Chain-Root — substrate names so consumers don't diverge.
  • Trust model: caller headers are claims, not assertions. Hosts MUST verify they trust the caller_host before honoring the chain — usually via a pre-shared key or mutual TLS. The substrate ships only the value object + parser; trust enforcement is a host concern.
  • Loop / depth limits: chain_depth exists to enable substrate-level loop protection (refuse to invoke at depth > N). Default ceiling needs a number — proposing 16 as a starting point that comfortably covers realistic agent collaboration patterns and refuses runaway recursion.
  • Composition with IterationBudget: chain depth is a natural budget dimension. Worth considering whether to expose it as IterationBudget('chain_depth', N) integration rather than a separate enforcement path.

Acceptance criteria

  • Substrate exposes the value object and a documented header convention.
  • A token authenticator can populate caller context alongside WP_Agent_Token resolution.
  • Execution principal carries the caller context through the conversation loop so policy adapters can consult it.
  • Smoke tests cover: no-headers (top-of-chain context), valid chain, depth-limit enforcement, malformed headers (rejected fail-closed).
  • Trust enforcement is documented as out-of-scope (host concern), with guidance on the recommended pattern.

AI assistance

  • AI assistance: Yes
  • Tool(s): Claude Code (Opus 4.7)
  • Used for: Identifying the cross-site A2A primitive missing from the just-shipped Auth subsystem and drafting the contract proposal grounded in data-machine's reference impl.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions