Skip to content

Commit 44fdf71

Browse files
authored
feat(agent): add Loki + CloudWatch Logs sources, agent-only templates
feat(agent): add Loki + CloudWatch Logs sources, agent-only templates
2 parents 7a20c12 + 871a713 commit 44fdf71

63 files changed

Lines changed: 2954 additions & 412 deletions

Some content is hidden

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

config/agent_email.tmpl

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<!DOCTYPE html>
2+
{{/*
3+
Versus Agent — Email template (HTML)
4+
*/}}
5+
{{- $sevColors := dict "critical" "#C70039" "high" "#E67E22" "medium" "#F1C40F" "low" "#27AE60" "info" "#3498DB" -}}
6+
{{- $sev := lower (or .Severity "info") -}}
7+
{{- $sevColor := or (index $sevColors $sev) "#3498DB" -}}
8+
<html>
9+
<head><meta charset="UTF-8"><title>Versus Agent — {{ or .AlertName "AI-detected incident" }}</title></head>
10+
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif; color:#222; max-width: 720px; margin: 0 auto; padding: 16px;">
11+
<h2 style="margin:0 0 8px 0;">🤖 Versus Agent — {{ or .AlertName "AI-detected incident" }}</h2>
12+
<p style="margin:0 0 16px 0;">
13+
<span style="display:inline-block; padding:2px 8px; border-radius:4px; background:{{ $sevColor }}; color:#fff; font-weight:600;">
14+
{{ upper (or .Severity "INFO") }}
15+
</span>
16+
</p>
17+
18+
<table style="border-collapse:collapse; width:100%;" cellpadding="6">
19+
<tr><td style="background:#f5f5f5; width:160px;"><b>Service</b></td><td>{{ or .ServiceName "_unknown" }}</td></tr>
20+
{{- if .Source }}
21+
<tr><td style="background:#f5f5f5;"><b>Source</b></td><td>{{ .Source }}</td></tr>
22+
{{- end }}
23+
{{- if .Category }}
24+
<tr><td style="background:#f5f5f5;"><b>Category</b></td><td>{{ .Category }}</td></tr>
25+
{{- end }}
26+
{{- if .Verdict }}
27+
<tr><td style="background:#f5f5f5;"><b>Verdict</b></td><td>{{ .Verdict }}</td></tr>
28+
{{- end }}
29+
{{- if .Confidence }}
30+
<tr><td style="background:#f5f5f5;"><b>Confidence</b></td><td>{{ printf "%.2f" .Confidence }}</td></tr>
31+
{{- end }}
32+
{{- if .Frequency }}
33+
<tr><td style="background:#f5f5f5;"><b>Frequency</b></td><td>{{ .Frequency }} (baseline {{ printf "%.2f" .Baseline }})</td></tr>
34+
{{- end }}
35+
{{- if .PatternID }}
36+
<tr><td style="background:#f5f5f5;"><b>Pattern</b></td><td><code>{{ .PatternID }}</code></td></tr>
37+
{{- end }}
38+
{{- if .PatternTemplate }}
39+
<tr><td style="background:#f5f5f5;"><b>Template</b></td><td><code>{{ .PatternTemplate }}</code></td></tr>
40+
{{- end }}
41+
</table>
42+
43+
<h3 style="margin-top:20px;">Summary</h3>
44+
<p>{{ or .Summary "(no summary)" }}</p>
45+
46+
{{- if .Suggestions }}
47+
<h3>Suggestions</h3>
48+
<ul>
49+
{{- range $i, $s := .Suggestions }}
50+
<li>{{ $s }}</li>
51+
{{- end }}
52+
</ul>
53+
{{- end }}
54+
55+
{{- if .Logs }}
56+
<h3>Sample log</h3>
57+
<pre style="background:#f5f5f5; padding:12px; border-radius:4px; overflow:auto;">{{ .Logs }}</pre>
58+
{{- end }}
59+
60+
{{- if .AckURL }}
61+
<p style="margin-top:24px;">
62+
<a href="{{ .AckURL }}" style="display:inline-block; padding:10px 18px; background:#3498DB; color:#fff; text-decoration:none; border-radius:4px;">Acknowledge alert</a>
63+
</p>
64+
{{- end }}
65+
</body>
66+
</html>

