# Local development setup

Run Omnia on a local cluster so you can change the operator or dashboard and see the result.

## Prerequisites

- Go 1.27 or later ([download](https://go.dev/dl/))
- Docker ([install](https://docs.docker.com/get-docker/))
- kubectl ([install](https://kubernetes.io/docs/tasks/tools/))
- kind ([install](https://kind.sigs.k8s.io/docs/user/quick-start/#installation))
- Helm ([install](https://helm.sh/docs/intro/install/))
- Tilt ([install](https://docs.tilt.dev/install.html)), recommended
- Node.js 20 or later ([download](https://nodejs.org/)), for dashboard development

## Quick start with Tilt (recommended)

### 1. Create a local cluster

```bash
kind create cluster --name omnia-dev
```

The Tiltfile refuses any context except `kind-omnia-dev`, `kind-kind`, `docker-desktop`, `minikube` and `orbstack`.

### 2. Start Tilt

```bash
# Core features only
tilt up

# With enterprise features
ENABLE_ENTERPRISE=true tilt up
```

Tilt builds the operator, facade, runtime and dashboard images, installs the CRDs with `make install`, renders `charts/omnia` with `charts/omnia/values-dev.yaml`, and sets up port forwards.

Set these environment variables before `tilt up` to add components.

| Variable | Default | Adds |
|----------|---------|------|
| `ENABLE_ENTERPRISE` | `false` | The arena-controller, promptkit-lsp and policy-broker, with Enterprise in dev mode (no licence) |
| `ENABLE_ARENA_BUILDER` | `false` | The Arena builder module in the `omnia-demo` namespace, on a local build of `Dockerfile.promptarena-sandbox`. Needs `ENABLE_ENTERPRISE` and an existing Provider named by `ARENA_BUILDER_PROVIDER` (default `arena-builder-haiku`) |
| `ENABLE_DEMO` | `false` | The `demo` workspace, skills, and its managed session-api and memory-api. The Ollama-backed agents also need `ENABLE_OLLAMA` |
| `ENABLE_OLLAMA` | `false` | Ollama model servers and the four demos that use them (11Gi of memory requests). Needs `ENABLE_DEMO` |
| `ENABLE_OBSERVABILITY` | `false` | Prometheus, Grafana, Loki, Tempo and Alloy |
| `ENABLE_FULL_STACK` | `false` | Istio and the Gateway API |
| `ENABLE_AUDIO_DEMO`, `ENABLE_VOICE_DEMO` | `false` | The Gemini audio and Gemini Live voice agents. Each needs a `gemini-credentials` Secret in `omnia-demo` |
| `ENABLE_MEMORY_DEMO` | `false` | A seeded memory galaxy across all tiers. Needs the embedding Secret that `make dev-embedding-secret` creates |
| `ENABLE_LANGCHAIN` | `false` | The LangChain runtime demo agents |

Redis and the NFS-backed workspace storage are always on under Tilt.

The demo toggles set `*.enabled` values of the `omnia-dev-content` chart, whose [README](https://github.com/AltairaLabs/Omnia/blob/main/charts/omnia-dev-content/README.md) lists what each deploys. The public demos (mortgage underwriting, claims triage, PII chat, the Larkmere portal, SharePoint/RAG) come from the `charts/omnia-demo` chart in [AltairaLabs/omnia-demo](https://github.com/AltairaLabs/omnia-demo).

### 3. Access the services

| Service | URL | Present when |
|---------|-----|--------------|
| Dashboard | http://localhost:3000 | Always |
| Operator API | http://localhost:8082 | Always |
| pgweb | http://localhost:8081 | Always |
| VS Code Server | http://localhost:8888 | Always |
| Grafana | http://localhost:3001 | `ENABLE_OBSERVABILITY` |

### 4. Develop with hot reload

Edit files in `dashboard/src/`. The change syncs into the pod and Next.js reloads it, with no image rebuild. Set `DASHBOARD_PROD=1` to build the production dashboard image instead, which has no hot reload and enables the Monaco LSP editor.

Tilt rebuilds the operator image when Go source changes.

### 5. View logs and stop

Press `s` in the Tilt UI to stream logs, or open http://localhost:10350. Run `tilt down` to stop.

## Manual setup (without Tilt)

### Create a local cluster

```bash
kind create cluster --name omnia-dev
```

### Build and load images

```bash
make docker-build IMG=omnia-operator:dev
docker build -t omnia-dashboard:dev ./dashboard

kind load docker-image omnia-operator:dev --name omnia-dev
kind load docker-image omnia-dashboard:dev --name omnia-dev
```

### Install with Helm

Install the CRDs first, as their own release. The app chart fails to render without them, and `helm upgrade` of the app chart never updates them.

```bash
helm install omnia-crds charts/omnia-crds -n omnia-system --create-namespace
```

This installs every kind, including the Enterprise ones. Pass `--set enterprise.enabled=false` to leave them out.

```bash
helm install omnia charts/omnia -n omnia-system \
  --set image.repository=omnia-operator \
  --set image.tag=dev \
  --set image.pullPolicy=Never \
  --set dashboard.enabled=true \
  --set dashboard.image.repository=omnia-dashboard \
  --set dashboard.image.tag=dev \
  --set dashboard.image.pullPolicy=Never
```

Reach the dashboard with a port-forward:

```bash
kubectl port-forward -n omnia-system deployment/omnia-dashboard 3000:3000
```

## Using Ollama for local LLM development

Ollama gives agents a local model with no API costs. Start Tilt with it:

```bash
make dev-ollama
```

This runs `ENABLE_DEMO=true ENABLE_OLLAMA=true tilt up`. It deploys two Ollama instances into `omnia-demo`: `ollama-chat` (`llama3.2:3b`) and `ollama-vision` (`llava:7b`). Each pulls its model on first start, which takes several minutes.

| Instance | Memory request | Volume |
|----------|----------------|--------|
| `ollama-chat` | 4Gi | 8Gi |
| `ollama-vision` | 7Gi | 12Gi |

In the cluster the servers answer at `http://ollama-chat.omnia-demo:11434` and `http://ollama-vision.omnia-demo:11434`. On the host, `localhost:11434` is chat and `localhost:11435` is vision.

The `vision-demo` agent in the `demo` workspace uses the vision instance and needs no API key. Open it from the dashboard to chat or send an image.

## Deploy Redis (optional)

Tilt runs Redis for you. For a manual install, the chart runs none, so deploy one and point the chart at it:

```bash
kubectl create namespace redis
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install redis bitnami/redis -n redis \
  --set auth.enabled=false \
  --set architecture=standalone

helm upgrade omnia charts/omnia -n omnia-system --reuse-values \
  --set redis.default.host=redis-master.redis.svc.cluster.local
```

## Encrypt without a KMS (development only)

Browser sessions (a `browserSession` Connection) and an encrypting SessionPrivacyPolicy need a KMS key. On a laptop or in a test cluster, the `local` provider stands in for one: a 32-byte AES key in a Kubernetes Secret.

:::danger[Never in production]
`local` is not a KMS. Anyone who can read the Secret can decrypt every record sealed under it, the key cannot be rotated, and nothing audits its use. The provider refuses to start unless the Secret sets `acknowledge-insecure-dev-only: "true"`. Services log an error each time they build it, the admission webhooks warn on every object that names it, and the doctor fails the encryption check of any service group that uses it.
:::

Create the Secret in the namespace of the object that encrypts:

```bash
hack/dev-kms-secret.sh <kube-context> agents dev-kms
```

Then name it:

```yaml
encryption:
  kmsProvider: local
  keyID: dev
  secretRef:
    name: dev-kms
```

The script leaves an existing Secret alone, because a new key would make everything sealed under the old one unreadable.

## Deploy test resources

Apply the sample manifests:

```bash
kubectl apply -k config/samples/
```

The samples include Enterprise kinds, so install the Enterprise CRDs first.

## Using demo mode for testing

To exercise an agent without LLM API costs, set the facade `handler` to `demo` or `echo`:

```yaml
apiVersion: omnia.altairalabs.ai/v1alpha1
kind: AgentRuntime
metadata:
  name: test-agent
spec:
  promptPackRef:
    name: test-prompts
  facades:
    - type: websocket
      handler: demo  # echo returns each input message unchanged
  context:
    type: memory
```

The `demo` handler streams canned responses and sends simulated `tool_call` and `tool_result` messages. Neither handler needs an API key.

## Verify

Check that the operator and dashboard pods are running:

```bash
kubectl get pods -n omnia-system
```

Then open the dashboard at http://localhost:3000.

## Troubleshooting

### Tilt not detecting file changes

Check the Tilt UI for sync status.

### Dashboard not hot-reloading

The dashboard runs `npm run dev`, which uses Next.js Fast Refresh. Check the browser console for errors. A `DASHBOARD_PROD=1` build has no hot reload.

### Operator not starting

```bash
kubectl logs -n omnia-system deployment/omnia-controller-manager
```

### Image not found / ImagePullBackOff

Set `image.pullPolicy=Never` and load the image into kind:

```bash
kind load docker-image <image>:<tag> --name omnia-dev
```

### WebSocket connection refused

Check that the agent Service has endpoints. The Service is named after the AgentRuntime:

```bash
kubectl get endpoints <agent-name> -n <agent-namespace>
```

## Related pages

- [Install with Helm](/how-to/operations/install-helm/)
- [Helm values reference](/reference/platform/helm-values/)
- [Install a licence](/how-to/operations/install-license/)
- [AgentRuntime reference](/reference/core/agentruntime/)
- [Architecture](/explanation/platform/architecture/)