Release 2.0.0: AI² with per-user auth, more OEG tools, chat memory and Helm chart

Release v2.0.0 of AI², the agent and MCP server that exposes OEG's CAMARA APIs to AI clients. Merges develop into main. Tag v2.0.0 after merge.

The agent now acts as the signed-in user, covers more OEG APIs, keeps chat memory in Redis and ships as a Helm chart.

What's new since v1.0.0-release

Authentication

  • Every request carries the user's Keycloak access token (Authorization: Bearer …).
  • The agent passes the token to the MCP server, which sends it to OEG, so the user's own roles decide what AI² may do.
  • AUTH_ENABLED=false turns this off for platforms deployed without login.

MCP tools

  • Edge Application Management: application definitions, edge zone retrieval, application instantiation, required resources for edge apps. Resources are typed to match the OEG schema.
  • Traffic Influence: create requests (device IPv4 address required).
  • Device Location: location retrieval.
  • Quality on Demand: tools updated, QoD deletion description corrected.
  • OEG error messages are shown to the user through a new ToolError.
  • OEG base URL defaults corrected for the CAMARA APIs.

Agent

  • Backend migrated from Quart to FastAPI.
  • Short per-session chat memory in Redis when REDIS_URL is set.
  • Prompt types for the tool agent, and support for multi-step operations.
  • Default model updated.

Deployment

  • Helm chart 2.0.0 with Redis integration. The platform chart deploys the same chart.
  • Image tags are 2.0.0.
  • Docker Compose for both modules and Redis.
  • Docker base images and builds fixed for slow networks.

CI and quality

  • Unit tests moved to pytest; static type checking, linting and formatting in the pipeline.
  • Image build and push as separate jobs, using buildx without provenance attestation on a docker-in-docker runner.
  • SAST configured in GitLab CI.

Housekeeping

  • Legacy MCP tools removed.
  • MCP module reads its environment variables at runtime, so a missing variable no longer fails the import.
  • Vendored Helm dependencies are no longer tracked in git.
  • Apache License 2.0 added.
  • READMEs rewritten for the module layout, authentication and memory.

Upgrade notes

  • Auth is on by default. Callers must send a bearer token, or set AUTH_ENABLED=false.
  • Redis is needed for chat memory. Without REDIS_URL the agent runs without memory.
  • OEG base URLs: check any overrides against the new defaults.
  • Removed: the Quart service and legacy MCP tools.

Specs

Implements the AI² spec in the architecture repo (ai2/).

Checks

  • CI green (tests, lint, type check, image build)
  • Helm chart installs with Redis and both images at 2.0.0
  • Authenticated call through agent → MCP → OEG works
  • Behaviour with AUTH_ENABLED=false confirmed

Release steps

  1. Merge to main.
  2. Tag v2.0.0 on the merge commit.``
Edited by Dimitrios Gogos

Merge request reports

Loading
+8 −5
Changes for ai_agent/README.md: 8 added lines, 5 removed lines.
Original line number Diff line number Diff line
@@ -12,8 +12,11 @@ ai_agent/
├── routes/                # API route definitions
│   ├── groq.py             # Groq-related endpoints
│   └── debug.py            # Debug / health endpoints
├── .env                   # Environment variables
├── prompts/               # System prompts for the agent and its memory
├── memory.py              # Redis session memory
├── security.py            # Bearer token check
├── app.py                 # Application entry point
├── Dockerfile
├── requirements.txt       # Python dependencies
```

@@ -67,8 +70,8 @@ MEMORY_KEY_PREFIX=ai_agent:session
### 1. Clone the Repository

```
git clone <your-repo-url>
cd ai_agent
git clone https://labs.etsi.org/rep/oop/code/ai2.git
cd ai2
```

### 2. Create a Virtual Environment (Recommended)
@@ -82,7 +85,7 @@ venv\Scripts\activate # Windows
### 3. Install Dependencies

```
pip install -r requirements.txt
pip install -r ai_agent/requirements.txt
```

### 4. Configure Environment Variables
@@ -104,7 +107,7 @@ docker build -t ai_agent .

Follow these steps to set up and run the app locally.

Start the backend server using:
Start the backend server from the repository root using:

```
python -m ai_agent.app
+4 −0
Changes for deployment/docker/README.md: 4 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -13,6 +13,9 @@ OEG_SERVICE_URL=http://your-oeg-api-url
GROQ_MODEL_NAME=qwen/qwen3.8-27b
```

Optional, with the defaults the compose file uses: `REDIS_URL` (`redis://redis:6379/0`),
`MEMORY_WINDOW_TURNS` (`10`) and `MEMORY_TTL_SECONDS` (`3600`) for the agent's session memory.

## Run
Start services
```
@@ -31,5 +34,6 @@ This allows you to interact with APIs and MCP servers individually, without atta
|-----------|-----------|
| mcp       | 8004      |
| ai-agent  | 9013      |
| redis     | 6385      |

---
 No newline at end of file
+12 −7
Changes for deployment/helm/ai2/templates/_helpers.tpl: 12 added lines, 7 removed lines.
Original line number Diff line number Diff line
@@ -38,7 +38,7 @@ app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{- define "ai2.secretName" -}}
{{- include "ai2.fullname" . }}-secrets
{{- .Values.secrets.existingSecret | default (printf "%s-secrets" (include "ai2.fullname" .)) -}}
{{- end }}

{{- define "ai2.mcpServiceName" -}}
@@ -46,13 +46,18 @@ app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{- define "ai2.aiAgentServiceName" -}}
{{- include "ai2.fullname" . }}-ai-agent
{{- .Values.aiAgent.service.name | default (dig "ai2" "host" (printf "%s-ai-agent" (include "ai2.fullname" .)) (.Values.global | default dict)) -}}
{{- end }}

{{- define "ai2.aiAgentServicePort" -}}
{{- .Values.aiAgent.service.port | default (dig "ai2" "port" 9013 (.Values.global | default dict)) -}}
{{- end }}

{{- define "ai2.oegUrl" -}}
{{- $g := .Values.global | default dict -}}
{{- .Values.mcp.oegUrl | default (printf "http://%s:%v" (dig "oeg" "host" "oeg" $g) (dig "oeg" "port" 80 $g)) -}}
{{- end }}

{{- define "ai2.redisServiceName" -}}
{{- if .Values.redis.fullnameOverride -}}
{{- printf "%s-master" .Values.redis.fullnameOverride -}}
{{- else -}}
{{- printf "%s-redis-master" .Release.Name -}}
{{- end -}}
{{- include "ai2.fullname" . }}-redis
{{- end }}
+34 −37
Changes for deployment/helm/ai2/templates/ai-agent-deployment.yaml: 34 added lines, 37 removed lines.
Original line number Diff line number Diff line
@@ -7,7 +7,7 @@ metadata:
    {{- include "ai2.labels" . | nindent 4 }}
    app.kubernetes.io/component: ai-agent
spec:
  replicas: {{ .Values.replicaCount.aiAgent }}
  replicas: {{ .Values.aiAgent.replicaCount }}
  selector:
    matchLabels:
      {{- include "ai2.selectorLabels" . | nindent 6 }}
@@ -17,39 +17,30 @@ spec:
      labels:
        {{- include "ai2.selectorLabels" . | nindent 8 }}
        app.kubernetes.io/component: ai-agent
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
        checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
    spec:
      initContainers:
        - name: wait-for-mcp
          image: curlimages/curl:latest
          image: {{ .Values.aiAgent.waitForMcpImage | quote }}
          imagePullPolicy: IfNotPresent
          command: ['sh', '-c']
          args:
            - |
              until curl -sf http://{{ include "ai2.mcpServiceName" . }}:{{ .Values.config.mcp.port }}/health; do
              until curl -sf http://{{ include "ai2.mcpServiceName" . }}:{{ .Values.mcp.port }}/health; do
                echo "Waiting for MCP to be ready..."
                sleep 3
              done
              echo ""
              echo "MCP is ready."
        {{- if .Values.redis.enabled }}
        - name: wait-for-redis
          image: "{{ .Values.redis.image.registry }}/{{ .Values.redis.image.repository }}:{{ .Values.redis.image.tag }}"
          imagePullPolicy: {{ .Values.redis.image.pullPolicy }}
          command: ['sh', '-c']
          args:
            - |
              until redis-cli -h {{ include "ai2.redisServiceName" . }} -p {{ .Values.redis.master.service.ports.redis }} ping | grep -q PONG; do
                echo "Waiting for Redis to be ready..."
                sleep 3
              done
              echo "Redis is ready."
        {{- end }}
      containers:
        - name: ai-agent
          image: "{{ .Values.images.aiAgent.repository }}:{{ .Values.images.aiAgent.tag }}"
          imagePullPolicy: {{ .Values.images.aiAgent.pullPolicy }}
          image: "{{ .Values.aiAgent.image.repository }}:{{ .Values.aiAgent.image.tag }}"
          imagePullPolicy: {{ .Values.aiAgent.image.pullPolicy }}
          ports:
            - name: http
              containerPort: {{ .Values.config.aiAgent.port | int }}
              containerPort: {{ .Values.aiAgent.port | int }}
              protocol: TCP
          env:
            - name: APP_PORT
@@ -67,28 +58,34 @@ spec:
                configMapKeyRef:
                  name: {{ include "ai2.configMapName" . }}
                  key: GROQ_MODEL_NAME
            - name: REDIS_URL
              valueFrom:
                configMapKeyRef:
                  name: {{ include "ai2.configMapName" . }}
                  key: REDIS_URL
            - name: MEMORY_WINDOW_TURNS
            {{- range list "REDIS_URL" "MEMORY_WINDOW_TURNS" "MEMORY_TTL_SECONDS" "MEMORY_KEY_PREFIX" "AUTH_ENABLED" }}
            - name: {{ . }}
              valueFrom:
                configMapKeyRef:
                  name: {{ include "ai2.configMapName" . }}
                  key: MEMORY_WINDOW_TURNS
            - name: MEMORY_TTL_SECONDS
              valueFrom:
                configMapKeyRef:
                  name: {{ include "ai2.configMapName" . }}
                  key: MEMORY_TTL_SECONDS
            - name: MEMORY_KEY_PREFIX
              valueFrom:
                configMapKeyRef:
                  name: {{ include "ai2.configMapName" . }}
                  key: MEMORY_KEY_PREFIX
                  name: {{ include "ai2.configMapName" $ }}
                  key: {{ . }}
            {{- end }}
            - name: GROQ_API_KEY
              valueFrom:
                secretKeyRef:
                  name: {{ include "ai2.secretName" . }}
                  key: GROQ_API_KEY
            # mcp_use reports usage to a third party by default, on every chat message.
            - name: MCP_USE_ANONYMIZED_TELEMETRY
              value: "false"
          readinessProbe:
            httpGet:
              path: /health
              port: http
            initialDelaySeconds: 5
            periodSeconds: 5
            timeoutSeconds: 3
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /health
              port: http
            initialDelaySeconds: 15
            periodSeconds: 10
            timeoutSeconds: 5
            failureThreshold: 3
+4 −4
Changes for deployment/helm/ai2/templates/ai-agent-service.yaml: 4 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -7,13 +7,13 @@ metadata:
    {{- include "ai2.labels" . | nindent 4 }}
    app.kubernetes.io/component: ai-agent
spec:
  type: {{ .Values.service.aiAgent.type }}
  type: {{ .Values.aiAgent.service.type }}
  ports:
    - name: http
      port: {{ .Values.service.aiAgent.port }}
      port: {{ include "ai2.aiAgentServicePort" . }}
      targetPort: http
      {{- if eq .Values.service.aiAgent.type "NodePort" }}
      nodePort: {{ .Values.service.aiAgent.nodePort }}
      {{- if and (eq .Values.aiAgent.service.type "NodePort") .Values.aiAgent.service.nodePort }}
      nodePort: {{ .Values.aiAgent.service.nodePort }}
      {{- end }}
      protocol: TCP
  selector:
Loading
Loading