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,241 @@
# ─────────────────────────────────────────────────────────────────────────────
# Alertmanager configuration for Claude Code Agent Monitor
#
# Replace placeholder values (marked with <PLACEHOLDER>) before deploying.
# ─────────────────────────────────────────────────────────────────────────────
global:
# SMTP defaults for email notifications
smtp_smarthost: "<SMTP_HOST>:587"
smtp_from: "alertmanager@example.com"
smtp_auth_username: "<SMTP_USERNAME>"
smtp_auth_password: "<SMTP_PASSWORD>"
smtp_require_tls: true
# Slack API URL (override per-receiver if needed)
slack_api_url: "<SLACK_WEBHOOK_URL>"
# PagerDuty URL
pagerduty_url: "https://events.pagerduty.com/v2/enqueue"
# Global resolve timeout
resolve_timeout: 5m
# ── Templates ───────────────────────────────────────────────────────────────
templates:
- "/etc/alertmanager/templates/*.tmpl"
# ── Inhibition rules ───────────────────────────────────────────────────────
# Prevent lower-severity alerts from firing when a higher-severity alert
# for the same service/alertname is already active.
inhibit_rules:
# If a critical alert is firing, suppress warning alerts for the same service
- source_matchers:
- severity = critical
target_matchers:
- severity = warning
equal:
- alertname
- service
- namespace
# If a critical alert is firing, suppress info alerts for the same service
- source_matchers:
- severity = critical
target_matchers:
- severity = info
equal:
- service
- namespace
# If a warning alert is firing, suppress info alerts for the same service
- source_matchers:
- severity = warning
target_matchers:
- severity = info
equal:
- service
- namespace
# If AgentMonitorDown is firing, suppress all other agent-monitor alerts
- source_matchers:
- alertname = AgentMonitorDown
target_matchers:
- service = agent-monitor
equal:
- namespace
# ── Route tree ──────────────────────────────────────────────────────────────
route:
# Default receiver for unmatched alerts
receiver: slack-default
# Group alerts by these labels
group_by:
- alertname
- environment
- namespace
# Wait before sending initial notification (allows grouping)
group_wait: 30s
# Wait before sending updates to an existing group
group_interval: 5m
# Wait before re-sending a resolved notification
repeat_interval: 4h
# Child routes (evaluated top-to-bottom, first match wins)
routes:
# ── Critical → PagerDuty + Slack ──────────────────────────────────────
- matchers:
- severity = critical
receiver: pagerduty-critical
group_wait: 10s
repeat_interval: 1h
continue: true # Also notify Slack
- matchers:
- severity = critical
receiver: slack-critical
group_wait: 10s
repeat_interval: 1h
# ── Warning → Slack ───────────────────────────────────────────────────
- matchers:
- severity = warning
receiver: slack-warning
group_wait: 30s
repeat_interval: 4h
# ── Info → Email ──────────────────────────────────────────────────────
- matchers:
- severity = info
receiver: email-info
group_wait: 1m
repeat_interval: 12h
# ── Watchdog (deadman's switch) ───────────────────────────────────────
- matchers:
- alertname = Watchdog
receiver: "null"
repeat_interval: 5m
# ── Receivers ───────────────────────────────────────────────────────────────
receivers:
# ── Null receiver (discard alerts) ────────────────────────────────────────
- name: "null"
# ── Slack: Default channel ────────────────────────────────────────────────
- name: slack-default
slack_configs:
- channel: "#agent-monitor-alerts"
send_resolved: true
username: "AlertManager"
icon_emoji: ":bell:"
title: >-
[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}]
{{ .CommonLabels.alertname }}
text: >-
{{ range .Alerts }}
*Alert:* {{ .Labels.alertname }} - `{{ .Labels.severity }}`
*Environment:* {{ .Labels.namespace }}
*Description:* {{ .Annotations.description }}
*Details:*
{{ range .Labels.SortedPairs }} • *{{ .Name }}:* `{{ .Value }}`
{{ end }}
{{ end }}
actions:
- type: button
text: "Dashboard :grafana:"
url: "{{ (index .Alerts 0).Annotations.dashboard_url }}"
- type: button
text: "Runbook :book:"
url: "{{ (index .Alerts 0).Annotations.runbook_url }}"
# ── Slack: Critical alerts ────────────────────────────────────────────────
- name: slack-critical
slack_configs:
- channel: "#agent-monitor-critical"
send_resolved: true
username: "AlertManager"
icon_emoji: ":rotating_light:"
color: >-
{{ if eq .Status "firing" }}danger{{ else }}good{{ end }}
title: >-
:rotating_light: [{{ .Status | toUpper }}]
{{ .CommonLabels.alertname }}
text: >-
{{ range .Alerts }}
*CRITICAL Alert:* {{ .Labels.alertname }}
*Environment:* {{ .Labels.namespace }}
*Description:* {{ .Annotations.description }}
*Summary:* {{ .Annotations.summary }}
*Started:* {{ .StartsAt.Format "2006-01-02 15:04:05 UTC" }}
{{ end }}
actions:
- type: button
text: "Dashboard"
url: "{{ (index .Alerts 0).Annotations.dashboard_url }}"
- type: button
text: "Runbook"
url: "{{ (index .Alerts 0).Annotations.runbook_url }}"
# ── Slack: Warning alerts ─────────────────────────────────────────────────
- name: slack-warning
slack_configs:
- channel: "#agent-monitor-alerts"
send_resolved: true
username: "AlertManager"
icon_emoji: ":warning:"
color: >-
{{ if eq .Status "firing" }}warning{{ else }}good{{ end }}
title: >-
:warning: [{{ .Status | toUpper }}]
{{ .CommonLabels.alertname }}
text: >-
{{ range .Alerts }}
*Warning:* {{ .Labels.alertname }}
*Environment:* {{ .Labels.namespace }}
*Description:* {{ .Annotations.description }}
{{ end }}
# ── PagerDuty: Critical alerts ───────────────────────────────────────────
- name: pagerduty-critical
pagerduty_configs:
- routing_key: "<PAGERDUTY_ROUTING_KEY>"
severity: >-
{{ if eq .CommonLabels.severity "critical" }}critical{{ else }}warning{{ end }}
description: >-
{{ .CommonAnnotations.summary }}
details:
environment: "{{ .CommonLabels.namespace }}"
alertname: "{{ .CommonLabels.alertname }}"
service: "{{ .CommonLabels.service }}"
description: "{{ .CommonAnnotations.description }}"
num_firing: "{{ .Alerts.Firing | len }}"
# ── Email: Info-level alerts ──────────────────────────────────────────────
- name: email-info
email_configs:
- to: "<ALERT_EMAIL_RECIPIENTS>"
send_resolved: true
headers:
Subject: >-
[Agent Monitor {{ .Status | toUpper }}]
{{ .CommonLabels.alertname }}
({{ .CommonLabels.namespace }})
html: |
<h2>{{ .CommonLabels.alertname }}</h2>
<p><b>Status:</b> {{ .Status }}</p>
<p><b>Environment:</b> {{ .CommonLabels.namespace }}</p>
<table border="1" cellpadding="5">
<tr><th>Alert</th><th>Severity</th><th>Description</th><th>Started</th></tr>
{{ range .Alerts }}
<tr>
<td>{{ .Labels.alertname }}</td>
<td>{{ .Labels.severity }}</td>
<td>{{ .Annotations.description }}</td>
<td>{{ .StartsAt.Format "2006-01-02 15:04:05 UTC" }}</td>
</tr>
{{ end }}
</table>
+140
View File
@@ -0,0 +1,140 @@
# Coralogix Integration
Full-stack observability for Claude Code Agent Monitor via [Coralogix](https://coralogix.com) — logs, metrics, traces, and SLO tracking through a single platform.
## Architecture
```mermaid
graph TB
subgraph "Kubernetes Cluster"
APP["Agent Monitor Pods"]
MCP["MCP Sidecar"]
OTEL["OTel Collector<br/>(DaemonSet)"]
end
APP -->|"metrics + logs"| OTEL
MCP -->|"metrics + logs"| OTEL
OTEL -->|"OTLP (gRPC)"| CX["Coralogix Platform"]
subgraph "Coralogix"
CX --> LOGS["Log Analytics<br/>DataPrime Queries"]
CX --> MET["Metrics<br/>PromQL + Recording Rules"]
CX --> TRACE["Distributed Tracing"]
CX --> ALERT["Alert Engine"]
CX --> DASH["Custom Dashboards"]
CX --> SLO["SLO Management"]
end
ALERT -->|"Critical"| PD["PagerDuty"]
ALERT -->|"Warning"| SLACK["Slack"]
style OTEL fill:#4f46e5,color:#fff
style CX fill:#1a1a2e,color:#fff
style LOGS fill:#7c3aed,color:#fff
style MET fill:#e6522c,color:#fff
style TRACE fill:#059669,color:#fff
style ALERT fill:#dc2626,color:#fff
style DASH fill:#f46800,color:#fff
style SLO fill:#0ea5e9,color:#fff
```
## Files
| File | Purpose |
|------|---------|
| `values.yaml` | Helm values for Coralogix OpenTelemetry Collector |
| `alerts.yaml` | Alert definitions (mirrors Prometheus/Alertmanager rules) |
| `dashboards.yaml` | Custom dashboard with 6 rows, 18 panels, SLO tracking |
| `coralogix-terraform.tf` | Terraform-managed alerts, parsing rules, recording rules |
## Quick Start
### 1. Add the Helm Repository
```bash
helm repo add coralogix https://cgx.jfrog.io/artifactory/coralogix-charts-virtual
helm repo update
```
### 2. Create the API Key Secret
```bash
kubectl create secret generic coralogix-keys \
--namespace agent-monitor \
--from-literal=PRIVATE_KEY=<YOUR_CORALOGIX_SEND_YOUR_DATA_KEY>
```
### 3. Deploy the OTel Collector
```bash
helm install coralogix-otel coralogix/opentelemetry \
--namespace agent-monitor \
-f deployments/monitoring/coralogix/values.yaml
```
### 4. Import the Dashboard
Upload `dashboards.yaml` via the Coralogix UI:
**Dashboards → Custom Dashboards → Import**
### 5. (Optional) Terraform-managed Alerts
```bash
cd deployments/monitoring/coralogix
export CORALOGIX_API_KEY="<your-key>"
export CORALOGIX_ENV="coralogix.com"
terraform init
terraform apply
```
## What Gets Collected
| Signal | Source | Destination |
|--------|--------|-------------|
| **Logs** | Pod stdout/stderr (JSON structured) | Coralogix Log Analytics |
| **Metrics** | Prometheus scrape (`/api/health`) | Coralogix Metrics |
| **K8s Metrics** | kubelet, cAdvisor, host metrics | Coralogix Metrics |
| **Traces** | OTLP from application (if instrumented) | Coralogix Tracing |
## Alert Parity
All 10 Prometheus/Alertmanager rules are replicated in Coralogix:
| Alert | Severity | Prometheus | Coralogix |
|-------|----------|:----------:|:---------:|
| Instance Down | Critical | ✓ | ✓ |
| High Error Rate | Critical | ✓ | ✓ |
| Pod Restart Loop | Critical | ✓ | ✓ |
| PV Nearly Full | Critical | ✓ | ✓ |
| High Latency | Warning | ✓ | ✓ |
| WebSocket Spike | Warning | ✓ | ✓ |
| High Memory | Warning | ✓ | ✓ |
| High CPU | Warning | ✓ | ✓ |
| HPA Maxed Out | Warning | ✓ | ✓ |
| Slow DB Queries | Warning | ✓ | ✓ |
## Dashboard Panels
The custom dashboard provides 18 panels across 6 rows:
1. **Overview** — Active sessions, request rate, WebSocket connections
2. **HTTP Performance** — Latency distribution, error rate, status codes
3. **Application Logs** — Error log stream (DataPrime), log volume by severity, hook throughput
4. **Infrastructure** — CPU, memory, pod status
5. **Database & Storage** — SQLite query duration, PV usage, network I/O
6. **SLO Tracking** — Availability SLO (99.9%), latency SLO (P95 < 500ms), error budget burn
## Coralogix Regions
Set `global.domain` in `values.yaml` to match your Coralogix region:
| Region | Domain |
|--------|--------|
| US1 | `coralogix.us` |
| US2 | `cx498.coralogix.com` |
| EU1 | `coralogix.com` |
| EU2 | `eu2.coralogix.com` |
| AP1 (India) | `coralogix.in` |
| AP2 (Singapore) | `coralogix.sg` |
@@ -0,0 +1,194 @@
# ─────────────────────────────────────────────────────────────────────────────
# Coralogix Alert Definitions for Claude Code Agent Monitor
#
# These alerts can be imported via the Coralogix Alerts API or Terraform
# provider (coralogix/coralogix). They mirror the Prometheus/Alertmanager
# rules in ../prometheus/rules/ for consistency across observability stacks.
#
# API import:
# curl -X POST "https://api.coralogix.com/api/v1/external/alerts" \
# -H "Authorization: Bearer $CORALOGIX_API_KEY" \
# -H "Content-Type: application/json" \
# -d @deployments/monitoring/coralogix/alerts.yaml
#
# Terraform:
# See the coralogix_alert resources in coralogix-terraform.tf
# ─────────────────────────────────────────────────────────────────────────────
alerts:
# ── Critical ───────────────────────────────────────────────────────────────
- name: "Agent Monitor Down"
description: "No metrics received from agent-monitor pods for > 2 minutes"
severity: critical
type: metric
condition:
metric_name: "up"
filter:
job: "agent-monitor"
threshold: 1
comparison: less_than
for_duration: "2m"
of_last: "5m"
notifications:
- integration: pagerduty
- integration: slack
channel: "#agent-monitor-critical"
labels:
service: agent-monitor
team: platform
- name: "High Error Rate"
description: "5xx error rate exceeds 5% of total requests for 5 minutes"
severity: critical
type: ratio
condition:
numerator:
query: 'http_requests_total{job="agent-monitor", status=~"5.."}'
denominator:
query: 'http_requests_total{job="agent-monitor"}'
threshold: 0.05
comparison: greater_than
for_duration: "5m"
notifications:
- integration: pagerduty
- integration: slack
channel: "#agent-monitor-critical"
labels:
service: agent-monitor
- name: "Pod Restart Loop"
description: "Agent Monitor pod has restarted > 5 times in 15 minutes"
severity: critical
type: metric
condition:
query: 'increase(kube_pod_container_status_restarts_total{namespace=~"agent-monitor.*", container="agent-monitor"}[15m])'
threshold: 5
comparison: greater_than
for_duration: "1m"
notifications:
- integration: pagerduty
- integration: slack
channel: "#agent-monitor-critical"
labels:
service: agent-monitor
- name: "Persistent Volume Nearly Full"
description: "SQLite persistent volume is > 90% full"
severity: critical
type: metric
condition:
query: '(kubelet_volume_stats_used_bytes{namespace=~"agent-monitor.*"} / kubelet_volume_stats_capacity_bytes{namespace=~"agent-monitor.*"}) * 100'
threshold: 90
comparison: greater_than
for_duration: "5m"
notifications:
- integration: pagerduty
- integration: slack
channel: "#agent-monitor-critical"
labels:
service: agent-monitor
# ── Warning ────────────────────────────────────────────────────────────────
- name: "High Latency"
description: "P95 request latency exceeds 2 seconds for 5 minutes"
severity: warning
type: metric
condition:
query: 'histogram_quantile(0.95, rate(http_request_duration_seconds_bucket{job="agent-monitor"}[5m]))'
threshold: 2
comparison: greater_than
for_duration: "5m"
notifications:
- integration: slack
channel: "#agent-monitor-alerts"
labels:
service: agent-monitor
- name: "WebSocket Connection Spike"
description: "Active WebSocket connections exceed 1000"
severity: warning
type: metric
condition:
metric_name: "websocket_connections_active"
filter:
job: "agent-monitor"
threshold: 1000
comparison: greater_than
for_duration: "2m"
notifications:
- integration: slack
channel: "#agent-monitor-alerts"
labels:
service: agent-monitor
- name: "High Memory Usage"
description: "Container memory usage exceeds 85% of limit"
severity: warning
type: metric
condition:
query: '(container_memory_working_set_bytes{namespace=~"agent-monitor.*", container="agent-monitor"} / container_spec_memory_limit_bytes{namespace=~"agent-monitor.*", container="agent-monitor"}) * 100'
threshold: 85
comparison: greater_than
for_duration: "5m"
notifications:
- integration: slack
channel: "#agent-monitor-alerts"
labels:
service: agent-monitor
- name: "High CPU Usage"
description: "Container CPU usage exceeds 80% for 10 minutes"
severity: warning
type: metric
condition:
query: '(rate(container_cpu_usage_seconds_total{namespace=~"agent-monitor.*", container="agent-monitor"}[5m]) / container_spec_cpu_quota{namespace=~"agent-monitor.*", container="agent-monitor"} * 100000)'
threshold: 80
comparison: greater_than
for_duration: "10m"
notifications:
- integration: slack
channel: "#agent-monitor-alerts"
labels:
service: agent-monitor
- name: "HPA Maxed Out"
description: "HPA replicas at max for 15 minutes — may need capacity increase"
severity: warning
type: metric
condition:
query: 'kube_horizontalpodautoscaler_status_current_replicas{namespace=~"agent-monitor.*"} == kube_horizontalpodautoscaler_spec_max_replicas{namespace=~"agent-monitor.*"}'
threshold: 1
comparison: greater_than_or_equal
for_duration: "15m"
notifications:
- integration: slack
channel: "#agent-monitor-alerts"
labels:
service: agent-monitor
- name: "Slow Database Queries"
description: "SQLite query duration exceeds 1 second"
severity: warning
type: metric
condition:
metric_name: "sqlite_query_duration_seconds"
filter:
job: "agent-monitor"
threshold: 1
comparison: greater_than
for_duration: "5m"
notifications:
- integration: slack
channel: "#agent-monitor-alerts"
labels:
service: agent-monitor
# ── Notification integrations ────────────────────────────────────────────────
# Configure these in Coralogix UI: Settings → Integrations → Outbound Webhooks
#
# Required integrations:
# - pagerduty: PagerDuty Events API v2 routing key
# - slack: Slack webhook for #agent-monitor-critical and #agent-monitor-alerts
# - email: (optional) Email notification group
@@ -0,0 +1,353 @@
# ─────────────────────────────────────────────────────────────────────────────
# Coralogix Terraform Integration for Claude Code Agent Monitor
#
# Provisions Coralogix resources via the official Terraform provider:
# - Alert rules (mirroring Prometheus/Alertmanager rules)
# - Log parsing rules for structured JSON ingestion
# - Recording rules for pre-aggregated SLO metrics
# - Dashboard provisioning
#
# Usage:
# export CORALOGIX_API_KEY="<your-send-your-data-key>"
# export CORALOGIX_ENV="<your-coralogix-domain>" # e.g. coralogix.com
# terraform init
# terraform plan
# terraform apply
#
# Requires: hashicorp/terraform >= 1.5, coralogix/coralogix >= 1.10
# ─────────────────────────────────────────────────────────────────────────────
terraform {
required_version = ">= 1.5"
required_providers {
coralogix = {
source = "coralogix/coralogix"
version = "~> 1.10"
}
}
}
provider "coralogix" {
# API key and environment are sourced from:
# CORALOGIX_API_KEY Send-Your-Data API key
# CORALOGIX_ENV Domain (e.g. coralogix.com, eu2.coralogix.com)
}
# ── Variables ────────────────────────────────────────────────────────────────
variable "environment" {
description = "Deployment environment (dev, staging, production)"
type = string
default = "production"
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "environment must be one of: dev, staging, production."
}
}
variable "notification_group_id" {
description = "Coralogix notification group ID for alert routing"
type = string
default = ""
}
variable "pagerduty_webhook_id" {
description = "Coralogix outbound webhook ID for PagerDuty integration"
type = string
default = ""
}
variable "slack_webhook_id" {
description = "Coralogix outbound webhook ID for Slack integration"
type = string
default = ""
}
locals {
app_name = "agent-monitor"
subsystem = "kubernetes"
alert_prefix = "[Agent Monitor]"
}
# ── Parsing Rules ────────────────────────────────────────────────────────────
# Structured JSON log parsing for agent-monitor application logs
resource "coralogix_rules_group" "agent_monitor_parsing" {
name = "${local.alert_prefix} Log Parsing"
description = "Parse structured JSON logs from Agent Monitor pods"
enabled = true
order = 1
rule_subgroups {
rules {
name = "JSON Extract"
description = "Extract structured fields from JSON application logs"
source_field = "text"
enabled = true
parse_json_field {
destination_field = "json"
keep_source_field = false
keep_destination_field = true
}
}
}
rule_subgroups {
rules {
name = "Severity Mapping"
description = "Map log level field to Coralogix severity"
source_field = "json.level"
enabled = true
extract {
regexp = "(?P<severity>debug|info|warn|error|fatal)"
}
}
}
}
# ── Recording Rules ──────────────────────────────────────────────────────────
# Pre-aggregate SLO metrics for efficient dashboard queries
resource "coralogix_recording_rule_group_set" "slo_metrics" {
name = "${local.alert_prefix} SLO Recording Rules"
groups {
name = "agent_monitor_slo"
interval = 60 # seconds
rules {
record = "agent_monitor:http_availability:ratio_rate5m"
expr = <<-EOT
1 - (
sum(rate(http_requests_total{job="agent-monitor", status=~"5.."}[5m]))
/
sum(rate(http_requests_total{job="agent-monitor"}[5m]))
)
EOT
labels = {
service = local.app_name
environment = var.environment
}
}
rules {
record = "agent_monitor:http_latency_p95:seconds_rate5m"
expr = <<-EOT
histogram_quantile(0.95,
sum(rate(http_request_duration_seconds_bucket{job="agent-monitor"}[5m])) by (le)
)
EOT
labels = {
service = local.app_name
environment = var.environment
}
}
rules {
record = "agent_monitor:websocket_connections:total"
expr = <<-EOT
sum(websocket_connections_active{job="agent-monitor"})
EOT
labels = {
service = local.app_name
environment = var.environment
}
}
}
}
# ── Alert Rules ──────────────────────────────────────────────────────────────
resource "coralogix_alert" "instance_down" {
name = "${local.alert_prefix} Instance Down"
description = "No metrics received from agent-monitor pods for > 2 minutes"
severity = "Critical"
enabled = true
metric {
promql {
text = "up{job=\"agent-monitor\"} == 0"
condition = "more_than"
threshold = 0
}
duration = "2m"
}
notifications_group {
dynamic "notification" {
for_each = var.pagerduty_webhook_id != "" ? [1] : []
content {
integration_id = var.pagerduty_webhook_id
}
}
dynamic "notification" {
for_each = var.slack_webhook_id != "" ? [1] : []
content {
integration_id = var.slack_webhook_id
}
}
}
labels = {
service = local.app_name
environment = var.environment
team = "platform"
}
}
resource "coralogix_alert" "high_error_rate" {
name = "${local.alert_prefix} High Error Rate"
description = "5xx error rate exceeds 5% of total requests for 5 minutes"
severity = "Critical"
enabled = true
metric {
promql {
text = <<-EOT
(
sum(rate(http_requests_total{job="agent-monitor", status=~"5.."}[5m]))
/
sum(rate(http_requests_total{job="agent-monitor"}[5m]))
) * 100 > 5
EOT
condition = "more_than"
threshold = 5
}
duration = "5m"
}
notifications_group {
dynamic "notification" {
for_each = var.pagerduty_webhook_id != "" ? [1] : []
content {
integration_id = var.pagerduty_webhook_id
}
}
}
labels = {
service = local.app_name
environment = var.environment
}
}
resource "coralogix_alert" "high_latency" {
name = "${local.alert_prefix} High Latency"
description = "P95 request latency exceeds 2 seconds for 5 minutes"
severity = "Warning"
enabled = true
metric {
promql {
text = "histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{job=\"agent-monitor\"}[5m])) by (le)) > 2"
condition = "more_than"
threshold = 2
}
duration = "5m"
}
notifications_group {
dynamic "notification" {
for_each = var.slack_webhook_id != "" ? [1] : []
content {
integration_id = var.slack_webhook_id
}
}
}
labels = {
service = local.app_name
environment = var.environment
}
}
resource "coralogix_alert" "high_memory" {
name = "${local.alert_prefix} High Memory Usage"
description = "Container memory usage exceeds 85% of limit"
severity = "Warning"
enabled = true
metric {
promql {
text = <<-EOT
(
container_memory_working_set_bytes{namespace=~"agent-monitor.*", container="agent-monitor"}
/
container_spec_memory_limit_bytes{namespace=~"agent-monitor.*", container="agent-monitor"}
) * 100 > 85
EOT
condition = "more_than"
threshold = 85
}
duration = "5m"
}
notifications_group {
dynamic "notification" {
for_each = var.slack_webhook_id != "" ? [1] : []
content {
integration_id = var.slack_webhook_id
}
}
}
labels = {
service = local.app_name
environment = var.environment
}
}
resource "coralogix_alert" "pod_restart_loop" {
name = "${local.alert_prefix} Pod Restart Loop"
description = "Agent Monitor pod has restarted > 5 times in 15 minutes"
severity = "Critical"
enabled = true
metric {
promql {
text = "increase(kube_pod_container_status_restarts_total{namespace=~\"agent-monitor.*\", container=\"agent-monitor\"}[15m]) > 5"
condition = "more_than"
threshold = 5
}
duration = "1m"
}
notifications_group {
dynamic "notification" {
for_each = var.pagerduty_webhook_id != "" ? [1] : []
content {
integration_id = var.pagerduty_webhook_id
}
}
}
labels = {
service = local.app_name
environment = var.environment
}
}
# ── Outputs ──────────────────────────────────────────────────────────────────
output "parsing_rule_group_id" {
description = "ID of the Coralogix parsing rule group"
value = coralogix_rules_group.agent_monitor_parsing.id
}
output "recording_rule_set_id" {
description = "ID of the Coralogix recording rule group set"
value = coralogix_recording_rule_group_set.slo_metrics.id
}
output "alert_ids" {
description = "IDs of all provisioned Coralogix alerts"
value = {
instance_down = coralogix_alert.instance_down.id
high_error_rate = coralogix_alert.high_error_rate.id
high_latency = coralogix_alert.high_latency.id
high_memory = coralogix_alert.high_memory.id
pod_restart = coralogix_alert.pod_restart_loop.id
}
}
@@ -0,0 +1,245 @@
# ─────────────────────────────────────────────────────────────────────────────
# Coralogix Custom Dashboard Agent Monitor
#
# Import via Coralogix UI: Dashboards → Custom Dashboards → Import
# Or via API:
# curl -X POST "https://api.coralogix.com/api/v1/external/grafana/api/dashboards/db" \
# -H "Authorization: Bearer $CORALOGIX_API_KEY" \
# -H "Content-Type: application/json" \
# -d @dashboards.yaml
#
# This dashboard mirrors the Grafana dashboard (../grafana/dashboards/) while
# leveraging Coralogix-native features: DataPrime queries, log correlation,
# distributed tracing waterfall, and Apdex scoring.
# ─────────────────────────────────────────────────────────────────────────────
dashboard:
name: "Agent Monitor Operations"
description: "Claude Code Agent Monitor: real-time operations, SLOs, and infrastructure health"
folder: "Agent Monitor"
tags:
- agent-monitor
- operations
- sre
# ── Row 1: Overview ──────────────────────────────────────────────────────
rows:
- name: "Overview"
panels:
- title: "Active Sessions"
type: line-chart
query:
type: metrics
promql: 'agent_monitor_active_sessions'
legend: "{{namespace}}"
span: 4
- title: "Request Rate (req/s)"
type: line-chart
query:
type: metrics
promql: 'sum(rate(http_requests_total{job="agent-monitor"}[5m]))'
legend: "Requests/sec"
span: 4
- title: "WebSocket Connections"
type: line-chart
query:
type: metrics
promql: 'websocket_connections_active{job="agent-monitor"}'
legend: "{{pod}}"
span: 4
# ── Row 2: HTTP Performance ────────────────────────────────────────────
- name: "HTTP Performance"
panels:
- title: "Latency Distribution (P50 / P95 / P99)"
type: line-chart
query:
type: metrics
promql: |
histogram_quantile(0.50, sum(rate(http_request_duration_seconds_bucket{job="agent-monitor"}[5m])) by (le))
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{job="agent-monitor"}[5m])) by (le))
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket{job="agent-monitor"}[5m])) by (le))
span: 6
- title: "Error Rate (%)"
type: line-chart
query:
type: metrics
promql: |
sum(rate(http_requests_total{job="agent-monitor", status=~"5.."}[5m]))
/
sum(rate(http_requests_total{job="agent-monitor"}[5m])) * 100
legend: "5xx %"
thresholds:
- value: 1
color: yellow
- value: 5
color: red
span: 3
- title: "Status Code Distribution"
type: bar-chart
query:
type: metrics
promql: 'sum by (status) (increase(http_requests_total{job="agent-monitor"}[1h]))'
span: 3
# ── Row 3: Logs (DataPrime) ────────────────────────────────────────────
- name: "Application Logs"
panels:
- title: "Error Logs"
type: dataprime
query: |
source logs
| filter $d.cx.application.name == 'agent-monitor'
| filter $d.severity == 'ERROR' || $d.severity == 'FATAL'
| select $m.timestamp, $d.k8s.pod.name, $d.message
| order by $m.timestamp desc
| limit 100
span: 6
- title: "Log Volume by Severity"
type: bar-chart
query:
type: dataprime
expression: |
source logs
| filter $d.cx.application.name == 'agent-monitor'
| count_group_by $d.severity as count
span: 3
- title: "Hook Event Throughput"
type: line-chart
query:
type: dataprime
expression: |
source logs
| filter $d.cx.application.name == 'agent-monitor'
| filter $d.message matches 'hook.*event'
| count_per_time 1m as throughput
span: 3
# ── Row 4: Infrastructure ──────────────────────────────────────────────
- name: "Infrastructure"
panels:
- title: "CPU Usage (%)"
type: line-chart
query:
type: metrics
promql: |
rate(container_cpu_usage_seconds_total{
namespace=~"agent-monitor.*",
container="agent-monitor"
}[5m]) * 100
legend: "{{pod}}"
span: 4
- title: "Memory Usage (MiB)"
type: line-chart
query:
type: metrics
promql: |
container_memory_working_set_bytes{
namespace=~"agent-monitor.*",
container="agent-monitor"
} / 1024 / 1024
legend: "{{pod}}"
span: 4
- title: "Pod Status"
type: gauge
query:
type: metrics
promql: |
count by (phase) (
kube_pod_status_phase{namespace=~"agent-monitor.*"}
)
span: 4
# ── Row 5: Database & Storage ──────────────────────────────────────────
- name: "Database & Storage"
panels:
- title: "SQLite Query Duration (ms)"
type: line-chart
query:
type: metrics
promql: 'sqlite_query_duration_seconds{job="agent-monitor"} * 1000'
span: 4
- title: "PV Usage (%)"
type: gauge
query:
type: metrics
promql: |
(kubelet_volume_stats_used_bytes{namespace=~"agent-monitor.*"}
/ kubelet_volume_stats_capacity_bytes{namespace=~"agent-monitor.*"}) * 100
thresholds:
- value: 70
color: yellow
- value: 90
color: red
span: 4
- title: "Network I/O (bytes/s)"
type: line-chart
query:
type: metrics
promql: |
sum by (pod) (rate(container_network_receive_bytes_total{namespace=~"agent-monitor.*"}[5m]))
sum by (pod) (rate(container_network_transmit_bytes_total{namespace=~"agent-monitor.*"}[5m]))
span: 4
# ── Row 6: SLO Tracking ────────────────────────────────────────────────
- name: "SLO Tracking"
panels:
- title: "Availability SLO (99.9% target)"
type: gauge
query:
type: metrics
promql: |
(1 - sum(rate(http_requests_total{job="agent-monitor", status=~"5.."}[30d]))
/ sum(rate(http_requests_total{job="agent-monitor"}[30d]))) * 100
thresholds:
- value: 99.9
color: green
- value: 99.5
color: yellow
- value: 99.0
color: red
span: 4
- title: "Latency SLO (P95 < 500ms)"
type: gauge
query:
type: metrics
promql: |
histogram_quantile(0.95,
sum(rate(http_request_duration_seconds_bucket{job="agent-monitor"}[30d])) by (le)
) * 1000
thresholds:
- value: 300
color: green
- value: 500
color: yellow
- value: 1000
color: red
span: 4
- title: "Error Budget Remaining"
type: gauge
query:
type: metrics
promql: |
(0.001 - sum(rate(http_requests_total{job="agent-monitor", status=~"5.."}[30d]))
/ sum(rate(http_requests_total{job="agent-monitor"}[30d])))
/ 0.001 * 100
thresholds:
- value: 50
color: green
- value: 25
color: yellow
- value: 0
color: red
span: 4
@@ -0,0 +1,180 @@
# ─────────────────────────────────────────────────────────────────────────────
# Coralogix OpenTelemetry Collector Helm Values
#
# Deploys the Coralogix OTel collector as a DaemonSet + Gateway for shipping
# logs, metrics, and traces from the Agent Monitor cluster.
#
# Prerequisites:
# 1. Add Coralogix Helm repo:
# helm repo add coralogix https://cgx.jfrog.io/artifactory/coralogix-charts-virtual
# helm repo update
# 2. Create the API key secret:
# kubectl create secret generic coralogix-keys \
# --namespace agent-monitor \
# --from-literal=PRIVATE_KEY=<YOUR_CORALOGIX_PRIVATE_KEY>
#
# Install:
# helm install coralogix-otel coralogix/opentelemetry \
# --namespace agent-monitor \
# -f deployments/monitoring/coralogix/values.yaml
# ─────────────────────────────────────────────────────────────────────────────
global:
# Coralogix domain set to your region's endpoint
# Options: coralogix.com | eu2.coralogix.com | coralogix.in | coralogix.us |
# cx498.coralogix.com | coralogix.eu | coralogix.sg
domain: "coralogix.com"
# Reference the API key from the pre-created K8s secret
clusterName: "agent-monitor"
# ── Secret reference ─────────────────────────────────────────────────────────
secret:
enabled: true
name: "coralogix-keys"
# Key in the secret containing the Coralogix Send-Your-Data API key
privateKeySecretRef:
key: "PRIVATE_KEY"
# ── Collector DaemonSet mode (node-level collection) ───────────────────────
opentelemetry-collector:
mode: daemonset
presets:
# Collect Kubernetes pod/container logs
logsCollection:
enabled: true
includeCollectorLogs: false
# Enrich telemetry with Kubernetes metadata
kubernetesAttributes:
enabled: true
extractAllPodLabels: true
extractAllPodAnnotations: false
# Collect host-level metrics (CPU, memory, disk, network)
hostMetrics:
enabled: true
# Collect kubelet/cAdvisor metrics
kubeletMetrics:
enabled: true
config:
receivers:
# Scrape Prometheus metrics from agent-monitor pods
prometheus:
config:
scrape_configs:
- job_name: "agent-monitor"
scrape_interval: 15s
kubernetes_sd_configs:
- role: pod
namespaces:
names:
- agent-monitor
- agent-monitor-staging
- agent-monitor-production
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: "true"
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $$1:$$2
target_label: __address__
# Receive OTLP from in-cluster services (gRPC + HTTP)
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
processors:
# Batch telemetry for efficient export
batch:
send_batch_size: 1024
send_batch_max_size: 2048
timeout: 5s
# Enrich with resource attributes
resource:
attributes:
- key: cx.application.name
value: "agent-monitor"
action: upsert
- key: cx.subsystem.name
from_attribute: k8s.container.name
action: upsert
- key: k8s.cluster.name
value: "agent-monitor"
action: upsert
# Memory limiter to prevent OOM
memory_limiter:
check_interval: 5s
limit_percentage: 80
spike_limit_percentage: 25
# Filter out noisy internal logs
filter/drop-internal:
logs:
exclude:
match_type: regexp
bodies:
- ".*kube-probe.*"
- ".*healthz.*"
exporters:
coralogix:
domain: "${CORALOGIX_DOMAIN}"
private_key: "${PRIVATE_KEY}"
application_name: "agent-monitor"
subsystem_name: "kubernetes"
timeout: 30s
service:
pipelines:
logs:
receivers: [otlp]
processors: [memory_limiter, resource, filter/drop-internal, batch]
exporters: [coralogix]
metrics:
receivers: [otlp, prometheus]
processors: [memory_limiter, resource, batch]
exporters: [coralogix]
traces:
receivers: [otlp]
processors: [memory_limiter, resource, batch]
exporters: [coralogix]
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
tolerations:
- operator: Exists
effect: NoSchedule
# ── Gateway mode (optional for centralized export) ─────────────────────────
opentelemetry-gateway:
enabled: false
replicaCount: 2
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: "1"
memory: "1Gi"
@@ -0,0 +1,707 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": { "type": "grafana", "uid": "-- Grafana --" },
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"enable": true,
"expr": "changes(kube_deployment_status_observed_generation{namespace=~\"$namespace\", deployment=~\"agent-monitor.*\"}[2m]) > 0",
"iconColor": "#6ED0E0",
"name": "Deployments",
"titleFormat": "Deployment updated"
}
]
},
"description": "Comprehensive monitoring dashboard for Claude Code Agent Monitor",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [],
"panels": [
{
"collapsed": false,
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 },
"id": 100,
"title": "Overview",
"type": "row"
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "thresholds" },
"mappings": [
{
"options": {
"0": { "color": "red", "text": "DOWN" },
"1": { "color": "green", "text": "UP" }
},
"type": "value"
}
],
"thresholds": {
"steps": [
{ "color": "red", "value": null },
{ "color": "green", "value": 1 }
]
}
}
},
"gridPos": { "h": 4, "w": 4, "x": 0, "y": 1 },
"id": 1,
"options": {
"colorMode": "background",
"graphMode": "none",
"justifyMode": "center",
"textMode": "value",
"reduceOptions": { "calcs": ["lastNotNull"] }
},
"title": "Uptime Status",
"type": "stat",
"targets": [
{
"expr": "up{job=\"agent-monitor\", namespace=~\"$namespace\"}",
"legendFormat": "{{instance}}",
"refId": "A"
}
]
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "thresholds" },
"unit": "dtdurations",
"thresholds": { "steps": [{ "color": "green", "value": null }] }
}
},
"gridPos": { "h": 4, "w": 4, "x": 4, "y": 1 },
"id": 2,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "center",
"textMode": "value",
"reduceOptions": { "calcs": ["lastNotNull"] }
},
"title": "Uptime Duration",
"type": "stat",
"targets": [
{
"expr": "time() - process_start_time_seconds{job=\"agent-monitor\", namespace=~\"$namespace\"}",
"legendFormat": "",
"refId": "A"
}
]
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "thresholds" },
"unit": "short",
"thresholds": {
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 2 },
{ "color": "red", "value": 0 }
]
}
}
},
"gridPos": { "h": 4, "w": 4, "x": 8, "y": 1 },
"id": 3,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "center",
"textMode": "value",
"reduceOptions": { "calcs": ["lastNotNull"] }
},
"title": "Pod Count",
"type": "stat",
"targets": [
{
"expr": "count(kube_pod_status_ready{namespace=~\"$namespace\", condition=\"true\"} == 1)",
"legendFormat": "Ready",
"refId": "A"
}
]
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "thresholds" },
"unit": "short",
"thresholds": {
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 1 },
{ "color": "red", "value": 3 }
]
}
}
},
"gridPos": { "h": 4, "w": 4, "x": 12, "y": 1 },
"id": 4,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "center",
"textMode": "value",
"reduceOptions": { "calcs": ["lastNotNull"] }
},
"title": "Pod Restarts (1h)",
"type": "stat",
"targets": [
{
"expr": "sum(increase(kube_pod_container_status_restarts_total{namespace=~\"$namespace\", container=\"agent-monitor\"}[1h]))",
"legendFormat": "",
"refId": "A"
}
]
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "thresholds" },
"unit": "short",
"thresholds": { "steps": [{ "color": "blue", "value": null }] }
}
},
"gridPos": { "h": 4, "w": 4, "x": 16, "y": 1 },
"id": 5,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "center",
"textMode": "value",
"reduceOptions": { "calcs": ["lastNotNull"] }
},
"title": "Active WebSocket Connections",
"type": "stat",
"targets": [
{
"expr": "sum(websocket_connections_active{job=\"agent-monitor\", namespace=~\"$namespace\"})",
"legendFormat": "",
"refId": "A"
}
]
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "thresholds" },
"unit": "decbytes",
"thresholds": {
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 5368709120 },
{ "color": "red", "value": 8589934592 }
]
}
}
},
"gridPos": { "h": 4, "w": 4, "x": 20, "y": 1 },
"id": 6,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "center",
"textMode": "value",
"reduceOptions": { "calcs": ["lastNotNull"] }
},
"title": "SQLite DB Size",
"type": "stat",
"targets": [
{
"expr": "kubelet_volume_stats_used_bytes{namespace=~\"$namespace\", persistentvolumeclaim=~\"agent-monitor.*\"}",
"legendFormat": "",
"refId": "A"
}
]
},
{
"collapsed": false,
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 5 },
"id": 101,
"title": "HTTP Traffic",
"type": "row"
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "palette-classic" },
"custom": {
"axisLabel": "req/s",
"drawStyle": "line",
"fillOpacity": 15,
"lineWidth": 2,
"pointSize": 5,
"spanNulls": false,
"stacking": { "mode": "none" }
},
"unit": "reqps"
},
"overrides": [
{
"matcher": { "id": "byRegexp", "options": "5.." },
"properties": [{ "id": "color", "value": { "fixedColor": "red", "mode": "fixed" } }]
},
{
"matcher": { "id": "byRegexp", "options": "4.." },
"properties": [{ "id": "color", "value": { "fixedColor": "yellow", "mode": "fixed" } }]
},
{
"matcher": { "id": "byRegexp", "options": "2.." },
"properties": [{ "id": "color", "value": { "fixedColor": "green", "mode": "fixed" } }]
}
]
},
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 6 },
"id": 10,
"options": {
"legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom" },
"tooltip": { "mode": "multi", "sort": "desc" }
},
"title": "Request Rate (RPS)",
"type": "timeseries",
"targets": [
{
"expr": "sum(rate(http_requests_total{job=\"agent-monitor\", namespace=~\"$namespace\"}[5m])) by (status)",
"legendFormat": "{{ status }}",
"refId": "A"
},
{
"expr": "sum(rate(http_requests_total{job=\"agent-monitor\", namespace=~\"$namespace\"}[5m]))",
"legendFormat": "Total",
"refId": "B"
}
]
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "palette-classic" },
"custom": {
"axisLabel": "%",
"drawStyle": "line",
"fillOpacity": 20,
"lineWidth": 2,
"thresholdsStyle": { "mode": "line" }
},
"max": 100,
"min": 0,
"unit": "percent",
"thresholds": {
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 2 },
{ "color": "red", "value": 5 }
]
}
}
},
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 6 },
"id": 11,
"options": {
"legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom" },
"tooltip": { "mode": "multi" }
},
"title": "Error Rate (%)",
"type": "timeseries",
"targets": [
{
"expr": "100 * sum(rate(http_requests_total{job=\"agent-monitor\", namespace=~\"$namespace\", status=~\"5..\"}[5m])) / sum(rate(http_requests_total{job=\"agent-monitor\", namespace=~\"$namespace\"}[5m]))",
"legendFormat": "5xx Error Rate",
"refId": "A"
},
{
"expr": "100 * sum(rate(http_requests_total{job=\"agent-monitor\", namespace=~\"$namespace\", status=~\"4..\"}[5m])) / sum(rate(http_requests_total{job=\"agent-monitor\", namespace=~\"$namespace\"}[5m]))",
"legendFormat": "4xx Error Rate",
"refId": "B"
}
]
},
{
"collapsed": false,
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 14 },
"id": 102,
"title": "Latency",
"type": "row"
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "palette-classic" },
"custom": {
"axisLabel": "seconds",
"drawStyle": "line",
"fillOpacity": 10,
"lineWidth": 2,
"spanNulls": false
},
"unit": "s",
"thresholds": {
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 0.5 },
{ "color": "red", "value": 1 }
]
}
}
},
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 15 },
"id": 20,
"options": {
"legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom" },
"tooltip": { "mode": "multi", "sort": "desc" }
},
"title": "Response Latency (p50 / p90 / p99)",
"type": "timeseries",
"targets": [
{
"expr": "histogram_quantile(0.50, sum(rate(http_request_duration_seconds_bucket{job=\"agent-monitor\", namespace=~\"$namespace\"}[5m])) by (le))",
"legendFormat": "p50",
"refId": "A"
},
{
"expr": "histogram_quantile(0.90, sum(rate(http_request_duration_seconds_bucket{job=\"agent-monitor\", namespace=~\"$namespace\"}[5m])) by (le))",
"legendFormat": "p90",
"refId": "B"
},
{
"expr": "histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket{job=\"agent-monitor\", namespace=~\"$namespace\"}[5m])) by (le))",
"legendFormat": "p99",
"refId": "C"
}
]
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "palette-classic" },
"custom": {
"drawStyle": "bars",
"fillOpacity": 80,
"lineWidth": 1,
"stacking": { "mode": "normal" }
},
"unit": "reqps"
}
},
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 15 },
"id": 21,
"options": {
"legend": { "calcs": ["mean"], "displayMode": "table", "placement": "bottom" },
"tooltip": { "mode": "multi" }
},
"title": "Request Rate by Endpoint",
"type": "timeseries",
"targets": [
{
"expr": "sum(rate(http_requests_total{job=\"agent-monitor\", namespace=~\"$namespace\"}[5m])) by (handler)",
"legendFormat": "{{ handler }}",
"refId": "A"
}
]
},
{
"collapsed": false,
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 23 },
"id": 103,
"title": "WebSocket & Connections",
"type": "row"
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "palette-classic" },
"custom": { "drawStyle": "line", "fillOpacity": 25, "lineWidth": 2, "spanNulls": false },
"unit": "short"
}
},
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 24 },
"id": 30,
"options": {
"legend": {
"calcs": ["mean", "max", "last"],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": { "mode": "multi" }
},
"title": "Active WebSocket Connections",
"type": "timeseries",
"targets": [
{
"expr": "websocket_connections_active{job=\"agent-monitor\", namespace=~\"$namespace\"}",
"legendFormat": "{{ pod }}",
"refId": "A"
},
{
"expr": "sum(websocket_connections_active{job=\"agent-monitor\", namespace=~\"$namespace\"})",
"legendFormat": "Total",
"refId": "B"
}
]
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "palette-classic" },
"custom": { "drawStyle": "line", "fillOpacity": 10, "lineWidth": 2 },
"unit": "short"
}
},
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 24 },
"id": 31,
"options": {
"legend": { "calcs": ["sum"], "displayMode": "table", "placement": "bottom" },
"tooltip": { "mode": "multi" }
},
"title": "WebSocket Messages (rate/s)",
"type": "timeseries",
"targets": [
{
"expr": "sum(rate(websocket_messages_sent_total{job=\"agent-monitor\", namespace=~\"$namespace\"}[5m]))",
"legendFormat": "Sent",
"refId": "A"
},
{
"expr": "sum(rate(websocket_messages_received_total{job=\"agent-monitor\", namespace=~\"$namespace\"}[5m]))",
"legendFormat": "Received",
"refId": "B"
}
]
},
{
"collapsed": false,
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 32 },
"id": 104,
"title": "Resource Usage",
"type": "row"
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "palette-classic" },
"custom": {
"drawStyle": "line",
"fillOpacity": 20,
"lineWidth": 2,
"thresholdsStyle": { "mode": "line+area" }
},
"unit": "bytes",
"thresholds": {
"steps": [
{ "color": "transparent", "value": null },
{ "color": "red", "value": 536870912 }
]
}
}
},
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 33 },
"id": 40,
"options": {
"legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom" },
"tooltip": { "mode": "multi" }
},
"title": "Memory Usage",
"type": "timeseries",
"targets": [
{
"expr": "container_memory_working_set_bytes{namespace=~\"$namespace\", container=\"agent-monitor\"}",
"legendFormat": "{{ pod }} (working set)",
"refId": "A"
},
{
"expr": "container_spec_memory_limit_bytes{namespace=~\"$namespace\", container=\"agent-monitor\"}",
"legendFormat": "{{ pod }} (limit)",
"refId": "B"
}
]
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "palette-classic" },
"custom": {
"drawStyle": "line",
"fillOpacity": 20,
"lineWidth": 2,
"thresholdsStyle": { "mode": "line" }
},
"unit": "percentunit",
"max": 1,
"min": 0,
"thresholds": {
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 0.7 },
{ "color": "red", "value": 0.9 }
]
}
}
},
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 33 },
"id": 41,
"options": {
"legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom" },
"tooltip": { "mode": "multi" }
},
"title": "CPU Usage",
"type": "timeseries",
"targets": [
{
"expr": "rate(container_cpu_usage_seconds_total{namespace=~\"$namespace\", container=\"agent-monitor\"}[5m])",
"legendFormat": "{{ pod }}",
"refId": "A"
}
]
},
{
"collapsed": false,
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 41 },
"id": 105,
"title": "Storage",
"type": "row"
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "palette-classic" },
"custom": {
"drawStyle": "line",
"fillOpacity": 20,
"lineWidth": 2,
"thresholdsStyle": { "mode": "line+area" }
},
"unit": "decbytes",
"thresholds": {
"steps": [
{ "color": "transparent", "value": null },
{ "color": "yellow", "value": 8589934592 },
{ "color": "red", "value": 9663676416 }
]
}
}
},
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 42 },
"id": 50,
"options": {
"legend": { "calcs": ["last", "max"], "displayMode": "table", "placement": "bottom" },
"tooltip": { "mode": "multi" }
},
"title": "SQLite DB Size (PV Usage)",
"type": "timeseries",
"targets": [
{
"expr": "kubelet_volume_stats_used_bytes{namespace=~\"$namespace\", persistentvolumeclaim=~\"agent-monitor.*\"}",
"legendFormat": "Used ({{ persistentvolumeclaim }})",
"refId": "A"
},
{
"expr": "kubelet_volume_stats_capacity_bytes{namespace=~\"$namespace\", persistentvolumeclaim=~\"agent-monitor.*\"}",
"legendFormat": "Capacity ({{ persistentvolumeclaim }})",
"refId": "B"
}
]
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "thresholds" },
"max": 100,
"min": 0,
"unit": "percent",
"thresholds": {
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 75 },
{ "color": "red", "value": 90 }
]
}
}
},
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 42 },
"id": 51,
"options": {
"orientation": "horizontal",
"reduceOptions": { "calcs": ["lastNotNull"] },
"showThresholdLabels": false,
"showThresholdMarkers": true
},
"title": "PV Usage (%)",
"type": "gauge",
"targets": [
{
"expr": "100 * kubelet_volume_stats_used_bytes{namespace=~\"$namespace\", persistentvolumeclaim=~\"agent-monitor.*\"} / kubelet_volume_stats_capacity_bytes{namespace=~\"$namespace\", persistentvolumeclaim=~\"agent-monitor.*\"}",
"legendFormat": "{{ persistentvolumeclaim }}",
"refId": "A"
}
]
}
],
"refresh": "30s",
"schemaVersion": 39,
"tags": ["agent-monitor", "nodejs", "websocket"],
"templating": {
"list": [
{
"current": { "selected": false, "text": "Prometheus", "value": "prometheus" },
"hide": 0,
"includeAll": false,
"label": "Data Source",
"name": "datasource",
"options": [],
"query": "prometheus",
"type": "datasource"
},
{
"current": {},
"datasource": { "type": "prometheus", "uid": "${datasource}" },
"definition": "label_values(up{job=\"agent-monitor\"}, namespace)",
"hide": 0,
"includeAll": true,
"label": "Namespace",
"multi": false,
"name": "namespace",
"query": { "qryType": 1, "query": "label_values(up{job=\"agent-monitor\"}, namespace)" },
"refresh": 2,
"type": "query"
}
]
},
"time": { "from": "now-6h", "to": "now" },
"timepicker": {
"refresh_intervals": ["10s", "30s", "1m", "5m", "15m"],
"time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"]
},
"timezone": "browser",
"title": "Claude Code Agent Monitor",
"uid": "agent-monitor-main",
"version": 1
}
@@ -0,0 +1,51 @@
# ─────────────────────────────────────────────────────────────────────────────
# Grafana datasource provisioning for Claude Code Agent Monitor
# ─────────────────────────────────────────────────────────────────────────────
apiVersion: 1
# Prevent users from deleting provisioned data sources in the UI
deleteDatasources:
- name: Prometheus
orgId: 1
datasources:
# ── Primary Prometheus datasource ─────────────────────────────────────────
- name: Prometheus
type: prometheus
uid: prometheus
access: proxy
url: http://prometheus-server:9090
isDefault: true
editable: false
jsonData:
# Scrape interval matches prometheus.yaml global setting
timeInterval: "15s"
# Query timeout
queryTimeout: "30s"
# HTTP method for queries
httpMethod: POST
# Manage alerts via Prometheus
manageAlerts: true
# Alerting rule settings
prometheusType: Prometheus
prometheusVersion: ">=2.45.0"
# Incremental querying for better performance
incrementalQuerying: true
incrementalQueryOverlapWindow: "10m"
# Exemplar trace support (uncomment if using tracing)
# exemplarTraceIdDestinations:
# - name: traceId
# datasourceUid: tempo
version: 1
# ── Alertmanager datasource ───────────────────────────────────────────────
- name: Alertmanager
type: alertmanager
uid: alertmanager
access: proxy
url: http://alertmanager:9093
editable: false
jsonData:
implementation: prometheus
handleGrafanaManagedAlerts: false
version: 1
@@ -0,0 +1,226 @@
# ─────────────────────────────────────────────────────────────────────────────
# Prometheus configuration for Claude Code Agent Monitor
# ─────────────────────────────────────────────────────────────────────────────
global:
scrape_interval: 15s
scrape_timeout: 10s
evaluation_interval: 15s
external_labels:
cluster: "${CLUSTER_NAME:agent-monitor}"
environment: "${ENVIRONMENT:production}"
# ── Rule files ──────────────────────────────────────────────────────────────
rule_files:
- /etc/prometheus/rules/*.rules.yaml
# ── Alertmanager ────────────────────────────────────────────────────────────
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
scheme: http
timeout: 10s
api_version: v2
# ── Scrape configs ──────────────────────────────────────────────────────────
scrape_configs:
# ── Agent Monitor application ─────────────────────────────────────────────
- job_name: "agent-monitor"
metrics_path: /api/health
scrape_interval: 15s
scrape_timeout: 5s
scheme: http
# Static target for standalone deployments
static_configs:
- targets:
- "agent-monitor:4820"
labels:
app: agent-monitor
component: server
# Relabeling to add standard labels
relabel_configs:
- source_labels: [__address__]
target_label: instance
- target_label: __metrics_path__
replacement: /api/health
metric_relabel_configs:
- source_labels: [__name__]
regex: "go_.*"
action: drop
# ── Agent Monitor MCP sidecar ─────────────────────────────────────────────
- job_name: "agent-monitor-mcp"
scrape_interval: 30s
static_configs:
- targets:
- "agent-monitor-mcp:8819"
labels:
app: agent-monitor
component: mcp
# ── Kubernetes service discovery (pods) ───────────────────────────────────
- job_name: "kubernetes-pods"
scrape_interval: 15s
kubernetes_sd_configs:
- role: pod
namespaces:
own_namespace: false
names:
- agent-monitor
- agent-monitor-dev
- agent-monitor-staging
- agent-monitor-production
relabel_configs:
# Only scrape pods with annotation prometheus.io/scrape=true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
# Use custom metrics path if annotated
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
# Use custom port if annotated
- source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
target_label: __address__
# Use custom scheme if annotated
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scheme]
action: replace
target_label: __scheme__
regex: (.+)
# Map pod labels to Prometheus labels
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
# Add namespace label
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: namespace
# Add pod name label
- source_labels: [__meta_kubernetes_pod_name]
action: replace
target_label: pod
# Add node name label
- source_labels: [__meta_kubernetes_pod_node_name]
action: replace
target_label: node
# ── Kubernetes service discovery (services) ──────────────────────────────
- job_name: "kubernetes-services"
scrape_interval: 15s
kubernetes_sd_configs:
- role: service
namespaces:
names:
- agent-monitor
- agent-monitor-dev
- agent-monitor-staging
- agent-monitor-production
relabel_configs:
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
action: replace
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
target_label: __address__
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: namespace
- source_labels: [__meta_kubernetes_service_name]
action: replace
target_label: service
# ── Kubernetes nodes ──────────────────────────────────────────────────────
- job_name: "kubernetes-nodes"
scrape_interval: 30s
scheme: https
kubernetes_sd_configs:
- role: node
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
insecure_skip_verify: false
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels: [__meta_kubernetes_node_name]
regex: (.+)
target_label: __metrics_path__
replacement: /api/v1/nodes/$1/proxy/metrics
# ── Kubernetes cadvisor (container metrics) ───────────────────────────────
- job_name: "kubernetes-cadvisor"
scrape_interval: 15s
scheme: https
kubernetes_sd_configs:
- role: node
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
insecure_skip_verify: false
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels: [__meta_kubernetes_node_name]
regex: (.+)
target_label: __metrics_path__
replacement: /api/v1/nodes/$1/proxy/metrics/cadvisor
metric_relabel_configs:
# Keep only container metrics for agent-monitor pods
- source_labels: [container]
regex: "agent-monitor.*"
action: keep
# ── Prometheus self-monitoring ────────────────────────────────────────────
- job_name: "prometheus"
static_configs:
- targets:
- "localhost:9090"
@@ -0,0 +1,310 @@
# ─────────────────────────────────────────────────────────────────────────────
# Prometheus alerting rules for Claude Code Agent Monitor
# ─────────────────────────────────────────────────────────────────────────────
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: agent-monitor-alerts
namespace: agent-monitor
labels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/component: monitoring
prometheus: kube-prometheus
role: alert-rules
spec:
groups:
# ── Availability alerts ─────────────────────────────────────────────────
- name: agent-monitor.availability
rules:
- alert: AgentMonitorDown
expr: up{job="agent-monitor"} == 0
for: 2m
labels:
severity: critical
service: agent-monitor
team: platform
annotations:
summary: "Agent Monitor is down"
description: >-
The Agent Monitor instance {{ $labels.instance }} has been
unreachable for more than 2 minutes. Immediate investigation
required.
runbook_url: "https://wiki.example.com/runbooks/agent-monitor-down"
dashboard_url: "https://grafana.example.com/d/agent-monitor"
- alert: AgentMonitorHighAvailabilityDegraded
expr: |
count(up{job="agent-monitor"} == 1)
< count(up{job="agent-monitor"})
for: 5m
labels:
severity: warning
service: agent-monitor
annotations:
summary: "Agent Monitor partial outage"
description: >-
Not all Agent Monitor replicas are healthy. Only
{{ $value }} of expected replicas are up.
# ── Error rate alerts ───────────────────────────────────────────────────
- name: agent-monitor.errors
rules:
- alert: HighErrorRate
expr: |
(
sum(rate(http_requests_total{job="agent-monitor", status=~"5.."}[5m]))
/
sum(rate(http_requests_total{job="agent-monitor"}[5m]))
) > 0.05
for: 5m
labels:
severity: warning
service: agent-monitor
annotations:
summary: "High HTTP error rate (>5%)"
description: >-
The Agent Monitor is returning 5xx errors at a rate of
{{ $value | humanizePercentage }} over the last 5 minutes.
dashboard_url: "https://grafana.example.com/d/agent-monitor?tab=errors"
- alert: CriticalErrorRate
expr: |
(
sum(rate(http_requests_total{job="agent-monitor", status=~"5.."}[5m]))
/
sum(rate(http_requests_total{job="agent-monitor"}[5m]))
) > 0.25
for: 2m
labels:
severity: critical
service: agent-monitor
annotations:
summary: "Critical HTTP error rate (>25%)"
description: >-
The Agent Monitor is returning 5xx errors at a rate of
{{ $value | humanizePercentage }}. Service may be severely degraded.
# ── Latency alerts ──────────────────────────────────────────────────────
- name: agent-monitor.latency
rules:
- alert: HighLatency
expr: |
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket{job="agent-monitor"}[5m])) by (le)
) > 1
for: 5m
labels:
severity: warning
service: agent-monitor
annotations:
summary: "High p99 response latency (>1s)"
description: >-
The 99th percentile response time for Agent Monitor is
{{ $value | humanizeDuration }}. Users may experience slow page loads.
- alert: HighMedianLatency
expr: |
histogram_quantile(0.50,
sum(rate(http_request_duration_seconds_bucket{job="agent-monitor"}[5m])) by (le)
) > 0.5
for: 5m
labels:
severity: warning
service: agent-monitor
annotations:
summary: "High median response latency (>500ms)"
description: >-
The median response time is {{ $value | humanizeDuration }}.
This affects the majority of requests.
# ── Resource alerts ─────────────────────────────────────────────────────
- name: agent-monitor.resources
rules:
- alert: HighMemoryUsage
expr: |
(
container_memory_working_set_bytes{
namespace=~"agent-monitor.*",
container="agent-monitor"
}
/
container_spec_memory_limit_bytes{
namespace=~"agent-monitor.*",
container="agent-monitor"
}
) > 0.90
for: 10m
labels:
severity: warning
service: agent-monitor
annotations:
summary: "High memory usage (>90% of limit)"
description: >-
Pod {{ $labels.pod }} is using {{ $value | humanizePercentage }}
of its memory limit. OOMKill risk is elevated.
- alert: HighCPUUsage
expr: |
(
rate(container_cpu_usage_seconds_total{
namespace=~"agent-monitor.*",
container="agent-monitor"
}[5m])
/
container_spec_cpu_quota{
namespace=~"agent-monitor.*",
container="agent-monitor"
}
* 100000
) > 0.85
for: 10m
labels:
severity: warning
service: agent-monitor
annotations:
summary: "High CPU usage (>85% of limit)"
description: >-
Pod {{ $labels.pod }} is using {{ $value | humanizePercentage }}
of its CPU limit. Consider scaling up.
- alert: PersistentVolumeNearlyFull
expr: |
(
kubelet_volume_stats_used_bytes{
namespace=~"agent-monitor.*",
persistentvolumeclaim=~"agent-monitor.*"
}
/
kubelet_volume_stats_capacity_bytes{
namespace=~"agent-monitor.*",
persistentvolumeclaim=~"agent-monitor.*"
}
) > 0.85
for: 15m
labels:
severity: warning
service: agent-monitor
annotations:
summary: "Persistent volume nearly full (>85%)"
description: >-
PVC {{ $labels.persistentvolumeclaim }} in namespace
{{ $labels.namespace }} is {{ $value | humanizePercentage }}
full. SQLite writes may fail when volume is exhausted.
- alert: PersistentVolumeCriticallyFull
expr: |
(
kubelet_volume_stats_used_bytes{
namespace=~"agent-monitor.*",
persistentvolumeclaim=~"agent-monitor.*"
}
/
kubelet_volume_stats_capacity_bytes{
namespace=~"agent-monitor.*",
persistentvolumeclaim=~"agent-monitor.*"
}
) > 0.95
for: 5m
labels:
severity: critical
service: agent-monitor
annotations:
summary: "Persistent volume critically full (>95%)"
description: >-
PVC {{ $labels.persistentvolumeclaim }} is at
{{ $value | humanizePercentage }} capacity.
Immediate action required to prevent data loss.
# ── WebSocket alerts ────────────────────────────────────────────────────
- name: agent-monitor.websocket
rules:
- alert: WebSocketConnectionsDrop
expr: |
(
max_over_time(websocket_connections_active{job="agent-monitor"}[10m])
- websocket_connections_active{job="agent-monitor"}
)
/
max_over_time(websocket_connections_active{job="agent-monitor"}[10m])
> 0.50
for: 5m
labels:
severity: warning
service: agent-monitor
annotations:
summary: "WebSocket connections dropped >50%"
description: >-
Active WebSocket connections have dropped by more than 50%
in the last 5 minutes (from
{{ with printf `max_over_time(websocket_connections_active{instance="%s"}[10m])` .Labels.instance | query }}{{ . | first | value }}{{ end }}
to {{ $value }}). This may indicate connectivity issues.
- alert: NoWebSocketConnections
expr: |
websocket_connections_active{job="agent-monitor"} == 0
and on() hour() >= 8 <= 20
for: 15m
labels:
severity: info
service: agent-monitor
annotations:
summary: "No active WebSocket connections during business hours"
description: >-
There are no active WebSocket connections to Agent Monitor
during expected business hours.
# ── Pod stability alerts ────────────────────────────────────────────────
- name: agent-monitor.stability
rules:
- alert: PodRestarting
expr: |
increase(
kube_pod_container_status_restarts_total{
namespace=~"agent-monitor.*",
container="agent-monitor"
}[15m]
) > 3
for: 0m
labels:
severity: critical
service: agent-monitor
annotations:
summary: "Pod restarting frequently (>3 in 15m)"
description: >-
Pod {{ $labels.pod }} in namespace {{ $labels.namespace }}
has restarted {{ $value }} times in the last 15 minutes.
Check logs: kubectl logs {{ $labels.pod }} -n {{ $labels.namespace }} --previous
- alert: PodNotReady
expr: |
kube_pod_status_ready{
namespace=~"agent-monitor.*",
condition="true"
} == 0
for: 5m
labels:
severity: warning
service: agent-monitor
annotations:
summary: "Pod not ready for >5 minutes"
description: >-
Pod {{ $labels.pod }} has been in a not-ready state for
more than 5 minutes.
- alert: DeploymentReplicasMismatch
expr: |
kube_deployment_spec_replicas{namespace=~"agent-monitor.*"}
!=
kube_deployment_status_ready_replicas{namespace=~"agent-monitor.*"}
for: 10m
labels:
severity: warning
service: agent-monitor
annotations:
summary: "Deployment replicas mismatch"
description: >-
Deployment {{ $labels.deployment }} has
{{ with printf `kube_deployment_status_ready_replicas{deployment="%s",namespace="%s"}` .Labels.deployment .Labels.namespace | query }}{{ . | first | value }}{{ end }}
ready replicas but
{{ with printf `kube_deployment_spec_replicas{deployment="%s",namespace="%s"}` .Labels.deployment .Labels.namespace | query }}{{ . | first | value }}{{ end }}
desired.