Skip to content

Commit 8a69705

Browse files
authored
Merge pull request #335 from kagent-dev/example-telgram-with-kagent
feat: add Telegram bot example documentation demonstrating kagent A2A…
2 parents d6256b0 + 2cbabf4 commit 8a69705

3 files changed

Lines changed: 382 additions & 0 deletions

File tree

src/app/docs/kagent/examples/page.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ import QuickLink from '@/components/quick-link';
2626
<QuickLink title="Slack and A2A" description="Manage Kubernetes resources in your cluster by chatting with kagent in Slack." href="/docs/kagent/examples/slack-a2a" />
2727
<QuickLink title="Discord and A2A" description="Manage Kubernetes resources in your cluster by chatting with kagent in Discord." href="/docs/kagent/examples/discord-a2a" />
2828
<QuickLink title="Human-in-the-Loop" description="Build an agent that pauses for your approval before creating, modifying, or deleting Kubernetes resources." href="/docs/kagent/examples/human-in-the-loop" />
29+
<QuickLink title="Telegram Bot" description="Build a Telegram bot to manage your Kubernetes cluster through kagent and A2A." href="/docs/kagent/examples/telegram-bot" />
2930
</div>
3031
</div>
201 KB
Loading
Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
---
2+
title: "Telegram Bot"
3+
pageOrder: 5
4+
description: "Build a Telegram bot that talks to your Kubernetes cluster through kagent and A2A"
5+
---
6+
7+
export const metadata = {
8+
title: "Build a Telegram Bot for Kubernetes with kagent and A2A",
9+
description: "Build a Telegram bot that talks to your Kubernetes cluster through kagent and A2A",
10+
author: "kagent.dev",
11+
};
12+
13+
# Talk to Your Kubernetes Cluster from Telegram
14+
15+
What if you could manage your Kubernetes cluster from Telegram? Not through a half-baked webhook that runs kubectl — but through a real AI agent that understands context, uses tools, responds intelligently, and asks for your approval before doing anything destructive?
16+
17+
In this guide, I’ll walk you through how I built exactly that: a Telegram bot that connects to a kagent AI agent running on my home lab Kubernetes cluster (Talos Linux on Proxmox), giving me full cluster operations from my phone. The entire thing is deployed via GitOps with ArgoCD, secrets come from HashiCorp Vault, and the bot uses the A2A (Agent-to-Agent) protocol to communicate with kagent.
18+
19+
The bot maintains conversation continuity across messages (so the agent remembers what you were talking about), and supports Human-in-the-Loop (HITL) approval — when the agent wants to run a destructive operation like deleting a resource or applying a manifest, it shows you Approve/Reject buttons in Telegram before proceeding.
20+
21+
No webhooks. No public endpoints. Just polling from inside the cluster.
22+
23+
## Architecture Overview
24+
25+
Here’s what we’re building:
26+
27+
![Arch](https://github.com/kagent-dev/website/blob/example-telgram-with-kagent/src/app/docs/kagent/examples/telegram-bot/arch.png)
28+
29+
---
30+
31+
# Steps
32+
33+
## Step 1: Create Your Telegram Bot
34+
35+
1. Open Telegram, search for **@BotFather**.
36+
2. Send `/newbot`, pick a name and username.
37+
3. Copy the **bot token** it gives you.
38+
39+
40+
---
41+
42+
## Step 2: Deploy a kagent Agent
43+
44+
> **Prerequisite:** kagent running in your cluster with `kmcp` CRDs installed. If not, hit the [quickstart](https://kagent.dev/docs/kagent/getting-started/quickstart) first.
45+
46+
This agent has Kubernetes tools, Helm tools, and Prometheus — and it's exposed over A2A so any client (our Telegram bot, or anything else) can talk to it. Feel free to make changes as you please:
47+
48+
```shell
49+
kubectl apply -f - <<EOF
50+
apiVersion: kagent.dev/v1alpha2
51+
kind: Agent
52+
metadata:
53+
name: telegram-k8s-agent
54+
namespace: kagent
55+
spec:
56+
description: "Kubernetes operations agent accessible via Telegram bot"
57+
type: Declarative
58+
declarative:
59+
modelConfig: default-model-config
60+
a2aConfig:
61+
skills:
62+
- id: k8s-operations
63+
name: Kubernetes Operations
64+
description: "Query, manage, and troubleshoot Kubernetes resources"
65+
examples:
66+
- "What pods are running in the default namespace?"
67+
- "Show me the logs for pod X"
68+
- "What Helm releases are installed?"
69+
tags: [kubernetes, operations]
70+
- id: cluster-monitoring
71+
name: Cluster Monitoring
72+
description: "Query Prometheus metrics and monitor cluster health"
73+
examples:
74+
- "What is the CPU usage across nodes?"
75+
tags: [monitoring, prometheus]
76+
systemMessage: |
77+
You are a Kubernetes operations agent accessible via Telegram.
78+
Keep responses concise and well-formatted for chat readability.
79+
tools:
80+
- type: McpServer
81+
mcpServer:
82+
apiGroup: kagent.dev
83+
kind: RemoteMCPServer
84+
name: kagent-tool-server
85+
toolNames:
86+
- k8s_get_resources
87+
- k8s_describe_resource
88+
- k8s_get_pod_logs
89+
- k8s_get_events
90+
- helm_list_releases
91+
- helm_get_release
92+
- prometheus_query_tool
93+
- datetime_get_current_time
94+
requireApproval:
95+
- k8s_delete_resource
96+
- k8s_apply_manifest
97+
- helm_upgrade
98+
- helm_uninstall
99+
EOF
100+
```
101+
102+
Notice `requireApproval` — anything destructive (deleting resources, applying manifests, Helm upgrades) goes through [Human-in-the-Loop](/docs/kagent/examples/human-in-the-loop) approval in the kagent UI first. Nobody's accidentally nuking prod from a Telegram chat.
103+
104+
Verify it's working:
105+
106+
```shell
107+
kubectl port-forward -n kagent svc/kagent-controller 8083:8083
108+
curl localhost:8083/api/a2a/kagent/telegram-k8s-agent/.well-known/agent.json
109+
```
110+
111+
You should get back JSON with the agent's name and skills.
112+
113+
---
114+
115+
## Step 3: Write the Bot
116+
117+
The bot is ~460 lines of Python using python-telegram-bot (polling mode) and httpx for A2A calls. It handles three main concerns: A2A communication with session continuity, HITL approval flow via Telegram inline keyboards, and robust response parsing.
118+
119+
* The critical detail: contextId goes inside the message object, not in params. On the first message, we omit it — kagent returns a contextId in the result. We store that per Telegram user and send it on every subsequent message. This is what makes “tell me about nginx” followed by “now scale it to 3” work as a coherent conversation.
120+
121+
122+
Let’s walk through it section by section.
123+
124+
* A2A Communication: The core of the bot — sending messages to kagent and tracking contextId for session continuity:
125+
* Response Parsing: kagent returns text in different locations depending on the response state. The bot tries three sources in order:
126+
* HITL Approval Flow: When kagent returns input-required, the bot parses the adk_request_confirmation data to figure out what tool the agent wants to run, then shows Telegram inline keyboard buttons:
127+
128+
129+
### requirements.txt
130+
131+
```
132+
python-telegram-bot>=21.0
133+
httpx>=0.27.0
134+
```
135+
136+
### main.py
137+
138+
```python
139+
"""Telegram bot that forwards messages to a kagent A2A agent."""
140+
141+
import logging, os, uuid
142+
from pathlib import Path
143+
144+
import httpx
145+
from telegram import Update
146+
from telegram.ext import Application, CommandHandler, MessageHandler, filters
147+
148+
logging.basicConfig(level=logging.INFO)
149+
150+
TELEGRAM_BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
151+
KAGENT_A2A_URL = os.environ["KAGENT_A2A_URL"]
152+
153+
154+
async def send_a2a_task(message_text: str, session_id: str) -> str:
155+
"""Send a message to the kagent A2A endpoint and return the response."""
156+
task_id = str(uuid.uuid4())
157+
payload = {
158+
"jsonrpc": "2.0",
159+
"id": task_id,
160+
"method": "message/send",
161+
"params": {
162+
"id": task_id,
163+
"message": {
164+
"role": "user",
165+
"parts": [{"kind": "text", "text": message_text}],
166+
},
167+
},
168+
}
169+
async with httpx.AsyncClient(timeout=120.0) as client:
170+
resp = await client.post(
171+
KAGENT_A2A_URL,
172+
json=payload,
173+
headers={"Content-Type": "application/json"},
174+
)
175+
resp.raise_for_status()
176+
data = resp.json()
177+
178+
result = data.get("result", {})
179+
artifacts = result.get("artifacts", [])
180+
if artifacts:
181+
parts = artifacts[-1].get("parts", [])
182+
texts = [p.get("text", "") for p in parts if p.get("kind") == "text"]
183+
if texts:
184+
return "\n".join(texts)
185+
return "Agent returned no text response."
186+
187+
188+
# Per-user session tracking
189+
user_sessions: dict[int, str] = {}
190+
191+
192+
def get_session(user_id: int) -> str:
193+
if user_id not in user_sessions:
194+
user_sessions[user_id] = str(uuid.uuid4())
195+
return user_sessions[user_id]
196+
197+
198+
async def start_command(update: Update, _) -> None:
199+
await update.message.reply_text(
200+
"Hello! I'm connected to a kagent Kubernetes agent.\n\n"
201+
"Send me any message and I'll forward it to the agent.\n\n"
202+
"Commands:\n"
203+
"/start - Show this message\n"
204+
"/new - Reset your conversation session"
205+
)
206+
207+
208+
async def new_command(update: Update, _) -> None:
209+
user_id = update.effective_user.id
210+
user_sessions[user_id] = str(uuid.uuid4())
211+
await update.message.reply_text("Session reset. Starting fresh conversation.")
212+
213+
214+
async def handle_message(update: Update, _) -> None:
215+
user_id = update.effective_user.id
216+
session_id = get_session(user_id)
217+
thinking_msg = await update.message.reply_text("Thinking...")
218+
try:
219+
response = await send_a2a_task(update.message.text, session_id)
220+
for i in range(0, len(response), 4000): # Telegram 4096 char limit
221+
chunk = response[i : i + 4000]
222+
if i == 0:
223+
await thinking_msg.edit_text(chunk)
224+
else:
225+
await update.message.reply_text(chunk)
226+
except Exception as e:
227+
await thinking_msg.edit_text(f"Error contacting agent: {e}")
228+
229+
230+
def main() -> None:
231+
app = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
232+
app.add_handler(CommandHandler("start", start_command))
233+
app.add_handler(CommandHandler("new", new_command))
234+
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
235+
Path("/tmp/bot-healthy").touch() # For k8s probes
236+
app.run_polling(drop_pending_updates=True)
237+
238+
239+
if __name__ == "__main__":
240+
main()
241+
```
242+
243+
It uses polling — no ingress, no webhooks, no TLS to set up. Each Telegram user gets their own session so conversations keep context. Long responses get chunked automatically (Telegram's 4096-char limit). The `/tmp/bot-healthy` file is there for Kubernetes liveness probes.
244+
245+
### Dockerfile
246+
247+
```dockerfile
248+
FROM python:3.12-slim
249+
WORKDIR /app
250+
COPY requirements.txt .
251+
RUN pip install --no-cache-dir -r requirements.txt
252+
COPY main.py .
253+
CMD ["python", "main.py"]
254+
```
255+
256+
```shell
257+
docker build -t your-registry/telegram-kagent-bot:latest .
258+
docker push your-registry/telegram-kagent-bot:latest
259+
```
260+
261+
---
262+
263+
## Step 4: Test It Locally
264+
265+
Before you deploy anything, make sure it actually works.
266+
267+
```shell
268+
# Terminal 1: port-forward kagent
269+
kubectl port-forward -n kagent svc/kagent-controller 8083:8083
270+
271+
# Terminal 2: run the bot
272+
pip install -r requirements.txt
273+
export TELEGRAM_BOT_TOKEN=your_token_from_botfather
274+
export KAGENT_A2A_URL=http://127.0.0.1:8083/api/a2a/kagent/telegram-k8s-agent/
275+
python main.py
276+
```
277+
278+
Open Telegram, find your bot, send "What pods are running?" — if you get a real answer, you're golden.
279+
280+
---
281+
282+
## Step 5: Deploy to Kubernetes
283+
284+
Store the bot token:
285+
286+
```shell
287+
kubectl create secret generic telegram-bot-token -n kagent \
288+
--from-literal=TELEGRAM_BOT_TOKEN="your_token_from_botfather"
289+
```
290+
291+
<details>
292+
<summary>Using External Secrets Operator instead?</summary>
293+
294+
```yaml
295+
apiVersion: external-secrets.io/v1
296+
kind: ExternalSecret
297+
metadata:
298+
name: telegram-bot-token
299+
namespace: kagent
300+
spec:
301+
refreshInterval: "1h"
302+
secretStoreRef:
303+
name: vault-backend
304+
kind: ClusterSecretStore
305+
target:
306+
name: telegram-bot-token
307+
creationPolicy: Owner
308+
data:
309+
- secretKey: TELEGRAM_BOT_TOKEN
310+
remoteRef:
311+
key: telegram
312+
property: api_key
313+
```
314+
315+
</details>
316+
317+
Deploy the bot:
318+
319+
```yaml
320+
apiVersion: apps/v1
321+
kind: Deployment
322+
metadata:
323+
name: telegram-bot
324+
namespace: kagent
325+
labels:
326+
app: telegram-bot
327+
spec:
328+
replicas: 1
329+
strategy:
330+
type: Recreate
331+
selector:
332+
matchLabels:
333+
app: telegram-bot
334+
template:
335+
metadata:
336+
labels:
337+
app: telegram-bot
338+
spec:
339+
containers:
340+
- name: bot
341+
image: your-registry/telegram-kagent-bot:latest
342+
env:
343+
- name: TELEGRAM_BOT_TOKEN
344+
valueFrom:
345+
secretKeyRef:
346+
name: telegram-bot-token
347+
key: TELEGRAM_BOT_TOKEN
348+
- name: KAGENT_A2A_URL
349+
value: "http://kagent-controller.kagent.svc.cluster.local:8083/api/a2a/kagent/telegram-k8s-agent/"
350+
livenessProbe:
351+
exec:
352+
command: ["cat", "/tmp/bot-healthy"]
353+
initialDelaySeconds: 10
354+
periodSeconds: 30
355+
resources:
356+
requests:
357+
cpu: 50m
358+
memory: 64Mi
359+
limits:
360+
cpu: 200m
361+
memory: 128Mi
362+
```
363+
364+
The strategy is `Recreate` on purpose — Telegram polling doesn't support multiple consumers. Two pods polling the same bot token means duplicate or missed messages.
365+
366+
```shell
367+
kubectl apply -f deployment.yaml
368+
```
369+
370+
---
371+
372+
## Take It Further
373+
374+
The interesting part isn't the Telegram bot — it's the pattern. The bot is just a transport layer. A2A is the interface. That means:
375+
376+
- **Multiple agents, one bot.** Add Telegram commands like `/k8s`, `/istio`, `/security` that route to different kagent agents.
377+
- **Any chat platform.** Swap `python-telegram-bot` for `discord.py` or `slack-bolt`. Everything else stays the same.
378+
- **Multi-modal.** kagent supports image parts — screenshot a Grafana dashboard and ask "what's wrong here?"
379+
- **AgentGateway.** Put [AgentGateway](https://agentgateway.dev) in front of the A2A endpoint for rate limiting, auth, and observability.
380+
381+
Questions? Come find us on [Discord](https://discord.gg/Fu3k65f2k3).

0 commit comments

Comments
 (0)