config/agent_lark.tmpl

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{{/*
2+
Versus Agent — Lark template (Markdown for interactive card)
3+
*/}}
4+
{{- $sevIcons := dict "critical" "🔴" "high" "🟠" "medium" "🟡" "low" "🟢" "info" "ℹ️" -}}
5+
{{- $sev := lower (or .Severity "info") -}}
6+
{{- $sevIcon := or (index $sevIcons $sev) "ℹ️" -}}
7+
**🤖 Versus Agent — {{ or .AlertName "AI-detected incident" }}**
8+
9+
**{{ $sevIcon }} Severity:** {{ upper (or .Severity "INFO") }}
10+
**Service:** {{ or .ServiceName "_unknown" }}
11+
{{- if .Source }}
12+
**Source:** {{ .Source }}
13+
{{- end }}
14+
{{- if .Category }}
15+
**Category:** {{ .Category }}
16+
{{- end }}
17+
{{- if .Verdict }}
18+
**Verdict:** {{ .Verdict }}
19+
{{- end }}
20+
{{- if .Confidence }}
21+
**Confidence:** {{ printf "%.2f" .Confidence }}
22+
{{- end }}
23+
24+
**Summary:**
25+
{{ or .Summary "(no summary)" }}
26+
27+
{{- if .Frequency }}
28+
**Frequency:** {{ .Frequency }} (baseline {{ printf "%.2f" .Baseline }})
29+
{{- end }}
30+
{{- if .PatternID }}
31+
**Pattern:** `{{ .PatternID }}`
32+
{{- end }}
33+
{{- if .PatternTemplate }}
34+
**Template:** `{{ .PatternTemplate }}`
35+
{{- end }}
36+
37+
{{- if .Suggestions }}
38+
39+
**Suggestions:**
40+
{{- range $i, $s := .Suggestions }}
41+
- {{ $s }}
42+
{{- end }}
43+
{{- end }}
44+
45+
{{- if .Logs }}
46+
47+
**Sample log:**
48+
```
49+
{{ .Logs }}
50+
```
51+
{{- end }}

config/agent_msteams.tmpl

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{{/*
2+
Versus Agent — Microsoft Teams template (Markdown via Power Automate)
3+
*/}}
4+
{{- $sevIcons := dict "critical" "🔴" "high" "🟠" "medium" "🟡" "low" "🟢" "info" "ℹ️" -}}
5+
{{- $sev := lower (or .Severity "info") -}}
6+
{{- $sevIcon := or (index $sevIcons $sev) "ℹ️" -}}
7+
**🤖 Versus Agent — {{ or .AlertName "AI-detected incident" }}**
8+
9+
**{{ $sevIcon }} Severity:** {{ upper (or .Severity "INFO") }}
10+
**Service:** {{ or .ServiceName "_unknown" }}
11+
{{- if .Source }}
12+
**Source:** {{ .Source }}
13+
{{- end }}
14+
{{- if .Category }}
15+
**Category:** {{ .Category }}
16+
{{- end }}
17+
{{- if .Verdict }}
18+
**Verdict:** {{ .Verdict }}
19+
{{- end }}
20+
{{- if .Confidence }}
21+
**Confidence:** {{ printf "%.2f" .Confidence }}
22+
{{- end }}
23+
24+
**Summary:**
25+
{{ or .Summary "(no summary)" }}
26+
27+
{{- if .Frequency }}
28+
**Frequency:** {{ .Frequency }} (baseline {{ printf "%.2f" .Baseline }})
29+
{{- end }}
30+
{{- if .PatternID }}
31+
**Pattern:** `{{ .PatternID }}`
32+
{{- end }}
33+
{{- if .PatternTemplate }}
34+
**Template:** `{{ .PatternTemplate }}`
35+
{{- end }}
36+
37+
{{- if .Suggestions }}
38+
39+
**Suggestions:**
40+
{{- range $i, $s := .Suggestions }}
41+
- {{ $s }}
42+
{{- end }}
43+
{{- end }}
44+
45+
{{- if .Logs }}
46+
47+
**Sample log:**
48+
```
49+
{{ .Logs }}
50+
```
51+
{{- end }}

