Authoring a custom runtime
Run your own container as an agent’s runtime by serving omnia.runtime.v1.RuntimeService and declaring the image on the AgentRuntime. The facade sidecar dials your container for every turn, and the policy broker, sessions and exposure stay as they are. A custom runtime needs no licence entitlement. A custom facade gets an admission warning when the Enterprise webhook is on and the licence lacks the custom-facade entitlement, and is admitted.
A minimal conformant runtime lives at examples/custom-runtime/av-preprocessor/ in the Omnia repository. examples/custom-runtime/ also shows how to run a runtime offline against a directory of manifests.
Prerequisites
Section titled “Prerequisites”- A container image in a registry the cluster can pull from.
- A gRPC server for
omnia.runtime.v1.RuntimeService, in any language. Generate stubs fromapi/proto/runtime/v1/runtime.protoat a pinned git ref and do not hand-copy the file. - A checkout of the Omnia repository, to build the conformance CLI in step 5.
Step 1: Declare the custom runtime
Section titled “Step 1: Declare the custom runtime”Set spec.framework.type: custom and spec.framework.image. Only promptkit has a built-in image. A custom or langchain AgentRuntime with no image blocks with FrameworkImageUnavailable.
apiVersion: omnia.altairalabs.ai/v1alpha1kind: AgentRuntimemetadata: name: my-agentspec: promptPackRef: name: my-pack framework: type: custom image: ghcr.io/acme/my-runtime:v1.0.0To set the image for every AgentRuntime of a type, pass the repeatable operator flag --framework-image=custom=ghcr.io/acme/my-runtime:v1.0.0. spec.framework.image takes precedence over the flag.
Step 2: Read the environment and open the ports
Section titled “Step 2: Read the environment and open the ports”The operator injects the runtime container’s wiring. Read these variables instead of the AgentRuntime CRD.
| Variable | Value |
|---|---|
OMNIA_AGENT_NAME | AgentRuntime name (from a pod label) |
OMNIA_NAMESPACE | Pod namespace |
OMNIA_GRPC_PORT | 9000. Serve RuntimeService here. |
OMNIA_HEALTH_PORT | 9001. Serve HTTP GET /healthz here. |
OMNIA_PROMPTPACK_PATH | The PromptPack file, /etc/omnia/pack/pack.json. Unset for a live pack. |
The readiness and liveness probes issue GET /healthz against the health port. A runtime that does not serve it never becomes Ready.
The facade dials localhost:9000. To run a runtime outside a cluster, set OMNIA_CONFIG_DIR to a directory of YAML manifests. Omnia’s Kubernetes client then serves the CRDs from those files instead of an API server. examples/custom-runtime/devroot/ is a working example.
Step 3: Serve the contract
Section titled “Step 3: Serve the contract”Implement the four RuntimeService methods below. Two more are optional, and the facade tolerates Unimplemented from either:
EndConversation: the facade calls it when a WebSocket conversation ends. Implement it only if you hold per-conversation resources worth releasing.WatchInvocation(contract 1.8.0): a stream of one function invocation’s tool calls and tool results, which the facade relays to a caller that asked for an event stream. Without it, that caller gets the outcome with no progress. If you implement it, sendreadyfirst, end the stream once the invocation has finished, and serve it to loopback callers only.
| Method | Behaviour |
|---|---|
Health | Return healthy, the contract_version you built against (semver), and your capabilities. |
Converse | A bidirectional stream of ClientMessage and ServerMessage. ClientMessage is not a oneof, so several fields can be set at once: handle each one you receive and do not drop a part silently. Your first ServerMessage is a RuntimeHello. |
Invoke | One-shot function mode (spec.mode: function). If you only serve spec.mode: agent, return Unimplemented and do not advertise invoke. |
HasConversation | Return RESUMABLE, NOT_FOUND or UNAVAILABLE for a session’s working context. |
Read caller identity from the flat x-omnia-* gRPC metadata. The raw bearer token is withheld. Do not forward the caller’s credentials to third-party tool upstreams. See identity and claims metadata for the keys.
If you write Go
Section titled “If you write Go”Implement Handler from github.com/altairalabs/omnia/pkg/runtime and call runtime.Serve. The SDK sends the RuntimeHello first, answers Health, runs client-tool round-trips and registers the standard gRPC health service. If Converse returns without calling Done, the SDK sends an empty one.
import ( "context" "net"
rt "github.com/altairalabs/omnia/pkg/runtime" "github.com/altairalabs/omnia/pkg/runtime/contract")
type myRuntime struct{}
func (myRuntime) Capabilities() []string { return []string{contract.CapabilityClientTools} }
func (myRuntime) Converse(ctx context.Context, turn rt.Turn, emit rt.Emitter) error { // Call your model or framework and stream output. if err := emit.Chunk("hello"); err != nil { return err } return emit.Done(rt.Done{Final: "hello"})}
func main() { lis, _ := net.Listen("tcp", ":9000") _ = rt.Serve(lis, myRuntime{})}To serve Invoke, implement the optional Invoker interface and list contract.CapabilityInvoke in Capabilities(). To answer resume probes, implement ConversationProber. A handler without it reports every session as UNAVAILABLE. The SDK refuses duplex sessions, so do not advertise duplex_audio from it.
To wrap Omnia’s own PromptKit runtime, import github.com/altairalabs/omnia/pkg/runtime/promptkit.
Step 4: Advertise capabilities
Section titled “Step 4: Advertise capabilities”List the optional surfaces you implement in HealthResponse.capabilities, and send the same list in the RuntimeHello. The capabilities table lists the known names. The set is open, so you can advertise a name outside it.
Advertise only what works. The conformance suite fails a runtime that advertises invoke or duplex_audio and then returns Unimplemented. The operator also requires duplex_audio and interruption for an AgentRuntime with duplex enabled, and invoke for one with a rest or mcp facade. If the runtime advertises less, the operator scales the agent to zero.
For a duplex session, the RuntimeHello can carry a MediaNegotiation counter-offer. The facade relays an audio counter-offer to the client as a session_config message and fails the session closed on a video one. See per-session negotiation.
Step 5: Run the conformance suite
Section titled “Step 5: Run the conformance suite”Build the CLI from the repository root and point it at your running runtime.
go build -o runtime-conformance ./cmd/runtime-conformance./runtime-conformance --addr localhost:9000The suite checks, over gRPC only:
Healthis healthy andcontract_versionis semver.- The first
Converseframe is aRuntimeHellowhose capabilities matchHealth. - A text turn ends with
done, and nodonearrives before the hello. - An empty
ClientMessagegets an on-protocol answer, not an internal stream error. - Capability honesty: with
invokeadvertised,Invokeworks. Without it,InvokereturnsUnimplemented. Withduplex_audioadvertised, a duplex session emits a frame. Without it, the duplex check is skipped.
The CLI prints a per-check table. It exits 0 when conformant, 1 when any check fails and 2 when it cannot dial the address.
Verify
Section titled “Verify”After you apply the AgentRuntime, confirm the operator accepted the runtime’s capabilities.
kubectl get agentruntime my-agent -o jsonpath='{.status.runtimeCapabilities}'kubectl get agentruntime my-agent \ -o jsonpath='{.status.conditions[?(@.type=="CapabilitiesSatisfied")].status}'The first command prints the capability list your runtime reported. The second prints True.
Troubleshooting
Section titled “Troubleshooting”The AgentRuntime reports FrameworkImageUnavailable
Section titled “The AgentRuntime reports FrameworkImageUnavailable”The custom type has no built-in image. Set spec.framework.image, or start the operator with --framework-image=custom=<image>.
The runtime pod never becomes Ready
Section titled “The runtime pod never becomes Ready”The probes call GET /healthz on OMNIA_HEALTH_PORT. Serve that route, in addition to the gRPC port.
CapabilitiesSatisfied is False with reason CapabilitiesMissing
Section titled “CapabilitiesSatisfied is False with reason CapabilitiesMissing”The runtime advertises less than the spec requires. Add the missing capability to your runtime or change the spec. The condition message names the missing and advertised capabilities.
Related pages
Section titled “Related pages”- Facade to runtime protocol: the message surface, capability table and identity metadata.
examples/custom-runtime/av-preprocessor/: a minimal conformant runtime.- Authoring a custom facade: the facade counterpart.