Skip to content

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.

  • A PromptPack and a Provider that are Ready in the workspace namespace.
  • A way to authenticate callers: spec.externalAuth on 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.

ConcernAgent modeFunction mode
TransportWebSocket (/ws)HTTP POST (/functions/{name})
Conversation startHuman-initiatedSystem-initiated (HTTP POST)
Input validationNone at the boundaryRequired JSON Schema
Output validationNone at the boundaryRequired JSON Schema
Audit trailsessions (+ messages, tool_calls, provider_calls, eval_results, runtime_events, runtime_event_batches)The same tables: each invocation is a session tagged function
Browser UIConsole / dashboard chatThe dashboard Functions page (schemas, invocations, invoke form)
  1. Write the AgentRuntime. Set spec.mode: function, declare one rest facade, and give both schemas.

    apiVersion: omnia.altairalabs.ai/v1alpha1
    kind: AgentRuntime
    metadata:
    name: summarizer
    namespace: my-workspace
    spec:
    mode: function
    promptPackRef:
    name: summarizer-pack
    facades:
    - type: rest
    port: 8080
    providers:
    - name: default
    providerRef:
    name: claude-sonnet
    inputSchema:
    type: object
    required: ["text"]
    properties:
    text:
    type: string
    description: "Text to summarise"
    maxWords:
    type: integer
    minimum: 10
    maximum: 500
    outputSchema:
    type: object
    required: ["summary"]
    properties:
    summary:
    type: string

    The runtime constrains the model to spec.outputSchema by default. To change that, set spec.outputFormat. See the AgentRuntime reference for the outputFormat values and the CEL rules that tie mode, the schemas and the facades together.

  2. Apply it and wait for Ready.

    Terminal window
    kubectl apply -f summarizer.yaml
    kubectl wait --for=condition=Ready agentruntime/summarizer -n my-workspace --timeout=60s
  3. Admit callers. The external route runs the validators built from spec.externalAuth (client keys, OIDC, edge trust). With none configured it answers every request with 401. 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: rest
    port: 8080
    extraEnv:
    - name: OMNIA_FACADE_ALLOW_UNAUTHENTICATED
    value: "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.

  4. Invoke the function. {name} is the AgentRuntime’s metadata.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}'
  5. 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 lists application/json, the stream is chosen only when its q value 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:

    EventWhenData, besides session_id
    startedfirst, before the run startsnone
    tool_callthe model called a toolid, name
    tool_resultthat tool finishedid, name, ok, duration_ms
    outputlast, on successthe same fields as the JSON response
    errorlast, on failurereason (the JSON response’s error code: input_invalid, output_invalid, runtime_error, rate_limited, budget_exceeded or internal_error; or timeout), detail, and raw_output or retry_after_seconds where the JSON response has them

    The stream always ends with exactly one output or error event. The output is validated against spec.outputSchema exactly as it is for the JSON response. The model’s text is not streamed, only tool progress. A :ping comment 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:

    VariableDefaultEffect
    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 an error event whose reason is timeout.
    OMNIA_FUNCTION_STREAM_MAX_CONCURRENT32How many streams the facade serves at once. Over it, the request gets 429 rate_limited with Retry-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_id from the session API. A browser’s EventSource sends only GET, so read the stream with fetch and response.body.getReader() instead.

  6. 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 send POST /functions/{name}?mode=async. The facade answers 202 with an invocation_id. Read the result back with GET /functions/{name}/invocations/{invocation_id}.

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: started
data: {"session_id":"9c0e2c1f-..."}
event: output
data: {"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.

The facade validates the request against spec.inputSchema and the model output against spec.outputSchema. The error body is JSON with error and detail.

CodeMeaning
400input_invalid: the request body failed spec.inputSchema. The body includes the validator error.
401unauthorized (plain-text body): the listener’s validator chain rejected the request.
404function_not_found: no function-mode runtime matched {name} on this facade.
405method_not_allowed: only POST is allowed.
413payload_too_large: the request body exceeded 1 MiB.
415unsupported_media_type: Content-Type must be application/json.
429rate_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.
501async_not_enabled: you sent ?mode=async or read an invocation, and the agent has no async queue.
502runtime_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.
504sync_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.