Skip to content

Commit f67f765

Browse files
committed
feat: add Slack bot integration for kagent with HITL support
Adds a production-ready Slack bot that provides a unified interface to interact with Kagent agents through Slack, including full Human-in-the-Loop (HITL) support for tool approvals. Key features: - Socket Mode bot for @mentions and direct messages - Dynamic agent discovery and intelligent routing - Real-time streaming responses with HITL approval workflow - RBAC with Slack user group integration - Prometheus metrics and structured logging - Complete Kubernetes manifests (Deployment, HPA, PDB, RBAC) HITL Implementation: - Structured DataPart decisions (decision_type: tool_approval) for reliable parsing - stream_agent_with_parts() method for mixed TextPart/DataPart messages - Agent contextId extraction and tracking for proper task resumption - Interactive approval buttons with streaming completion responses - Follows A2A protocol patterns for structured messaging The bot integrates with Kagent's A2A protocol via JSON-RPC 2.0 and supports all agent types (Declarative, BYO, etc.). Follows bolt-python async patterns. Tested end-to-end with live agents. Signed-off-by: apexlnc <[email protected]>
1 parent 1634791 commit f67f765

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+3694
-0
lines changed

helm/kagent/templates/_helpers.tpl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,19 @@ Engine labels
106106
{{ include "kagent.labels" . }}
107107
app.kubernetes.io/component: engine
108108
{{- end }}
109+
110+
{{/*
111+
Slackbot selector labels
112+
*/}}
113+
{{- define "kagent.slackbot.selectorLabels" -}}
114+
{{ include "kagent.selectorLabels" . }}
115+
app.kubernetes.io/component: slackbot
116+
{{- end }}
117+
118+
{{/*
119+
Slackbot labels
120+
*/}}
121+
{{- define "kagent.slackbot.labels" -}}
122+
{{ include "kagent.labels" . }}
123+
app.kubernetes.io/component: slackbot
124+
{{- end }}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{{- if .Values.slackbot.enabled -}}
2+
apiVersion: v1
3+
kind: ConfigMap
4+
metadata:
5+
name: {{ include "kagent.fullname" . }}-slackbot-config
6+
namespace: {{ include "kagent.namespace" . }}
7+
labels:
8+
{{- include "kagent.slackbot.labels" . | nindent 4 }}
9+
data:
10+
permissions.yaml: |
11+
# Agent-level permissions configuration
12+
# Configure which users/groups can access which agents via Slackbot
13+
14+
# Example: Restrict k8s-agent to SRE team
15+
# agent_permissions:
16+
# kagent/k8s-agent:
17+
# user_groups:
18+
# - S0T8FCWSB # Replace with your Slack user group ID
19+
# users:
20+
21+
# deny_message: "K8s agent requires @sre-team membership"
22+
23+
# Default: If agent not listed above, it's public (accessible to all)
24+
{{- toYaml .Values.slackbot.permissions | nindent 4 }}
25+
26+
# Global settings
27+
settings:
28+
user_group_cache_ttl: {{ .Values.slackbot.permissions.settings.user_group_cache_ttl | default 300 }}
29+
{{- end }}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
{{- if .Values.slackbot.enabled -}}
2+
apiVersion: apps/v1
3+
kind: Deployment
4+
metadata:
5+
name: {{ include "kagent.fullname" . }}-slackbot
6+
namespace: {{ include "kagent.namespace" . }}
7+
labels:
8+
{{- include "kagent.slackbot.labels" . | nindent 4 }}
9+
spec:
10+
{{- if not .Values.slackbot.autoscaling.enabled }}
11+
replicas: {{ .Values.slackbot.replicas }}
12+
{{- end }}
13+
selector:
14+
matchLabels:
15+
{{- include "kagent.slackbot.selectorLabels" . | nindent 6 }}
16+
template:
17+
metadata:
18+
annotations:
19+
{{- with .Values.slackbot.podAnnotations | default .Values.podAnnotations }}
20+
{{- toYaml . | nindent 8 }}
21+
{{- end }}
22+
labels:
23+
{{- include "kagent.slackbot.selectorLabels" . | nindent 8 }}
24+
spec:
25+
{{- with .Values.imagePullSecrets }}
26+
imagePullSecrets:
27+
{{- toYaml . | nindent 8 }}
28+
{{- end }}
29+
securityContext:
30+
{{- toYaml .Values.podSecurityContext | nindent 8 }}
31+
serviceAccountName: {{ include "kagent.fullname" . }}-slackbot
32+
{{- with .Values.slackbot.nodeSelector }}
33+
nodeSelector:
34+
{{- toYaml . | nindent 8 }}
35+
{{- end }}
36+
{{- with .Values.slackbot.tolerations }}
37+
tolerations:
38+
{{- toYaml . | nindent 8 }}
39+
{{- end }}
40+
containers:
41+
- name: slackbot
42+
securityContext:
43+
{{- toYaml .Values.securityContext | nindent 12 }}
44+
image: "{{ .Values.slackbot.image.registry | default .Values.registry }}/{{ .Values.slackbot.image.repository }}:{{ coalesce .Values.tag .Values.slackbot.image.tag .Chart.Version }}"
45+
imagePullPolicy: {{ .Values.slackbot.image.pullPolicy | default .Values.imagePullPolicy }}
46+
env:
47+
# Slack credentials from secret
48+
- name: SLACK_BOT_TOKEN
49+
valueFrom:
50+
secretKeyRef:
51+
name: {{ include "kagent.fullname" . }}-slackbot-secrets
52+
key: slack-bot-token
53+
- name: SLACK_APP_TOKEN
54+
valueFrom:
55+
secretKeyRef:
56+
name: {{ include "kagent.fullname" . }}-slackbot-secrets
57+
key: slack-app-token
58+
- name: SLACK_SIGNING_SECRET
59+
valueFrom:
60+
secretKeyRef:
61+
name: {{ include "kagent.fullname" . }}-slackbot-secrets
62+
key: slack-signing-secret
63+
# Kagent configuration
64+
- name: KAGENT_BASE_URL
65+
value: "http://{{ include "kagent.fullname" . }}-controller.{{ include "kagent.namespace" . }}.svc.cluster.local:{{ .Values.controller.service.ports.port }}"
66+
- name: KAGENT_TIMEOUT
67+
value: {{ .Values.slackbot.config.kagentTimeout | quote }}
68+
# Server configuration
69+
- name: SERVER_HOST
70+
value: {{ .Values.slackbot.config.serverHost | quote }}
71+
- name: SERVER_PORT
72+
value: {{ .Values.slackbot.config.serverPort | quote }}
73+
# Logging
74+
- name: LOG_LEVEL
75+
value: {{ .Values.slackbot.config.logLevel | quote }}
76+
# Permissions file
77+
- name: PERMISSIONS_FILE
78+
value: "/app/config/permissions.yaml"
79+
{{- with .Values.slackbot.env }}
80+
{{- toYaml . | nindent 12 }}
81+
{{- end }}
82+
ports:
83+
- name: http
84+
containerPort: {{ .Values.slackbot.config.serverPort }}
85+
protocol: TCP
86+
resources:
87+
{{- toYaml .Values.slackbot.resources | nindent 12 }}
88+
livenessProbe:
89+
httpGet:
90+
path: /health
91+
port: http
92+
initialDelaySeconds: 30
93+
periodSeconds: 10
94+
timeoutSeconds: 5
95+
failureThreshold: 3
96+
readinessProbe:
97+
httpGet:
98+
path: /ready
99+
port: http
100+
initialDelaySeconds: 5
101+
periodSeconds: 5
102+
timeoutSeconds: 3
103+
failureThreshold: 3
104+
volumeMounts:
105+
- name: tmp
106+
mountPath: /tmp
107+
- name: permissions-config
108+
mountPath: /app/config
109+
readOnly: true
110+
volumes:
111+
- name: tmp
112+
emptyDir: {}
113+
- name: permissions-config
114+
configMap:
115+
name: {{ include "kagent.fullname" . }}-slackbot-config
116+
items:
117+
- key: permissions.yaml
118+
path: permissions.yaml
119+
{{- end }}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{{- if and .Values.slackbot.enabled .Values.slackbot.autoscaling.enabled -}}
2+
apiVersion: autoscaling/v2
3+
kind: HorizontalPodAutoscaler
4+
metadata:
5+
name: {{ include "kagent.fullname" . }}-slackbot
6+
namespace: {{ include "kagent.namespace" . }}
7+
labels:
8+
{{- include "kagent.slackbot.labels" . | nindent 4 }}
9+
spec:
10+
scaleTargetRef:
11+
apiVersion: apps/v1
12+
kind: Deployment
13+
name: {{ include "kagent.fullname" . }}-slackbot
14+
minReplicas: {{ .Values.slackbot.autoscaling.minReplicas }}
15+
maxReplicas: {{ .Values.slackbot.autoscaling.maxReplicas }}
16+
metrics:
17+
{{- if .Values.slackbot.autoscaling.targetCPUUtilizationPercentage }}
18+
- type: Resource
19+
resource:
20+
name: cpu
21+
target:
22+
type: Utilization
23+
averageUtilization: {{ .Values.slackbot.autoscaling.targetCPUUtilizationPercentage }}
24+
{{- end }}
25+
{{- if .Values.slackbot.autoscaling.targetMemoryUtilizationPercentage }}
26+
- type: Resource
27+
resource:
28+
name: memory
29+
target:
30+
type: Utilization
31+
averageUtilization: {{ .Values.slackbot.autoscaling.targetMemoryUtilizationPercentage }}
32+
{{- end }}
33+
{{- with .Values.slackbot.autoscaling.behavior }}
34+
behavior:
35+
{{- toYaml . | nindent 4 }}
36+
{{- end }}
37+
{{- end }}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{{- if and .Values.slackbot.enabled .Values.slackbot.podDisruptionBudget.enabled -}}
2+
apiVersion: policy/v1
3+
kind: PodDisruptionBudget
4+
metadata:
5+
name: {{ include "kagent.fullname" . }}-slackbot
6+
namespace: {{ include "kagent.namespace" . }}
7+
labels:
8+
{{- include "kagent.slackbot.labels" . | nindent 4 }}
9+
spec:
10+
{{- if .Values.slackbot.podDisruptionBudget.minAvailable }}
11+
minAvailable: {{ .Values.slackbot.podDisruptionBudget.minAvailable }}
12+
{{- end }}
13+
{{- if .Values.slackbot.podDisruptionBudget.maxUnavailable }}
14+
maxUnavailable: {{ .Values.slackbot.podDisruptionBudget.maxUnavailable }}
15+
{{- end }}
16+
selector:
17+
matchLabels:
18+
{{- include "kagent.slackbot.selectorLabels" . | nindent 6 }}
19+
{{- end }}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{{- if and .Values.slackbot.enabled .Values.slackbot.rbac.create -}}
2+
apiVersion: rbac.authorization.k8s.io/v1
3+
kind: Role
4+
metadata:
5+
name: {{ include "kagent.fullname" . }}-slackbot
6+
namespace: {{ include "kagent.namespace" . }}
7+
labels:
8+
{{- include "kagent.slackbot.labels" . | nindent 4 }}
9+
rules:
10+
# Slackbot doesn't need K8s API access - it only talks to Slack and kagent HTTP API
11+
# These minimal permissions are for potential future features (e.g., checking pod status)
12+
{{- with .Values.slackbot.rbac.rules }}
13+
{{- toYaml . | nindent 2 }}
14+
{{- end }}
15+
{{- end }}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{{- if and .Values.slackbot.enabled .Values.slackbot.rbac.create -}}
2+
apiVersion: rbac.authorization.k8s.io/v1
3+
kind: RoleBinding
4+
metadata:
5+
name: {{ include "kagent.fullname" . }}-slackbot
6+
namespace: {{ include "kagent.namespace" . }}
7+
labels:
8+
{{- include "kagent.slackbot.labels" . | nindent 4 }}
9+
roleRef:
10+
apiGroup: rbac.authorization.k8s.io
11+
kind: Role
12+
name: {{ include "kagent.fullname" . }}-slackbot
13+
subjects:
14+
- kind: ServiceAccount
15+
name: {{ include "kagent.fullname" . }}-slackbot
16+
namespace: {{ include "kagent.namespace" . }}
17+
{{- end }}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{{- if .Values.slackbot.enabled -}}
2+
apiVersion: v1
3+
kind: Secret
4+
metadata:
5+
name: {{ include "kagent.fullname" . }}-slackbot-secrets
6+
namespace: {{ include "kagent.namespace" . }}
7+
labels:
8+
{{- include "kagent.slackbot.labels" . | nindent 4 }}
9+
type: Opaque
10+
stringData:
11+
{{- if .Values.slackbot.secrets.slackBotToken }}
12+
slack-bot-token: {{ .Values.slackbot.secrets.slackBotToken | quote }}
13+
{{- else }}
14+
# IMPORTANT: Replace with your actual Slack Bot Token (xoxb-...)
15+
# Or set via: --set slackbot.secrets.slackBotToken=xoxb-...
16+
slack-bot-token: ""
17+
{{- end }}
18+
{{- if .Values.slackbot.secrets.slackAppToken }}
19+
slack-app-token: {{ .Values.slackbot.secrets.slackAppToken | quote }}
20+
{{- else }}
21+
# IMPORTANT: Replace with your actual Slack App Token (xapp-...)
22+
# Or set via: --set slackbot.secrets.slackAppToken=xapp-...
23+
slack-app-token: ""
24+
{{- end }}
25+
{{- if .Values.slackbot.secrets.slackSigningSecret }}
26+
slack-signing-secret: {{ .Values.slackbot.secrets.slackSigningSecret | quote }}
27+
{{- else }}
28+
# IMPORTANT: Replace with your actual Slack Signing Secret
29+
# Or set via: --set slackbot.secrets.slackSigningSecret=...
30+
slack-signing-secret: ""
31+
{{- end }}
32+
{{- end }}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{{- if .Values.slackbot.enabled -}}
2+
apiVersion: v1
3+
kind: Service
4+
metadata:
5+
name: {{ include "kagent.fullname" . }}-slackbot
6+
namespace: {{ include "kagent.namespace" . }}
7+
labels:
8+
{{- include "kagent.slackbot.labels" . | nindent 4 }}
9+
{{- with .Values.slackbot.service.annotations }}
10+
annotations:
11+
{{- toYaml . | nindent 4 }}
12+
{{- end }}
13+
spec:
14+
type: {{ .Values.slackbot.service.type }}
15+
ports:
16+
- port: {{ .Values.slackbot.service.ports.port }}
17+
targetPort: {{ .Values.slackbot.service.ports.targetPort }}
18+
protocol: TCP
19+
name: http
20+
selector:
21+
{{- include "kagent.slackbot.selectorLabels" . | nindent 4 }}
22+
{{- end }}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{{- if .Values.slackbot.enabled -}}
2+
apiVersion: v1
3+
kind: ServiceAccount
4+
metadata:
5+
name: {{ include "kagent.fullname" . }}-slackbot
6+
namespace: {{ include "kagent.namespace" . }}
7+
labels:
8+
{{- include "kagent.slackbot.labels" . | nindent 4 }}
9+
{{- end }}

0 commit comments

Comments
 (0)