config/agent_slack.tmpl

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{{/*
2+
Versus Agent — Slack template (mrkdwn)
3+
Used when the incident originates from the AI SRE Agent (detect mode).
4+
Available fields: .AlertName .Summary .Severity .Category .Confidence
5+
.Suggestions .ServiceName .Source .PatternID .PatternTemplate
6+
.Frequency .Baseline .Verdict .Logs .Status .AckURL
7+
*/}}
8+
{{- $sevIcons := dict "critical" "🔴" "high" "🟠" "medium" "🟡" "low" "🟢" "info" "ℹ️" -}}
9+
{{- $sev := lower (or .Severity "info") -}}
10+
{{- $sevIcon := or (index $sevIcons $sev) "ℹ️" -}}
11+
*🤖 Versus Agent — {{ or .AlertName "AI-detected incident" }}*
12+
*{{ $sevIcon }} Severity:* {{ upper (or .Severity "INFO") }}
13+
*Service:* {{ or .ServiceName "_unknown" }}
14+
{{- if .Source }}
15+
*Source:* {{ .Source }}
16+
{{- end }}
17+
{{- if .Category }}
18+
*Category:* {{ .Category }}
19+
{{- end }}
20+
{{- if .Verdict }}
21+
*Verdict:* {{ .Verdict }}
22+
{{- end }}
23+
{{- if .Confidence }}
24+
*Confidence:* {{ printf "%.2f" .Confidence }}
25+
{{- end }}
26+
27+
*Summary:*
28+
{{ or .Summary "(no summary)" }}
29+
30+
{{- if .Frequency }}
31+
*Frequency:* {{ .Frequency }} (baseline {{ printf "%.2f" .Baseline }})
32+
{{- end }}
33+
{{- if .PatternID }}
34+
*Pattern:* `{{ .PatternID }}`
35+
{{- end }}
36+
{{- if .PatternTemplate }}
37+
*Template:* `{{ .PatternTemplate }}`
38+
{{- end }}
39+
40+
{{- if .Suggestions }}
41+
42+
*Suggestions:*
43+
{{- range $i, $s := .Suggestions }}
44+
• {{ $s }}
45+
{{- end }}
46+
{{- end }}
47+
48+
{{- if .Logs }}
49+
50+
*Sample log:*
51+
```
52+
{{ .Logs }}
53+
```
54+
{{- end }}

config/agent_sources.yaml

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
# -----------------------------------------------------------------------------
22
# Agent signal sources.
33
#
4-
# Loaded by the main config when `agent.sources_path` points at this file.
5-
# Keeping sources separate makes it easy to swap fixtures (file → ES) and to
6-
# manage per-environment source lists without touching the rest of the config.
4+
# Loaded automatically by Versus when this file sits next to the main
5+
# config file (path is hardcoded — no `sources_path` setting). Drop the
6+
# file or rename it to disable all sources without editing config.yaml.
77
#
88
# Each entry must have:
99
# name: unique identifier (used in cursor keys and admin views)
10-
# type: "file" or "elasticsearch"
10+
# type: "file" | "elasticsearch" | "loki" | "cloudwatchlogs"
1111
# enable: true | false
12-
# Plus one of `file:` / `elasticsearch:` blocks depending on `type`.
12+
# Plus the matching block (`file:` / `elasticsearch:` / `loki:` /
13+
# `cloudwatchlogs:`) for the chosen type.
1314
# -----------------------------------------------------------------------------
1415
sources:
1516
# File source — easiest way to test the agent end-to-end. Drop a log file
@@ -72,3 +73,36 @@ sources:
7273
# - host.name
7374
# - error.stack_trace
7475
# page_size: 500
76+
77+
# Grafana Loki source.
78+
# - name: loki-prod
79+
# type: loki
80+
# enable: false
81+
# loki:
82+
# address: http://loki:3100 # or https://logs-prod-...grafana.net for Grafana Cloud
83+
# # tenant_id: ${LOKI_TENANT_ID} # multi-tenant Loki / Grafana Cloud
84+
# # username: ${LOKI_USERNAME} # Grafana Cloud: instance ID
85+
# # password: ${LOKI_PASSWORD} # Grafana Cloud: API token
86+
# # bearer_token: ${LOKI_TOKEN} # alternative to user/pass
87+
# # insecure_skip_verify: false
88+
# query: '{app="api",env="prod"} |= "error"'
89+
# severity_field: level # read from stream labels
90+
# extra_labels:
91+
# - app
92+
# - namespace
93+
# page_size: 500
94+
95+
# AWS CloudWatch Logs source.
96+
# Auth uses the standard AWS SDK chain (env vars, ~/.aws/credentials,
97+
# IAM instance/task role). Required IAM:
98+
# logs:FilterLogEvents on the target log group.
99+
# - name: cwlogs-lambda
100+
# type: cloudwatchlogs
101+
# enable: false
102+
# cloudwatchlogs:
103+
# region: us-east-1
104+
# log_group_name: /aws/lambda/my-function
105+
# # log_stream_prefix: 2026/05/12 # narrow to recent streams (cheaper)
106+
# # filter_pattern: '?ERROR ?Exception' # CloudWatch filter syntax (NOT regex)
107+
# page_size: 500
108+

