# Kibana Anomaly Detection

> Elastic ML anomaly detection skill — investigation/RCA, score explanation,

- **Type:** Skill
- **Install:** `agentstack add skill-elastic-agent-skills-kibana-anomaly-detection`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [elastic](https://agentstack.voostack.com/s/elastic)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [elastic](https://github.com/elastic)
- **Source:** https://github.com/elastic/agent-skills/tree/main/skills/kibana/kibana-anomaly-detection

## Install

```sh
agentstack add skill-elastic-agent-skills-kibana-anomaly-detection
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Elastic ML Anomaly Detection

Single skill covering all anomaly detection work against **Kibana Agent Builder** MCP at
`{KIBANA_URL}/api/agent_builder/mcp`. Use the **Mode Selector** below to pick the right approach for the user's question
— modes share the same tool surface and concepts.

## Platform

- Read path: ES|QL against `.ml-anomalies-*`, `.ml-config`, `.ml-notifications-*`, `.ml-annotations-*`
- Always-available: `platform.core.execute_esql` (plus additional platform tools for search, index mapping, and
  documentation — see `scripts/agent_builder_constants.json`)
- ML API spec (if available): `.kibana_ai_openapi_spec_elasticsearch` — see
  [references/anomaly-detection-openapi-spec-discover.md](references/anomaly-detection-openapi-spec-discover.md) for
  discovery pattern.
- **Run `ad_validate_ml_tool_permissions` first** when tools return empty/misleading results — missing privileges are
  the most common cause of false negatives. Full permissions matrix:
  [references/permissions-matrix.md](references/permissions-matrix.md).

## Mode Selector

| User intent                                                                   | Mode                                                                                                   |
| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| "What broke?" / RCA / cross-job / blast radius / influencers / log categories | **Investigate**                                                                                        |
| "Why score high/low?" / renormalization / model bounds / forecasts            | **Explain**                                                                                            |
| Missing docs / memory limit / datafeed stopped / CCS / lifecycle / calendars  | **Troubleshoot**                                                                                       |
| Create a job / configure a datafeed / start analysis / retrieve results       | **Manage**                                                                                             |
| Security framing (attack chains, MITRE, exfil)                                | Investigate + [references/security-anomaly-expert.md](references/security-anomaly-expert.md)           |
| Observability/SRE framing (degradation, capacity, deployment regression)      | Investigate + [references/observability-anomaly-expert.md](references/observability-anomaly-expert.md) |

When a question spans modes: **Investigate → Explain → Troubleshoot**. Don't blend mode logic — finish one before moving
on.

---

## Score Quick Reference

- `record_score` bands: **>75** critical · **50–75** warning · **25–50** minor · **> record_score` → renormalization (model saw worse anomalies later)
- `actual  one high score — composite cross-job signal often beats single-detector severity

> Full score definitions, renormalization mechanics, and `anomaly_score_explanation` components:
> [references/score-reference.md](references/score-reference.md).

## Core concepts

Treat `.ml-anomalies-*` as three layers, accessed via `result_type`:

- **`bucket`** — bucket-level unusualness per `bucket_span`. `anomaly_score` is the aggregate across all detectors.
- **`record`** — finest-grained rows with `actual` vs `typical`, `probability`, `record_score`,
  `anomaly_score_explanation`.
- **`influencer`** — entity contributions ranked within a bucket (`influencer_score`).

Read scores this way:

- `anomaly_score` / `record_score` = **current normalized** values (move as the model sees new extremes).
- `initial_anomaly_score` / `initial_record_score` = **immutable snapshots** from detection time.
- Compare `actual` to `typical`; use `probability` for raw likelihood.
- Map entities via `partition_field_value` / `by_field_value` / `over_field_value`.
- Read `multi_bucket_impact` (-5 to +5) to separate single-bucket spikes from sustained trends.

---

## Mode: Investigate — RCA

**When:** "what broke?", "which entity caused this?", cross-job correlation, blast radius, attack/cascade chains.

### Tool chain

| Phase                 | Tools                                                                                                          |
| --------------------- | -------------------------------------------------------------------------------------------------------------- |
| Discovery             | `ad_get_available_metadata`, `ad_get_jobs`, `ad_discover_related_jobs`, `ad_discover_jobs_by_datafeed_index`   |
| Timeline / scope      | `ad_query_anomaly_timeline`                                                                                    |
| Cross-job / entities  | `ad_rca_cross_job_entity_match`, `ad_rca_multi_job_entities`, `ad_rca_entity_profile`                          |
| Records / influencers | `ad_query_anomaly_records`, `ad_query_influencers`                                                             |
| RCA depth             | `ad_rca_detector_fingerprint`, `ad_rca_correlation`, `ad_rca_blast_radius`, `ad_rca_score_reassessment`        |
| Evidence / categories | `ad_get_job_datafeed_config`, `ad_rca_source_evidence`, `ad_get_categories`, `ad_search_log_category_examples` |

### Protocol

Follow the 14-step sequence in [references/protocols/investigation.md](references/protocols/investigation.md). High
level: `ad_get_available_metadata` → pair `ad_discover_jobs_by_datafeed_index` with `ad_discover_related_jobs` →
`ad_query_anomaly_timeline` → rank with `ad_rca_multi_job_entities` (`min_job_count=2`) → `ad_rca_detector_fingerprint`
→ drill with `ad_query_anomaly_records` + `ad_query_influencers` (low `min_score=25`) → profile with
`ad_rca_entity_profile` → order with `ad_rca_correlation` → confirm with `ad_rca_source_evidence`. When
`by_field_name == "mlcategory"`, compare with `ad_get_categories` + paired `ad_search_log_category_examples` (baseline
vs. anomaly window).

Finish with a written RCA: **root cause entity · affected jobs · temporal progression · fault class
(resource/network/application) · severity · recommended actions**. Worked example:
[references/worked-example.md](references/worked-example.md). Full ES|QL templates and parameters:
[references/investigate-anomaly-esql-tools.md](references/investigate-anomaly-esql-tools.md).

### Rules

1. **Multi-job entities are prime suspects; single-job entities are usually victims.** Use `min_job_count=2`.
2. **Earliest anomaly timestamp wins** — sort `ad_rca_correlation` by timestamp; first-appearing entity = origin.
3. **`multi_bucket_impact ≥ 3` = sustained behavioral shift**, weight higher than transient spikes.
4. **Never close an RCA without `ad_rca_source_evidence`** — raw source documents are ground truth.
5. **Use low `min_score` (25 or lower) for influencer queries** — high thresholds miss correlated entities.

---

## Mode: Explain — Score / model behavior

**When:** "why is my score 30/90?", "score dropped overnight", "what is renormalization?", "why wasn't this detected?".

### Score types

| Field                  | Scope           | Meaning                                                                 |
| ---------------------- | --------------- | ----------------------------------------------------------------------- |
| `record_score`         | Single record   | Normalized severity after renormalization.                              |
| `initial_record_score` | Single record   | Score at detection time. Gap vs `record_score` = renormalization drift. |
| `anomaly_score`        | Bucket          | Aggregate severity across all detectors in a bucket.                    |
| `influencer_score`     | Entity × bucket | How anomalous a specific entity is in that bucket.                      |

### `anomaly_score_explanation` components

| Component                        | Effect  | What it means                                                |
| -------------------------------- | ------- | ------------------------------------------------------------ |
| `anomaly_length`                 | ↑ score | More consecutive anomalous buckets                           |
| `single_bucket_impact`           | ↑ score | Lower probability → higher impact                            |
| `multi_bucket_impact`            | ↑ score | Sustained pattern contribution                               |
| `anomaly_characteristics_impact` | ↑ score | Mean shift vs. variance change                               |
| `high_variance_penalty`          | ↓ score | Noisy data → wide bounds → anomaly less surprising           |
| `incomplete_bucket_penalty`      | ↓ score | Bucket has less data than expected (ingest lag, sparse data) |

### Why a score looks wrong

- **Unexpectedly low:** `high_variance_penalty`, renormalization,  `hard_limit` corrupts model state and causes downstream missing-doc false alarms (categorizer silently skips events
> for unknown categories). **Fix memory before fixing `query_delay`.**

### Memory concepts

| Field                               | Meaning                                                 |
| ----------------------------------- | ------------------------------------------------------- |
| `model_bytes`                       | Current memory used                                     |
| `peak_model_bytes`                  | High-water mark since job opened                        |
| `model_bytes_memory_limit`          | Configured `model_memory_limit`                         |
| `memory_status`                     | `ok` / `soft_limit` (pruning) / `hard_limit` (critical) |
| `total_by_field_count > 100k`       | `by_field` cardinality too high — dominant driver       |
| `total_partition_field_count > 10k` | Partition explosion                                     |
| `total_category_count > 10k`        | Too many distinct log patterns                          |

Prefer **`ad_estimate_memory_requirement`** (samples cardinality from source, calls Estimate Model Memory API) over
heuristics like `peak_model_bytes * 1.3` — the heuristic ignores pure influencer and categorization memory.

### Datafeed & timing concepts

- **`query_delay`** — how far behind real time the datafeed queries. Too small → missing docs; too large → slower
  alerts. Set to **P95 ingest latency + buffer** (default `60s`–`120s`).
- **`delayed_data_check_config`** — how aggressively the datafeed checks for late data.
- **`bucket_span`** — analysis interval. Align with data granularity and detection window.
- **`frequency`** — defaults to `min(query_delay, bucket_span / 2)`.

### Lifecycle for config changes (memory limit, query_delay)

1. Stop datafeed: `ad_manage_datafeed` (`action=_stop`)
2. Close job
3. Update config: `ad_update_model_memory_limit`, `ad_update_datafeed_query_delay`,
   `ad_update_delayed_data_check_config`
4. Open job: `ad_open_job`
5. Start datafeed: `ad_manage_datafeed` (`action=_start`)

Recover a corrupted period without resetting the whole model: `ad_revert_model_snapshot`.

### Tool surface

| Category               | Tools                                                                                                                                                                                                   |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Permissions / metadata | `ad_validate_ml_tool_permissions`, `ad_get_available_metadata`, `ad_get_jobs`                                                                                                                           |
| Job + datafeed state   | `ad_get_job_datafeed_config`, `ad_get_job_messages`, `ad_manage_datafeed`, `ad_preview_datafeed_with_latency`                                                                                           |
| Timing / missing docs  | `ad_ts_delayed_data_annotations`, `ad_ts_bucket_event_gaps`, `ad_ts_ingest_latency_estimate`, `ad_update_datafeed_query_delay`, `ad_update_delayed_data_check_config`, `ad_wf_troubleshoot_query_delay` |
| Memory                 | `ad_ts_model_memory_health`, `ad_wf_ts_field_cardinality`, `ad_estimate_memory_requirement`, `ad_update_model_memory_limit`, `ad_wf_troubleshoot_memory_limit`                                          |
| Model / lifecycle      | `ad_get_model_snapshots`, `ad_revert_model_snapshot`, `ad_open_job`, `ad_create_job`                                                                                                                    |
| CCS                    | `ad_ts_ccs_diagnostics`                                                                                                                                                                                 |
| Calendars              | `ad_get_calendar_events`, `ad_create_calendar_event`                                                                                                                                                    |

Full parameter tables, ES|QL templates, and REST step lists:
[references/troubleshoot-anomaly-tool-reference.md](references/troubleshoot-anomaly-tool-reference.md).

### Rules

1. **`ad_validate_ml_tool_permissions` first** — missing privileges produce misleading empty results.
2. **Fix memory before `query_delay`** — `hard_limit` corrupts state; `query_delay` fixes on a memory-limited job are
   wasted.
3. **Stop the datafeed before updating it.** Updating a running datafeed is rejected.
4. **Close the job before updating memory limit.** Sequence above.
5. **Prefer workflow tools (`ad_wf_*`) over manually chaining diagnostics** for complex decisions.
6. **`ad_preview_datafeed_with_latency` before starting** — confirm the datafeed returns data after config changes.

---

## Mode: Manage — Create / configure jobs

**When:** "set up a job", "create an ML detector", "monitor X over time", "detect rare/unusual/anomalous values".

### 4-step workflow

```text
PUT  _ml/anomaly_detectors/          # 1. Define job        (ad_create_job)
PUT  _ml/datafeeds/datafeed-         # 2. Define datafeed   (ad_create_datafeed)
POST _ml/anomaly_detectors//_open    # 3a. Open job         (ad_open_job)
POST _ml/datafeeds/datafeed-/_start  # 3b. Start datafeed   (ad_manage_datafeed action=_start)
GET  _ml/anomaly_detectors//results/records  # 4. Read results
```

### Process

1. **Build configs.** Parse the user request into job + datafeed JSON with no null fields.
2. **Apply smart defaults:**

   | Field            | Default                                 | Override when                                     |
   | ---------------- | --------------------------------------- | ------------------------------------------------- |
   | `bucket_span`    | `"15m"`                                 | User specifies a different span                   |
   | `time_field`     | `"@timestamp"`                          | User names a different timestamp field            |
   | `index`          | `"logs-*"`                              | User specifies an index or pattern                |
   | `datafeed_query` | `{"match_all": {}}`                     | User mentions filters, processes, or time windows |
   | `influencers`    | by/over/partition fields from detectors | User adds extra influencer fields                 |
   | `job_id`         | Generated from user description         | User provides an explicit ID                      |
   | `query_delay`    | `"60s"`                                 | P95 ingest latency is higher                      |

3. **Choose detector function** from user intent — full table in
   [references/anomaly-detection-functions.md](references/anomaly-detection-functions.md):
   - "high CPU" / "unusually large" → `high_mean` or `high_sum`
   - "rare logins" / "unusual values" → `rare` (variants b

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [elastic](https://github.com/elastic)
- **Source:** [elastic/agent-skills](https://github.com/elastic/agent-skills)
- **License:** Apache-2.0

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-elastic-agent-skills-kibana-anomaly-detection
- Seller: https://agentstack.voostack.com/s/elastic
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
