4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
288 lines
11 KiB
Python
288 lines
11 KiB
Python
from __future__ import annotations
|
|
|
|
from tools.investigation.stages.gather_evidence.prompt import (
|
|
_relevant_sources,
|
|
build_investigation_system_prompt,
|
|
format_alert_context,
|
|
)
|
|
from tools.investigation.stages.gather_evidence.tools import STAGNATION_NUDGE
|
|
|
|
|
|
def test_build_investigation_system_prompt_non_hermes_uses_generic_category_instruction() -> None:
|
|
prompt = build_investigation_system_prompt({"alert_source": "grafana"})
|
|
|
|
assert "Root cause category taxonomy" in prompt
|
|
assert "connection_exhaustion" in prompt
|
|
assert "[database]" in prompt
|
|
assert "Hermes root cause category taxonomy" not in prompt
|
|
assert "agent_hang" not in prompt
|
|
|
|
|
|
def test_build_investigation_system_prompt_includes_dependency_traversal_rule() -> None:
|
|
prompt = build_investigation_system_prompt({"alert_source": "grafana"})
|
|
|
|
assert "Dependency traversal (connection failures only)" in prompt
|
|
assert "connection refused" in prompt
|
|
assert "does not bias localization" in prompt
|
|
|
|
|
|
def test_build_investigation_system_prompt_includes_incident_command_phases() -> None:
|
|
prompt = build_investigation_system_prompt({"alert_source": "grafana"})
|
|
|
|
assert "incident commander" in prompt
|
|
assert "Investigation phases" in prompt
|
|
assert "Phase 1 — Triage" in prompt
|
|
assert "Phase 2 — Hypothesis" in prompt
|
|
assert "Phase 3 — Verification" in prompt
|
|
assert "Phase 4 — Mitigation" in prompt
|
|
assert "## How to work" not in prompt
|
|
|
|
|
|
def test_build_investigation_system_prompt_includes_missing_context_rule() -> None:
|
|
prompt = build_investigation_system_prompt({"alert_source": "grafana"})
|
|
|
|
assert "Follow-up questions" in prompt
|
|
assert "Recent deploys" in prompt
|
|
assert "ending with `?`" in prompt
|
|
assert "Do not stall waiting for answers" in prompt
|
|
|
|
|
|
def test_build_investigation_system_prompt_includes_alignment_and_tradeoffs() -> None:
|
|
prompt = build_investigation_system_prompt({"alert_source": "grafana"})
|
|
|
|
assert "Keeping the team aligned" in prompt
|
|
assert "Hypotheses:" in prompt
|
|
assert "Verification:" in prompt
|
|
assert "Follow-up questions:" in prompt
|
|
assert "Remediation trade-offs:" in prompt
|
|
assert "blast radius" in prompt
|
|
assert "reversibility" in prompt
|
|
|
|
|
|
def test_stagnation_nudge_matches_incident_command_output_contract() -> None:
|
|
nudge = STAGNATION_NUDGE.lower()
|
|
|
|
assert "triage complete" in nudge
|
|
assert "status block" in nudge
|
|
assert "hypotheses" in nudge
|
|
assert "verification" in nudge
|
|
assert "follow-up questions" in nudge
|
|
assert "remediation trade-offs" in nudge
|
|
|
|
|
|
def test_build_investigation_system_prompt_hermes_includes_hermes_taxonomy_only() -> None:
|
|
prompt = build_investigation_system_prompt({"alert_source": "hermes"})
|
|
|
|
assert "Hermes root cause category taxonomy" in prompt
|
|
assert "agent_hang" in prompt
|
|
assert "delivery_hang" in prompt
|
|
assert "ghost_session" in prompt
|
|
assert "connection_exhaustion" not in prompt
|
|
|
|
|
|
def test_generic_alert_matches_relevant_integration_by_content() -> None:
|
|
context = format_alert_context(
|
|
{
|
|
"alert_name": "High error rate in payments ETL",
|
|
"alert_source": "generic",
|
|
"pipeline_name": "payments_etl",
|
|
"severity": "critical",
|
|
"message": "payments_etl is failing with repeated database connection errors",
|
|
"resolved_integrations": {
|
|
"postgresql": {"host": "orders-db", "database": "orders", "port": 5432},
|
|
},
|
|
}
|
|
)
|
|
|
|
assert "Call these tools first (from: postgresql" in context
|
|
|
|
|
|
def test_generic_alert_excludes_unrelated_integrations() -> None:
|
|
context = format_alert_context(
|
|
{
|
|
"alert_name": "High error rate in payments ETL",
|
|
"alert_source": "generic",
|
|
"pipeline_name": "payments_etl",
|
|
"severity": "critical",
|
|
"message": "payments_etl is failing with repeated database connection errors",
|
|
"resolved_integrations": {
|
|
"postgresql": {"host": "orders-db", "database": "orders", "port": 5432},
|
|
"datadog": {"connection_verified": True, "api_key": "x", "app_key": "y"},
|
|
},
|
|
}
|
|
)
|
|
|
|
# Only the content-relevant integration is in the call-first list; Datadog
|
|
# has no content signal here and must be relegated to secondary.
|
|
assert "Call these tools first (from: postgresql)" in context
|
|
assert "Secondary integrations" in context
|
|
|
|
|
|
def test_generic_alert_without_signal_does_not_fan_out() -> None:
|
|
context = format_alert_context(
|
|
{
|
|
"alert_name": "Something went wrong",
|
|
"alert_source": "generic",
|
|
"pipeline_name": "widgets",
|
|
"severity": "critical",
|
|
"message": "an unexpected problem occurred",
|
|
"resolved_integrations": {
|
|
"datadog": {"connection_verified": True, "api_key": "x", "app_key": "y"},
|
|
"vercel": {"connection_verified": True, "token": "z"},
|
|
},
|
|
}
|
|
)
|
|
|
|
# No content signal points to a specific integration: the agent must be told
|
|
# to pick relevant ones, NOT to call every connected integration first.
|
|
assert "Call these tools first" not in context
|
|
assert "call only the integration(s) directly relevant" in context
|
|
assert "Do not call integrations" in context
|
|
|
|
|
|
def test_generic_alert_honors_context_sources_annotation() -> None:
|
|
context = format_alert_context(
|
|
{
|
|
"alert_name": "Something went wrong",
|
|
"alert_source": "generic",
|
|
"pipeline_name": "widgets",
|
|
"severity": "critical",
|
|
"message": "an unexpected problem occurred",
|
|
"raw_alert": {
|
|
"commonAnnotations": {"context_sources": "datadog"},
|
|
},
|
|
"resolved_integrations": {
|
|
"datadog": {"connection_verified": True, "api_key": "x", "app_key": "y"},
|
|
"vercel": {"connection_verified": True, "token": "z"},
|
|
},
|
|
}
|
|
)
|
|
|
|
assert "Call these tools first (from: datadog)" in context
|
|
|
|
|
|
def test_alert_context_uses_planned_actions_when_present() -> None:
|
|
context = format_alert_context(
|
|
{
|
|
"alert_name": "High error rate",
|
|
"alert_source": "generic",
|
|
"pipeline_name": "payments",
|
|
"severity": "critical",
|
|
"planned_actions": ["get_sre_guidance"],
|
|
"plan_rationale": "Knowledge guidance is the selected fallback.",
|
|
"resolved_integrations": {
|
|
"grafana": {"url": "http://grafana", "api_key": "x"},
|
|
"datadog": {"connection_verified": True, "api_key": "x", "app_key": "y"},
|
|
},
|
|
}
|
|
)
|
|
|
|
assert "Use the planned investigation actions first" in context
|
|
assert "`get_sre_guidance`" in context
|
|
assert "Plan rationale: Knowledge guidance is the selected fallback." in context
|
|
assert "`query_datadog_logs`" not in context
|
|
|
|
|
|
def test_relevant_sources_matches_db_symptom_and_excludes_unrelated() -> None:
|
|
state = {
|
|
"alert_name": "High error rate in payments ETL",
|
|
"alert_source": "generic",
|
|
"pipeline_name": "payments_etl",
|
|
"message": "payments_etl is failing with repeated database connection errors",
|
|
}
|
|
tools_by_source = {"postgresql": [], "vercel": [], "knowledge": []}
|
|
|
|
# The DB-connection symptom matches Postgres; Vercel is irrelevant and the
|
|
# secondary "knowledge" source is never a candidate.
|
|
assert _relevant_sources(state, tools_by_source) == ["postgresql"]
|
|
|
|
|
|
def test_relevant_sources_empty_when_no_content_signal() -> None:
|
|
state = {
|
|
"alert_name": "Something is wrong",
|
|
"alert_source": "generic",
|
|
"pipeline_name": "mystery",
|
|
"message": "an unexplained problem occurred",
|
|
}
|
|
tools_by_source = {"postgresql": [], "vercel": []}
|
|
|
|
assert _relevant_sources(state, tools_by_source) == []
|
|
|
|
|
|
def test_relevant_sources_honors_explicit_context_sources() -> None:
|
|
state = {
|
|
"alert_name": "Something is wrong",
|
|
"alert_source": "generic",
|
|
"pipeline_name": "mystery",
|
|
"message": "an unexplained problem occurred",
|
|
"raw_alert": {"commonAnnotations": {"context_sources": "vercel"}},
|
|
}
|
|
tools_by_source = {"postgresql": [], "vercel": []}
|
|
|
|
# Explicit declaration wins even though the content has no Vercel keyword.
|
|
assert _relevant_sources(state, tools_by_source) == ["vercel"]
|
|
|
|
|
|
def test_alert_context_includes_incident_window_since_until_keys() -> None:
|
|
context = format_alert_context(
|
|
{
|
|
"alert_name": "Kubernetes job failed",
|
|
"alert_source": "generic",
|
|
"pipeline_name": "kubernetes_etl_pipeline",
|
|
"severity": "critical",
|
|
"incident_window": {
|
|
"since": "2026-02-18T22:10:00Z",
|
|
"until": "2026-02-19T00:10:00Z",
|
|
"source": "alert.startsAt",
|
|
"confidence": 1.0,
|
|
},
|
|
}
|
|
)
|
|
|
|
assert "Incident window: 2026-02-18T22:10:00Z → 2026-02-19T00:10:00Z" in context
|
|
|
|
|
|
def test_alert_context_accepts_legacy_incident_window_start_end_keys() -> None:
|
|
context = format_alert_context(
|
|
{
|
|
"alert_name": "Legacy window shape",
|
|
"alert_source": "generic",
|
|
"pipeline_name": "widgets",
|
|
"severity": "warning",
|
|
"incident_window": {
|
|
"start": "2026-01-01T00:00:00Z",
|
|
"end": "2026-01-01T02:00:00Z",
|
|
},
|
|
}
|
|
)
|
|
|
|
assert "Incident window: 2026-01-01T00:00:00Z → 2026-01-01T02:00:00Z" in context
|
|
|
|
|
|
def test_alert_context_points_to_primary_source_without_duplicating_tool_metadata() -> None:
|
|
context = format_alert_context(
|
|
{
|
|
"alert_name": "RDS latency spike",
|
|
"alert_source": "rds",
|
|
"pipeline_name": "orders",
|
|
"severity": "critical",
|
|
"resolved_integrations": {
|
|
"rds": {"db_instance_identifier": "orders-db", "region": "us-east-1"},
|
|
"postgresql": {"host": "orders-db", "database": "orders", "port": 5432},
|
|
},
|
|
}
|
|
)
|
|
|
|
# The alert context still orients the agent toward the primary integration
|
|
# and names the relevant tool.
|
|
assert "Call these tools first (from: rds" in context
|
|
assert "`describe_rds_instance`" in context
|
|
|
|
# But it no longer re-lists every tool's full description and metadata: those
|
|
# now live only in the structured tool schemas handed to the model, so the
|
|
# prompt does not duplicate them.
|
|
assert "## Available tools (by integration)" not in context
|
|
assert "source_id=aws_rds" not in context
|
|
assert "evidence=deployment_metadata" not in context
|
|
assert "avoid=" not in context
|