Authoring a custom facade
Run your own image as an agent’s facade so the agent speaks a protocol Omnia does not ship, while the runtime sidecar, policy broker, sessions and exposure stay as they are. Your container terminates the client protocol, authenticates those clients, and sends each turn to the runtime sidecar over gRPC with the caller’s identity attached as metadata.
examples/custom-facade/ in the Omnia repository is a minimal conformant facade. Start from it.
Prerequisites
Section titled “Prerequisites”- A PromptPack, the Providers the agent uses, and an image the cluster can pull.
- An AgentRuntime in
mode: agent. The CRD rejects a custom facade inmode: function. - If the Enterprise webhook is installed: a licence that includes custom facades, or dev mode (Enterprise enabled, no licence), which admits everything.
Declare the facade
Section titled “Declare the facade”-
Add one
type: customentry tospec.facades[]withimageset. The CRD rejects a custom facade withoutimage.apiVersion: omnia.altairalabs.ai/v1alpha1kind: AgentRuntimemetadata:name: my-agentspec:mode: agentpromptPackRef:name: my-packfacades:- type: customimage: ghcr.io/acme/my-facade:v1.0.0port: 8080managementPlane: trueexpose:enabled: trueportdefaults to8080,managementPlanedefaults totrue, andexpose.enableddefaults tofalse. -
Make the custom facade the only facade. The API server rejects a
customentry beside any other: the operator takes the image, port,extraEnv,managementPlane,drainTimeoutandexposefrom the primary facade, and awebsocket,restora2aentry would outrankcustom.
drainTimeout sets the pod’s termination grace period (the drain timeout plus 30 seconds). The operator passes neither drainTimeout nor clientToolTimeout to your container. See the AgentRuntime reference for every facade field.
Read the injected contract
Section titled “Read the injected contract”Your container reads environment variables and binds ports. The diagram shows the ports in the agent pod.
flowchart LR Client -->|"your protocol :8080"| Facade Dashboard -->|"management JWT :18080"| Facade Kubelet -->|"/healthz /readyz :8081"| Facade subgraph Pod["Agent pod"] Facade["facade container (your image)"] Runtime["runtime container"] Facade -->|"gRPC localhost:9000"| Runtime end-
Read these environment variables.
Variable Value OMNIA_AGENT_NAMEAgentRuntime name, from the pod’s app.kubernetes.io/instancelabelOMNIA_NAMESPACEPod namespace OMNIA_FACADE_PORTExternal listen port ( facades[].port, default8080)OMNIA_HEALTH_PORT8081OMNIA_HANDLER_MODEfacades[].handler, defaultruntimeOMNIA_RUNTIME_ADDRESSlocalhost:9000. Set only when the handler mode isruntime.OMNIA_MGMT_PLANE_JWKS_URLDashboard JWKS URL. Set only when the dashboard is enabled. OMNIA_VARIANTstableorcandidatePOD_IPPod IP OMNIA_ROUTE_REDIS_URLRedis URL. Set only with a Redis context store. OMNIA_TRACING_ENABLED,_ENDPOINT,_INSECURESet only when tracing is enabled on the operator The operator appends
facades[].extraEnvto the environment. -
Bind these ports.
Port Purpose Required OMNIA_FACADE_PORTYour external protocol listener Yes 8081/healthzand/readyzYes 18080Management-plane twin Only when managementPlaneistrue
Call the runtime
Section titled “Call the runtime”Your facade is the gRPC client. Dial OMNIA_RUNTIME_ADDRESS and call omnia.runtime.v1.RuntimeService, defined in api/proto/runtime/v1/runtime.proto. In mode: agent use the streaming Converse RPC. Invoke serves mode: function.
- Open a
Conversestream and send aClientMessagewithsession_idand eithercontentorparts. - Handle each
ServerMessagevariant:chunk: a partial text fragment. Forward it to your client.tool_call: a client-side tool call whenexecutionisTOOL_EXECUTION_CLIENT. Forward it, collect the result, and send it back on the same stream as aClientToolResultin aClientMessage. Server-side tool calls never appear on the stream.done: the turn is complete, withfinal_content,partsandusage.error: a machine-readablecodeand amessage.media_chunk: raw binary media (no base64).interruption: a barge-in on duplex audio. Clear buffered audio.runtime_helloandheartbeat: the runtime’s capabilities and a keepalive. Do not forward either.
- For duplex audio, set
duplex_starton the firstClientMessageof the stream and send inbound frames asaudio_input.
The facade and runtime protocol reference lists every message and field.
Authenticate and propagate identity
Section titled “Authenticate and propagate identity”Omnia does not sit in front of your external port. Validate your protocol’s credential, then attach the caller’s identity to every Converse call as lowercase x-omnia-* gRPC metadata. ToolPolicy rules that read identity.* see empty values without it.
- Emit a key only when its value is non-empty. The keys are
x-omnia-agent-name,x-omnia-namespace,x-omnia-session-id,x-omnia-request-id,x-omnia-user-id,x-omnia-user-email,x-omnia-origin,x-omnia-workspaceand onex-omnia-claim-<name>per identity claim. The flat metadata keys table defines each one, includingx-omnia-subject-id. - In Go, build the metadata with
PropagationFieldsandToGRPCMetadatafrompkg/policy, as the example does. - Do not emit
x-omnia-providerorx-omnia-model. The runtime sets both and overwrites any value you send. - Do not forward the caller’s
Authorizationtoken to the runtime. The propagation map excludes it so that tool upstreams never receive the caller’s credential. Keep the raw token inside your process.
See Configure tool policies for writing rules against the identity you send.
Serve health checks
Section titled “Serve health checks”The operator probes your container on port 8081:
- Readiness:
GET /readyz - Liveness:
GET /healthz
- Serve both endpoints and return a
2xxstatus when healthy. Without them the pod never becomes Ready and never joins the Service endpoints. - Serve
/readyzonly once your runtime connection and external listener are up. - Serve
GET /prestopon the same port. The operator’s pre-stop hook calls it beforeSIGTERM. The built-in facade holds the request for 5 seconds so endpoint removal can propagate.
Serve the management-plane twin
Section titled “Serve the management-plane twin”Set managementPlane: false to skip this step. The operator then allocates no facade-mgmt Service port and no status.managementEndpoints entry, and the dashboard’s “Try this agent” view does not work against the agent.
With managementPlane: true, the operator adds a ClusterIP-only facade-mgmt Service port for 18080 and publishes it as status.managementEndpoints.ws. Your container must:
- Bind a second listener on port
18080. - Accept only RS256 JWTs management-api mints for the dashboard, verified against the keys at
OMNIA_MGMT_PLANE_JWKS_URL. Fetch keys on demand and refetch when a key ID is unknown. The built-in facade also requires theaudclaimomnia-facadeand rejects a token whoseagentclaim names another agent. - Reject every request without a valid token.
- Serve the WebSocket protocol at
/wsfor the dashboard’s debug view, which management-api relays as a WebSocket client.
expose.enabled: true creates a host-based HTTPRoute to the agent’s Service on the facade port when the platform has a default-exposure Gateway. Exposure adds no authentication.
Verify
Section titled “Verify”-
Apply the AgentRuntime and check its phase.
Terminal window kubectl get agentruntime my-agent -o jsonpath='{.status.phase}{"\n"}'The phase is
Running. -
Check the pod and the facade log.
Terminal window kubectl get pods -l app.kubernetes.io/instance=my-agentkubectl logs deploy/my-agent -c facadeEvery container in the pod is Ready.
-
With the management plane on, read the published port.
Terminal window kubectl get agentruntime my-agent -o jsonpath='{.status.managementEndpoints}{"\n"}'The output contains
"ws":18080. -
Send a request through your protocol and confirm the reply.
Troubleshooting
Section titled “Troubleshooting”The AgentRuntime is rejected at admission
Section titled “The AgentRuntime is rejected at admission”The Enterprise webhook found no licence entitlement. Install a licence, or run Enterprise without a licence for dev mode. See Install a licence.
The pod never becomes Ready
Section titled “The pod never becomes Ready”The container does not return 2xx on :8081/readyz. Read the pod events with kubectl describe pod.
Related pages
Section titled “Related pages”- Facade and runtime protocol: the gRPC messages and metadata contract.
- AgentRuntime reference:
facades[]fields. - Configure agent authentication: how the built-in facade authenticates.
- Configure tool policies: rules against the identity your facade sends.
- Authoring a custom runtime: the runtime counterpart.