Skip to content

[FEATURE] Set appProtocol: kgateway.dev/a2a on agent Service when a2aConfig is set #1808

Closed

Description

@davidkarlsen

📋 Prerequisites

📝 Feature Summary

When a kagent Agent CR has spec.declarative.a2aConfig populated, set appProtocol: kgateway.dev/a2a on the agent's Service port so agentgateway automatically switches to A2A protocol handling — same way it already does for MCP backends marked with appProtocol: kgateway.dev/mcp.

❓ Problem Statement / Motivation

agentgateway's documented Kubernetes integration for A2A (docs) relies on the Service appProtocol marker:

"Notice that the Service uses the appProtocol: kgateway.dev/a2a setting. This way, agentgateway configures the agentgateway proxy to use the A2A protocol."

Without this marker, the proxy treats traffic as opaque HTTP and skips A2A-specific behaviors like agent-card url rewriting. That breaks the round-trip: a card fetched through the gateway points back at the in-cluster Service hostname, which external A2A clients can't reach.

The kagent operator (go/core/internal/controller/translator/agent/conversion.go) already reads appProtocol to find the MCP port on referenced Services, but does not write any appProtocol value on the agent's own generated Service. So the obvious fix — patch the Service in-place — gets reverted on the next reconcile loop within ~10s (verified empirically on v0.8.0).

Today the only workaround is a sibling "wrapper" Service per agent in a separate manifest, with a duplicated pod selector. Six agents in our deployment ⇒ six wrapper Services. Two boilerplate Services per A2A agent is a lot of Kubernetes for what should be a one-liner in the operator's translator.

💡 Proposed Solution

In internal/controller/translator/agent/conversion.go (or wherever the agent Service is generated), set the appProtocol field on the agent port when the source Agent CR has spec.declarative.a2aConfig non-nil:

agentPort := corev1.ServicePort{
    Name:        "http",
    Port:        8080,
    TargetPort:  intstr.FromInt(8080),
    Protocol:    corev1.ProtocolTCP,
    AppProtocol: ptr.To("kgateway.dev/a2a"),  // ← new, when a2aConfig is set
}

A bare http Agent (no A2A) would keep the field unset, exactly as today.

🔄 Alternatives Considered

  1. In-place oc patch of the operator-managed Service — reverted by the controller within seconds.
  2. Wrapper Services in user manifests — works but doubles Service count for every A2A agent.
  3. A separate Service field on the Agent CR allowing arbitrary spec overrides — more flexible but heavier; the appProtocol case is mechanical and doesn't need configurability.
  4. Mutating webhook — fights the operator on every reconcile; brittle.

Additional Context

  • Reproduced on kagent v0.8.0, agentgateway v1.1.0, OpenShift 4.x.
  • oc explain agentgatewaybackends.agentgateway.dev.spec confirms agentgateway has no dedicated a2a backend type yet — appProtocol on the Service is the only documented integration mechanism for A2A in K8s.
  • Same pattern is already in production for MCP (appProtocol: kgateway.dev/mcp on argocd-mcp-server, db2-luw-mcp-server, etc.).
  • Code reference: go/core/internal/controller/translator/agent/conversion.go already has the surrounding shape — it reads svc.Spec.Ports[].AppProtocol for MCP detection but never writes it back on agent services.

Happy to send a PR if there's interest.

Activity

  1. davidkarlsen commented on May 7, 2026

    @davidkarlsen
    ContributorAuthor

    Update from downstream client implementation

    Implementing an A2A client downstream (Spring AI tool callbacks in our Slack bot) hit a related discovery gap that's adjacent to this issue. Posting here so it stays linked.

    Empirical probe (kagent v0.8.0 + agentgateway v1.1.0)

    With the wrapper-Service workaround from this issue's "Alternatives Considered #2" in place, per-agent discovery already works via the spec path:

    GET /a2a/snow-agent/.well-known/agent-card.json   → 200 application/json (full card)
    GET /a2a/promql-agent/.well-known/agent-card.json → 200 application/json (full card)
    

    But there is no gateway-level enumeration. Probing the routed gateway root:

    GET /.well-known/agent-card.json   → 406 mcp: client must accept both application/json and text/event-stream
    GET /agents                        → 406 (same)
    GET /a2a/                          → 406 (same)
    

    The MCP federation backend catches them via the default HTTPRoute rule. oc explain agentgatewaybackends.agentgateway.dev.spec confirms agentgateway v1.1.0 has no native a2a backend kind, so there's nothing on the gateway side to expose a routed-agent catalog either.

    Net effect: an A2A client today has to keep a static list of agent names even though kagent already knows the full set from its Agent CRs (and the operator's controller-runtime cache).

    A small runtime quirk worth noting — Go-ADK agents (e.g. snow-agent) serve only /.well-known/agent-card.json (the legacy /.well-known/agent.json returns 503), while Python-ADK agents (e.g. promql-agent) serve both. Per A2A's discovery doc, the new path is canonical, but downstream clients that follow the older docs get bitten.

    Spec / framing context

    The A2A spec on discovery explicitly leaves the registry API undefined: "the A2A specification does not prescribe a standard API for curated registries." Solo's own write-up positions the gateway as the natural resolver layer here: "Agent discovery, naming and resolution — the missing pieces to A2A".

    Suggested follow-up (separate from the appProtocol fix this issue tracks)

    A sibling to kagent's existing :8083/mcp listing (which enumerates MCP tool servers) but for A2A agents would close the gap. The appProtocol fix in this issue + a listing endpoint would let downstream A2A clients drop the static app.a2a.agents list entirely.

    Happy to file as a separate kagent issue if preferred — keeping this one focused on the appProtocol writeback.

  2. added a commit that references this issue on May 14, 2026
    e03ee3a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions