37 — Knowledge Management Service (KMS) Deployment¶
Knowledge Management Service provides the document ingestion pipeline, vector search (Weaviate), and knowledge-base retrieval that backs ASK Assistant's RAG flows. It runs four workloads: a FastAPI server and three Hatchet workers (ingestion, generic, live-ingestion).
Architecture¶
flowchart TB
subgraph ask["ask namespace"]
API["knowledge-management-service\n(FastAPI :8000)"]
IW["ingestion-worker\n(Hatchet — batch docs)"]
GW["generic-worker\n(Hatchet — general)"]
LW["live-ingestion-worker\n(Hatchet — realtime)"]
MIG["migration-job\n(alembic)"]
WEV["weaviate\n(vector DB, subchart)"]
end
AST["assistant-service\n(tooling — KMS tool calls)"]
CORE["core-service\n(auth / org context)"]
HATCHET["hatchet-engine\n(:7070 gRPC plaintext)"]
PG["knowledge-pg\n(CNPG cluster)"]
FOUNDRY["foundry-litellm\n(:4000 LLM gateway)"]
S3["Ceph RGW\ns3.cl1.sq4.aegis.internal"]
API --> PG
API --> WEV
API --> CORE
IW --> PG
IW --> WEV
IW --> HATCHET
IW --> FOUNDRY
IW --> S3
GW --> PG
GW --> WEV
GW --> HATCHET
GW --> FOUNDRY
LW --> PG
LW --> WEV
LW --> HATCHET
LW --> S3
MIG --> PG
AST --> API
style WEV fill:#1e3a5f,stroke:#4a90d9,color:#fff
style PG fill:#2d5016,stroke:#6ab04c,color:#fff
Workload roles¶
| Workload | Function | Hard dependencies |
|---|---|---|
knowledge-management-service |
REST API — chat/search/upload endpoints | knowledge-pg, Weaviate, core-service |
ingestion-worker |
Batch document ingestion pipeline (OCR, chunking, embedding → Weaviate) | Hatchet, knowledge-pg, Weaviate, Foundry, Ceph S3 |
generic-worker |
General async workflows dispatched by the API | Hatchet, knowledge-pg, Weaviate, Foundry |
live-ingestion-worker |
Real-time / streaming document ingest | Hatchet, knowledge-pg, Weaviate, S3 |
migration-job |
alembic upgrade head — runs every ArgoCD sync (wave 1) |
knowledge-pg |
Air-Gap Changes from PP Baseline¶
| Concern | PP (on-prem Abu Dhabi) | Air-Gap (mod-auh1-dev) | Production note |
|---|---|---|---|
| Image registry | Azure CR (ai71uaenprodask71acr01.azurecr.io) |
Harbor (harbor.cl1.sq4.aegis.internal/ask/knowledge-management-service) |
Harbor is the production-correct alternative for air-gap |
| PostgreSQL | Bitnami subchart (postgresql.enabled: true) |
Disabled — reuse knowledge-pg CNPG cluster |
CNPG is production-correct (HA, point-in-time recovery) |
| Weaviate | External service in weaviate namespace (weaviate.weaviate.svc.cluster.local) |
Subchart enabled in ask namespace |
Production deviation: should be in its own namespace with independent lifecycle |
| ExternalSecrets | Azure Key Vault (provider: azure) |
Vault KV v2 (provider: vault) |
Vault is production-correct for air-gap |
| S3 storage | Azure S3 (s3.pp.gov.ae) |
Ceph RGW (s3.cl1.sq4.aegis.internal) |
Ceph RGW is functionally equivalent; requires verify_ssl: false for self-signed |
| Foundry endpoint | External shared LiteLLM (foundry.core.uaen.ai71services.ai) |
In-cluster LiteLLM (foundry-litellm.foundry.svc.cluster.local:4000/v1) |
Same pattern as assistant-service |
| Hatchet | Hatchet in hatchet namespace |
Hatchet in ask namespace — HATCHET_CLIENT_HOST_PORT override required |
Same fix applied to assistant-service |
| Autoscaling | HPA enabled (1–25 replicas/worker) | Disabled for bring-up (1 replica each) | Re-enable HPA for production load |
Weaviate namespace deviation
PP deploys Weaviate in its own weaviate namespace (separate lifecycle, storage class, admin boundary). For bring-up we use the KMS chart's built-in Weaviate subchart which deploys it into the ask namespace. This is a bring-up convenience — the production-correct approach is a separate namespace + independent ArgoCD Application for Weaviate. When Weaviate is in ask namespace the TOML vector_db host is knowledge-management-service-weaviate.ask.svc.cluster.local (not weaviate.weaviate.svc.cluster.local).
Pre-Engagement Requirements (external teams)¶
Use this as the engagement intake checklist
These are deliverables that must be requested from other teams before KMS can be deployed — they cannot be self-served by the deploying engineer. Collect all of them up front. Each row is a hand-off to a named team with exactly what to ask for and why.
| # | Requirement | Request from | What they must provide | Why |
|---|---|---|---|---|
| R1 | Ceph S3 service user | Storage / Ceph team | uid=ask-kms with access_key + secret_key, scoped read/write on bucket ask-knowledge-management (s3:GetObject/PutObject/DeleteObject/ListBucket). Endpoint https://s3.cl1.sq4.aegis.internal. |
KMS ingestion workers store/retrieve uploaded documents + processed artifacts in this bucket. |
| R2 | Foundry LLM access | Platform / Foundry team | LiteLLM endpoint http://foundry-litellm.foundry.svc.cluster.local:4000/v1 + an API key (virtual key preferred over the master key). Confirm the KMS-dedicated model qwen/qwen3-5-122b-a10b-kms and embedding model qwen/qwen3-embedding-0-6b (1024-dim) are served. |
KMS calls Foundry for embeddings + LLM enrichment during ingestion. The -kms instance isolates ingestion load from interactive traffic. |
| R3 | Hatchet client token | Platform team (or self-retrieve from cluster) | A valid Hatchet JWT for the ask tenant. Retrievable from hatchet-client-config secret in-cluster. |
KMS's 3 Hatchet workers (ingestion, generic, live-ingestion) register with the engine. |
| R4 | knowledge-pg database |
DBA / Platform team | A CNPG cluster knowledge-pg in the ask namespace, reachable at knowledge-pg-rw.ask.svc.cluster.local, with a superuser secret so postgresInit can create the knowledge_management DB + kms_user role. |
KMS API + migration job + workers persist metadata here. |
| R5 | GitLab repo + ArgoCD project access | Platform / GitOps team | Push access to ai71/ask71/ask-knowledge-management-service and the mod-ask-devops repo; the mod-auh1-dev-ask.ask AppProject must permit the KMS Application. |
GitOps delivery of the chart + Application. |
Secrets handling
Credentials from R1–R3 are stored in Vault only (secret/ask/knowledge-management-service/auh1-dev)
and surfaced to the pods via the External Secrets Operator — never committed to Git or pasted into
values files. See Phase 3.
The detailed request text + commands for R1 (Ceph S3 user) are in Phase 2.
Prerequisites (deployer checklist)¶
- [ ] R1 Ceph S3 user
ask-kmscreated + keys received (Phase 2) - [ ] R2 Foundry endpoint + key confirmed, KMS + embedding models served
- [ ] R3 Hatchet client token retrieved
- [ ] R4
knowledge-pgCNPG cluster running inasknamespace - [ ] Hatchet engine running with
SERVER_GRPC_INSECURE=t(plaintext gRPC) - [ ]
foundry-litellmservice reachable infoundrynamespace - [ ]
core-servicerunning inasknamespace - [ ] Harbor:
ask/ask-knowledge-management-service:stagingimage mirrored (Phase 1) - [ ] Harbor:
ask/semitechnologies/weaviate:1.34.0image mirrored (Phase 1) - [ ] Ceph S3 bucket
ask-knowledge-managementcreated (Phase 2) - [ ] Vault secret at
secret/ask/knowledge-management-service/auh1-devseeded (Phase 3)
Phase 1 — Image Mirroring to Harbor¶
Mirroring runs from the bastion via core42-mirror-image.py (proxy ON) — not from the Mac. The
Mac VPN only routes to the internal 100.115.x network (Harbor, GitLab); reaching ACR / Docker Hub
requires the cluster forward proxy 10.96.137.37:3128, which is only routable from the bastion. On
the Mac you only edit scripts/harbor-mirror/mirror-config.yaml; the bastion runs the copy.
1a + 1b. Config entries (in mirror-config.yaml)¶
# KMS application image — from ACR, by tag (no digest pin)
- { name: ask-knowledge-management-service, source: acr, project: ask, tags: ["staging"] }
# Weaviate — from Docker Hub. The weaviate subchart uses image.registry + image.repo (NOT
# image.repository), so the Harbor path must be ask/semitechnologies/weaviate to match
# values: weaviate.image.repo = ask/semitechnologies/weaviate
- { name: semitechnologies/weaviate, dest_name: semitechnologies/weaviate, source: docker.io, project: ask, tags: ["1.34.0"] }
Run the mirror (on bastion, proxy ON)¶
cd ~/harbor-images # bastion copy of the harbor-mirror scripts
./core42-mirror-image.py --only knowledge # → ask/ask-knowledge-management-service:staging
./core42-mirror-image.py --only weaviate # → ask/semitechnologies/weaviate:1.34.0
# expect [PUSHED] for each (or [SKIP] already in Harbor on re-runs)
Harbor image paths must match the values file exactly
- App image →
harbor.cl1.sq4.aegis.internal/ask/ask-knowledge-management-service:staging(note theask-prefix — the ACR repo isask-knowledge-management-service). - Weaviate →
harbor.cl1.sq4.aegis.internal/ask/semitechnologies/weaviate:1.34.0; the chart builds the ref fromimage.registry+image.repo, sorepomust be the nestedask/semitechnologies/weaviate.
Phase 2 — Ceph S3 Bucket + User¶
Create a dedicated S3 user and bucket for KMS. The pattern mirrors what was done for core-service.
# From a Ceph node or the Rook toolbox pod:
radosgw-admin user create \
--uid=ask-kms \
--display-name="ASK Knowledge Management Service"
# → copy access_key + secret_key from the JSON "keys" array
# Create the bucket (from bastion):
AWS_ACCESS_KEY_ID=<access_key> AWS_SECRET_ACCESS_KEY=<secret_key> \
aws --endpoint-url https://s3.cl1.sq4.aegis.internal --no-verify-ssl \
s3 mb s3://ask-knowledge-management
# Verify:
AWS_ACCESS_KEY_ID=<access_key> AWS_SECRET_ACCESS_KEY=<secret_key> \
aws --endpoint-url https://s3.cl1.sq4.aegis.internal --no-verify-ssl \
s3 ls
Expected output: ask-knowledge-management listed.
Phase 3 — Vault Secret¶
Seed the KMS secret at secret/ask/knowledge-management-service/auh1-dev. All keys are loaded via ESO extract into knowledge-management-service-secret.
# On bastion
export VAULT_ADDR=https://vault.cl1.sq4.aegis.internal
vault login # or use existing token
# Seed the KMS secret (use vault kv patch to preserve any existing keys)
vault kv put secret/ask/knowledge-management-service/auh1-dev \
HATCHET__CLIENT_TOKEN="<paste-real-jwt-from-hatchet-client-config>" \
STORAGE__S3__ACCESS_KEY_ID="<ceph-access-key-from-phase-2>" \
STORAGE__S3__SECRET_ACCESS_KEY="<ceph-secret-key-from-phase-2>" \
SECRET_KEY="$(openssl rand -hex 32)" \
API_KEY="$(openssl rand -hex 32)"
Hatchet JWT
Copy the real JWT from the existing hatchet-client-config secret — do NOT generate a placeholder.
kubectl get secret -n ask hatchet-client-config \
-o jsonpath='{.data.HATCHET_CLIENT_TOKEN}' | base64 -d
@format {env[HATCHET__CLIENT_TOKEN]}.
3b. DB password secret¶
The CNPG operator creates the kms_user password secret automatically (via the postgresInit job bootstrap). If needed, create a placeholder and let postgresInit override it:
kubectl create secret generic knowledge-management-service-db-password \
-n ask \
--from-literal=password="$(openssl rand -hex 24)" \
--dry-run=client -o yaml | kubectl apply -f -
The postgresInit job will create the kms_user role in knowledge-pg with this password.
Phase 4 — Sanitized Helm Chart¶
The sanitized repo lives at /Users/tarun.mittal/Documents/modgpt/mod-ask-knowledge-management-service/. It contains only the helm/ directory (no application source code).
4a. ExternalSecrets template — Vault branch¶
The original externalsecrets.yaml template only has azure and aws branches. Add a vault branch (ESO extract with a path key):
# In helm/templates/externalsecrets.yaml — add after the aws branch:
{{- else if eq $config.provider "vault" }}
dataFrom:
- extract:
key: {{ $config.secretKey }}
{{- end }}
This is identical in shape to the AWS extract — ESO's vault provider supports the same extract.key syntax pointing to the Vault KV v2 path (without the secret/data/ prefix — ESO adds it automatically when using the vault ClusterSecretStore).
4b. Values file — values-mod-auh1-dev-ask.yaml¶
See values-mod-auh1-dev-ask.yaml below (created in Phase 4 of the sanitized repo).
4c. TOML config — mod-auh1-dev-ask.toml¶
The TOML replaces PP-specific hostnames and S3 endpoints with in-cluster DNS and Ceph RGW.
Key differences from PP TOML (ai71-pp-auh1-prod-ask.toml):
| Key | PP value | Air-gap value |
|---|---|---|
vector_db.http_host |
weaviate.weaviate.svc.cluster.local |
knowledge-management-service-weaviate.ask.svc.cluster.local |
vector_db.grpc_host |
weaviate-grpc.weaviate.svc.cluster.local |
knowledge-management-service-weaviate-grpc.ask.svc.cluster.local |
storage.s3.endpoint_url |
https://s3.pp.gov.ae/ |
https://s3.cl1.sq4.aegis.internal/ |
document_understanding.foundry_endpoint |
http://litellm.foundry.svc.cluster.local/v1 |
http://foundry-litellm.foundry.svc.cluster.local:4000/v1 |
core_service.services_base_url |
http://core-service.ask.svc.cluster.local |
http://core-service.ask.svc.cluster.local (same) |
Phase 5 — ArgoCD Application¶
Add KMS to the ask ApplicationSet in mod-ask-devops. KMS follows the same multi-source pattern as other ask services.
# In ask-apps ApplicationSet — add one entry:
- name: knowledge-management-service
repoURL: https://gitlab.cl1.sq4.aegis.internal/modgpt/mod-ask-knowledge-management-service.git
targetRevision: main
path: helm
valuesFiles:
- environments/values-mod-auh1-dev-ask.yaml
Or push the chart to GitLab and create the ArgoCD Application via the GitOps repo.
Phase 6 — Verify¶
6a. Pre-flight — all pods Running¶
Expected workloads:
| Workload | Expected status |
|---|---|
knowledge-management-service |
Running |
knowledge-management-service-ingestion-worker |
Running |
knowledge-management-service-generic-worker |
Running |
knowledge-management-service-live-ingestion-worker |
Running |
knowledge-management-service-migration (Job) |
Completed |
6b. Migration job¶
kubectl get job -n ask | grep knowledge
# expect: knowledge-management-service-migration 1/1 <age>
kubectl logs -n ask job/knowledge-management-service-migration | tail -20
# expect: "Running upgrade..." → "Done"
6c. API health¶
# Direct (from within cluster)
kubectl exec -n ask deploy/knowledge-management-service -- \
curl -sf http://localhost:8000/health/
# expect: {"status": "ok"} or similar 200
# Via ingress (if enabled)
curl -sk https://api.ask.mod.auh1.dev.dir/ask71/v2/kmsvc/health/
6d. Weaviate connectivity¶
kubectl exec -n ask deploy/knowledge-management-service -- \
curl -sf http://knowledge-management-service-weaviate:80/v1/.well-known/ready
# expect: {"status": "ok"}
6e. Hatchet worker registration¶
kubectl logs -n ask deploy/knowledge-management-service-ingestion-worker --since=5m | \
grep -iE "hatchet|worker|runner|connect|error|socket"
# expect: "starting runner..." — same as assistant-service pattern
6f. S3 connectivity¶
kubectl exec -n ask deploy/knowledge-management-service-ingestion-worker -- \
python3 -c "
import boto3, os
s3 = boto3.client('s3',
endpoint_url='https://s3.cl1.sq4.aegis.internal',
aws_access_key_id=os.environ['STORAGE__S3__ACCESS_KEY_ID'],
aws_secret_access_key=os.environ['STORAGE__S3__SECRET_ACCESS_KEY'],
verify=False
)
buckets = [b['Name'] for b in s3.list_buckets()['Buckets']]
print('Buckets:', buckets)
assert 'ask-knowledge-management' in buckets, 'bucket missing!'
print('✅ S3 OK')
"
6g. Assistant-service tooling reachability test¶
From the assistant-service-tooling pod (which calls KMS):
kubectl exec -n ask deploy/assistant-service-tooling -- \
curl -sf http://knowledge-management-service.ask.svc.cluster.local/health/ \
&& echo "✅ KMS reachable from tooling" || echo "❌ KMS unreachable"
Phase 7 — KMS Org Onboarding (one-time, required)¶
KMS maintains its own organisation registry separate from core-service. Until an org is onboarded,
every collection create returns 404 organization_not_onboarded. This is a one-time call per org
after deployment — it provisions the Weaviate tenant and writes the org's model config to the KMS DB.
What it sets¶
| Field | Value (air-gap) | Source |
|---|---|---|
embedding_model_id |
qwen/qwen3-embedding-0-6b |
KMS TOML default |
vlm_model_name |
qwen/qwen3-5-122b-a10b-kms |
KMS TOML default |
blend_factor |
0.75 |
KMS TOML default |
parsing_strategy |
generic |
KMS TOML default |
chunking_strategy |
hierarchical |
KMS TOML default |
keyword_extraction |
false |
KMS TOML default |
Omitting all fields in the request body uses the KMS TOML defaults — the correct approach for the air-gap env where all models are set in config rather than per-org.
Command (run from bastion, one-time per org)¶
kubectl exec -i -n ask deploy/assistant-service-intelligence -- env PW="$SUPERPW" python3 - <<'EOF'
import os, httpx
KMS="http://knowledge-management-service.ask.svc.cluster.local"
CS="http://core-service.ask.svc.cluster.local"
tok=httpx.post(CS+"/api/v1/auth/login", json={
"org_id":"378447542100166005",
"email":"[email protected]",
"password":os.environ["PW"]}, timeout=20).json().get("sessionToken")
H={"Authorization":"Bearer "+tok}
# onboard — empty body uses TOML defaults
r=httpx.post(KMS+"/organizations/onboard", headers=H, json={}, timeout=20)
print("onboard", r.status_code, r.text)
# confirm
r=httpx.get(KMS+"/organizations/me", headers=H, timeout=10)
print("org/me", r.status_code, r.text)
EOF
Verified (2026-06-23):
onboard 200
{"embedding_model_id":"qwen/qwen3-embedding-0-6b","blend_factor":0.75,"keyword_extraction":false,
"parsing_strategy":"generic","chunking_strategy":"hierarchical","pre_retrieval_filter":false,
"post_retrieval_filter":false,"small_file_max_threshold":2000,"tabular_preview_rows":5,
"vlm_model_name":"qwen/qwen3-5-122b-a10b-kms"}
org/me 200
{"embedding_model_id":"qwen/qwen3-embedding-0-6b","blend_factor":0.75,"keyword_extraction":false,
"parsing_strategy":"generic","chunking_strategy":"hierarchical","pre_retrieval_filter":false,
"post_retrieval_filter":false,"small_file_max_threshold":2000,"tabular_preview_rows":5,
"vlm_model_name":"qwen/qwen3-5-122b-a10b-kms"}
Must run before any collection or document operation
Calling POST /collections/ without onboarding returns 404 organization_not_onboarded.
Onboarding is idempotent — safe to re-run.
Superadmin credentials required
The onboard endpoint requires an org-admin or superadmin session token. The machine PAT
(APP__CORE_SERVICES_MACHINE_TOKEN) is not a user session and will be rejected.
Troubleshooting¶
Migration job fails — relation "knowledge_service.*" does not exist¶
The alembic migration ran but the schema target doesn't exist. Check that the postgresInit job completed first:
kubectl get job -n ask | grep kms
kubectl logs -n ask job/knowledge-management-service-postgres-init | tail -20
If the init job failed, the database or user wasn't created. Re-run manually:
kubectl exec -n ask knowledge-pg-1 -- \
psql -U postgres -c "CREATE DATABASE knowledge_management;"
kubectl exec -n ask knowledge-pg-1 -- \
psql -U postgres -d knowledge_management -c "CREATE USER kms_user WITH PASSWORD '<password>';"
kubectl exec -n ask knowledge-pg-1 -- \
psql -U postgres -d knowledge_management -c "GRANT ALL ON DATABASE knowledge_management TO kms_user;"
Weaviate not ready — workers crash on start¶
Workers connect to Weaviate at startup. If Weaviate hasn't completed its startup (takes 30–90 s on first boot), workers may fail:
kubectl get pod -n ask -l app=weaviate -o wide
kubectl logs -n ask -l app=weaviate --since=5m | tail -30
Workers have failureThreshold: 180 / periodSeconds: 10 on startupProbe — they retry for 30 min. Wait for Weaviate ready before diagnosing worker crashes.
HATCHET__CLIENT_TOKEN missing / invalid JWT¶
Same root cause as assistant-service. Ensure the Vault secret has a real JWT (not a placeholder):
vault kv get -field=HATCHET__CLIENT_TOKEN secret/ask/knowledge-management-service/auh1-dev | \
python3 -c "import sys,base64,json; parts=sys.stdin.read().strip().split('.'); print(json.loads(base64.b64decode(parts[1]+'==')))"
# expect: JSON with grpc_broadcast_address, iss, aud fields
S3 upload fails — SSL: CERTIFICATE_VERIFY_FAILED¶
Set verify_ssl = false in the TOML [storage.s3] section. Ceph RGW uses a self-signed cert in the air-gap env.
DATABASE__HOST not set — can't resolve host at startup¶
The extraEnv block must inject DATABASE__HOST. Confirm:
kubectl exec -n ask deploy/knowledge-management-service -- env | grep DATABASE__HOST
# expect: DATABASE__HOST=knowledge-pg-rw.ask.svc.cluster.local
If missing, add it to extraEnv in the values file and force-sync.