# Demo: from an empty cluster to a hypothesis in your chat A step-by-step walkthrough that doubles as the script for a screen recording. Every step is a command you can run; timings are what they take on a laptop with Docker Desktop. Total: about 25 minutes the first time, 8 minutes once the images are cached. Choose your model before you start: | | Local (Ollama) | Cloud (Gemini, DeepSeek, …) | |---|---|---| | Data leaves the cluster | never | yes, redacted | | Cost per alert | $0 | cents, or a free tier | | Time to hypothesis | 30–60 s on CPU | ~7 s | | Hard-case accuracy (docs/BENCHMARKS.md) | 2/5 | 5/5 | For a recording, use the cloud model: the waits are short. Show the local one for the privacy point. ## Scene 1 — the cluster (2 min) ```bash kind create cluster --name minus20 kubectl get nodes ``` What to say: one node, nothing installed, no monitoring stack needed for this demo. Minus20 reads Kubernetes events directly. ## Scene 2 — install with one command (3 min) Telegram: create a bot with @BotFather, put its token in a Secret, and find your chat id (message the bot, then open `https://api.telegram.org/bot/getUpdates` in a browser; `chat.id`). ```bash kubectl create namespace minus20 kubectl -n minus20 create secret generic m20-telegram --from-literal=bot-token='' ``` Local model: ```bash ollama pull qwen2.5:7b && ollama pull nomic-embed-text && ollama pull qwen2.5vl:7b helm upgrade --install m20 oci://ghcr.io/maxuver/charts/minus20 -n minus20 \ --set config.store=postgres \ --set config.notifier=telegram --set config.telegramChatId= \ --set config.llmProvider=ollama --set config.ollamaUrl=http://host.docker.internal:11434 \ --set agent.enabled=true --set mcp.enabled=true ``` Cloud model (Gemini through the OpenAI-compatible endpoint; the same lines work for DeepSeek or Groq with another URL): ```bash kubectl -n minus20 create secret generic m20-llm --from-literal=openai-api-key='' helm upgrade --install m20 oci://ghcr.io/maxuver/charts/minus20 -n minus20 \ --set config.store=postgres \ --set config.notifier=telegram --set config.telegramChatId= \ --set config.llmProvider=openai \ --set config.openaiBaseUrl=https://generativelanguage.googleapis.com/v1beta/openai/ \ --set config.openaiModel=gemini-3.6-flash \ --set config.ollamaUrl=http://host.docker.internal:11434 \ --set agent.enabled=true --set mcp.enabled=true kubectl -n minus20 get pods -w ``` What to say: the chart and images come from GitHub Container Registry, so there is nothing to clone or build. Six pods: ingest, worker, Redis, Postgres, the agent, the MCP server. Point at the ServiceAccount: ```bash sa=system:serviceaccount:minus20:m20-analyzer kubectl auth can-i list events --as=$sa -A # yes kubectl auth can-i delete pods --as=$sa -A # no kubectl auth can-i create pods/exec --as=$sa -A # no ``` It can look. It cannot touch. ## Scene 3 — break something (1 min) ```bash kubectl -n minus20 run billing-api --image=busybox --restart=Always --command -- \ sh -c "echo 'ERROR could not connect to postgres:5432'; sleep 2; exit 1" kubectl -n minus20 get pod billing-api -w ``` CrashLoopBackOff within a minute. In a real cluster Alertmanager fires `KubePodCrashLooping` and posts a webhook; here we post the same webhook by hand so the demo needs no Prometheus: ```bash kubectl -n minus20 port-forward svc/m20-ingest-api 8080:8080 & curl -s -X POST localhost:8080/webhook/alertmanager -H 'content-type: application/json' \ -d @services/ingest-api/tests/fixtures/crashloop.json ``` (Edit the fixture's `namespace` to `minus20` and `pod` to `billing-api` first, or use a copy. The full autonomous loop with a real PrometheusRule and Alertmanager is in `docs/LOCAL-SETUP.md`.) ## Scene 4 — the hypothesis arrives (1 min, or 10 s on a cloud model) Show Telegram. The message carries: likely cause, confidence, evidence lines, the cheapest way to disprove it, blast radius, next steps, and the footer: backend, latency, cost, and `#id`. What to say: this is the reflex path. One model call, hard timeout, daily budget, PII redacted before the call. If the model is down, the raw alert still arrives (send another one with the model unreachable to prove it). ## Scene 5 — ask the agent (2–3 min) In the same chat: ``` /status why is billing-api crashing? what changed in minus20 in the last hour? ``` Then send a screenshot of any terminal error with a caption. Then: ``` /wrong the database was fine, the Secret with credentials was missing why is billing-api crashing? ``` The second answer quotes the verdict: "the last time this happened the real cause was…". That memory lives in the customer's Postgres, not with a vendor. ``` /report 30 ``` What to say: top causes, night and weekend share, blast radius, how often the assistant was right according to the engineers. The review a manager asks for on Monday, generated in seconds. ## Scene 6 — the audit trail (1 min) ```bash kubectl -n minus20 port-forward svc/m20-web-ui 8090:8080 & ``` Open http://localhost:8090, expand an incident, open "What the model was shown (redacted)". What did it see when it said that. Regulated teams ask for exactly this. ## Scene 7 — the same tools from your own agent (optional, 1 min) ```bash kubectl -n minus20 port-forward svc/m20-mcp 8765:8765 & ``` Add `http://localhost:8765/mcp` to Claude Code or Gemini CLI as an MCP server and ask "has billing-api crashed before, and what was the real cause?" The agent calls `search_memory`. Read-only, annotated as such. ## Scene 8 — clean up (30 s) ```bash kind delete cluster --name minus20 ``` ## If something does not work - No message in Telegram: `kubectl -n minus20 logs deploy/m20-analyzer-worker`. A `status=analysis_failed` line shows the provider's error, without URLs. - The agent is silent: `kubectl -n minus20 logs deploy/m20-agent`; the chat id must be in the allow-list, and a photo needs the vision model pulled. - Pods Pending on a cloud cluster: the Postgres PVC needs a volume driver (on EKS, the EBS CSI add-on; see `infra/terraform/addons.tf`) **and** a StorageClass: EKS's `gp2` is not the default, so `--set postgres.storageClass=gp2`.