# Configure workspace service groups

Give a workspace a service group (the session-api and memory-api that its agents use to persist conversations and cross-session memory) and point agents at it.

An AgentRuntime selects a group by name with `spec.serviceGroup` (default `default`). The group's endpoints come from the workspace's `status.services[]`.

:::caution[Agents need a service group to persist anything]
Omnia has no cluster-wide default session or memory endpoint. If an agent's `serviceGroup` is not in the workspace's `status.services[]`, the agent still starts but has no session store: conversations, token usage and cost are not recorded, and memory is unavailable.
:::

## Prerequisites

- A [workspace](/how-to/workspaces/manage-workspaces/). Managed services deploy into the workspace namespace.
- A reachable PostgreSQL instance with one database for session data and one for memory. Both can live on the same server.

## Provision a managed service group

In `managed` mode (the default) the operator deploys session-api and memory-api for you.

1. Create the database Secrets in the workspace namespace. Each Secret has one key, `POSTGRES_CONN`, holding a PostgreSQL connection string.

   ```bash
   kubectl create secret generic customer-support-session-db \
     -n omnia-customer-support \
     --from-literal=POSTGRES_CONN='postgres://USER:PASSWORD@HOST:5432/sessions?sslmode=require'

   kubectl create secret generic customer-support-memory-db \
     -n omnia-customer-support \
     --from-literal=POSTGRES_CONN='postgres://USER:PASSWORD@HOST:5432/memory?sslmode=require'
   ```

   In production, create these Secrets from your secret manager (External Secrets, CSI driver) instead of from the command line.

2. Add the group to the `spec.services` list of your existing workspace:

   ```bash
   kubectl edit workspace customer-support
   ```

   ```yaml
   spec:
     services:
       - name: default          # AgentRuntimes reference this in spec.serviceGroup
         mode: managed
         session:
           database:
             secretRef:
               name: customer-support-session-db
         memory:
           database:
             secretRef:
               name: customer-support-memory-db
           providerRef:
             name: memory-embedding   # an embedding-role Provider in the namespace
   ```

   A managed group must configure at least one of `session` and `memory`. The operator provisions only the services you configure. Memory requires an embedding Provider: a `memory` block without `providerRef` provisions no memory-api (see [troubleshooting](#the-group-is-not-ready-because-of-a-provider)).

:::caution
Edit the workspace that owns the namespace. A second Workspace that names the same namespace is refused with a `NamespaceConflict` condition.
:::

For each managed group, the operator creates in the workspace namespace:

- a session-api Deployment and Service named `session-<workspace>-<group>`, on port 8080;
- a memory-api Deployment and Service named `memory-<workspace>-<group>`, on port 8080;
- the ServiceAccounts and RoleBindings those pods need, including access to the database Secret.

## Point agents at the group

Set `spec.serviceGroup` on the AgentRuntime to a `spec.services[].name`. Omit it to use `default`.

```yaml
apiVersion: omnia.altairalabs.ai/v1alpha1
kind: AgentRuntime
metadata:
  name: support-bot
  namespace: omnia-customer-support
spec:
  serviceGroup: default
  # ...
```

## Use external services

Set `mode: external` to point a group at a session-api and memory-api that you run elsewhere. The operator creates no Deployments and publishes the URLs you supply:

```yaml
spec:
  services:
    - name: default
      mode: external
      external:
        sessionURL: https://session.internal.example.com
        memoryURL: https://memory.internal.example.com
```

`memoryURL` is required. `sessionURL` is optional. Both must start with `http://` or `https://`. The operator does not health-check the endpoints, so an external group is `ready` as soon as you apply it.

### Key the external services

Each workspace pseudonymises users under its own key, which the operator derives from the install key and writes to the `omnia-workspace-pseudonym-key` Secret (data `key`) in the workspace namespace. An external session-api or memory-api serves exactly one workspace:

- Run it with that workspace's key as `OMNIA_PSEUDONYM_HMAC_KEY`. Agents, the operator and management-api state that workspace's key fingerprint on every call. A service bound to another key refuses those calls with `409 pseudonym_key_mismatch`, so you cannot point a Workspace at another workspace's session-api or memory-api.
- One external session-api cannot serve several workspaces. Run one per workspace. Several of them, and the operator's managed ones, may share a database: each binds its own namespace.
- Set `OMNIA_NAMESPACE` on an external session-api to the workspace's namespace. It binds its key under that namespace. Without it, the session-api binds under the namespace its pod runs in, or under `default` outside a cluster. Two session-apis that bind under the same namespace on one database collide: the second is refused at startup.
- An external memory-api binds its key under the Workspace UID it resolves from `--workspace`. Every workspace on one memory database must use the same embedding model, which the database records once.

### Run a group without session-api

Omit `sessionURL` to run the group with no session-api:

```yaml
spec:
  services:
    - name: default
      mode: external
      external:
        memoryURL: https://memory.internal.example.com
```

Agents in the group start normally and write no sessions, messages, token usage or cost. The facade does not contact a session-api. The dashboard views that read session data (sessions, cost, provider calls, eval results) report "Session API not configured" for the workspace.

## Add the per-workspace privacy service (Enterprise)

Set `spec.privacy` to deploy a per-workspace privacy-api (consent, opt-out, the compliance audit hub and DSAR erasure). It needs its own database Secret with a `POSTGRES_CONN` key.

```yaml
spec:
  privacy:
    database:
      secretRef:
        name: customer-support-privacy-db
```

privacy-api serves its routes only when Enterprise is enabled on the install. Without a licence it runs in dev mode with every feature on. See [Configure privacy policies](/how-to/privacy/configure-privacy-policies/) and [Manage user consent](/how-to/privacy/manage-user-consent/).

## Verify

1. Check that the Deployments have a ready replica:

   ```bash
   kubectl get deploy -n omnia-customer-support
   ```

   You see `session-customer-support-default` and `memory-customer-support-default`.

2. Read the group status:

   ```bash
   kubectl get workspace customer-support -o jsonpath='{.status.services}'
   ```

   The `default` entry has `ready: true`. Its `sessionURL` and `memoryURL` have the form `http://<deployment>.<namespace>:8080`.

A managed group is `ready` when every service it configures has a ready replica. The workspace condition `ServicesReady` is `True` when every group is ready.

## Troubleshooting

### The workspace phase is Degraded

`ServicesReady` is `False`. Its message names the group and the first failing condition:

```bash
kubectl describe workspace customer-support
```

If a Deployment never becomes ready, read the session-api or memory-api pod logs. A wrong host, a missing database or failed authentication in `POSTGRES_CONN` stops the pod from starting.

### The group is not ready because of a Provider

A group whose `memory.providerRef` or `memory.gate.providerRef` does not resolve to a valid Provider provisions no memory-api, and the group reports not ready. The group's `EmbeddingProviderReady` or `GateProviderReady` condition gives the reason. The session-api keeps running.

A group that configures `memory` without `memory.providerRef` is treated the same way: `EmbeddingProviderReady` is `False` with reason `ProviderNotConfigured`, and no memory-api serves the group. Memory has no mode without embeddings. Set `memory.providerRef` to an embedding-role Provider to bring memory-api back. The operator deletes the group's memory-api Deployment and Service. Its memories stay in the database, and memory-api returns on the same store once the Provider is set. A DSAR erasure in the workspace fails with `memory held back for group <name>` until then, since nothing can reach those memories.

### An agent records nothing

Read the facade and runtime logs for `session store fallback` or `session-api URL resolution failed`. The log names the service group and the reason. Check that the name in `spec.serviceGroup` appears in `status.services[]`.

## Related pages

- [Workspace reference](/reference/core/workspace/#services) lists every `services[]` field, including `redis`, `privacyPolicyRef`, `autoscaling`, `podOverrides` and `memory.providerRef`.
- [Manage workspaces](/how-to/workspaces/manage-workspaces/) covers namespace, RBAC and access control.
- [Isolate workspace content](/how-to/workspaces/isolate-workspace-content/) covers shared storage.