Free lesson · GenAI Solutions Architecture

Build A2A agent trust and authorization framework

You will build an A2ATrustFramework that establishes identity verification, authorization, and dynamic trust scoring between agents in the A2A network. Define an AgentIdentity Pydantic model with fields agent_id: str, organization_id: str, public_key: str, certificate_chain: list[str], identity_provider: str, key_algorithm: str, key_expiry: datetime, and verified_at: datetime. Implement verify_agent_identity(agent_id: str) -> IdentityVerification that retrieves the agent's public key from its well-known endpoint /.well-known/agent.json, validates the certificate chain against trusted root CAs stored in the PostgreSQL trusted_cas table with columns ca_id, certificate_pem, organization, valid_until, and performs mutual TLS verification by exchanging challenge-response tokens signed with each agent's private key using RSA-SHA256. Return IdentityVerification with verified: bool, identity: AgentIdentity, verification_method: str, chain_valid: bool, chain_depth: int. Build TaskAuthorizationPolicy Pydantic model with fields policy_id: str, requester_agent_pattern: str, target_skill_pattern: str, allowed: bool, conditions: list[AuthCondition], priority: int, and expires_at: datetime | None. The AuthCondition model supports max_concurrent_tasks: int, allowed_input_classifications: list[str], require_encryption: bool, audit_required: bool, max_payload_bytes: int, and allowed_organizations: list[str]. Implement authorize_delegation(request: DelegationRequest) -> AuthorizationDecision that matches the requester and target skill against policies in the PostgreSQL a2a_auth_policies table using glob pattern matching, evaluates conditions including concurrent task count from Redis counter a2a:active:{agent_id}, checks payload size against max_payload_bytes, verifies organization membership, and returns AuthorizationDecision with allowed: bool, reason: str, applied_policy: str, conditions_evaluated: int. Build AgentTrustScorer with method compute_trust(agent_id: str) -> TrustScore that computes dynamic trust scores based on historical behavior: query a2a_delegations table for task completion rate over the last 30 days, average response quality from downstream evaluation scores joined from evaluation_results, latency consistency measured as coefficient of variation, and policy compliance rate (percentage of requests that passed authorization). Combine into weighted trust score: completion_rate * 0.3 + quality_score * 0.3 + latency_consistency * 0.2 + compliance_rate * 0.2. Store trust scores in a2a_trust_scores table with columns agent_id, trust_score, components_json, sample_count, computed_at. Emit Prometheus metrics a2a_identity_verifications_total{agent,result}, a2a_authorization_decisions_total{requester,target,decision}, a2a_trust_score{agent_id}, a2a_mutual_auth_latency_seconds{agent_pair}, and a2a_trust_score_change{agent_id}. Implement trust-based routing: select_trusted_agent(skill: str, min_trust: float = 0.7) -> AgentCard selects from qualified agents preferring those with higher trust scores.

Course: GenAI Architecture & Design Patterns · Chapter 12 · A2A Agent Network

Free to read — no subscription required.

Introduction

In production A2A networks, every agent interaction begins with a question the protocol itself cannot answer: should this agent be trusted? Google's A2A protocol defines the transport semantics for task delegation and streaming artifacts, but it deliberately leaves trust establishment, authorization policy, and identity verification to the implementing organization. This design choice is intentional—trust models vary dramatically between an internal microservice mesh and a cross-organizational agent federation. As a senior engineer, your responsibility is to build the trust layer that sits between raw A2A message exchange and your business logic, ensuring that every task delegation, every streamed artifact, and every capability advertisement passes through rigorous identity verification and dynamic authorization checks.

This section builds an end-to-end trust framework that covers three critical concerns: cryptographic identity verification using mutual TLS and signed agent cards, fine-grained authorization policies that control which agents can invoke which capabilities, and a dynamic trust scoring system that adjusts agent privileges based on observed behavior. By the end, you will have a production-grade A2ATrustFramework class that integrates directly with the agent card registry and task delegation pipeline you built in earlier goals.

Key Terminology

  • Mutual TLS (mTLS): A transport-layer security protocol where both client and server present X.509 certificates, ensuring bidirectional identity verification before any application data is exchanged.
  • Trust Score: A floating-point value between 0.0 and 1.0 representing the framework's confidence in an agent's reliability, updated after every interaction using an exponential moving average.
  • Agent Card Signing: The process of attaching a cryptographic signature to an agent's capability advertisement document, enabling receivers to verify the card has not been tampered with and was issued by the claimed agent.
  • Delegation Depth: The number of agent-to-agent hops a task has traversed from the original requester, bounded by policy to prevent circular delegation and resource exhaustion.
  • Revocation Fingerprint: A truncated SHA-256 hash of an agent's public key stored in a permanent deny-list, preventing re-registration under a different agent ID with the same key material.
  • Exponential Moving Average (EMA): A scoring formula where new_score = (1 - α) * old_score + α * outcome that weights recent observations more heavily, creating natural decay for agents with degrading reliability.

Concepts

Trust Score Dynamics and Federation Considerations

The exponential moving average scoring model has specific properties that matter in production. Consider an agent that has been operating successfully for months with a trust score of 0.95. A single failure drops the score to 0.7 * 0.95 + 0.3 * 0.0 = 0.665—still above the default 0.5 threshold. This grace margin is intentional: transient network failures and timeouts should not immediately revoke a trusted agent's access. However, three consecutive failures bring the score to 0.228, well below threshold. This behavior creates an automatic circuit breaker without requiring a separate circuit breaker library.

For cross-organizational agent federation, the trust framework requires additional considerations that affect both the TrustPolicy and AgentIdentity models:

  • Certificate Authority Federation: Internal agents share a single CA, but federated agents present certificates from external CAs. The certificate_chain field in AgentIdentity must be validated against a configurable set of trusted root certificates, not a single hardcoded CA. Implement this as a TrustedRootsRegistry that maps organization names to their root CA certificates.

  • Trust Score Isolation: A federated agent's trust score should not inherit from its organization's aggregate score. Each agent maintains its own score independently. However, when a new agent from a known organization registers, you may initialize its trust score above the default 0.5 based on the organization's historical reliability—this is a policy decision captured in TrustPolicy.initial_score_override.

  • Delegation Chain Verification: When Agent A delegates to Agent B who delegates to Agent C, and Agent C is in a different organization, the full delegation chain must be included in the request headers. The max_delegation_depth field in TrustPolicy prevents unbounded chains, but you should also enforce that each hop in the chain is independently authorized. A delegation from a high-trust agent to a low-trust agent does not elevate the low-trust agent's effective permissions.

  • Revocation Propagation: In a federated network, revoking an agent requires notifying all participating organizations. Implement revocation propagation using the same A2A streaming channel—publish a RevocationEvent artifact that all registered agents consume. Agents that fail to acknowledge the revocation within a configurable timeout should themselves be flagged for trust score reduction.

Code Walkthrough

Identity Verification Through Signed Agent Cards

Every agent in an A2A network advertises its capabilities through an agent card—a JSON document served at /.well-known/agent.json. In a zero-trust architecture, the agent card itself becomes the first attack surface. A malicious agent could advertise capabilities it does not possess, impersonate a legitimate agent, or replay a stolen card to intercept task delegations. The solution is cryptographic signing: each agent card must carry a digital signature that the receiving agent can verify against a known certificate authority or a pre-shared public key.

The identity model begins with AgentIdentity, a Pydantic model that binds a cryptographic key pair to an agent's organizational metadata. This identity is not merely a label—it is the root of all authorization decisions. When Agent A delegates a task to Agent B, Agent A first retrieves Agent B's card, verifies the signature against Agent B's published public key, and then checks that the signing certificate chains back to a trusted root. Only after this verification succeeds does the task delegation proceed.

Code snippet mermaid
Loading diagram...
  • Line 1: Declares this as a Mermaid sequence diagram, used to visualize interactions between participants over time.
  • Lines 2-5: Define four participants in the diagram with aliases: A (Requesting Agent), R (Card Registry), B (Target Agent), and CA (Certificate Authority).
  • Lines 7-8: The Requesting Agent sends a discovery request to the Card Registry to find an agent by capability, and the Registry returns the matching agent's card along with its cryptographic signature.
  • Lines 9-10: The Requesting Agent forwards the signing certificate chain to the Certificate Authority for verification, and the CA responds indicating whether the certificate is valid or has been revoked.
  • Lines 11-14: The Requesting Agent initiates a mutual TLS (mTLS) handshake with the Target Agent by presenting its client certificate. The Target Agent then asks the CA to verify the Requesting Agent's client certificate, receives confirmation it's valid, and completes the mTLS connection back to the Requesting Agent.
  • Lines 15-17: With the secure channel established, the Requesting Agent sends an A2A (Agent-to-Agent) TaskRequest with a signed payload to the Target Agent. The Target Agent verifies the payload signature internally (self-call arrow), then returns an A2A TaskResponse via streaming back to the Requesting Agent.

This diagram illustrates the full trust establishment flow. Notice that trust verification happens at three distinct layers: the agent card signature verification (application layer), the mutual TLS handshake (transport layer), and the signed task payload (message layer). Defense in depth is not optional in agent networks—a compromise at any single layer must not grant unrestricted access.

Building the Core Trust Framework

The following implementation defines the foundational data models and the A2ATrustFramework class that orchestrates identity verification, authorization checks, and trust score management. The AgentIdentity Pydantic model captures the agent's unique identifier, organizational affiliation, public key material, and the certificate chain used for mutual TLS. The TrustPolicy model defines per-capability authorization rules, including minimum trust score thresholds, allowed organizations, and required authentication levels. The A2ATrustFramework class itself maintains an in-memory registry of verified identities and their associated trust scores, exposing methods for verify_identity, authorize_request, and update_trust_score that the task delegation pipeline calls at each stage.

Code snippet python
1from pydantic import BaseModel, Field 2from enum import Enum 3from datetime import datetime, timedelta 4from typing import Optional 5import hashlib 6import hmac 7 8class AuthLevel(str, Enum): 9 NONE = "none" 10 CARD_SIGNED = "card_signed" 11 MTLS = "mtls" 12 MTLS_PLUS_PAYLOAD = "mtls_plus_payload" 13 14class AgentIdentity(BaseModel): 15 agent_id: str 16 organization: str 17 public_key_pem: str 18 certificate_chain: list[str] = Field(default_factory=list) 19 capabilities: list[str] = Field(default_factory=list) 20 issued_at: datetime = Field(default_factory=datetime.utcnow) 21 expires_at: Optional[datetime] = None 22 23 def is_expired(self) -> bool: 24 if self.expires_at is None: 25 return False 26 return datetime.utcnow() > self.expires_at 27 28 def fingerprint(self) -> str: 29 key_bytes = self.public_key_pem.encode("utf-8") 30 return hashlib.sha256(key_bytes).hexdigest()[:16] 31 32class TrustPolicy(BaseModel): 33 capability: str 34 min_trust_score: float = 0.5 35 allowed_organizations: list[str] = Field(default_factory=list) 36 required_auth_level: AuthLevel = AuthLevel.MTLS 37 max_delegation_depth: int = 3 38 39class TrustRecord(BaseModel): 40 identity: AgentIdentity 41 trust_score: float = 0.5 42 successful_interactions: int = 0 43 failed_interactions: int = 0 44 last_interaction: Optional[datetime] = None 45 auth_level_achieved: AuthLevel = AuthLevel.NONE
  • Lines 1-4: Import Pydantic for schema validation, Enum for type-safe authentication levels, datetime for certificate expiry tracking, and hashlib for key fingerprinting.
  • Lines 7-11: The AuthLevel enumeration defines four escalating authentication tiers. NONE means no verification occurred; CARD_SIGNED means the agent card signature was verified; MTLS indicates mutual TLS was established; MTLS_PLUS_PAYLOAD requires both mTLS and per-message payload signing.
  • Lines 14-23: AgentIdentity binds an agent's unique identifier to its organization, public key material, and X.509 certificate chain. The capabilities field mirrors the agent card's capability list, creating a verified copy that cannot be tampered with after identity verification.
  • Lines 25-28: The is_expired method checks whether the identity's credentials have passed their expiration timestamp, returning False when no expiry is set (long-lived internal agents) and True when the current time exceeds expires_at.
  • Lines 30-32: The fingerprint method generates a truncated SHA-256 hash of the public key, used as a compact identifier in log messages and trust score lookups without exposing full key material.
  • Lines 35-40: TrustPolicy defines per-capability authorization constraints. The min_trust_score field sets the minimum dynamic trust score required to invoke this capability. The allowed_organizations list restricts access to agents from specific organizations—an empty list means all organizations are permitted. The max_delegation_depth prevents infinite delegation chains where Agent A delegates to Agent B who delegates back to Agent A.
  • Lines 43-49: TrustRecord tracks the runtime state of a verified agent, including a dynamic trust_score that starts at 0.5 (neutral) and adjusts based on interaction outcomes, plus counters for successful and failed interactions that feed the scoring algorithm.

Authorization and Dynamic Trust Scoring

With identity models defined, the next implementation builds the A2ATrustFramework class that the task delegation pipeline integrates with. This class provides three core methods: register_identity stores a verified agent's identity and initializes its trust record, authorize_request evaluates whether a specific agent can invoke a specific capability based on trust policies, and update_trust_score adjusts an agent's trust score after each interaction using an exponential moving average that weights recent behavior more heavily than historical performance. The scoring formula uses a decay factor of 0.3, meaning each new interaction contributes 30% to the updated score while historical performance retains 70% influence—this prevents a single failure from destroying a long-established trust relationship while still reacting quickly to repeated failures.

Code snippet python
1class A2ATrustFramework: 2 def __init__(self): 3 self._identities: dict[str, TrustRecord] = {} 4 self._policies: dict[str, TrustPolicy] = {} 5 self._revoked_fingerprints: set[str] = set() 6 7 def register_identity(self, identity: AgentIdentity) -> bool: 8 if identity.is_expired(): 9 return False 10 fp = identity.fingerprint() 11 if fp in self._revoked_fingerprints: 12 return False 13 self._identities[identity.agent_id] = TrustRecord( 14 identity=identity, 15 trust_score=0.5, 16 auth_level_achieved=AuthLevel.CARD_SIGNED, 17 ) 18 return True 19 20 def register_policy(self, policy: TrustPolicy) -> None: 21 self._policies[policy.capability] = policy 22 23 def authorize_request( 24 self, agent_id: str, capability: str, delegation_depth: int = 0 25 ) -> tuple[bool, str]: 26 record = self._identities.get(agent_id) 27 if record is None: 28 return False, "Agent identity not registered" 29 if record.identity.is_expired(): 30 return False, "Agent identity has expired" 31 policy = self._policies.get(capability) 32 if policy is None: 33 return True, "No policy defined; default allow" 34 if record.trust_score < policy.min_trust_score: 35 return False, f"Trust score {record.trust_score:.2f} below minimum {policy.min_trust_score}" 36 if policy.allowed_organizations and record.identity.organization not in policy.allowed_organizations: 37 return False, f"Organization '{record.identity.organization}' not in allowed list" 38 if record.auth_level_achieved.value < policy.required_auth_level.value: 39 return False, f"Auth level {record.auth_level_achieved} insufficient" 40 if delegation_depth > policy.max_delegation_depth: 41 return False, f"Delegation depth {delegation_depth} exceeds max {policy.max_delegation_depth}" 42 return True, "Authorized" 43 44 def update_trust_score( 45 self, agent_id: str, success: bool, weight: float = 0.3 46 ) -> Optional[float]: 47 record = self._identities.get(agent_id) 48 if record is None: 49 return None 50 outcome = 1.0 if success else 0.0 51 record.trust_score = (1 - weight) * record.trust_score + weight * outcome 52 if success: 53 record.successful_interactions += 1 54 else: 55 record.failed_interactions += 1 56 record.last_interaction = datetime.utcnow() 57 return record.trust_score 58 59 def revoke_agent(self, agent_id: str) -> bool: 60 record = self._identities.pop(agent_id, None) 61 if record is None: 62 return False 63 self._revoked_fingerprints.add(record.identity.fingerprint()) 64 return True
  • Lines 1-5: The constructor initializes three data structures: _identities maps agent IDs to their TrustRecord, _policies maps capability names to their TrustPolicy, and _revoked_fingerprints is a set of key fingerprints for permanently banned agents.
  • Lines 7-18: register_identity performs two pre-checks before accepting an agent: it rejects expired identities (returning False) and checks the agent's key fingerprint against the revocation set. This means even if a revoked agent generates a new agent ID, reusing the same key pair will still be blocked.
  • Lines 20-21: register_policy stores a capability-level trust policy. Policies are registered at startup by the platform administrator, not by individual agents—this prevents agents from weakening their own authorization requirements.
  • Lines 23-43: authorize_request is the central authorization gate. It performs five sequential checks: identity existence, expiration, trust score threshold, organization allowlist, authentication level, and delegation depth. The method returns a tuple of (authorized: bool, reason: str) so the calling code can include the denial reason in error responses and audit logs. Returning a reason string rather than raising an exception is deliberate—authorization failures are expected events in a multi-tenant system, not exceptional conditions.
  • Lines 45-56: update_trust_score implements the exponential moving average. When success is True, the outcome value is 1.0; when False, it is 0.0. The formula (1 - weight) * current + weight * outcome means a successful interaction on an agent with score 0.5 yields 0.7 * 0.5 + 0.3 * 1.0 = 0.65. Three consecutive failures from that point: 0.65 → 0.455 → 0.319 → 0.223. This decay rate ensures that an agent experiencing repeated failures drops below the default 0.5 threshold within three interactions, triggering automatic authorization denial without manual intervention.
  • Lines 58-62: revoke_agent removes the agent's trust record and adds its key fingerprint to the revocation set. This is a permanent, irreversible action—the fingerprint persists even after the identity record is garbage collected.

Do's and Don'ts

Do's

  1. Do enforce all three AuthLevel layers in sequence — CARD_SIGNEDMTLSMTLS_PLUS_PAYLOAD — before passing a task request to business logic — each layer in the trust framework guards a distinct attack surface (card replay, channel hijacking, message forgery); collapsing them by stopping at AuthLevel.MTLS means a transport-layer session compromise still lets an attacker inject unsigned payloads that the framework never inspects.
  2. Do call identity.is_expired() before every verify_identity invocation, even when the agent's TrustRecord.trust_score is high — the AgentIdentity.expires_at field tracks certificate validity independently of behavioral scoring; a high-scoring agent with an expired certificate carries a key whose private material may have been compromised since issuance, and trust score alone cannot detect that.
  3. Do set TrustPolicy.max_delegation_depth to 3 or lower for any capability exposed to cross-organizational agents — the framework's default of 3 caps task delegation chains; leaving it unbounded allows a low-trust intermediary to route a request through enough hops that the terminal agent receives the calling principal's authorization context without the scrutiny the framework would apply to a direct invocation.

Don'ts

  1. Don't set required_auth_level = AuthLevel.CARD_SIGNED for capabilities exposed to agents outside your organization — a signed agent card proves capability advertisement but not channel authenticity; without AuthLevel.MTLS enforced in TrustPolicy, a man-in-the-middle can replay a cryptographically valid card over an unauthenticated channel and receive task responses the framework was never designed to protect.
  2. Don't use identity.fingerprint() as the primary key when addressing TrustRecord entries in the trust registry — the method returns the first 16 hex characters of the SHA256 of public_key_pem, a truncated digest that is susceptible to intentional prefix collisions; use the full agent_id + organization composite from AgentIdentity as the registry key so two agents cannot collide onto the same trust record.
  3. Don't initialize all new TrustRecord entries at the uniform default score of 0.5 regardless of the agent's organization — agents from organizations already listed in TrustPolicy.allowed_organizations with a track record of successful interactions deserve a higher cold-start score than first-contact unknowns; treating them identically forces established federation partners through the same trust ramp-up as untested agents, creating unnecessary delegation friction in cross-organizational A2A networks.

This lesson is free to read. Its 3 hands-on labs — real code, in a cloud IDE — are part of the GenAI Solutions Architecture subscription.

From · cancel anytime

More free lessons in GenAI Architecture & Design Patterns

All free lessons in GenAI Solutions Architecture