-
Notifications
You must be signed in to change notification settings - Fork 513
Revert moving Kubernetes instructions from system prompt to skill and other improvements to evals #2051
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Revert moving Kubernetes instructions from system prompt to skill and other improvements to evals #2051
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
7e03b5f
Add Docker-only Loki regression eval + fix OpenRouter classifier routing
claude 4c06b74
Add n=5 docker-loki sweep results: confirms 14-day regression
claude 8505334
Add trace-level diff: 7 vs 8 LLM calls, identical per-call cost
claude 4941a47
Sweep two candidate fixes — neither alone restores baseline
claude 145be73
Identify root cause: fully reverting PR #2040 restores baseline
claude 0c37654
Apply fix: revert PR #2040 + clean up
claude d2286a9
Address CodeRabbit review on eval 259 fixture
claude 46aa96e
Add eval-regression investigation methodology to CLAUDE.md
claude fa4bb04
Tighten eval-regression methodology in CLAUDE.md
claude 8aa2f31
Merge remote-tracking branch 'origin/master' into claude/investigate-…
claude 00a888e
Fix two bugs in eval baseline comparison reporting
claude 6cb0e8e
Add "Total (all)" summary row to historical comparison tables
claude File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 0 additions & 38 deletions
38
holmes/plugins/skills/builtin/kubernetes-troubleshooting/SKILL.md
This file was deleted.
Oops, something went wrong.
125 changes: 125 additions & 0 deletions
125
...llm/fixtures/test_ask_holmes/259_loki_historical_logs_pod_deleted_docker/generate_logs.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| #!/usr/bin/env python3 | ||
| """Generate the same historical log timeline as 101_loki_historical_logs_pod_deleted | ||
| and push it directly to a local Loki instance (no Promtail, no Kubernetes). | ||
|
|
||
| Usage: | ||
| python generate_logs.py <loki_url> | ||
|
|
||
| Pushes logs to <loki_url>/loki/api/v1/push with the same labels Holmes expects. | ||
| """ | ||
|
|
||
| import json | ||
| import random | ||
| import sys | ||
| import urllib.error | ||
| import urllib.request | ||
| from datetime import datetime, timedelta | ||
|
|
||
| random.seed(100) | ||
|
|
||
| NAMESPACE = "app-259" | ||
| POD_NAME = "payment-api-259-d8f7b9c4-abc12" | ||
| SERVICE = "payment-api" | ||
| BATCH_SIZE = 200 | ||
|
|
||
|
|
||
| def push(loki_url, streams_by_level): | ||
| """Push grouped streams to Loki.""" | ||
| streams = [] | ||
| for level, values in streams_by_level.items(): | ||
| if not values: | ||
| continue | ||
| streams.append( | ||
| { | ||
| "stream": { | ||
| "job": "payment-api", | ||
| "namespace": NAMESPACE, | ||
| "pod_name": POD_NAME, | ||
| "service": SERVICE, | ||
| "level": level, | ||
| }, | ||
| "values": values, | ||
| } | ||
| ) | ||
| if not streams: | ||
| return | ||
| payload = {"streams": streams} | ||
| req = urllib.request.Request( | ||
| loki_url + "/loki/api/v1/push", | ||
| data=json.dumps(payload).encode("utf-8"), | ||
| headers={"Content-Type": "application/json"}, | ||
| ) | ||
| try: | ||
| urllib.request.urlopen(req, timeout=30) | ||
| except urllib.error.HTTPError as e: | ||
| body = e.read().decode("utf-8", errors="replace")[:500] | ||
| raise SystemExit(f"Loki push failed {e.code}: {body}") | ||
|
|
||
|
|
||
| def log_entry(level, message, **extra): | ||
| return { | ||
| "level": level, | ||
| "message": message, | ||
| "service": SERVICE, | ||
| "pod": POD_NAME, | ||
| **extra, | ||
| } | ||
|
|
||
|
|
||
| def generate(loki_url): | ||
| """Generate the same logs as 101 (and 100a's incident period), push in batches.""" | ||
| problem_start = datetime(2025, 8, 2, 13, 45, 0) | ||
| problem_end = datetime(2025, 8, 2, 14, 45, 0) | ||
|
|
||
| current = datetime(2025, 8, 1, 12, 0, 0) | ||
| scenario_end = datetime(2025, 8, 4, 14, 0, 0) | ||
|
aantn marked this conversation as resolved.
|
||
|
|
||
| streams_by_level: dict[str, list[list[str]]] = {} | ||
| total = 0 | ||
|
|
||
| def add(ts: datetime, level: str, data: dict): | ||
| ts_nano = str(int(ts.timestamp() * 1e9)) | ||
| streams_by_level.setdefault(level, []).append([ts_nano, json.dumps(data)]) | ||
|
|
||
| while current < scenario_end: | ||
| if random.random() < 0.05: | ||
| add( | ||
| current, | ||
| "INFO", | ||
| log_entry( | ||
| "INFO", | ||
| "Payment processed successfully", | ||
| payment_id=f"PAY-{random.randint(1000, 9999)}", | ||
| ), | ||
| ) | ||
| total += 1 | ||
|
|
||
| if problem_start <= current <= problem_end: | ||
| if random.random() < 0.4: | ||
| add( | ||
| current, | ||
| "ERROR", | ||
| log_entry( | ||
| "ERROR", | ||
| "Failed to acquire database connection - pool exhausted", | ||
| wait_time_ms=random.randint(1000, 5000), | ||
| queue_length=random.randint(5, 15), | ||
| ), | ||
| ) | ||
| total += 1 | ||
|
|
||
| current += timedelta(minutes=random.randint(1, 5)) | ||
|
|
||
| # Flush in batches | ||
| pending = sum(len(v) for v in streams_by_level.values()) | ||
| if pending >= BATCH_SIZE: | ||
| push(loki_url, streams_by_level) | ||
| streams_by_level = {} | ||
|
|
||
| push(loki_url, streams_by_level) | ||
| print(f"Pushed {total} historical log entries to {loki_url}") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| url = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:3100" | ||
| generate(url.rstrip("/")) | ||
37 changes: 37 additions & 0 deletions
37
...llm/fixtures/test_ask_holmes/259_loki_historical_logs_pod_deleted_docker/loki-config.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| auth_enabled: false | ||
| server: | ||
| http_listen_port: 3100 | ||
| ingester: | ||
| query_store_max_look_back_period: -1 | ||
| max_chunk_age: 17520h | ||
| wal: | ||
| enabled: false | ||
| lifecycler: | ||
| address: 127.0.0.1 | ||
| ring: | ||
| kvstore: | ||
| store: inmemory | ||
| replication_factor: 1 | ||
| final_sleep: 0s | ||
| chunk_store_config: | ||
| max_look_back_period: 17520h | ||
| schema_config: | ||
| configs: | ||
| - from: 2020-01-01 | ||
| store: boltdb | ||
| object_store: filesystem | ||
| schema: v11 | ||
| index: | ||
| prefix: index_ | ||
| period: 168h | ||
| storage_config: | ||
| boltdb: | ||
| directory: /tmp/loki/index | ||
| filesystem: | ||
| directory: /tmp/loki/chunks | ||
| limits_config: | ||
| enforce_metric_name: false | ||
| reject_old_samples: false | ||
| reject_old_samples_max_age: 104w | ||
| max_entries_limit_per_query: 5000 | ||
| max_query_lookback: 17520h |
77 changes: 77 additions & 0 deletions
77
...s/llm/fixtures/test_ask_holmes/259_loki_historical_logs_pod_deleted_docker/test_case.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| user_prompt: "The payment-api pod in namespace app-259 had issues on August 2, 2025 around 13:45 UTC. What happened?" | ||
|
|
||
| description: | | ||
| Docker-only variant of 101_loki_historical_logs_pod_deleted. | ||
|
|
||
| Stands up a Loki container locally (no Kubernetes), pushes a historical | ||
| log timeline directly to Loki's HTTP API, and lets Holmes investigate | ||
| using only the grafana/loki toolset. The "pod was deleted" framing is preserved | ||
| by simply never running the pod — Holmes only has Loki to work with. | ||
|
|
||
| Use this variant to reproduce the regression on a developer laptop, or in any | ||
| environment where Kubernetes is unavailable (e.g. cgroup-v1 sandboxes). | ||
|
|
||
| expected_output: | ||
| - Between 2025-08-02 13:45 UTC and 14:45 UTC, the payment-api pod logged the ERROR "Failed to acquire database connection - pool exhausted" | ||
| - The root cause is database connection pool exhaustion | ||
|
|
||
| include_tool_calls: true | ||
|
|
||
| tags: | ||
| - logs | ||
| - easy | ||
| - loki | ||
| - fast | ||
|
|
||
| setup_timeout: 180 | ||
|
|
||
| before_test: | | ||
| set -e | ||
| CONTAINER=holmes-eval-loki-259 | ||
| PORT=3259 | ||
|
|
||
| # Clean up any leftover container from a previous run | ||
| docker rm -f "$CONTAINER" >/dev/null 2>&1 || true | ||
|
|
||
| # Start Loki with config that allows ingestion of old samples | ||
| docker run -d --name "$CONTAINER" -p "$PORT:3100" \ | ||
| -v "$(pwd)/loki-config.yaml:/etc/loki/local-config.yaml:ro" \ | ||
| grafana/loki:2.9.0 -config.file=/etc/loki/local-config.yaml >/dev/null | ||
|
|
||
| # Wait for Loki to be ready | ||
| READY=false | ||
| for i in $(seq 1 60); do | ||
| if curl -sf "http://localhost:$PORT/ready" 2>/dev/null | grep -q ready; then | ||
| READY=true | ||
| break | ||
| fi | ||
| sleep 1 | ||
| done | ||
| if [ "$READY" != "true" ]; then | ||
| echo "ERROR: Loki not ready after 60s" | ||
| docker logs "$CONTAINER" | tail -40 | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Push the historical log timeline directly via Loki's push API | ||
| python3 generate_logs.py "http://localhost:$PORT" | ||
|
|
||
| # Verify the incident logs landed in Loki | ||
| for i in $(seq 1 30); do | ||
| ERROR_COUNT=$(curl -sG "http://localhost:$PORT/loki/api/v1/query_range" \ | ||
| --data-urlencode 'query={namespace="app-259",level="ERROR"}' \ | ||
| --data-urlencode 'start=2025-08-02T13:00:00Z' \ | ||
| --data-urlencode 'end=2025-08-02T15:00:00Z' \ | ||
| --data-urlencode 'limit=1' 2>/dev/null | grep -o '"values"' | wc -l) | ||
| if [ "$ERROR_COUNT" -gt "0" ]; then | ||
| echo "Historical ERROR logs ready in Loki" | ||
| exit 0 | ||
| fi | ||
| sleep 1 | ||
| done | ||
|
|
||
| echo "ERROR: no historical ERROR logs found in Loki after push" | ||
| exit 1 | ||
|
|
||
| after_test: | | ||
| docker rm -f holmes-eval-loki-259 >/dev/null 2>&1 || true |
19 changes: 19 additions & 0 deletions
19
tests/llm/fixtures/test_ask_holmes/259_loki_historical_logs_pod_deleted_docker/toolsets.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| toolsets: | ||
| # Disable everything Kubernetes/Helm-related — this test runs against a plain | ||
| # Docker Loki container with no cluster to query. | ||
| kubernetes/logs: | ||
| enabled: false | ||
| kubernetes/core: | ||
| enabled: false | ||
| helm/core: | ||
| enabled: false | ||
| connectivity_check: | ||
| enabled: false | ||
| grafana/loki: | ||
| enabled: true | ||
| config: | ||
| api_url: http://localhost:3259 | ||
| api_key: "" | ||
| labels: | ||
| pod: pod_name | ||
| namespace: namespace |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.