How Universal Commerce Protocol Manages Profile Caching and Discovery Footprint

Universal Commerce Protocol enforces a 60-second minimum cache TTL for profile responses, mandates HTTPS-only fetching with no redirects, and recommends fixed-size LRU caches combined with global rate limiting to bound discovery footprint.

Universal Commerce Protocol (UCP) treats profiles—JSON documents describing business capabilities, signing keys, and service endpoints—as stable, cache-able resources. According to the specification in docs/specification/overview.md, the protocol defines strict caching requirements and discovery footprint boundaries to ensure that profile fetching remains efficient even as the network scales to millions of parties. These mechanisms prevent discovery storms while maintaining the cryptographic trust established through validated profile documents.

Mandatory Cache-Control Headers

Every profile response must include a Cache-Control header that is public and specifies a minimum max-age of 60 seconds. This guarantees that compliant implementations can safely store profiles in shared caches for at least one minute, regardless of the origin’s specific directives.

"Profile responses MUST include a Cache-Control header with public and max-age of at least 60 seconds." – docs/specification/overview.md

Because profiles represent a party's stable identity and capabilities rather than per-transaction configuration, they are inherently safe to cache. The specification reinforces this by requiring shared cache support with this minimum TTL floor, ensuring that high-traffic scenarios do not overwhelm origin servers with redundant requests.

Discovery Footprint Management Strategies

To prevent unbounded growth in memory and network usage during discovery, UCP recommends a multi-layered approach to footprint reduction.

Fixed-Size LRU Caching

Implementations should maintain a fixed-size cache, typically an LRU (Least-Recently-Used) cache, to store resolved profiles. This caps memory usage regardless of how many unique profile URLs are encountered across the network.

"- Fixed-size profile cache (e.g., LRU) — bounds memory regardless of the number of unique profile URLs encountered." – docs/specification/overview.md

Rate Limiting and Failure Back-off

Complementary to caching, the protocol recommends global rate limits on outbound discovery fetches to prevent traffic spikes. Additionally, implementations should implement exponential back-off on repeated failures to reduce load against hostile or temporarily unavailable endpoints.

"- Global rate limit on discovery fetches … - Backoff on repeated failures …" – docs/specification/overview.md

Asynchronous Discovery Mechanisms

When a profile cannot be resolved synchronously without blocking the request, UCP permits returning a 503 Service Unavailable response with a Retry-After header. The implementation fetches the profile in the background, and subsequent client retries hit the now-cached profile.

"- Asynchronous discovery — defer profile resolution by responding with a 503 … and resolve the profile in the background; when the platform retries, the validated profile is cached and capability negotiation proceeds synchronously." – docs/specification/overview.md

Safety Requirements for Profile Fetching

UCP prescribes strict transport rules to prevent abuse during profile retrieval. As defined in docs/specification/overview.md, implementations:

  1. Must reject profile URLs not served over HTTPS.
  2. Must not follow HTTP redirects (3xx responses).
  3. Should enforce reasonable connect and response timeouts to prevent hanging connections.

These constraints ensure that profile discovery does not introduce open redirects or man-in-the-middle vulnerabilities, maintaining the integrity of the trust chain established in source/discovery/profile_schema.json.

Implementation Examples

The following code demonstrates practical application of the caching, rate-limiting, and safety requirements specified in the UCP documentation.

Python LRU Profile Cache with Safety Checks

This example implements a fixed-size LRU cache that enforces the HTTPS-only rule, rejects redirects, and validates the mandatory Cache-Control header.

from collections import OrderedDict
import requests
import time

