From ab7435a43ad634c83b7ee43f5e3b007007f7b1a5 Mon Sep 17 00:00:00 2001 From: Andrew Stoltz Date: Mon, 13 Apr 2026 19:12:08 -0500 Subject: [PATCH] Update Agent Zero, Asterisk, and Telephony K8s manifests - Update agent-zero deployment configuration - Update Asterisk configmap and deployment - Update telephony service manifest Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/agent-zero/agent-zero.yaml | 103 ++++++++++++++++++------ apps/asterisk/configmap.yaml | 137 ++++++++++++++++++++++++++++---- apps/asterisk/deployment.yaml | 28 +++---- apps/telephony/telephony.yaml | 55 +++++++------ 4 files changed, 243 insertions(+), 80 deletions(-) diff --git a/apps/agent-zero/agent-zero.yaml b/apps/agent-zero/agent-zero.yaml index 9b9c8cd..6a39ba6 100644 --- a/apps/agent-zero/agent-zero.yaml +++ b/apps/agent-zero/agent-zero.yaml @@ -2,13 +2,14 @@ # Agent Zero AI Stack — NUC Deployment (RKE2 Bare-Metal) # ============================================================================= # Deploys: AgentZero (agent UI) on RKE2 cluster with Blue Jay profile -# Ollama: edge1 Pi 5 at 10.0.57.17:11434 (qwen2.5-coder:7b + nomic-embed-text) +# Ollama: workstation-first via BLUEJAY-WS (10.0.56.20:11434) with edge1 Pi 5 +# fallback (10.0.57.17:11434) # Target: RKE2 bare-metal cluster, namespace: agent-zero # Profile: Blue Jay (21 tools, 3 prompts, 4 extensions, theme) # # Differences from LOCAL (WSL K3s): # - Uses Longhorn StorageClass (not local-path) -# - Connects to edge1 Pi 5 Ollama (not workstation R9700) +# - Prefers workstation Ollama on the R9700, falls back to edge1 Pi 5 # - NO Anthropic API key (free/local models only) # - NO Piper TTS or Kiwix (edge1 handles TTS, no Wikipedia needed) # - NO hostPath volumes — profile/tools/extensions loaded via ConfigMaps @@ -90,7 +91,7 @@ subjects: # ============================================================================= # Agent Zero — AI Agent Web UI (NUC Edition, Blue Jay Profile) # ============================================================================= -# Connects to edge1 Pi 5 Ollama (free, local models only) +# Connects to a local proxy that routes to workstation Ollama first and edge1 second # Blue Jay profile with 21 tools, 3 prompts, 4 extensions --- @@ -104,7 +105,7 @@ metadata: annotations: agent-zero/deployment: "nuc" agent-zero/profile: "bluejay" - agent-zero/ollama: "edge1 Pi 5 (10.0.57.17:11434)" + agent-zero/ollama: "BLUEJAY-WS primary (10.0.56.20:11434), edge1 fallback (10.0.57.17:11434)" spec: replicas: 1 selector: @@ -119,18 +120,19 @@ spec: spec: serviceAccountName: agent-zero initContainers: - # Wait for edge1 Pi 5 Ollama to be reachable before starting Agent Zero. + # Wait for either workstation or edge1 Ollama to be reachable before starting Agent Zero. - name: wait-for-ollama image: busybox:1.37 command: ["sh", "-c"] args: - | - echo "Waiting for Ollama at edge1 (10.0.57.17:11434)..." - until wget -qO- --timeout=2 http://10.0.57.17:11434/api/tags >/dev/null 2>&1; do - echo "edge1 Ollama not ready, retrying in 5s..." + echo "Waiting for Ollama at BLUEJAY-WS or edge1..." + until wget -qO- --timeout=2 http://10.0.56.20:11434/api/tags >/dev/null 2>&1 || \ + wget -qO- --timeout=2 http://10.0.57.17:11434/api/tags >/dev/null 2>&1; do + echo "No Ollama endpoint ready yet, retrying in 5s..." sleep 5 done - echo "edge1 Ollama is reachable!" + echo "At least one Ollama endpoint is reachable." # Assemble the Blue Jay profile directory structure from ConfigMaps. # ConfigMaps can't create nested dirs, so we copy into the workspace PVC. - name: setup-bluejay @@ -177,6 +179,50 @@ spec: - name: bluejay-theme mountPath: /tmp/bluejay-theme containers: + - name: ollama-proxy + image: nginx:1.27-alpine + command: ["/bin/sh", "-c"] + args: + - | + cat > /etc/nginx/nginx.conf <<'NGINX' + worker_processes 1; + events { worker_connections 1024; } + http { + upstream ollama_upstream { + server 10.0.56.20:11434 max_fails=2 fail_timeout=10s; + server 10.0.57.17:11434 backup; + keepalive 16; + } + server { + listen 11434; + location / { + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header Host $host; + proxy_connect_timeout 5s; + proxy_read_timeout 600s; + proxy_send_timeout 600s; + proxy_next_upstream error timeout invalid_header http_502 http_503 http_504; + proxy_pass http://ollama_upstream; + } + } + } + NGINX + exec nginx -g 'daemon off;' + ports: + - containerPort: 11434 + readinessProbe: + httpGet: + path: /api/tags + port: 11434 + initialDelaySeconds: 5 + periodSeconds: 15 + livenessProbe: + httpGet: + path: /api/tags + port: 11434 + initialDelaySeconds: 10 + periodSeconds: 30 - name: agent-zero image: agent0ai/agent-zero:latest command: ["/bin/bash", "-c"] @@ -194,10 +240,11 @@ spec: ln -sfn /a0/work/.bluejay/agents/bluejay /a0/agents/bluejay # Write model config BEFORE initialize.sh loads it # The _model_config plugin reads config.json (NOT config.yaml) - # Default is OpenRouter; override to local Ollama on edge1 + # Default is OpenRouter; override to the local proxy, which prefers + # the workstation and falls back to edge1 automatically. mkdir -p /a0/usr/plugins/_model_config cat > /a0/usr/plugins/_model_config/config.json << 'MODELCFG' - {"allow_chat_override":true,"chat_model":{"provider":"ollama","name":"qwen2.5-coder:7b","api_base":"http://10.0.57.17:11434","ctx_length":8192,"ctx_history":0.7,"vision":false,"kwargs":{"temperature":0,"num_ctx":8192}},"utility_model":{"provider":"ollama","name":"qwen2.5-coder:7b","api_base":"http://10.0.57.17:11434","ctx_length":8192,"ctx_input":0.7,"kwargs":{"num_ctx":8192}},"embedding_model":{"provider":"ollama","name":"nomic-embed-text","api_base":"http://10.0.57.17:11434","kwargs":{}}} + {"allow_chat_override":true,"chat_model":{"provider":"ollama","name":"gemma3:12b","api_base":"http://127.0.0.1:11434","ctx_length":8192,"ctx_history":0.7,"vision":false,"kwargs":{"temperature":0,"num_ctx":8192}},"utility_model":{"provider":"ollama","name":"qwen2.5:1.5b","api_base":"http://127.0.0.1:11434","ctx_length":8192,"ctx_input":0.7,"kwargs":{"num_ctx":8192}},"embedding_model":{"provider":"ollama","name":"nomic-embed-text","api_base":"http://127.0.0.1:11434","kwargs":{}}} MODELCFG # Strip heredoc indentation sed -i 's/^ //' /a0/usr/plugins/_model_config/config.json @@ -209,42 +256,42 @@ spec: # Agent identity - name: AGENT_NAME value: "Blue Jay (NUC)" - # Chat model — qwen2.5-coder:7b on edge1 Pi 5 + # Chat model — workstation primary, edge1 fallback via local proxy - name: A0_SET_chat_model_provider value: "ollama" - name: A0_SET_chat_model_name - value: "qwen2.5-coder:7b" + value: "gemma3:12b" - name: A0_SET_chat_model_api_base - value: "http://10.0.57.17:11434" + value: "http://127.0.0.1:11434" - name: A0_SET_chat_model_ctx_length value: "8192" - name: A0_SET_chat_model_kwargs value: '{"temperature": 0, "num_ctx": 8192}' - # Utility model — same as chat (only one model available) + # Utility model — fast small helper tier through the same proxy - name: A0_SET_util_model_provider value: "ollama" - name: A0_SET_util_model_name - value: "qwen2.5-coder:7b" + value: "qwen2.5:1.5b" - name: A0_SET_util_model_api_base - value: "http://10.0.57.17:11434" + value: "http://127.0.0.1:11434" - name: A0_SET_util_model_kwargs value: '{"num_ctx": 2048}' - # Embedding model — nomic on edge1 + # Embedding model — nomic through the same proxy - name: A0_SET_embed_model_provider value: "ollama" - name: A0_SET_embed_model_name value: "nomic-embed-text" - name: A0_SET_embed_model_api_base - value: "http://10.0.57.17:11434" - # Browser model — disabled (no vision model on Pi) + value: "http://127.0.0.1:11434" + # Browser model — small Gemma candidate through the same proxy - name: A0_SET_browser_model_provider value: "ollama" - name: A0_SET_browser_model_name - value: "qwen2.5-coder:7b" + value: "gemma3:4b" - name: A0_SET_browser_model_api_base - value: "http://10.0.57.17:11434" + value: "http://127.0.0.1:11434" - name: A0_SET_browser_model_vision - value: "false" + value: "true" # Agent profile — Blue Jay personality, tools, and system prompt - name: A0_SET_agent_profile value: "bluejay" @@ -297,7 +344,7 @@ spec: command: - /bin/bash - -c - - "curl -sf http://localhost:80/ > /dev/null && curl -sf --connect-timeout 3 http://10.0.57.17:11434/api/tags > /dev/null" + - "curl -sf http://localhost:80/ > /dev/null && curl -sf --connect-timeout 3 http://127.0.0.1:11434/api/tags > /dev/null" periodSeconds: 30 failureThreshold: 2 resources: @@ -435,7 +482,13 @@ spec: protocol: UDP - port: 53 protocol: TCP - # Ollama on edge1 + # Ollama on BLUEJAY-WS + - to: + - ipBlock: + cidr: 10.0.56.20/32 + ports: + - port: 11434 + # Ollama on edge1 fallback - to: - ipBlock: cidr: 10.0.57.17/32 diff --git a/apps/asterisk/configmap.yaml b/apps/asterisk/configmap.yaml index b682949..7d23828 100644 --- a/apps/asterisk/configmap.yaml +++ b/apps/asterisk/configmap.yaml @@ -155,11 +155,11 @@ data: remove_existing=yes qualify_frequency=60 - ; Extension 103 - Office 3 - [103](phone-template) - auth=auth103 - aors=103 - callerid="Office 3" <103> + ; Extension 103 - Office 3 + [103](phone-template) + auth=auth103 + aors=103 + callerid="Office 3" <103> [auth103] type=auth @@ -167,13 +167,90 @@ data: username=103 password=bluejay-ext-103 - [103] - type=aor - max_contacts=1 - remove_existing=yes - qualify_frequency=60 - - extensions.conf: | + [103] + type=aor + max_contacts=1 + remove_existing=yes + qualify_frequency=60 + + ; Test endpoints 901-904 for softphone proof + [test-endpoint](!) + type=endpoint + context=from-internal + transport=transport-udp + disallow=all + allow=ulaw + allow=alaw + direct_media=no + rtp_symmetric=yes + force_rport=yes + rewrite_contact=yes + + [901](test-endpoint) + auth=auth901 + aors=901 + callerid="Proof Caller" <901> + + [auth901] + type=auth + auth_type=userpass + username=901 + password=test-sip-secret-901 + + [901] + type=aor + max_contacts=1 + remove_existing=yes + + [902](test-endpoint) + auth=auth902 + aors=902 + callerid="Proof Callee" <902> + + [auth902] + type=auth + auth_type=userpass + username=902 + password=test-sip-secret-901 + + [902] + type=aor + max_contacts=1 + remove_existing=yes + + [903](test-endpoint) + auth=auth903 + aors=903 + callerid="Proof Endpoint 3" <903> + + [auth903] + type=auth + auth_type=userpass + username=903 + password=test-sip-secret-901 + + [903] + type=aor + max_contacts=1 + remove_existing=yes + + [904](test-endpoint) + auth=auth904 + aors=904 + callerid="Proof Endpoint 4" <904> + + [auth904] + type=auth + auth_type=userpass + username=904 + password=test-sip-secret-901 + + [904] + type=aor + max_contacts=1 + remove_existing=yes + + extensions.conf: | [general] static=yes writeprotect=no @@ -191,11 +268,37 @@ data: same => n,Hangup() [from-internal] - ; Internal extension-to-extension dialing - exten => _1XX,1,Dial(PJSIP/${EXTEN},30) - same => n,Hangup() - - ; Outbound via Twilio SIP trunk (11-digit US) + ; Internal extension-to-extension dialing + exten => _1XX,1,Dial(PJSIP/${EXTEN},30) + same => n,Hangup() + + ; Softphone proof endpoints and utility extensions + exten => _9XX,1,NoOp(Proof call to ${EXTEN}) + same => n,Dial(PJSIP/${EXTEN},30) + same => n,Hangup() + + exten => 999,1,Answer() + same => n,Playback(demo-echotest) + same => n,Echo() + same => n,Hangup() + + exten => 998,1,Answer() + same => n,Milliwatt() + same => n,Hangup() + + exten => 997,1,Answer() + same => n,Wait(0.5) + same => n,Playback(hello-world) + same => n,Wait(1) + same => n,Hangup() + + exten => 996,1,Answer() + same => n,Wait(0.5) + same => n,Read(DIGITS,,4,,,5) + same => n,SayDigits(${DIGITS}) + same => n,Hangup() + + ; Outbound via Twilio SIP trunk (11-digit US) exten => _1NXXNXXXXXX,1,Set(CALLERID(num)=+13202332529) same => n,Dial(PJSIP/+${EXTEN}@twilio-trunk,60) same => n,Hangup() diff --git a/apps/asterisk/deployment.yaml b/apps/asterisk/deployment.yaml index c8b9846..1545123 100644 --- a/apps/asterisk/deployment.yaml +++ b/apps/asterisk/deployment.yaml @@ -16,22 +16,22 @@ spec: metadata: labels: app: asterisk - spec: - hostNetwork: true - dnsPolicy: ClusterFirstWithHostNet + spec: + nodeSelector: + kubernetes.io/hostname: rke2-agent1 + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet securityContext: fsGroup: 0 - initContainers: - - name: install-sounds - image: busybox:latest - command: - - sh - - -c - - | - mkdir -p /sounds/en && - wget -qO- http://downloads.asterisk.org/pub/telephony/sounds/asterisk-core-sounds-en-ulaw-current.tar.gz | tar xz -C /sounds/en/ && - wget -qO- http://downloads.asterisk.org/pub/telephony/sounds/asterisk-extra-sounds-en-ulaw-current.tar.gz | tar xz -C /sounds/en/ && - echo "Sound files installed: $(find /sounds/en -type f | wc -l) files" + initContainers: + - name: install-sounds + image: busybox:latest + command: + - sh + - -c + - | + mkdir -p /sounds/en && + echo "Leaving /var/lib/asterisk/sounds/en empty until the sound bootstrap lane is fixed." volumeMounts: - name: sounds mountPath: /sounds/en diff --git a/apps/telephony/telephony.yaml b/apps/telephony/telephony.yaml index c9e8d4c..9f848b2 100644 --- a/apps/telephony/telephony.yaml +++ b/apps/telephony/telephony.yaml @@ -46,26 +46,31 @@ data: "Twilio": { "VoiceUrl": "https://telephony.flowercore.io/api/twilio/webhooks/voice/incoming", "StatusCallbackUrl": "https://telephony.flowercore.io/api/twilio/webhooks/voice/status" - }, - "Asterisk": { - "BaseUrl": "http://localhost:8088", - "Username": "flowercore", - "Password": "bluejay-asterisk-ari", - "Application": "flowercore-pbx", - "ReconnectDelaySeconds": 5, - "MaxReconnectDelaySeconds": 60 - } - }, - "Ari": { - "BaseUrl": "http://localhost:8088", - "Username": "flowercore", - "Password": "bluejay-asterisk-ari", - "Application": "flowercore-pbx", - "ReconnectDelaySeconds": 5, - "MaxReconnectDelaySeconds": 60 - }, - "Tts": { - "PiperUrl": "http://10.0.57.15:8500", + }, + "Asterisk": { + "BaseUrl": "http://10.0.56.12:8088", + "Username": "flowercore", + "Password": "bluejay-asterisk-ari", + "Application": "flowercore-pbx", + "ReconnectDelaySeconds": 5, + "MaxReconnectDelaySeconds": 60 + } + }, + "Ari": { + "BaseUrl": "http://10.0.56.12:8088", + "Username": "flowercore", + "Password": "bluejay-asterisk-ari", + "Application": "flowercore-pbx", + "ReconnectDelaySeconds": 5, + "MaxReconnectDelaySeconds": 60 + }, + "Sip": { + "Domain": "10.0.56.207", + "Port": 5060, + "Transport": "udp" + }, + "Tts": { + "PiperUrl": "http://10.0.57.15:8500", "DefaultEngine": "piper", "SampleRate": 8000 }, @@ -112,10 +117,12 @@ spec: labels: app: telephony-web spec: - securityContext: - fsGroup: 1654 - initContainers: - - name: fix-data-perms + securityContext: + fsGroup: 1654 + nodeSelector: + kubernetes.io/hostname: rke2-agent1 + initContainers: + - name: fix-data-perms image: busybox:latest command: ["sh", "-c", "chown -R 1654:1654 /data"] volumeMounts: