Adding tools to agents
In this tutorial we give an agent a calculator tool. We deploy a small HTTP service, register it in a ToolRegistry, and attach the registry to an AgentRuntime.
What you will build
Section titled “What you will build”The agent calls the calculator service through a ToolRegistry handler. The runtime reads the handler from the ToolRegistry and makes the call, not the browser.
flowchart LR client[Dashboard console] --> facade[AgentRuntime facade] facade --> runtime[AgentRuntime runtime] runtime -->|reads handlers| registry[ToolRegistry agent-tools] runtime -->|POST /calculate| svc[Service calculator]The ToolRegistry supports six handler types: http, grpc, mcp, openapi, client and platform. We use http. The ToolRegistry reference lists them all.
Prerequisites
Section titled “Prerequisites”- A running Omnia install with an AgentRuntime named
my-assistant, a PromptPack namedassistant-packand a Provider namedmy-provider. The getting started tutorial creates them, together with the dashboard sign-in we use in step 5.
Step 1: create a tool service
Section titled “Step 1: create a tool service”The calculator is a short Python service that reads {"expression": "..."} from a POST /calculate body and replies with {"result": <number>}. We keep its code in a ConfigMap and run it on the public python:3.12-alpine image, together with a Service in front of it:
apiVersion: v1kind: ConfigMapmetadata: name: calculator-code namespace: defaultdata: server.py: | import ast import json import operator from http.server import BaseHTTPRequestHandler, HTTPServer
OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv}
def evaluate(node): if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): return node.value if isinstance(node, ast.BinOp) and type(node.op) in OPS: return OPS[type(node.op)](evaluate(node.left), evaluate(node.right)) raise ValueError("unsupported expression")
class Handler(BaseHTTPRequestHandler): def do_POST(self): if self.path != "/calculate": self.send_error(404) return body = json.loads(self.rfile.read(int(self.headers["Content-Length"]))) try: tree = ast.parse(body["expression"], mode="eval") out, code = {"result": evaluate(tree.body)}, 200 except Exception as err: out, code = {"error": str(err)}, 400 data = json.dumps(out).encode() self.send_response(code) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(data))) self.end_headers() self.wfile.write(data)
HTTPServer(("", 8080), Handler).serve_forever()---apiVersion: apps/v1kind: Deploymentmetadata: name: calculator-service namespace: defaultspec: replicas: 1 selector: matchLabels: app: calculator template: metadata: labels: app: calculator spec: containers: - name: calculator image: python:3.12-alpine command: ["python", "/app/server.py"] ports: - containerPort: 8080 volumeMounts: - name: code mountPath: /app volumes: - name: code configMap: name: calculator-code---apiVersion: v1kind: Servicemetadata: name: calculator namespace: defaultspec: selector: app: calculator ports: - port: 80 targetPort: 8080Save the manifests as calculator.yaml and apply them:
kubectl apply -f calculator.yamlThe output lists the objects:
configmap/calculator-code createddeployment.apps/calculator-service createdservice/calculator createdWait for the service to start:
kubectl rollout status deployment/calculator-serviceThe command ends with deployment "calculator-service" successfully rolled out.
Step 2: create a ToolRegistry
Section titled “Step 2: create a ToolRegistry”Create a ToolRegistry with an HTTP handler that points at the service:
apiVersion: omnia.altairalabs.ai/v1alpha1kind: ToolRegistrymetadata: name: agent-tools namespace: defaultspec: handlers: - name: calculator type: http httpConfig: endpoint: "http://calculator.default.svc.cluster.local:80/calculate" method: POST contentType: application/json tool: name: calculate description: "Perform mathematical calculations" inputSchema: type: object properties: expression: type: string description: "Mathematical expression to evaluate" required: [expression] timeout: "10s"Apply it:
kubectl apply -f toolregistry.yamlThe output confirms the object:
toolregistry.omnia.altairalabs.ai/agent-tools createdStep 3: check tool discovery
Section titled “Step 3: check tool discovery”Read the registry’s status:
kubectl get toolregistry agent-tools -o yamlThe status lists the tool and reports the registry as Ready:
status: phase: Ready discoveredToolsCount: 1 discoveredTools: - handlerName: calculator name: calculate status: Available endpoint: http://calculator.default.svc.cluster.local:80/calculate conditions: - type: HandlersValid status: "True" - type: ToolsDiscovered status: "True"The conditions list also holds Ready, Degraded and PolicyBound. We show only the two that matter here.
Step 4: connect the registry to your agent
Section titled “Step 4: connect the registry to your agent”We add toolRegistryRef to the AgentRuntime. The rest of the manifest stays as in the getting started tutorial, so a patch changes only that field:
kubectl patch agentruntime my-assistant --type merge \ -p '{"spec":{"toolRegistryRef":{"name":"agent-tools"}}}'The output shows the change:
agentruntime.omnia.altairalabs.ai/my-assistant patchedRead the field back:
kubectl get agentruntime my-assistant -o jsonpath='{.spec.toolRegistryRef.name}'The command prints agent-tools.
Step 5: declare the tool in the pack
Section titled “Step 5: declare the tool in the pack”The runtime offers the model only the tools that a prompt in the PromptPack lists in tools. The registry executes the tool, and the pack decides whether the model may call it. Until the pack lists calculate, the AgentRuntime reports PackContentValid as False and the model never sees the calculator.
We add "tools": ["calculate"] to the main prompt in the assistant-prompts ConfigMap from the getting started tutorial. Replace configmap.yaml with the following and apply it:
apiVersion: v1kind: ConfigMapmetadata: name: assistant-prompts namespace: defaultdata: pack.json: | { "$schema": "https://promptpack.org/schema/latest/promptpack.schema.json", "id": "assistant", "name": "Assistant", "version": "1.0.0", "template_engine": { "version": "v1", "syntax": "{{variable}}" }, "prompts": { "main": { "id": "main", "name": "Main Assistant", "version": "1.0.0", "system_template": "You are a helpful AI assistant. Be concise and accurate in your responses. Always be polite and professional.", "tools": ["calculate"], "parameters": { "temperature": 0.7, "max_tokens": 4096 } } } }kubectl apply -f configmap.yamlThe output shows the change:
configmap/assistant-prompts configuredThe PromptPack controller re-reads the ConfigMap and records a new content digest. The operator puts that digest in the agent pod’s environment, so the pod rolls. Wait for the new pod:
kubectl rollout status deployment/my-assistantThen check the pack condition:
kubectl get agentruntime my-assistant \ -o jsonpath='{.status.conditions[?(@.type=="PackContentValid")].status}'The command prints True.
Step 6: test tool invocation
Section titled “Step 6: test tool invocation”We chat through the dashboard console, as in the getting started tutorial. Port-forward the dashboard:
kubectl port-forward -n omnia-system svc/omnia-dashboard 3000:3000Browse to http://localhost:3000 and sign in as admin with the password tutorial-admin-pw. Open Agents, open my-assistant and select Console. Type a message that needs the calculator and press Enter:
What is 25 * 4?The agent replies with a sentence that contains the result, 100. The wording varies from run to run.
The console shows the reply and no tool_call or tool_result message, because the runtime executes http tools server-side and never forwards them over the WebSocket. Only client tools appear there. The runtime records the call in session-api, and the tool execution model shows how to read it back.
Next steps
Section titled “Next steps”- ToolRegistry reference: every handler type and field.
- Connect MCP servers and connect OpenAPI services: self-describing handlers, and how to combine handler types in one registry.
- Build a tool backend: the HTTP and gRPC contracts your own backend must implement.
- Test a tool before wiring it to an agent: exercise a handler from the dashboard.
- Authenticate tools: bearer and basic secrets, projected ServiceAccount tokens, and the secret-handling model.
- Advanced HTTP tools: URL templates, static injection, request and response mapping, redaction, and retry policies.
- Client-side tools: browser tools with user consent.
- Observability: monitor tool calls.