config/agent_telegram.tmpl

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{{/*
2+
Versus Agent — Telegram template (HTML parse_mode)
3+
*/}}
4+
{{- $sevIcons := dict "critical" "🔴" "high" "🟠" "medium" "🟡" "low" "🟢" "info" "ℹ️" -}}
5+
{{- $sev := lower (or .Severity "info") -}}
6+
{{- $sevIcon := or (index $sevIcons $sev) "ℹ️" -}}
7+
<b>🤖 Versus Agent — {{ or .AlertName "AI-detected incident" }}</b>
8+
9+
<b>{{ $sevIcon }} Severity:</b> {{ upper (or .Severity "INFO") }}
10+
<b>Service:</b> {{ or .ServiceName "_unknown" }}
11+
{{- if .Source }}
12+
<b>Source:</b> {{ .Source }}
13+
{{- end }}
14+
{{- if .Category }}
15+
<b>Category:</b> {{ .Category }}
16+
{{- end }}
17+
{{- if .Verdict }}
18+
<b>Verdict:</b> {{ .Verdict }}
19+
{{- end }}
20+
{{- if .Confidence }}
21+
<b>Confidence:</b> {{ printf "%.2f" .Confidence }}
22+
{{- end }}
23+
24+
<b>Summary:</b>
25+
{{ or .Summary "(no summary)" }}
26+
27+
{{- if .Frequency }}
28+
<b>Frequency:</b> {{ .Frequency }} (baseline {{ printf "%.2f" .Baseline }})
29+
{{- end }}
30+
{{- if .PatternID }}
31+
<b>Pattern:</b> <code>{{ .PatternID }}</code>
32+
{{- end }}
33+
{{- if .PatternTemplate }}
34+
<b>Template:</b> <code>{{ .PatternTemplate }}</code>
35+
{{- end }}
36+
37+
{{- if .Suggestions }}
38+
39+
<b>Suggestions:</b>
40+
{{- range $i, $s := .Suggestions }}
41+
• {{ $s }}
42+
{{- end }}
43+
{{- end }}
44+
45+
{{- if .Logs }}
46+
47+
<b>Sample log:</b>
48+
<pre>{{ .Logs }}</pre>
49+
{{- end }}

config/agent_viber.tmpl

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{{/*
2+
Versus Agent — Viber template (plain text)
3+
*/}}
4+
{{- $sevIcons := dict "critical" "🔴" "high" "🟠" "medium" "🟡" "low" "🟢" "info" "ℹ️" -}}
5+
{{- $sev := lower (or .Severity "info") -}}
6+
{{- $sevIcon := or (index $sevIcons $sev) "ℹ️" -}}
7+
🤖 Versus Agent — {{ or .AlertName "AI-detected incident" }}
8+
9+
{{ $sevIcon }} Severity: {{ upper (or .Severity "INFO") }}
10+
Service: {{ or .ServiceName "_unknown" }}
11+
{{- if .Source }}
12+
Source: {{ .Source }}
13+
{{- end }}
14+
{{- if .Category }}
15+
Category: {{ .Category }}
16+
{{- end }}
17+
{{- if .Verdict }}
18+
Verdict: {{ .Verdict }}
19+
{{- end }}
20+
{{- if .Confidence }}
21+
Confidence: {{ printf "%.2f" .Confidence }}
22+
{{- end }}
23+
24+
Summary:
25+
{{ or .Summary "(no summary)" }}
26+
27+
{{- if .Frequency }}
28+
Frequency: {{ .Frequency }} (baseline {{ printf "%.2f" .Baseline }})
29+
{{- end }}
30+
{{- if .PatternID }}
31+
Pattern: {{ .PatternID }}
32+
{{- end }}
33+
{{- if .PatternTemplate }}
34+
Template: {{ .PatternTemplate }}
35+
{{- end }}
36+
37+
{{- if .Suggestions }}
38+
39+
Suggestions:
40+
{{- range $i, $s := .Suggestions }}
41+
- {{ $s }}
42+
{{- end }}
43+
{{- end }}
44+
45+
{{- if .Logs }}
46+
47+
Sample log:
48+
{{ .Logs }}
49+
{{- end }}

config/config.yaml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,6 @@ agent:
170170
batch_max: 5000 # safety cap per tick
171171
signal_max_bytes: 65536 # cap on Signal.Raw
172172

173-
# Signal sources are kept in a separate file so users can manage them
174-
# independently of the main config. Path is resolved relative to this
175-
# config file. Override via env: AGENT_SOURCES_PATH.
176-
sources_path: ./agent_sources.yaml
177173

178174
redaction:
179175
enable: true

0 commit comments

Comments
 (0)