feat: Claude Code Monitor — lanes, pipelines and a merged workspace

Internal SmartGift build of a Claude Code monitoring dashboard.

Lanes: a durable unit of parallel agent work, one per working directory,
tracked across session restarts. Managed lanes are git worktrees the
dashboard provisions and can reset or remove behind a three-check destroy
guard and a counted preflight; adopted lanes are directories you already
own and are never destroyable.

Pipelines: a lane moves through pipeline stages. A stage the agent declares
with evidence renders green; a stage inferred from the tool-event stream
renders dashed amber and never counts as done. Detection is forward-only
within a 30-minute window, and never writes the declared stage.

Workspace: one page at /run with a lane grid, the selected lane's pipeline,
and a full Claude console behind a disclosure.
This commit is contained in:
2026-07-29 17:07:45 +07:00
commit 57dc91585d
783 changed files with 221743 additions and 0 deletions
@@ -0,0 +1,79 @@
╔══════════════════════════════════════════════════════════════════════╗
║ Claude Code Agent Monitor - Deployment Notes ║
╚══════════════════════════════════════════════════════════════════════╝
{{- $fullName := include "agent-monitor.fullname" . -}}
🎉 {{ $fullName }} has been deployed to namespace "{{ .Release.Namespace }}"!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📡 Accessing the Dashboard:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
URL: http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get -n {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ $fullName }})
export NODE_IP=$(kubectl get nodes -o jsonpath="{.items[0].status.addresses[0].address}")
echo "Dashboard URL: http://$NODE_IP:$NODE_PORT"
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
Watch status with:
kubectl get -n {{ .Release.Namespace }} svc {{ $fullName }} -w
export SERVICE_IP=$(kubectl get svc -n {{ .Release.Namespace }} {{ $fullName }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo "Dashboard URL: http://$SERVICE_IP:{{ .Values.service.port }}"
{{- else }}
Port-forward to access locally:
kubectl port-forward -n {{ .Release.Namespace }} svc/{{ $fullName }} 4820:{{ .Values.service.port }}
Then open: http://localhost:4820
{{- end }}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔍 Health Check:
kubectl exec -n {{ .Release.Namespace }} deploy/{{ $fullName }} -- wget -qO- http://localhost:{{ .Values.service.targetPort }}/api/health
🧪 Run Tests:
helm test {{ .Release.Name }} -n {{ .Release.Namespace }}
📊 View Logs:
kubectl logs -n {{ .Release.Namespace }} -l app.kubernetes.io/name={{ include "agent-monitor.name" . }} -f
📈 Check Pods:
kubectl get pods -n {{ .Release.Namespace }} -l app.kubernetes.io/name={{ include "agent-monitor.name" . }}
{{- if .Values.persistence.enabled }}
💾 Persistent Storage:
SQLite data is stored on PVC: {{ $fullName }}-data ({{ .Values.persistence.size }})
{{- end }}
{{- if .Values.mcp.enabled }}
🔌 MCP Sidecar:
MCP server is running on port {{ .Values.mcp.port }}
Access via: kubectl port-forward -n {{ .Release.Namespace }} svc/{{ $fullName }} {{ .Values.mcp.port }}:{{ .Values.mcp.port }}
{{- end }}
{{- if .Values.autoscaling.enabled }}
📐 Autoscaling:
Min replicas: {{ .Values.autoscaling.minReplicas }}
Max replicas: {{ .Values.autoscaling.maxReplicas }}
CPU target: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}%
Memory target: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}%
{{- end }}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -0,0 +1,88 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "agent-monitor.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this
(by the DNS naming spec). If release name contains the chart name it will be used
as a full name.
*/}}
{{- define "agent-monitor.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "agent-monitor.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "agent-monitor.labels" -}}
helm.sh/chart: {{ include "agent-monitor.chart" . }}
{{ include "agent-monitor.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/part-of: claude-code-agent-monitor
{{- end }}
{{/*
Selector labels
*/}}
{{- define "agent-monitor.selectorLabels" -}}
app.kubernetes.io/name: {{ include "agent-monitor.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "agent-monitor.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "agent-monitor.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
Return the container image string
*/}}
{{- define "agent-monitor.image" -}}
{{- $tag := default .Chart.AppVersion .Values.image.tag -}}
{{- if .Values.image.registry -}}
{{- printf "%s/%s:%s" .Values.image.registry .Values.image.repository $tag -}}
{{- else -}}
{{- printf "%s:%s" .Values.image.repository $tag -}}
{{- end -}}
{{- end }}
{{/*
Return the MCP sidecar container image string
*/}}
{{- define "agent-monitor.mcpImage" -}}
{{- $tag := default .Chart.AppVersion .Values.mcp.image.tag -}}
{{- if .Values.mcp.image.registry -}}
{{- printf "%s/%s:%s" .Values.mcp.image.registry .Values.mcp.image.repository $tag -}}
{{- else -}}
{{- printf "%s:%s" .Values.mcp.image.repository $tag -}}
{{- end -}}
{{- end }}
@@ -0,0 +1,13 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "agent-monitor.fullname" . }}-config
labels:
{{- include "agent-monitor.labels" . | nindent 4 }}
data:
{{- range $key, $value := .Values.env }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- range $key, $value := .Values.extraEnv }}
{{ $key }}: {{ $value | quote }}
{{- end }}
@@ -0,0 +1,159 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "agent-monitor.fullname" . }}
labels:
{{- include "agent-monitor.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
revisionHistoryLimit: {{ .Values.deployment.revisionHistoryLimit | default 5 }}
{{- with .Values.deployment.strategy }}
strategy:
{{- toYaml . | nindent 4 }}
{{- end }}
selector:
matchLabels:
{{- include "agent-monitor.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "agent-monitor.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "agent-monitor.serviceAccountName" . }}
automountServiceAccountToken: false
{{- with .Values.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
# ── Main application container ────────────────────────────────────
- name: {{ .Chart.Name }}
{{- with .Values.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
image: {{ include "agent-monitor.image" . }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
protocol: TCP
{{- with .Values.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.startupProbe }}
startupProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
envFrom:
- configMapRef:
name: {{ include "agent-monitor.fullname" . }}-config
{{- with .Values.extraEnvFrom }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
lifecycle:
preStop:
exec:
# Allow in-flight requests (including WebSockets) to drain before SIGTERM
command: ["sh", "-c", "sleep 5"]
volumeMounts:
- name: data
mountPath: /app/data
- name: tmp
mountPath: /tmp
{{- if .Values.mcp.enabled }}
# ── MCP sidecar container ─────────────────────────────────────────
- name: mcp
{{- with .Values.mcp.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
image: {{ include "agent-monitor.mcpImage" . }}
imagePullPolicy: {{ .Values.mcp.image.pullPolicy }}
ports:
- name: mcp
containerPort: {{ .Values.mcp.port }}
protocol: TCP
env:
{{- range $key, $value := .Values.mcp.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
startupProbe:
tcpSocket:
port: mcp
failureThreshold: 30
periodSeconds: 2
livenessProbe:
tcpSocket:
port: mcp
initialDelaySeconds: 15
periodSeconds: 20
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
tcpSocket:
port: mcp
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
{{- with .Values.mcp.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
- name: tmp
mountPath: /tmp
{{- end }}
volumes:
- name: data
{{- if .Values.persistence.enabled }}
persistentVolumeClaim:
claimName: {{ .Values.persistence.existingClaim | default (printf "%s-data" (include "agent-monitor.fullname" .)) }}
{{- else }}
emptyDir: {}
{{- end }}
- name: tmp
emptyDir:
sizeLimit: 100Mi
terminationGracePeriodSeconds: 30
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.topologySpreadConstraints }}
topologySpreadConstraints:
{{- toYaml . | nindent 8 }}
{{- end }}
@@ -0,0 +1,53 @@
{{- if .Values.autoscaling.enabled -}}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "agent-monitor.fullname" . }}
labels:
{{- include "agent-monitor.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "agent-monitor.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
behavior:
scaleDown:
stabilizationWindowSeconds: {{ .Values.autoscaling.scaleDownStabilizationWindowSeconds | default 300 }}
policies:
- type: Pods
value: 1
periodSeconds: 60
- type: Percent
value: 10
periodSeconds: 60
selectPolicy: Min
scaleUp:
stabilizationWindowSeconds: 30
policies:
- type: Pods
value: 2
periodSeconds: 60
- type: Percent
value: 50
periodSeconds: 60
selectPolicy: Max
{{- end }}
@@ -0,0 +1,41 @@
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "agent-monitor.fullname" . }}
labels:
{{- include "agent-monitor.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "agent-monitor.fullname" $ }}
port:
name: http
{{- end }}
{{- end }}
{{- end }}
@@ -0,0 +1,46 @@
{{- if .Values.networkPolicy.enabled -}}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "agent-monitor.fullname" . }}
labels:
{{- include "agent-monitor.labels" . | nindent 4 }}
spec:
podSelector:
matchLabels:
{{- include "agent-monitor.selectorLabels" . | nindent 6 }}
policyTypes:
- Ingress
- Egress
ingress:
# Allow HTTP traffic to the application port from any pod (ingress controllers, etc.)
- ports:
- port: {{ .Values.service.targetPort }}
protocol: TCP
{{- if .Values.mcp.enabled }}
# Allow MCP traffic when sidecar is enabled
- ports:
- port: {{ .Values.mcp.port }}
protocol: TCP
{{- end }}
{{- with .Values.networkPolicy.additionalIngressRules }}
{{- toYaml . | nindent 4 }}
{{- end }}
egress:
# Allow DNS resolution
- ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
# Allow outbound HTTPS (for external API calls)
- ports:
- port: 443
protocol: TCP
# Allow internal communication within the cluster
- to:
- podSelector: {}
{{- with .Values.networkPolicy.additionalEgressRules }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
@@ -0,0 +1,18 @@
{{- if .Values.podDisruptionBudget.enabled -}}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ include "agent-monitor.fullname" . }}
labels:
{{- include "agent-monitor.labels" . | nindent 4 }}
spec:
{{- if .Values.podDisruptionBudget.minAvailable }}
minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}
{{- end }}
{{- if .Values.podDisruptionBudget.maxUnavailable }}
maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }}
{{- end }}
selector:
matchLabels:
{{- include "agent-monitor.selectorLabels" . | nindent 6 }}
{{- end }}
@@ -0,0 +1,23 @@
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) -}}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "agent-monitor.fullname" . }}-data
labels:
{{- include "agent-monitor.labels" . | nindent 4 }}
{{- with .Values.persistence.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
accessModes:
{{- range .Values.persistence.accessModes }}
- {{ . }}
{{- end }}
{{- if .Values.persistence.storageClass }}
storageClassName: {{ .Values.persistence.storageClass | quote }}
{{- end }}
resources:
requests:
storage: {{ .Values.persistence.size }}
{{- end }}
@@ -0,0 +1,33 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "agent-monitor.fullname" . }}
labels:
{{- include "agent-monitor.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
# Sticky sessions for WebSocket connections
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800
ports:
- name: http
port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
{{- if and (eq .Values.service.type "NodePort") .Values.service.nodePort }}
nodePort: {{ .Values.service.nodePort }}
{{- end }}
{{- if .Values.mcp.enabled }}
- name: mcp
port: {{ .Values.mcp.port }}
targetPort: mcp
protocol: TCP
{{- end }}
selector:
{{- include "agent-monitor.selectorLabels" . | nindent 4 }}
@@ -0,0 +1,13 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "agent-monitor.serviceAccountName" . }}
labels:
{{- include "agent-monitor.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken | default false }}
{{- end }}
@@ -0,0 +1,29 @@
{{- if and .Values.monitoring.enabled .Values.monitoring.serviceMonitor.enabled -}}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ include "agent-monitor.fullname" . }}
{{- if .Values.monitoring.serviceMonitor.namespace }}
namespace: {{ .Values.monitoring.serviceMonitor.namespace }}
{{- end }}
labels:
{{- include "agent-monitor.labels" . | nindent 4 }}
{{- with .Values.monitoring.serviceMonitor.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
selector:
matchLabels:
{{- include "agent-monitor.selectorLabels" . | nindent 6 }}
{{- if .Values.monitoring.serviceMonitor.namespace }}
namespaceSelector:
matchNames:
- {{ .Release.Namespace }}
{{- end }}
endpoints:
- port: http
path: {{ .Values.monitoring.serviceMonitor.path | default "/api/metrics" }}
interval: {{ .Values.monitoring.serviceMonitor.interval | default "30s" }}
scrapeTimeout: {{ .Values.monitoring.serviceMonitor.scrapeTimeout | default "10s" }}
honorLabels: {{ .Values.monitoring.serviceMonitor.honorLabels | default false }}
{{- end }}
@@ -0,0 +1,34 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "agent-monitor.fullname" . }}-test-connection"
labels:
{{- include "agent-monitor.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
restartPolicy: Never
securityContext:
runAsNonRoot: true
runAsUser: 65534
runAsGroup: 65534
seccompProfile:
type: RuntimeDefault
containers:
- name: wget
image: busybox:1.36
command: ['wget']
args:
- '--timeout=10'
- '--tries=3'
- '-qO-'
- 'http://{{ include "agent-monitor.fullname" . }}:{{ .Values.service.port }}/api/health'
securityContext:
runAsNonRoot: true
runAsUser: 65534
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL