Define functions
Deploy a PromptPack as a callable HTTP endpoint with a validated request body and a validated response, for jobs such as summarising a document or extracting structured data from free text.
Prerequisites
Section titled “Prerequisites”- A PromptPack and a Provider that are
Readyin the workspace namespace. - A way to authenticate callers:
spec.externalAuthon the AgentRuntime, or the development flag in step 3.
A function-mode AgentRuntime differs from an agent-mode one as follows. If you want a WebSocket chat agent, leave spec.mode at its default of agent.
| Concern | Agent mode | Function mode |
|---|---|---|
| Transport | WebSocket (/ws) | HTTP POST (/functions/{name}) |
| Conversation start | Human-initiated | System-initiated (HTTP POST) |
| Input validation | None at the boundary | Required JSON Schema |
| Output validation | None at the boundary | Required JSON Schema |
| Audit trail | sessions (+ messages, tool_calls, provider_calls, eval_results, runtime_events, runtime_event_batches) | The same tables: each invocation is a session tagged function |
| Browser UI | Console / dashboard chat | The dashboard Functions page (schemas, invocations, invoke form) |
-
Write the AgentRuntime. Set
spec.mode: function, declare onerestfacade, and give both schemas.apiVersion: omnia.altairalabs.ai/v1alpha1kind: AgentRuntimemetadata:name: summarizernamespace: my-workspacespec:mode: functionpromptPackRef:name: summarizer-packfacades:- type: restport: 8080providers:- name: defaultproviderRef:name: claude-sonnetinputSchema:type: objectrequired: ["text"]properties:text:type: stringdescription: "Text to summarise"maxWords:type: integerminimum: 10maximum: 500outputSchema:type: objectrequired: ["summary"]properties:summary:type: stringThe runtime constrains the model to
spec.outputSchemaby default. To change that, setspec.outputFormat. See the AgentRuntime reference for theoutputFormatvalues and the CEL rules that tiemode, the schemas and the facades together. -
Apply it and wait for
Ready.Terminal window kubectl apply -f summarizer.yamlkubectl wait --for=condition=Ready agentruntime/summarizer -n my-workspace --timeout=60s -
Admit callers. The external route runs the validators built from
spec.externalAuth(client keys, OIDC, edge trust). With none configured it answers every request with401. See Configure authentication to set one up.For a development or CI cluster only, admit unauthenticated requests by setting the flag on the facade container:
spec:facades:- type: restport: 8080extraEnv:- name: OMNIA_FACADE_ALLOW_UNAUTHENTICATEDvalue: "true"Never set this flag in production. Management-plane JWTs (the dashboard) and ScheduledFunction dispatch are accepted only on the internal management-plane listener, not on this route.
-
Invoke the function.
{name}is the AgentRuntime’smetadata.name, and a pod serves only that one function.Terminal window kubectl port-forward -n my-workspace svc/summarizer 8080:8080 &curl -X POST http://localhost:8080/functions/summarizer \-H "Content-Type: application/json" \-d '{"text": "Omnia is a Kubernetes operator for AI agent deployments.", "maxWords": 20}' -
To watch a long invocation as it runs, ask for an event stream. Send the same request with
Accept: text/event-stream. If the header also listsapplication/json, the stream is chosen only when itsqvalue is at least as high:Terminal window curl -N -X POST http://localhost:8080/functions/summarizer \-H "Content-Type: application/json" \-H "Accept: text/event-stream" \-d '{"text": "Omnia is a Kubernetes operator for AI agent deployments.", "maxWords": 20}'The response is Server-Sent Events. Each event’s data is a JSON object that carries the invocation’s
session_id:Event When Data, besides session_idstartedfirst, before the run starts none tool_callthe model called a tool id,nametool_resultthat tool finished id,name,ok,duration_msoutputlast, on success the same fields as the JSON response errorlast, on failure reason(the JSON response’s error code:input_invalid,output_invalid,runtime_error,rate_limited,budget_exceededorinternal_error; ortimeout),detail, andraw_outputorretry_after_secondswhere the JSON response has themThe stream always ends with exactly one
outputorerrorevent. The output is validated againstspec.outputSchemaexactly as it is for the JSON response. The model’s text is not streamed, only tool progress. A:pingcomment line every 15 seconds keeps idle proxies from closing a quiet connection.The 30-second synchronous limit does not apply to a stream. Two bounds apply instead, set in the primary facade’s
extraEnv:Variable Default Effect OMNIA_FUNCTION_STREAM_MAX_DURATION270sThe longest a stream may run, at most 290s. At the limit the run is cancelled and the stream ends with anerrorevent whosereasonistimeout.OMNIA_FUNCTION_STREAM_MAX_CONCURRENT32How many streams the facade serves at once. Over it, the request gets 429 rate_limitedwithRetry-After: 5.A value that does not parse, or is out of range, stops the facade from starting.
If the client disconnects, the run is cancelled. The session is still closed with its final status, so read the outcome back by
session_idfrom the session API. A browser’sEventSourcesends onlyGET, so read the stream withfetchandresponse.body.getReader()instead. -
To run work longer than 30 seconds without holding a connection open, invoke asynchronously. Set
spec.facades[].async.enabled: true(it needs a Redis queue backend), then sendPOST /functions/{name}?mode=async. The facade answers202with aninvocation_id. Read the result back withGET /functions/{name}/invocations/{invocation_id}.
Verify
Section titled “Verify”A successful call returns 200 with the model output and metadata. The values below are illustrative:
{ "output": { "summary": "Omnia is a K8s operator for managing AI agents." }, "invocation_id": "9c0e2c1f-...", "duration_ms": 842, "usage": { "input_tokens": 41, "output_tokens": 12, "cost_usd": 0.0008 }}An event-stream call returns 200 with Content-Type: text/event-stream. Its last event is output on success:
event: starteddata: {"session_id":"9c0e2c1f-..."}
event: outputdata: {"duration_ms":842,"invocation_id":"9c0e2c1f-...","output":{"summary":"..."},"session_id":"9c0e2c1f-..."}The invocation is recorded as a session tagged function. The dashboard Functions page lists the function and its invocations. See Sessions for how function sessions are recorded.
Troubleshooting
Section titled “Troubleshooting”The facade validates the request against spec.inputSchema and the model output against spec.outputSchema. The error body is JSON with error and detail.
| Code | Meaning |
|---|---|
| 400 | input_invalid: the request body failed spec.inputSchema. The body includes the validator error. |
| 401 | unauthorized (plain-text body): the listener’s validator chain rejected the request. |
| 404 | function_not_found: no function-mode runtime matched {name} on this facade. |
| 405 | method_not_allowed: only POST is allowed. |
| 413 | payload_too_large: the request body exceeded 1 MiB. |
| 415 | unsupported_media_type: Content-Type must be application/json. |
| 429 | rate_limited: the runtime call returned ResourceExhausted, which is how the agent’s spec.rateLimit refuses a call. Retry-After carries the wait in whole seconds when the limiter knows it. Also budget_exceeded, with no Retry-After: the workspace is over its budget and its budgetExceededAction is block. A call with ?mode=async is refused the same way when it is submitted; one already accepted runs. |
| 501 | async_not_enabled: you sent ?mode=async or read an invocation, and the agent has no async queue. |
| 502 | runtime_error (the runtime sidecar failed) or output_invalid (the model output failed spec.outputSchema). output_invalid includes raw_output so you can debug the pack. |
| 504 | sync_timeout: the synchronous call ran past its 30-second limit and was cancelled. Retry with ?mode=async. |
An event-stream call answers 200 once the stream starts, whatever the outcome. The failure is the terminal error event, whose reason is the code in this table. Only a request refused before the stream starts (401, 404, 413, 415, and 429 when the facade is already serving its limit of streams) gets a status code. A ?mode=async request is queued even when it sends Accept: text/event-stream.
To tighten the contract, make spec.outputSchema strict: a loose schema lets model drift reach callers, and a tight one surfaces it as 502 output_invalid.
Editing spec.inputSchema or spec.outputSchema rolls the Deployment. You do not need to restart the pod.
examples/echo-function/ in the repository holds a PromptPack, a Provider and an AgentRuntime to apply as a starting point.