Skip to content

feat(a2a): per-request agent-card URL resolution (follow-up to #616) #647

@bokelley

Description

@bokelley

Problem (follow-up to closed #616)

PR for #616 shipped Option A — the static serve(public_url=...) kwarg — and the issue closed COMPLETED. Option B (trust_forwarded_headers for per-request resolution) was explicitly deferred per the dx-expert review.

This is fine for single-host deployments. For multi-tenant subdomain deployments, where every tenant has its own public host, the static kwarg can only advertise one URL. Adopters still need a per-request rewrite to surface the correct tenant host in /.well-known/agent-card.json.

Concrete adopter scenario

bokelley/salesagent runs multi-tenant on subdomains: acme.scope3.com, beta.scope3.com, etc. Each tenant's buyer agent fetches the agent card from its own host. A static public_url=https://scope3.com/ would advertise the wrong host to every non-apex tenant.

Today we work around this with a 190-LOC ASGI middleware (core/middleware/agent_card_public_url.py) that buffers the /.well-known/agent-card.json response and rewrites localhost URLs from X-Forwarded-Host per request. It composes cleanly with the new static kwarg (only rewrites loopback, so it no-ops when public_url is set) but it's adopter-side code maintaining an SDK-shaped behaviour.

Proposed shape

Two options, both addressing the per-request case the deferral explicitly called out:

A) serve(trust_forwarded_headers=True) — when set, the agent-card route honours X-Forwarded-Host and X-Forwarded-Proto at response-build time. Defaults to False so unconfigured behaviour is unchanged. Concerns from the original deferral:

  • Trust boundary: gate via the kwarg — adopters explicitly opt in only when they know they're behind a trusted proxy. Matches how Flask / FastAPI / starlette handle the same headers.
  • Thread-safety on shared pb.AgentCard: build the response card per-request rather than once at server-init. The card payload is small, the marginal CPU is negligible vs. the network IO.

B) serve(public_url=Callable[[Request], str]) — accept a callable in addition to a static string. Adopters provide the resolution logic themselves. More general, less opinionated about trust boundaries.

Either closes the multi-tenant subdomain gap. A is the cleaner zero-config option for the common case (most multi-tenant adopters have a reverse proxy that sends X-Forwarded-Host); B keeps the SDK out of the trust-policy business at the cost of every adopter writing the resolver.

Why a new issue vs reopening #616

#616 closed COMPLETED — fine, the static kwarg is real and useful. Re-opening it would muddy the changelog. This is a separate ask that depends on the closed work landing first.

Source

Our deferral entry in bokelley/salesagent core/SDK_FEEDBACK.md: "make the agent-card URL X-Forwarded-Host-aware when no static public_url is configured, OR accept a Callable[[Request], str] for per-request resolution."

Local tracker: bokelley/salesagent#103.

Related

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions