# Expose agents

Route traffic from outside the cluster to an agent's facade through a Gateway API Gateway.

## Prerequisites

- A Kubernetes cluster with the Gateway API CRDs and a Gateway controller.
- An AgentRuntime with `spec.externalAuth` set. Without it the external listener has no validators and rejects every request with a 401. See [Configure authentication](/how-to/security/configure-authentication/).

Install the Gateway API CRDs:

```bash
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/standard-install.yaml
```

If you have no controller, Istio is one option:

```bash
helm repo add istio https://istio-release.storage.googleapis.com/charts
helm repo update

helm install istio-base istio/base -n istio-system --create-namespace
helm install istiod istio/istiod -n istio-system --wait
```

## Enable the gateway

Set these Helm values. `className` must match your controller (`istio`, `envoy`, and so on):

```yaml
gateway:
  enabled: true
  name: agents
  className: istio
```

The chart creates a Gateway named `<fullname>-agents` in the release namespace. For a release named `omnia` in `omnia-system` that is `omnia-agents`. For all `gateway` and `internalGateway` values, see [Helm values](/reference/platform/helm-values/#gateway-api).

## Route to the agent

Choose one.

### Let the operator create the route

Set `defaultExposure` in the Helm values. The operator then creates a host-based HTTPRoute for each agent that opts in:

```yaml
defaultExposure:
  enabled: true
  baseDomain: agents.example.com
  gateway:
    name: omnia-agents
    namespace: omnia-system
```

Opt each facade in on its own entry, or use the **Expose externally** switch on the agent's **Connectivity** tab in the dashboard, which sets it on the primary facade:

```yaml
spec:
  facades:
    - type: websocket
      expose:
        enabled: true
    - type: a2a
      expose:
        enabled: true
```

The operator creates one HTTPRoute per exposed facade, each to that facade's own Service port. The hostname is `<agent>.<namespace>.<baseDomain>` for the primary facade and `<agent>-<type>.<namespace>.<baseDomain>` for a secondary one, so the agent above is reachable at `wss://<agent>.<namespace>.<baseDomain>/ws` and `https://<agent>-a2a.<namespace>.<baseDomain>/a2a`. Set `expose.host` on a facade to override its hostname; do not give two facades the same host, because every route matches the root path and the Gateway serves only the older one. The Gateway listener's certificate must cover each hostname. See [`facades[].expose`](/reference/core/agentruntime/#facadesexpose).

### Write the route yourself

The backend is the agent's Service, which has the agent's name, on the facade port (8080 by default). Host-based routing needs no rewrite:

```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: my-agent
  namespace: default
spec:
  parentRefs:
    - name: omnia-agents
      namespace: omnia-system
  hostnames:
    - "my-agent.agents.example.com"
  rules:
    - backendRefs:
        - name: my-agent
          port: 8080
```

To share one hostname between agents, match a path prefix and strip it. The facade serves `/ws`, so without the rewrite it receives `/my-agent/ws` and rejects the connection:

```yaml
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /my-agent
      filters:
        - type: URLRewrite
          urlRewrite:
            path:
              type: ReplacePrefixMatch
              replacePrefixMatch: /
      backendRefs:
        - name: my-agent
          port: 8080
```

## Enable HTTPS

Create the TLS Secret in the Gateway's namespace:

```bash
kubectl create secret tls agents-tls \
  --cert=path/to/cert.pem \
  --key=path/to/key.pem \
  -n omnia-system
```

Enable the HTTPS listener:

```yaml
gateway:
  listeners:
    https:
      enabled: true
      tlsSecretName: agents-tls
```

In a hand-written route, add `sectionName: https` to the `parentRefs` entry.

## Verify

Print the endpoints the operator discovered from the route:

```bash
kubectl get agentruntime my-agent \
  -o jsonpath='{.status.facade.endpoints}'
```

The same list appears on the agent's **Connectivity** tab in the dashboard. Each entry has a `url` and a `valid` flag.

Get the Gateway address and point the hostname at it in DNS:

```bash
kubectl get gateway omnia-agents -n omnia-system \
  -o jsonpath='{.status.addresses[0].value}'
```

Connect with a credential that `spec.externalAuth` accepts. `<url>` is the `url` from the endpoint list:

```bash
websocat -H "Authorization: Bearer $TOKEN" <url>
```

## Troubleshooting

An endpoint with `valid: false` is advertised but will not connect. A path-prefix route without the `ReplacePrefixMatch` rewrite above gives the reason `path prefix not stripped before facade`. Add the rewrite or switch to host-based routing.

A 401 means the request reached the facade but no `spec.externalAuth` validator admitted it. Check that the agent sets `externalAuth` and that the client sends a matching credential.

No endpoints listed: the route's backend `name` must equal the AgentRuntime name and its `port` must be a facade port, and the parent Gateway must resolve.

Check that the Gateway and route are accepted:

```bash
kubectl describe gateway omnia-agents -n omnia-system
kubectl describe httproute my-agent
```

## Related pages

- [Configure authentication](/how-to/security/configure-authentication/)
- [Authentication](/explanation/security/authentication/)
- [Helm values](/reference/platform/helm-values/#gateway-api)
- [AgentRuntime reference](/reference/core/agentruntime/)