class LRUProfileCache:
    def __init__(self, max_items: int = 256, ttl: int = 60):
        self.max_items = max_items
        self.ttl = ttl                 # seconds

        self.store = OrderedDict()     # url -> (timestamp, profile)

    def _is_fresh(self, ts: float) -> bool:
        return (time.time() - ts) < self.ttl

    def get(self, url: str):
        # Return cached profile if fresh

        entry = self.store.get(url)
        if entry and self._is_fresh(entry[0]):
            # refresh LRU order

            self.store.move_to_end(url)
            return entry[1]
        # otherwise fetch

        return self._fetch_and_store(url)

    def _fetch_and_store(self, url: str):
        # Enforce HTTPS and no redirects

        if not url.startswith("https://"):
            raise ValueError("Profile URL must use HTTPS")
        resp = requests.get(url, allow_redirects=False, timeout=5)
        resp.raise_for_status()
        # Verify required Cache‑Control header

        cc = resp.headers.get("Cache-Control", "")
        if "public" not in cc or "max-age" not in cc:
            raise ValueError("Profile response missing required Cache-Control")
        profile = resp.json()
        # Insert into cache

        self.store[url] = (time.time(), profile)
        self.store.move_to_end(url)
        if len(self.store) > self.max_items:
            self.store.popitem(last=False)   # evict LRU

        return profile

Node.js Global Rate Limiter

This example uses bottleneck to enforce global rate limits on discovery traffic, ensuring the global rate limit strategy caps outbound requests.

const Bottleneck = require('bottleneck');

// One global limiter for all outbound profile fetches
const limiter = new Bottleneck({
  maxConcurrent: 5,
  minTime: 200          // at most 5 requests per second
});

async function fetchProfile(url) {
  return limiter.schedule(() => _fetch(url));
}

async function _fetch(url) {
  if (!url.startsWith('https://')) throw new Error('HTTPS required');
  const res = await fetch(url, { redirect: 'error', timeout: 5000 });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const cc = res.headers.get('cache-control') || '';
  if (!/public/.test(cc) || !/max-age=/.test(cc)) {
    throw new Error('Invalid Cache-Control header');
  }
  return await res.json();
}

Asynchronous Discovery with 503 Response

This pattern demonstrates handling missing profiles by returning a 503 status, allowing background fetching while keeping the client request non-blocking.

def handle_request(request):
    profile_url = request.headers.get('UCP-Agent')
    try:
        profile = profile_cache.get(profile_url)   # may raise

    except Exception:
        # Profile not yet cached – reply with 503 and let client retry

        return (503, {'Retry-After': '5'}, b'Profile discovery in progress')
    # Continue with normal processing using `profile`

    …

Summary

  • Cache-Control is mandatory: All profile responses must include Cache-Control: public, max-age=60 or higher, as defined in docs/specification/overview.md.
  • Memory is bounded: Fixed-size LRU caches prevent discovery footprint from growing with network size.
  • Traffic is shaped: Global rate limits and failure back-off protect against spikes and retry storms.
  • Security is enforced: HTTPS-only fetching with no redirects prevents interception and open-redirect attacks.
  • Async resolution is supported: 503 responses with Retry-After allow non-blocking profile discovery.

Frequently Asked Questions

What is the minimum cache duration for UCP profiles?

According to docs/specification/overview.md lines 1035-1037, profile responses must include a Cache-Control header specifying public and a max-age of at least 60 seconds. This guarantees that shared caches can store profiles for a minimum of one minute.

How does Universal Commerce Protocol prevent memory exhaustion during discovery?

The specification recommends implementing a fixed-size LRU cache for storing profiles. As noted in docs/specification/overview.md lines 1065-1067, this strategy bounds memory usage regardless of how many unique profile URLs a platform encounters, preventing unbounded growth as the network scales.

What happens when a profile cannot be fetched synchronously?

Per docs/specification/overview.md lines 1071-1074, implementations may return a 503 Service Unavailable status with a Retry-After header and resolve the profile asynchronously in the background. When the client retries, the validated profile is already cached, allowing capability negotiation to proceed synchronously.

Why does UCP disallow redirects for profile fetching?

To prevent open-redirect vulnerabilities and man-in-the-middle attacks, the protocol mandates that implementations must not follow HTTP redirects (3xx responses) and must reject non-HTTPS URLs. These requirements are specified in docs/specification/overview.md lines 1078-1080 as part of the core safety rules for profile retrieval.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →